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 ×eries{interval: interval, total: totalMetric, samples: samples} }