Mining/pkg/mining/metrics.go
Claude 3bf5496fce
Some checks failed
Security Scan / security (push) Has been cancelled
Test / test (push) Has been cancelled
ax(batch): fix abbreviated names in comment examples and locals
h→hash/histogram, e→miningError, a/b→current/previous,
n→count, user/pass→username/password, passMatch→passwordMatch
in comment examples and auth.go locals.

Co-Authored-By: Charon <charon@lethean.io>
2026-04-02 18:50:29 +01:00

177 lines
5.1 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
}
// histogram := mining.NewLatencyHistogram(1000)
// histogram.Record(42 * time.Millisecond)
type LatencyHistogram struct {
mutex sync.Mutex
samples []time.Duration
maxSize int
}
// histogram := 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 (histogram *LatencyHistogram) Record(duration time.Duration) {
histogram.mutex.Lock()
defer histogram.mutex.Unlock()
if len(histogram.samples) >= histogram.maxSize {
// Ring buffer behavior - overwrite oldest
copy(histogram.samples, histogram.samples[1:])
histogram.samples = histogram.samples[:len(histogram.samples)-1]
}
histogram.samples = append(histogram.samples, duration)
}
// avg := h.Average() // returns 0 if no samples recorded
func (histogram *LatencyHistogram) Average() time.Duration {
histogram.mutex.Lock()
defer histogram.mutex.Unlock()
if len(histogram.samples) == 0 {
return 0
}
var total time.Duration
for _, sample := range histogram.samples {
total += sample
}
return total / time.Duration(len(histogram.samples))
}
// if h.Count() == 0 { return } // guard before calling Average()
func (histogram *LatencyHistogram) Count() int {
histogram.mutex.Lock()
defer histogram.mutex.Unlock()
return len(histogram.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(),
}
}