Checkpoint

This commit is contained in:
2025-10-06 22:25:23 +02:00
parent a23259cfdc
commit a254b306f2
48 changed files with 3327 additions and 212 deletions

104
stats/stats.go Normal file
View File

@@ -0,0 +1,104 @@
package stats
import (
"encoding/json"
"math"
"sort"
"strconv"
"sync/atomic"
)
// Metric is a single meter (counter, gauge or histogram, optionally - with history)
type Metric interface {
Add(n float64)
String() string
}
// metric is an extended private interface with some additional internal
// methods used by timeseries. Counters, gauges and histograms implement it.
type metric interface {
Metric
Reset()
Aggregate(roll int, samples []metric)
}
type multimetric []*timeseries
func (mm multimetric) Add(n float64) {
for _, m := range mm {
m.Add(n)
}
}
func (mm multimetric) MarshalJSON() ([]byte, error) {
b := []byte(`{"metrics":[`)
for i, m := range mm {
if i != 0 {
b = append(b, ',')
}
x, _ := json.Marshal(m)
b = append(b, x...)
}
b = append(b, ']', '}')
return b, nil
}
func (mm multimetric) String() string {
return mm[len(mm)-1].String()
}
func newMetric(builder func() metric, frames ...string) Metric {
if len(frames) == 0 {
return builder()
}
if len(frames) == 1 {
return newTimeseries(builder, frames[0])
}
mm := multimetric{}
for _, frame := range frames {
mm = append(mm, newTimeseries(builder, frame))
}
sort.Slice(mm, func(i, j int) bool {
a, b := mm[i], mm[j]
return a.interval.Seconds()*float64(len(a.samples)) < b.interval.Seconds()*float64(len(b.samples))
})
return mm
}
// NewCounter returns a counter metric that increments the value with each
// incoming number.
func NewCounter(frames ...string) Metric {
return newMetric(func() metric { return &counter{} }, frames...)
}
type counter struct {
count uint64
}
func (c *counter) String() string { return strconv.FormatFloat(c.value(), 'g', -1, 64) }
func (c *counter) Reset() { atomic.StoreUint64(&c.count, math.Float64bits(0)) }
func (c *counter) value() float64 { return math.Float64frombits(atomic.LoadUint64(&c.count)) }
func (c *counter) Add(n float64) {
for {
old := math.Float64frombits(atomic.LoadUint64(&c.count))
new := old + n
if atomic.CompareAndSwapUint64(&c.count, math.Float64bits(old), math.Float64bits(new)) {
return
}
}
}
func (c *counter) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Type string `json:"type"`
Count float64 `json:"count"`
}{"c", c.value()})
}
func (c *counter) Aggregate(roll int, samples []metric) {
c.Reset()
for _, s := range samples {
c.Add(s.(*counter).value())
}
}