Mining/pkg/mining/metrics.go
Claude 5832752616
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
ax(mining): replace prose description with usage example on DefaultMetrics
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>
2026-04-02 17:19:00 +01:00

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(),
}
}