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

105
stats/timeseries.go Normal file
View File

@@ -0,0 +1,105 @@
package stats
import (
"encoding/json"
"fmt"
"sync"
"time"
)
type timeseries struct {
sync.Mutex
now time.Time
size int
interval time.Duration
total metric
samples []metric
}
func (ts *timeseries) Reset() {
ts.total.Reset()
for _, s := range ts.samples {
s.Reset()
}
}
func (ts *timeseries) roll() {
t := time.Now()
roll := int((t.Round(ts.interval).Sub(ts.now.Round(ts.interval))) / ts.interval)
ts.now = t
n := len(ts.samples)
if roll <= 0 {
return
}
if roll >= len(ts.samples) {
ts.Reset()
} else {
for i := 0; i < roll; i++ {
tmp := ts.samples[n-1]
for j := n - 1; j > 0; j-- {
ts.samples[j] = ts.samples[j-1]
}
ts.samples[0] = tmp
ts.samples[0].Reset()
}
ts.total.Aggregate(roll, ts.samples)
}
}
func (ts *timeseries) Add(n float64) {
ts.Lock()
defer ts.Unlock()
ts.roll()
ts.total.Add(n)
ts.samples[0].Add(n)
}
func (ts *timeseries) MarshalJSON() ([]byte, error) {
ts.Lock()
defer ts.Unlock()
ts.roll()
return json.Marshal(struct {
Interval float64 `json:"interval"`
Total Metric `json:"total"`
Samples []metric `json:"samples"`
}{float64(ts.interval) / float64(time.Second), ts.total, ts.samples})
}
func (ts *timeseries) String() string {
ts.Lock()
defer ts.Unlock()
ts.roll()
return ts.total.String()
}
func newTimeseries(builder func() metric, frame string) *timeseries {
var (
totalNum, intervalNum int
totalUnit, intervalUnit rune
)
units := map[rune]time.Duration{
's': time.Second,
'm': time.Minute,
'h': time.Hour,
'd': time.Hour * 24,
'w': time.Hour * 24 * 7,
'M': time.Hour * 24 * 30,
'y': time.Hour * 24 * 365,
}
fmt.Sscanf(frame, "%d%c%d%c", &totalNum, &totalUnit, &intervalNum, &intervalUnit)
interval := units[intervalUnit] * time.Duration(intervalNum)
if interval == 0 {
interval = time.Minute
}
totalDuration := units[totalUnit] * time.Duration(totalNum)
if totalDuration == 0 {
totalDuration = interval * 15
}
n := int(totalDuration / interval)
samples := make([]metric, n, n)
for i := 0; i < n; i++ {
samples[i] = builder()
}
totalMetric := builder()
return &timeseries{interval: interval, total: totalMetric, samples: samples}
}