AX principle 2: comments show HOW with real values, not WHAT the name already says. "DefaultMetrics is the global metrics instance" restates the declaration. Replace with concrete field access examples an agent can copy directly. Co-Authored-By: Charon <charon@lethean.io>
177 lines
4.9 KiB
Go
177 lines
4.9 KiB
Go
package mining
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// snapshot := mining.GetMetricsSnapshot()
|
|
// snapshot["miners_started"].(int64)
|
|
type Metrics struct {
|
|
// API metrics
|
|
RequestsTotal atomic.Int64
|
|
RequestsErrored atomic.Int64
|
|
RequestLatency *LatencyHistogram
|
|
|
|
// Miner metrics
|
|
MinersStarted atomic.Int64
|
|
MinersStopped atomic.Int64
|
|
MinersErrored atomic.Int64
|
|
|
|
// Stats collection metrics
|
|
StatsCollected atomic.Int64
|
|
StatsRetried atomic.Int64
|
|
StatsFailed atomic.Int64
|
|
|
|
// WebSocket metrics
|
|
WSConnections atomic.Int64
|
|
WSMessages atomic.Int64
|
|
|
|
// P2P metrics
|
|
P2PMessagesSent atomic.Int64
|
|
P2PMessagesReceived atomic.Int64
|
|
P2PConnectionsTotal atomic.Int64
|
|
}
|
|
|
|
// h := mining.NewLatencyHistogram(1000)
|
|
// h.Record(42 * time.Millisecond)
|
|
type LatencyHistogram struct {
|
|
mutex sync.Mutex
|
|
samples []time.Duration
|
|
maxSize int
|
|
}
|
|
|
|
// h := mining.NewLatencyHistogram(1000) // retain up to 1000 latency samples
|
|
func NewLatencyHistogram(maxSize int) *LatencyHistogram {
|
|
return &LatencyHistogram{
|
|
samples: make([]time.Duration, 0, maxSize),
|
|
maxSize: maxSize,
|
|
}
|
|
}
|
|
|
|
// h.Record(42 * time.Millisecond) // call after each request completes
|
|
func (h *LatencyHistogram) Record(d time.Duration) {
|
|
h.mutex.Lock()
|
|
defer h.mutex.Unlock()
|
|
|
|
if len(h.samples) >= h.maxSize {
|
|
// Ring buffer behavior - overwrite oldest
|
|
copy(h.samples, h.samples[1:])
|
|
h.samples = h.samples[:len(h.samples)-1]
|
|
}
|
|
h.samples = append(h.samples, d)
|
|
}
|
|
|
|
// avg := h.Average() // returns 0 if no samples recorded
|
|
func (h *LatencyHistogram) Average() time.Duration {
|
|
h.mutex.Lock()
|
|
defer h.mutex.Unlock()
|
|
|
|
if len(h.samples) == 0 {
|
|
return 0
|
|
}
|
|
|
|
var total time.Duration
|
|
for _, d := range h.samples {
|
|
total += d
|
|
}
|
|
return total / time.Duration(len(h.samples))
|
|
}
|
|
|
|
// if h.Count() == 0 { return } // guard before calling Average()
|
|
func (h *LatencyHistogram) Count() int {
|
|
h.mutex.Lock()
|
|
defer h.mutex.Unlock()
|
|
return len(h.samples)
|
|
}
|
|
|
|
// mining.DefaultMetrics.MinersStarted.Load()
|
|
// mining.DefaultMetrics.RequestLatency.Average()
|
|
var DefaultMetrics = &Metrics{
|
|
RequestLatency: NewLatencyHistogram(1000),
|
|
}
|
|
|
|
// RecordRequest(true, 42*time.Millisecond) // errored request
|
|
// RecordRequest(false, 5*time.Millisecond) // successful request
|
|
func RecordRequest(errored bool, latency time.Duration) {
|
|
DefaultMetrics.RequestsTotal.Add(1)
|
|
if errored {
|
|
DefaultMetrics.RequestsErrored.Add(1)
|
|
}
|
|
DefaultMetrics.RequestLatency.Record(latency)
|
|
}
|
|
|
|
// RecordMinerStart() // call after miner.Start() succeeds
|
|
func RecordMinerStart() {
|
|
DefaultMetrics.MinersStarted.Add(1)
|
|
}
|
|
|
|
// RecordMinerStop() // call after miner.Stop() completes
|
|
func RecordMinerStop() {
|
|
DefaultMetrics.MinersStopped.Add(1)
|
|
}
|
|
|
|
// RecordMinerError() // call when a miner crashes or fails to respond
|
|
func RecordMinerError() {
|
|
DefaultMetrics.MinersErrored.Add(1)
|
|
}
|
|
|
|
// RecordStatsCollection(false, false) // successful first-attempt collection
|
|
// RecordStatsCollection(true, false) // succeeded after retry
|
|
// RecordStatsCollection(true, true) // failed after retry
|
|
func RecordStatsCollection(retried bool, failed bool) {
|
|
DefaultMetrics.StatsCollected.Add(1)
|
|
if retried {
|
|
DefaultMetrics.StatsRetried.Add(1)
|
|
}
|
|
if failed {
|
|
DefaultMetrics.StatsFailed.Add(1)
|
|
}
|
|
}
|
|
|
|
// RecordWSConnection(true) // client connected
|
|
// RecordWSConnection(false) // client disconnected
|
|
func RecordWSConnection(connected bool) {
|
|
if connected {
|
|
DefaultMetrics.WSConnections.Add(1)
|
|
} else {
|
|
DefaultMetrics.WSConnections.Add(-1)
|
|
}
|
|
}
|
|
|
|
// RecordWSMessage() // call after broadcasting an event to clients
|
|
func RecordWSMessage() {
|
|
DefaultMetrics.WSMessages.Add(1)
|
|
}
|
|
|
|
// RecordP2PMessage(true) // outbound message dispatched
|
|
// RecordP2PMessage(false) // inbound message received
|
|
func RecordP2PMessage(sent bool) {
|
|
if sent {
|
|
DefaultMetrics.P2PMessagesSent.Add(1)
|
|
} else {
|
|
DefaultMetrics.P2PMessagesReceived.Add(1)
|
|
}
|
|
}
|
|
|
|
// snapshot := mining.GetMetricsSnapshot()
|
|
// snapshot["requests_total"].(int64)
|
|
func GetMetricsSnapshot() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"requests_total": DefaultMetrics.RequestsTotal.Load(),
|
|
"requests_errored": DefaultMetrics.RequestsErrored.Load(),
|
|
"request_latency_avg_ms": DefaultMetrics.RequestLatency.Average().Milliseconds(),
|
|
"request_latency_samples": DefaultMetrics.RequestLatency.Count(),
|
|
"miners_started": DefaultMetrics.MinersStarted.Load(),
|
|
"miners_stopped": DefaultMetrics.MinersStopped.Load(),
|
|
"miners_errored": DefaultMetrics.MinersErrored.Load(),
|
|
"stats_collected": DefaultMetrics.StatsCollected.Load(),
|
|
"stats_retried": DefaultMetrics.StatsRetried.Load(),
|
|
"stats_failed": DefaultMetrics.StatsFailed.Load(),
|
|
"ws_connections": DefaultMetrics.WSConnections.Load(),
|
|
"ws_messages": DefaultMetrics.WSMessages.Load(),
|
|
"p2p_messages_sent": DefaultMetrics.P2PMessagesSent.Load(),
|
|
"p2p_messages_received": DefaultMetrics.P2PMessagesReceived.Load(),
|
|
}
|
|
}
|