Ctrl K
ComunidadGuías…

internal/handler/benchmark_handler.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handler

import (
	"bytes"
	"encoding/json"
	"net/http"
	"strconv"

	"github.com/johnzastrow/actalog/internal/domain"
	"github.com/johnzastrow/actalog/internal/service"
	"github.com/johnzastrow/actalog/pkg/logger"
	"github.com/johnzastrow/actalog/pkg/middleware"
)

// BenchmarkHandler handles HTTP requests for benchmarks
type BenchmarkHandler struct {
	service *service.BenchmarkService
	logger  *logger.Logger
}

// NewBenchmarkHandler creates a new benchmark handler
func NewBenchmarkHandler(service *service.BenchmarkService, logger *logger.Logger) *BenchmarkHandler {
	return &BenchmarkHandler{
		service: service,
		logger:  logger,
	}
}

// RunBenchmark handles POST /api/benchmark
// @Summary      Run benchmark suite
// @Description  Execute the full benchmark suite to test database, serialization, and business logic performance
// @Tags         benchmark
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        concurrent query bool false "Include concurrent operation tests (default: false)"
// @Param        cleanup query bool false "Skip cleanup after run if set to false (default: true)"
// @Param        records query int false "Number of records to generate for benchmark testing (default: 1000, max: 500000)"
// @Success      200 {object} map[string]interface{} "Benchmark results"
// @Failure      401 {object} ErrorResponse "Unauthorized"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /benchmark [post]
func (h *BenchmarkHandler) RunBenchmark(w http.ResponseWriter, r *http.Request) {
	userID, ok := middleware.GetUserID(r.Context())
	if !ok {
		h.logger.Warn("Unauthorized benchmark attempt: no user context")
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	// Parse record count from query parameters
	recordCount := domain.DefaultRecordCount
	if recordsStr := r.URL.Query().Get("records"); recordsStr != "" {
		if parsed, err := strconv.Atoi(recordsStr); err == nil && parsed > 0 {
			recordCount = parsed
			if recordCount > domain.MaxRecordCount {
				recordCount = domain.MaxRecordCount
			}
		}
	}

	// Parse options from query parameters
	options := domain.BenchmarkOptions{
		IncludeConcurrent: r.URL.Query().Get("concurrent") == "true",
		Cleanup:           r.URL.Query().Get("cleanup") != "false", // Default to true
		RecordCount:       recordCount,
	}

	h.logger.Info("Running benchmark suite: user_id=%d concurrent=%v cleanup=%v records=%d", userID, options.IncludeConcurrent, options.Cleanup, options.RecordCount)

	result, err := h.service.RunBenchmark(userID, options)
	if err != nil {
		h.logger.Error("Benchmark failed: user_id=%d error=%v", userID, err)
		http.Error(w, "Benchmark failed: "+err.Error(), http.StatusInternalServerError)
		return
	}

	h.logger.Info("Benchmark completed: user_id=%d overall=%s duration_ms=%.2f ops=%d success=%d failed=%d records=%d",
		userID, result.Overall, result.TotalDurationMs, result.TotalOperations, result.SuccessfulOperations, result.FailedOperations, result.RecordCount)

	// Buffer the JSON response to set Content-Length header
	// This avoids chunked transfer encoding issues with long-running requests
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(result); err != nil {
		h.logger.Error("Failed to encode benchmark result: error=%v", err)
		http.Error(w, "Failed to encode result", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
	w.Write(buf.Bytes())
}

// GetBenchmarkStatus handles GET /api/benchmark/status
// @Summary      Get benchmark status
// @Description  Quick status check for the benchmark system
// @Tags         benchmark
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Success      200 {object} map[string]interface{} "Benchmark status"
// @Failure      401 {object} ErrorResponse "Unauthorized"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /benchmark/status [get]
func (h *BenchmarkHandler) GetBenchmarkStatus(w http.ResponseWriter, r *http.Request) {
	_, ok := middleware.GetUserID(r.Context())
	if !ok {
		h.logger.Warn("Unauthorized benchmark status request: no user context")
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	status, err := h.service.GetBenchmarkStatus()
	if err != nil {
		h.logger.Error("Failed to get benchmark status: error=%v", err)
		http.Error(w, "Failed to get status: "+err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(status)
}

// CleanupBenchmarkData handles DELETE /api/admin/benchmark/data
// @Summary      Cleanup all benchmark data (Admin)
// @Description  Delete all benchmark data from the database
// @Tags         benchmark
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Success      200 {object} map[string]string "Cleanup result"
// @Failure      401 {object} ErrorResponse "Unauthorized"
// @Failure      403 {object} ErrorResponse "Forbidden"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/benchmark/data [delete]
func (h *BenchmarkHandler) CleanupBenchmarkData(w http.ResponseWriter, r *http.Request) {
	userRole, _ := middleware.GetUserRole(r.Context())
	if userRole != "admin" {
		h.logger.Warn("Unauthorized benchmark cleanup attempt: user_role=%s", userRole)
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	err := h.service.CleanupAllBenchmarkData()
	if err != nil {
		h.logger.Error("Failed to cleanup benchmark data: error=%v", err)
		http.Error(w, "Failed to cleanup: "+err.Error(), http.StatusInternalServerError)
		return
	}

	h.logger.Info("Benchmark data cleaned up by admin")

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{
		"status":  "success",
		"message": "All benchmark data has been deleted",
	})
}