243 lines
6.5 KiB
Go
243 lines
6.5 KiB
Go
package metrics
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"veza-backend-api/internal/errors"
|
|
)
|
|
|
|
// TimeWindow représente une fenêtre de temps avec des métriques agrégées
|
|
type TimeWindow struct {
|
|
Start time.Time `json:"start"`
|
|
End time.Time `json:"end"`
|
|
Errors int64 `json:"errors"`
|
|
Requests int64 `json:"requests"`
|
|
ErrorsByCode map[errors.ErrorCode]int64 `json:"errors_by_code"`
|
|
ErrorsByHTTPStatus map[int]int64 `json:"errors_by_http_status"`
|
|
}
|
|
|
|
// AggregatedMetrics gère l'agrégation des métriques sur des fenêtres de temps
|
|
type AggregatedMetrics struct {
|
|
mu sync.RWMutex
|
|
windows map[string][]TimeWindow // key: "1m", "5m", "1h"
|
|
|
|
// Configuration des fenêtres en secondes
|
|
windowSizes map[string]time.Duration
|
|
maxWindows map[string]int // Nombre max de fenêtres à garder par type
|
|
}
|
|
|
|
// NewAggregatedMetrics crée une nouvelle instance de AggregatedMetrics
|
|
func NewAggregatedMetrics() *AggregatedMetrics {
|
|
agg := &AggregatedMetrics{
|
|
windows: make(map[string][]TimeWindow),
|
|
windowSizes: map[string]time.Duration{
|
|
"1m": 1 * time.Minute,
|
|
"5m": 5 * time.Minute,
|
|
"1h": 1 * time.Hour,
|
|
},
|
|
maxWindows: map[string]int{
|
|
"1m": 60, // Garder 60 fenêtres de 1 minute = 1 heure
|
|
"5m": 12, // Garder 12 fenêtres de 5 minutes = 1 heure
|
|
"1h": 24, // Garder 24 fenêtres de 1 heure = 24 heures
|
|
},
|
|
}
|
|
|
|
// Démarrer la routine de nettoyage
|
|
go agg.cleanupRoutine()
|
|
|
|
return agg
|
|
}
|
|
|
|
// AddError enregistre une erreur dans les fenêtres d'agrégation
|
|
func (a *AggregatedMetrics) AddError(windowType string, code errors.ErrorCode, httpStatus int) {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
|
|
// Initialiser la fenêtre si elle n'existe pas
|
|
if _, exists := a.windows[windowType]; !exists {
|
|
a.windows[windowType] = []TimeWindow{}
|
|
}
|
|
|
|
windowSize, ok := a.windowSizes[windowType]
|
|
if !ok {
|
|
// Fenêtre non supportée
|
|
return
|
|
}
|
|
|
|
// Trouver ou créer la fenêtre active
|
|
windowStart := now.Truncate(windowSize)
|
|
windowEnd := windowStart.Add(windowSize)
|
|
|
|
// Chercher la fenêtre active
|
|
found := false
|
|
for i := range a.windows[windowType] {
|
|
if a.windows[windowType][i].Start.Equal(windowStart) {
|
|
// Fenêtre existante - mettre à jour
|
|
a.windows[windowType][i].Errors++
|
|
a.windows[windowType][i].ErrorsByCode[code]++
|
|
a.windows[windowType][i].ErrorsByHTTPStatus[httpStatus]++
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
// Créer une nouvelle fenêtre
|
|
newWindow := TimeWindow{
|
|
Start: windowStart,
|
|
End: windowEnd,
|
|
Errors: 1,
|
|
Requests: 0,
|
|
ErrorsByCode: make(map[errors.ErrorCode]int64),
|
|
ErrorsByHTTPStatus: make(map[int]int64),
|
|
}
|
|
newWindow.ErrorsByCode[code] = 1
|
|
newWindow.ErrorsByHTTPStatus[httpStatus] = 1
|
|
a.windows[windowType] = append(a.windows[windowType], newWindow)
|
|
}
|
|
|
|
// Nettoyer les anciennes fenêtres (garder seulement les plus récentes)
|
|
a.cleanupWindows(windowType)
|
|
}
|
|
|
|
// AddRequest enregistre une requête dans les fenêtres d'agrégation
|
|
func (a *AggregatedMetrics) AddRequest(windowType string) {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
|
|
// Initialiser la fenêtre si elle n'existe pas
|
|
if _, exists := a.windows[windowType]; !exists {
|
|
a.windows[windowType] = []TimeWindow{}
|
|
}
|
|
|
|
windowSize, ok := a.windowSizes[windowType]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Trouver ou créer la fenêtre active
|
|
windowStart := now.Truncate(windowSize)
|
|
|
|
// Chercher la fenêtre active
|
|
found := false
|
|
for i := range a.windows[windowType] {
|
|
if a.windows[windowType][i].Start.Equal(windowStart) {
|
|
a.windows[windowType][i].Requests++
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
// Créer une nouvelle fenêtre
|
|
newWindow := TimeWindow{
|
|
Start: windowStart,
|
|
End: windowStart.Add(windowSize),
|
|
Errors: 0,
|
|
Requests: 1,
|
|
ErrorsByCode: make(map[errors.ErrorCode]int64),
|
|
ErrorsByHTTPStatus: make(map[int]int64),
|
|
}
|
|
a.windows[windowType] = append(a.windows[windowType], newWindow)
|
|
}
|
|
|
|
// Nettoyer les anciennes fenêtres
|
|
a.cleanupWindows(windowType)
|
|
}
|
|
|
|
// GetAggregated retourne les métriques agrégées pour un type de fenêtre
|
|
func (a *AggregatedMetrics) GetAggregated(windowType string) []TimeWindow {
|
|
a.mu.RLock()
|
|
defer a.mu.RUnlock()
|
|
|
|
if windows, exists := a.windows[windowType]; exists {
|
|
// Retourner une copie pour éviter les modifications concurrentes
|
|
result := make([]TimeWindow, len(windows))
|
|
for i, w := range windows {
|
|
result[i] = w
|
|
// Copier les maps
|
|
result[i].ErrorsByCode = make(map[errors.ErrorCode]int64)
|
|
result[i].ErrorsByHTTPStatus = make(map[int]int64)
|
|
for k, v := range w.ErrorsByCode {
|
|
result[i].ErrorsByCode[k] = v
|
|
}
|
|
for k, v := range w.ErrorsByHTTPStatus {
|
|
result[i].ErrorsByHTTPStatus[k] = v
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
return []TimeWindow{}
|
|
}
|
|
|
|
// GetAllAggregated retourne toutes les métriques agrégées
|
|
func (a *AggregatedMetrics) GetAllAggregated() map[string][]TimeWindow {
|
|
a.mu.RLock()
|
|
defer a.mu.RUnlock()
|
|
|
|
result := make(map[string][]TimeWindow)
|
|
for windowType := range a.windows {
|
|
result[windowType] = a.GetAggregated(windowType)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// cleanupWindows nettoie les anciennes fenêtres pour un type donné
|
|
func (a *AggregatedMetrics) cleanupWindows(windowType string) {
|
|
max, ok := a.maxWindows[windowType]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if len(a.windows[windowType]) <= max {
|
|
return
|
|
}
|
|
|
|
// Garder seulement les fenêtres les plus récentes
|
|
windows := a.windows[windowType]
|
|
|
|
// Trier par date (les plus récentes en premier)
|
|
// Les fenêtres sont normalement déjà ordonnées, mais on s'assure
|
|
// On garde les max dernières
|
|
if len(windows) > max {
|
|
startIdx := len(windows) - max
|
|
a.windows[windowType] = windows[startIdx:]
|
|
}
|
|
}
|
|
|
|
// cleanupRoutine nettoie périodiquement les anciennes fenêtres
|
|
func (a *AggregatedMetrics) cleanupRoutine() {
|
|
ticker := time.NewTicker(1 * time.Minute) // Nettoyer chaque minute
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
a.mu.Lock()
|
|
|
|
now := time.Now()
|
|
|
|
// Nettoyer les fenêtres expirées pour chaque type
|
|
for windowType, windows := range a.windows {
|
|
windowSize := a.windowSizes[windowType]
|
|
maxAge := windowSize * time.Duration(a.maxWindows[windowType])
|
|
|
|
validWindows := []TimeWindow{}
|
|
for _, w := range windows {
|
|
// Garder les fenêtres qui ne sont pas trop anciennes
|
|
if now.Sub(w.End) < maxAge {
|
|
validWindows = append(validWindows, w)
|
|
}
|
|
}
|
|
|
|
a.windows[windowType] = validWindows
|
|
}
|
|
|
|
a.mu.Unlock()
|
|
}
|
|
}
|