mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-25 19:02:22 +01:00
111 lines
2.7 KiB
Go
111 lines
2.7 KiB
Go
package period
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/bytedance/sonic"
|
|
)
|
|
|
|
type Entries[T any] struct {
|
|
entries [maxEntries]T
|
|
index int
|
|
count int
|
|
interval time.Duration
|
|
lastAdd time.Time
|
|
}
|
|
|
|
const maxEntries = 100
|
|
|
|
func newEntries[T any](duration time.Duration) *Entries[T] {
|
|
interval := max(duration/maxEntries, time.Second)
|
|
return &Entries[T]{
|
|
interval: interval,
|
|
lastAdd: time.Now(),
|
|
}
|
|
}
|
|
|
|
func (e *Entries[T]) Add(now time.Time, info T) {
|
|
if now.Sub(e.lastAdd) < e.interval {
|
|
return
|
|
}
|
|
e.addWithTime(now, info)
|
|
}
|
|
|
|
// addWithTime adds an entry with a specific timestamp without interval checking.
|
|
// This is used internally for reconstructing historical data.
|
|
func (e *Entries[T]) addWithTime(timestamp time.Time, info T) {
|
|
e.entries[e.index] = info
|
|
e.index = (e.index + 1) % maxEntries
|
|
if e.count < maxEntries {
|
|
e.count++
|
|
}
|
|
e.lastAdd = timestamp
|
|
}
|
|
|
|
// validateInterval checks if the current interval matches the expected interval for the duration.
|
|
// Returns true if valid, false if the interval needs to be recalculated.
|
|
func (e *Entries[T]) validateInterval(expectedDuration time.Duration) bool {
|
|
expectedInterval := max(expectedDuration/maxEntries, time.Second)
|
|
return e.interval == expectedInterval
|
|
}
|
|
|
|
// fixInterval recalculates and sets the correct interval based on the expected duration.
|
|
func (e *Entries[T]) fixInterval(expectedDuration time.Duration) {
|
|
e.interval = max(expectedDuration/maxEntries, time.Second)
|
|
}
|
|
|
|
func (e *Entries[T]) Get() []T {
|
|
if e.count < maxEntries {
|
|
return e.entries[:e.count]
|
|
}
|
|
var res [maxEntries]T
|
|
if e.index >= e.count {
|
|
copy(res[:], e.entries[:e.count])
|
|
} else {
|
|
copy(res[:], e.entries[e.index:])
|
|
copy(res[e.count-e.index:], e.entries[:e.index])
|
|
}
|
|
return res[:]
|
|
}
|
|
|
|
type entriesJSON[T any] struct {
|
|
Entries []T `json:"entries"`
|
|
Interval time.Duration `json:"interval"`
|
|
}
|
|
|
|
func (e *Entries[T]) MarshalJSON() ([]byte, error) {
|
|
return sonic.Marshal(entriesJSON[T]{
|
|
Entries: e.Get(),
|
|
Interval: e.interval,
|
|
})
|
|
}
|
|
|
|
func (e *Entries[T]) UnmarshalJSON(data []byte) error {
|
|
var v entriesJSON[T]
|
|
v.Entries = make([]T, 0, maxEntries)
|
|
if err := json.Unmarshal(data, &v); err != nil {
|
|
return err
|
|
}
|
|
if len(v.Entries) == 0 {
|
|
return nil
|
|
}
|
|
entries := v.Entries
|
|
if len(entries) > maxEntries {
|
|
entries = entries[:maxEntries]
|
|
}
|
|
|
|
// Set the interval first before adding entries.
|
|
e.interval = v.Interval
|
|
|
|
// Add entries with proper time spacing to respect the interval.
|
|
now := time.Now()
|
|
for i, info := range entries {
|
|
// Calculate timestamp based on entry position and interval.
|
|
// Most recent entry gets current time, older entries get earlier times.
|
|
entryTime := now.Add(-time.Duration(len(entries)-1-i) * e.interval)
|
|
e.addWithTime(entryTime, info)
|
|
}
|
|
return nil
|
|
}
|