stillbox/pkg/stats/stats.go
2025-02-22 18:00:09 -05:00

86 lines
1.9 KiB
Go

package stats
import (
"context"
"time"
"dynatron.me/x/stillbox/internal/cache"
"dynatron.me/x/stillbox/internal/common"
"dynatron.me/x/stillbox/internal/jsontypes"
"dynatron.me/x/stillbox/pkg/calls"
"dynatron.me/x/stillbox/pkg/calls/callstore"
"dynatron.me/x/stillbox/pkg/services"
)
const DefaultExpiration = 5 * time.Minute
type Stats interface {
GetCallStats(ctx context.Context, interval calls.StatsInterval) (*calls.Stats, error)
}
type statsKeyType string
const StatsKey statsKeyType = "stats"
func CtxWithStats(ctx context.Context, s Stats) context.Context {
return services.WithValue(ctx, StatsKey, s)
}
func FromCtx(ctx context.Context) Stats {
s, ok := services.Value(ctx, StatsKey).(Stats)
if !ok {
panic("no stats in context")
}
return s
}
type stats struct {
cs callstore.Store
sc cache.Cache[calls.StatsInterval, *calls.Stats]
}
func NewStats(cst callstore.Store, cacheExp time.Duration) *stats {
s := &stats{
cs: cst,
sc: cache.New[calls.StatsInterval, *calls.Stats](cache.WithExpiration(cacheExp)),
}
return s
}
func (s *stats) GetCallStats(ctx context.Context, interval calls.StatsInterval) (*calls.Stats, error) {
st, has := s.sc.Get(interval)
if has {
return st, nil
}
var start time.Time
now := time.Now()
end := now
bnd := common.NewTimeBounder(common.WithLocation(now.Location()))
switch interval {
case calls.IntervalHour:
start = now.Add(-24 * time.Hour) // one day
case calls.IntervalDay:
start = now.Add(-7 * 24 * time.Hour) // one week
case calls.IntervalWeek:
start, end = bnd.GetMonthlyBounds(now)
start, _ = bnd.GetWeeklyBounds(start)
_, end = bnd.GetWeeklyBounds(end)
case calls.IntervalMonth:
start = now.Add(-365 * 24 * time.Hour) // one year
default:
return nil, calls.ErrInvalidInterval
}
st, err := s.cs.CallStats(ctx, interval, jsontypes.Time(start), jsontypes.Time(end))
if err != nil {
return nil, err
}
s.sc.Set(interval, st)
return st, nil
}