98 lines
2.6 KiB
Go
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
|
|
}
|