Show 90-day uptime bars on the status page

Every check is also counted in a per-day table that outlives the raw checks, so the public page
can draw one bar per UTC day with a tooltip, as most hosted status pages do. Existing databases
are backfilled once from the checks they still have.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-27 04:22:54 -06:00
1 parent 73f45fb3ae
commit 4ea4fe5289
7 files changed
+221 -14

No files matched your search

+17 -1
View File
@@ -45,6 +45,9 @@ CREATE INDEX IF NOT EXISTS messages_topic ON messages(topic, time);
CREATE TABLE IF NOT EXISTS checks (
monitor_id TEXT NOT NULL, time INTEGER NOT NULL, ok INTEGER NOT NULL, ms INTEGER NOT NULL, detail TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS checks_monitor ON checks(monitor_id, time);
-- One row per monitor per UTC day, kept far longer than raw checks: it feeds the status page's uptime bars.
CREATE TABLE IF NOT EXISTS daily (
monitor_id TEXT NOT NULL, day TEXT NOT NULL, checks INTEGER NOT NULL, fails INTEGER NOT NULL, PRIMARY KEY (monitor_id, day));
CREATE TABLE IF NOT EXISTS mstate (
monitor_id TEXT PRIMARY KEY, status TEXT NOT NULL, since INTEGER NOT NULL, last_check INTEGER NOT NULL,
last_ms INTEGER NOT NULL, fails INTEGER NOT NULL, last_ping INTEGER NOT NULL, detail TEXT NOT NULL);
@@ -78,7 +81,19 @@ func OpenStore(dir string, box *secretbox.Box) (*Store, error) {
db.Close()
return nil, err
}
return &Store{db: db, box: box}, nil
st := &Store{db: db, box: box}
st.backfillDaily()
return st, nil
}
// backfillDaily fills the daily table from the raw checks, but only when it is empty: a database
// from before the table existed. After that every check is counted as it is recorded.
func (s *Store) backfillDaily() {
var have int
if s.db.QueryRow(`SELECT count(*) FROM daily`).Scan(&have) == nil && have == 0 {
s.db.Exec(`INSERT OR IGNORE INTO daily(monitor_id,day,checks,fails)
SELECT monitor_id, date(time,'unixepoch'), count(*), count(*)-sum(ok) FROM checks GROUP BY monitor_id, date(time,'unixepoch')`)
}
}
func (s *Store) Close() error { return s.db.Close() }
@@ -234,6 +249,7 @@ func (s *Store) events(scope, ref string) []Event {
func (s *Store) prune(now time.Time, messageDays, checkDays int) {
s.db.Exec(`DELETE FROM messages WHERE time < ?`, now.AddDate(0, 0, -messageDays).Unix())
s.db.Exec(`DELETE FROM checks WHERE time < ?`, now.AddDate(0, 0, -checkDays).Unix())
s.db.Exec(`DELETE FROM daily WHERE day < ?`, now.UTC().AddDate(0, 0, -400).Format("2006-01-02"))
s.db.Exec(`DELETE FROM sessions WHERE expires < ?`, now.Unix())
s.db.Exec(`DELETE FROM alerts WHERE status='resolved' AND resolved_at < ? AND incident_id=''`, now.AddDate(0, 0, -90).Unix())
}