Files
pika/pkg/agent/service/metrics_store.go
T
2026-08-12 18:59:29 +08:00

98 lines
2.6 KiB
Go

package service
import (
"sort"
"sync"
"github.com/pika-monitor/pika/internal/protocol"
)
// metricsStore is a constant-size, latest-wins snapshot store. Delivery state
// uses an internal sequence instead of sample timestamps, which may collide or
// move backwards when the system clock changes.
type metricsStore struct {
mu sync.RWMutex
latest map[protocol.MetricType]versionedMetric
nextSeq uint64
ackedSeq uint64
generation uint64
}
type versionedMetric struct {
sample protocol.MetricSample
seq uint64
}
// metricsCursor identifies exactly the snapshot observed by pending. The
// generation prevents a delayed acknowledgement from an old connection from
// advancing the cursor after reset.
type metricsCursor struct {
seq uint64
generation uint64
}
func newMetricsStore() *metricsStore {
return &metricsStore{latest: make(map[protocol.MetricType]versionedMetric)}
}
// put stores samples in collection order. The last inserted sample for a
// metric type wins, regardless of its wall-clock timestamp.
func (s *metricsStore) put(samples []protocol.MetricSample) {
s.mu.Lock()
defer s.mu.Unlock()
for _, sample := range samples {
s.nextSeq++
s.latest[sample.Type] = versionedMetric{sample: sample, seq: s.nextSeq}
}
}
// pending returns samples not acknowledged on the current connection. It does
// not mutate delivery state; the caller must acknowledge the cursor only after
// the complete network write succeeds.
func (s *metricsStore) pending() ([]protocol.MetricSample, metricsCursor) {
s.mu.RLock()
defer s.mu.RUnlock()
cursor := metricsCursor{seq: s.ackedSeq, generation: s.generation}
samples := make([]protocol.MetricSample, 0, len(s.latest))
for _, metric := range s.latest {
if metric.seq <= s.ackedSeq {
continue
}
samples = append(samples, metric.sample)
if metric.seq > cursor.seq {
cursor.seq = metric.seq
}
}
sort.Slice(samples, func(i, j int) bool {
if samples[i].Timestamp == samples[j].Timestamp {
return samples[i].Type < samples[j].Type
}
return samples[i].Timestamp < samples[j].Timestamp
})
return samples, cursor
}
// ack commits a successful write. Stale and old-connection acknowledgements
// are harmless.
func (s *metricsStore) ack(cursor metricsCursor) {
s.mu.Lock()
defer s.mu.Unlock()
if cursor.generation == s.generation && cursor.seq > s.ackedSeq {
s.ackedSeq = cursor.seq
}
}
// reset starts a new delivery generation and makes the current full snapshot
// pending for the new connection.
func (s *metricsStore) reset() {
s.mu.Lock()
defer s.mu.Unlock()
s.generation++
s.ackedSeq = 0
}