mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-05 23:03:48 +00:00
6a69694ab3
* Tackling the CPU / memory spikes after some time. * Update admin dashboard, fix the circuit breaker and request coalescing.
109 lines
2.5 KiB
Go
109 lines
2.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// RPSTracker tracks requests per second using periodic sampling
|
|
type RPSTracker struct {
|
|
lastCount atomic.Int64
|
|
lastSampleTime atomic.Int64 // Unix nano
|
|
currentRPS uint64 // stored as uint64, accessed with atomic operations
|
|
mu sync.RWMutex // for currentRPS updates
|
|
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.mu.Lock()
|
|
atomic.StoreUint64(&r.currentRPS, uint64(rps*100))
|
|
r.mu.Unlock()
|
|
}
|
|
|
|
// Reset for next sample
|
|
r.lastCount.Store(0)
|
|
r.lastSampleTime.Store(nowNano)
|
|
}
|
|
|
|
// GetCurrentRPS returns the current requests per second
|
|
func (r *RPSTracker) GetCurrentRPS() float64 {
|
|
r.mu.RLock()
|
|
centirps := atomic.LoadUint64(&r.currentRPS)
|
|
r.mu.RUnlock()
|
|
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
|
|
}
|