From 5da1eb294411cbd4aaa26cc747c4d3e889e57cb1 Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Sat, 26 Oct 2024 07:53:25 -0400 Subject: [PATCH] Simulation initial --- pkg/gordio/alerting/alerting.go | 7 ++-- pkg/gordio/alerting/simulate.go | 59 +++++++++++++++++++++++++++++++++ pkg/gordio/alerting/stats.go | 14 ++++---- pkg/gordio/alerting/stats.html | 7 ++++ 4 files changed, 78 insertions(+), 9 deletions(-) create mode 100644 pkg/gordio/alerting/simulate.go diff --git a/pkg/gordio/alerting/alerting.go b/pkg/gordio/alerting/alerting.go index 12c9bfa..de18fcb 100644 --- a/pkg/gordio/alerting/alerting.go +++ b/pkg/gordio/alerting/alerting.go @@ -39,6 +39,7 @@ type alerter struct { scorer trending.Scorer[cl.Talkgroup] scores trending.Scores[cl.Talkgroup] lastScore time.Time + sim *Simulation } type noopAlerter struct{} @@ -126,7 +127,7 @@ func (as *alerter) startBackfill(ctx context.Context) { now := time.Now() since := now.Add(-24 * time.Hour * time.Duration(as.cfg.LookbackDays)) log.Debug().Time("since", since).Msg("starting stats backfill") - count, err := as.backfill(ctx, since) + count, err := as.backfill(ctx, since, now) if err != nil { log.Error().Err(err).Msg("backfill failed") return @@ -143,11 +144,11 @@ func (as *alerter) score(ctx context.Context, now time.Time) { sort.Sort(as.scores) } -func (as *alerter) backfill(ctx context.Context, since time.Time) (count int, err error) { +func (as *alerter) backfill(ctx context.Context, since time.Time, until time.Time) (count int, err error) { db := database.FromCtx(ctx) const backfillStatsQuery = `SELECT system, talkgroup, call_date FROM calls WHERE call_date > $1 AND call_date < $2` - rows, err := db.Query(ctx, backfillStatsQuery, since, timeseries.DefaultClock.Now()) + rows, err := db.Query(ctx, backfillStatsQuery, since, until) if err != nil { return count, err } diff --git a/pkg/gordio/alerting/simulate.go b/pkg/gordio/alerting/simulate.go new file mode 100644 index 0000000..0ca5af4 --- /dev/null +++ b/pkg/gordio/alerting/simulate.go @@ -0,0 +1,59 @@ +package alerting + +import ( + "context" + "encoding/json" + "net/http" + "sort" + "time" + + "dynatron.me/x/stillbox/internal/trending" + cl "dynatron.me/x/stillbox/pkg/calls" + "dynatron.me/x/stillbox/pkg/gordio/config" + + "github.com/rs/zerolog/log" +) + +type Simulation struct { + config.Alerting + ScoreStart time.Time `json:"scoreStart"` + + clock offsetClock + *alerter `json:"-"` +} + +func (s *Simulation) Simulate(ctx context.Context) trending.Scores[cl.Talkgroup] { + s.Enable = true + s.alerter = New(s.Alerting, WithClock(&s.clock)).(*alerter) + s.alerter.sim = s + now := time.Now() + sinceLookback := now.Add(-24 * time.Hour * time.Duration(s.LookbackDays)) + + s.backfill(ctx, sinceLookback, s.ScoreStart) + + for s.clock = offsetClock(now.Sub(s.ScoreStart)); now.After(now.Add(s.clock.Duration())); s.clock += offsetClock(time.Minute) { + s.backfill(ctx, s.clock.Now().Add(-1*time.Minute), s.clock.Now()) + s.scores = s.scorer.Score() + } + + s.lastScore = now + sort.Sort(s.scores) + + return s.scores +} + +func simulateHandler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + s := new(Simulation) + err := json.NewDecoder(r.Body).Decode(s) + if err != nil { + log.Error().Err(err).Msg("simulate decode") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // TODO: sanity check that ScoreStart is not before lookback and lookback is sane + + s.Simulate(ctx) + s.tgStats(w, r) +} diff --git a/pkg/gordio/alerting/stats.go b/pkg/gordio/alerting/stats.go index 164b565..0457fad 100644 --- a/pkg/gordio/alerting/stats.go +++ b/pkg/gordio/alerting/stats.go @@ -60,13 +60,15 @@ func (as *alerter) tgStats(w http.ResponseWriter, r *http.Request) { } renderData := struct { - TGs map[calls.Talkgroup]database.GetTalkgroupsByPackedIDsRow - Scores trending.Scores[calls.Talkgroup] - LastScore time.Time + TGs map[calls.Talkgroup]database.GetTalkgroupsByPackedIDsRow + Scores trending.Scores[calls.Talkgroup] + LastScore time.Time + Simulation *Simulation }{ - TGs: tgMap, - Scores: as.scores, - LastScore: as.lastScore, + TGs: tgMap, + Scores: as.scores, + LastScore: as.lastScore, + Simulation: as.sim, } w.WriteHeader(http.StatusOK) diff --git a/pkg/gordio/alerting/stats.html b/pkg/gordio/alerting/stats.html index 61e4e56..37dfaa8 100644 --- a/pkg/gordio/alerting/stats.html +++ b/pkg/gordio/alerting/stats.html @@ -46,7 +46,14 @@ +
+
+ {{ if .Simluation }} + + + + {{ end }}
Simulating from {{ .Simulation.ScoreStart }} until now
System TG