Simulation initial
This commit is contained in:
parent
dbcef1f229
commit
5da1eb2944
4 changed files with 78 additions and 9 deletions
|
@ -39,6 +39,7 @@ type alerter struct {
|
||||||
scorer trending.Scorer[cl.Talkgroup]
|
scorer trending.Scorer[cl.Talkgroup]
|
||||||
scores trending.Scores[cl.Talkgroup]
|
scores trending.Scores[cl.Talkgroup]
|
||||||
lastScore time.Time
|
lastScore time.Time
|
||||||
|
sim *Simulation
|
||||||
}
|
}
|
||||||
|
|
||||||
type noopAlerter struct{}
|
type noopAlerter struct{}
|
||||||
|
@ -126,7 +127,7 @@ func (as *alerter) startBackfill(ctx context.Context) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
since := now.Add(-24 * time.Hour * time.Duration(as.cfg.LookbackDays))
|
since := now.Add(-24 * time.Hour * time.Duration(as.cfg.LookbackDays))
|
||||||
log.Debug().Time("since", since).Msg("starting stats backfill")
|
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 {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("backfill failed")
|
log.Error().Err(err).Msg("backfill failed")
|
||||||
return
|
return
|
||||||
|
@ -143,11 +144,11 @@ func (as *alerter) score(ctx context.Context, now time.Time) {
|
||||||
sort.Sort(as.scores)
|
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)
|
db := database.FromCtx(ctx)
|
||||||
const backfillStatsQuery = `SELECT system, talkgroup, call_date FROM calls WHERE call_date > $1 AND call_date < $2`
|
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 {
|
if err != nil {
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
59
pkg/gordio/alerting/simulate.go
Normal file
59
pkg/gordio/alerting/simulate.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -60,13 +60,15 @@ func (as *alerter) tgStats(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderData := struct {
|
renderData := struct {
|
||||||
TGs map[calls.Talkgroup]database.GetTalkgroupsByPackedIDsRow
|
TGs map[calls.Talkgroup]database.GetTalkgroupsByPackedIDsRow
|
||||||
Scores trending.Scores[calls.Talkgroup]
|
Scores trending.Scores[calls.Talkgroup]
|
||||||
LastScore time.Time
|
LastScore time.Time
|
||||||
|
Simulation *Simulation
|
||||||
}{
|
}{
|
||||||
TGs: tgMap,
|
TGs: tgMap,
|
||||||
Scores: as.scores,
|
Scores: as.scores,
|
||||||
LastScore: as.lastScore,
|
LastScore: as.lastScore,
|
||||||
|
Simulation: as.sim,
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
|
@ -46,7 +46,14 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="simform">
|
||||||
|
</div>
|
||||||
<table>
|
<table>
|
||||||
|
{{ if .Simluation }}
|
||||||
|
<tr>
|
||||||
|
<td colspan="10">Simulating from {{ .Simulation.ScoreStart }} until now</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
<tr>
|
<tr>
|
||||||
<th>System</th>
|
<th>System</th>
|
||||||
<th>TG</th>
|
<th>TG</th>
|
||||||
|
|
Loading…
Reference in a new issue