package mining import ( "sync" "sync/atomic" "time" ) // Metrics provides simple instrumentation counters for the mining package. // These can be exposed via Prometheus or other metrics systems in the future. 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 } // LatencyHistogram tracks request latencies with basic percentile support. type LatencyHistogram struct { mu 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.mu.Lock() defer h.mu.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.mu.Lock() defer h.mu.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.mu.Lock() defer h.mu.Unlock() return len(h.samples) } // DefaultMetrics is the global metrics instance. 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(), } }