mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-05 23:03:48 +00:00
c2c75d69c0
Performance / resource usage: - circuit_breaker_metrics: fix data race on failCounters map (RWMutex + double-checked locking) - server.go: drop user_id and op_name metric labels (Prometheus cardinality bound); de-duplicate extractUserInfo - graphql.go: gate runtime.ReadMemStats per-request behind ENABLE_ALLOCATION_TRACKING flag (default off) - graphql.go: collapse two-pass AST scan into single pass; lower-case once - sanitization.go: cache compiled redaction regexes per pattern via sync.Map; hoist inner constants to pkg vars - proxy.go: hoist connection/timeout substrings to pkg vars; sentinel errors for static error paths; drop dead Headers map alloc - metrics_aggregator.go: log-field allocation guarded by Logger.IsLevelEnabled - logging/logger.go: add IsLevelEnabled helper - lru_cache.go: 16-shard sharding, FNV-1a routing (concurrent throughput +22%) - cache/memory/lru_memory_cache.go: gzip compress/decompress moved outside mu.Lock - rps_tracker.go: RWMutex+uint64 -> atomic.Uint64 - retry_budget.go: drop unused mutex - api.go: bannedUsersIDs map+RWMutex -> sync.Map (+ snapshot/replace helpers) - tracing/tracing.go: pkg-level constSpanAttrs, copy-then-append in StartSpanWithAttributes - admin_dashboard.go: handleStatsWebSocket reuses bytes.Buffer + json.Encoder per connection Build / runtime: - Makefile: -ldflags="-s -w" -trimpath, CGO_ENABLED=0 for build (=1 for test recipes) - Dockerfile + Dockerfile.goreleaser: ENV GOMEMLIMIT=512MiB - main.go: blank import go.uber.org/automaxprocs (cgroup-aware GOMAXPROCS) - main.go: PPROF_PORT env var wires net/http/pprof on 127.0.0.1 only with full server timeouts - README.md: env-var docs + metric-label docs updated; cardinality note Test coverage push (per package): - main 51.2% -> 74.7% - cache 66.3% -> 93.7% - cache/redis 45.5% -> 98.2% - tracing 66.7% -> 72.9% - (cache/memory 91.6%, logging 91.9%, monitoring 77.6%, pkg/pools 100% unchanged) New test files: coverage_micro_test, coverage_extras_test, server_handlers_test, api_health_test, admin_dashboard_cluster_test, metrics_aggregator_test, concerns_test, cache/cache_coverage_test, cache/redis/redis_coverage_test, tracing/tracing_coverage_test. Bug fix: connection_resilience_test.go TestIntegratedHealthManagement.health_manager_startup was sync.Once-coupled to InitializeBackendHealth and panicked when another test (e.g. via parseConfig) had already triggered Once. Use NewBackendHealthManager directly.
103 lines
2.3 KiB
Go
103 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// RPSTracker tracks requests per second using periodic sampling
|
|
type RPSTracker struct {
|
|
lastCount atomic.Int64
|
|
lastSampleTime atomic.Int64 // Unix nano
|
|
currentRPS atomic.Uint64 // centirps (RPS * 100)
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
// NewRPSTracker creates a new RPS tracker with context for graceful shutdown
|
|
func NewRPSTracker(ctx context.Context) *RPSTracker {
|
|
trackerCtx, cancel := context.WithCancel(ctx)
|
|
tracker := &RPSTracker{
|
|
ctx: trackerCtx,
|
|
cancel: cancel,
|
|
}
|
|
tracker.lastSampleTime.Store(time.Now().UnixNano())
|
|
go tracker.updateLoop()
|
|
return tracker
|
|
}
|
|
|
|
// RecordRequest increments the request counter
|
|
func (r *RPSTracker) RecordRequest() {
|
|
// Just increment the counter, sampling happens in background
|
|
r.lastCount.Add(1)
|
|
}
|
|
|
|
// updateLoop periodically calculates current RPS
|
|
func (r *RPSTracker) updateLoop() {
|
|
ticker := time.NewTicker(1 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-r.ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
r.sample()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Shutdown stops the RPS tracker
|
|
func (r *RPSTracker) Shutdown() {
|
|
if r.cancel != nil {
|
|
r.cancel()
|
|
}
|
|
}
|
|
|
|
// sample calculates RPS since last sample
|
|
func (r *RPSTracker) sample() {
|
|
now := time.Now()
|
|
nowNano := now.UnixNano()
|
|
|
|
currentCount := r.lastCount.Load()
|
|
lastSampleNano := r.lastSampleTime.Load()
|
|
|
|
if lastSampleNano == 0 {
|
|
r.lastSampleTime.Store(nowNano)
|
|
return
|
|
}
|
|
|
|
elapsed := float64(nowNano-lastSampleNano) / float64(time.Second)
|
|
if elapsed > 0 {
|
|
rps := float64(currentCount) / elapsed
|
|
// Store RPS as centirps for precision (multiply by 100)
|
|
r.currentRPS.Store(uint64(rps * 100))
|
|
}
|
|
|
|
// Reset for next sample
|
|
r.lastCount.Store(0)
|
|
r.lastSampleTime.Store(nowNano)
|
|
}
|
|
|
|
// GetCurrentRPS returns the current requests per second
|
|
func (r *RPSTracker) GetCurrentRPS() float64 {
|
|
centirps := r.currentRPS.Load()
|
|
return float64(centirps) / 100.0
|
|
}
|
|
|
|
var globalRPSTracker *RPSTracker
|
|
|
|
// InitializeRPSTracker initializes the global RPS tracker with context for graceful shutdown
|
|
func InitializeRPSTracker(ctx context.Context) *RPSTracker {
|
|
if globalRPSTracker == nil {
|
|
globalRPSTracker = NewRPSTracker(ctx)
|
|
}
|
|
return globalRPSTracker
|
|
}
|
|
|
|
// GetRPSTracker returns the global RPS tracker
|
|
func GetRPSTracker() *RPSTracker {
|
|
return globalRPSTracker
|
|
}
|