Files
cv-site/internal/handlers/benchmarks_test.go
T
juanatsap 71d9258c58 feat: add application-level data caching for CV/UI
Eliminate per-request file I/O by loading CV and UI data once at startup.

## Problem
- LoadCV() and LoadUI() were called on every request
- Each call read from disk and unmarshaled JSON
- 6 locations affected: cv_cmdk, cv_helpers, cv_contact

## Solution
- New `internal/cache` package with language-keyed cache
- Data loaded once at startup via `cache.New(["en", "es"])`
- Handlers use `h.dataCache.GetCV(lang)` / `GetUI(lang)`
- Thread-safe concurrent reads via sync.RWMutex
- Deep copy for mutable slices (Experience, Projects)

## Performance
- Before: ~3ms file I/O per request
- After: <1µs cache lookup (~3000x improvement)

## Files
- internal/cache/data_cache.go (new)
- internal/cache/data_cache_test.go (new)
- internal/cache/README.md (new)
- internal/handlers/cv.go (added dataCache field)
- internal/handlers/cv_*.go (use cache)
- main.go (initialize cache at startup)
2025-12-06 15:57:23 +00:00

120 lines
2.9 KiB
Go

package handlers
import (
"net/http"
"net/http/httptest"
"testing"
)
// BenchmarkHome benchmarks the Home handler
func BenchmarkHome(b *testing.B) {
handler := newTestCVHandler(b, "localhost:8080", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest(http.MethodGet, "/?lang=en", nil)
w := httptest.NewRecorder()
handler.Home(w, req)
}
}
// BenchmarkCVContent benchmarks the CVContent handler
func BenchmarkCVContent(b *testing.B) {
handler := newTestCVHandler(b, "localhost:8080", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest(http.MethodGet, "/cv?lang=en", nil)
w := httptest.NewRecorder()
handler.CVContent(w, req)
}
}
// BenchmarkToggleLength benchmarks the ToggleLength handler
func BenchmarkToggleLength(b *testing.B) {
handler := newTestCVHandler(b, "localhost:8080", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
req := httptest.NewRequest(http.MethodPost, "/toggle-length", nil)
req.AddCookie(&http.Cookie{Name: "cv-length", Value: "short"})
w := httptest.NewRecorder()
handler.ToggleLength(w, req)
}
}
// BenchmarkParsePDFExportRequest benchmarks request parsing
func BenchmarkParsePDFExportRequest(b *testing.B) {
req := httptest.NewRequest(http.MethodGet, "/export-pdf?lang=en&length=long&icons=show&version=with_skills", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := ParsePDFExportRequest(req)
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkPrepareTemplateData benchmarks template data preparation
func BenchmarkPrepareTemplateData(b *testing.B) {
handler := newTestCVHandler(b, "localhost:8080", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := handler.prepareTemplateData("en")
if err != nil {
b.Fatal(err)
}
}
}
// BenchmarkResponseTypes benchmarks response creation
func BenchmarkSuccessResponse(b *testing.B) {
data := map[string]interface{}{
"status": "ok",
"count": 100,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = SuccessResponse(data)
}
}
func BenchmarkNewErrorResponse(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = NewErrorResponse("INVALID_INPUT", "Invalid request parameter")
}
}
// BenchmarkParallelHome benchmarks Home handler under parallel load
func BenchmarkParallelHome(b *testing.B) {
handler := newTestCVHandler(b, "localhost:8080", nil)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
req := httptest.NewRequest(http.MethodGet, "/?lang=en", nil)
w := httptest.NewRecorder()
handler.Home(w, req)
}
})
}
// BenchmarkParallelToggleLength benchmarks toggle under parallel load
func BenchmarkParallelToggleLength(b *testing.B) {
handler := newTestCVHandler(b, "localhost:8080", nil)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
req := httptest.NewRequest(http.MethodPost, "/toggle-length", nil)
req.AddCookie(&http.Cookie{Name: "cv-length", Value: "short"})
w := httptest.NewRecorder()
handler.ToggleLength(w, req)
}
})
}