feat: Monitor grath zoom
This commit is contained in:
@@ -36,6 +36,7 @@ var ScopedCollections = []string{
|
||||
"monitors",
|
||||
"incidents",
|
||||
"monitor_rollups",
|
||||
"monitor_samples",
|
||||
"notification_channels",
|
||||
"console_sessions",
|
||||
"audit_logs",
|
||||
|
||||
@@ -221,6 +221,7 @@ func DeleteMonitor(instanceID, monitorID string) error {
|
||||
}
|
||||
db.Col("incidents").DeleteMany(ctx, bson.M{"monitor_id": monitorID, "instance_id": instanceID})
|
||||
db.Col("monitor_rollups").DeleteMany(ctx, bson.M{"monitor_id": monitorID, "instance_id": instanceID})
|
||||
db.Col("monitor_samples").DeleteMany(ctx, bson.M{"monitor_id": monitorID, "instance_id": instanceID})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -258,6 +259,31 @@ func UptimeRollups(instanceID, monitorID string, since time.Time) ([]models.Roll
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MaxMonitorSamples bounds one range read. At the 10s floor, 48h is 17,280
|
||||
// checks; the chart buckets them anyway, so a cap costs nothing visible and
|
||||
// stops one monitor pulling a megabyte of JSON per poll.
|
||||
const MaxMonitorSamples = 6000
|
||||
|
||||
// MonitorSamples returns individual check results since a point in time,
|
||||
// oldest first. Samples older than MonitorSampleTTL have expired, so an early
|
||||
// `since` silently returns a shorter window rather than an error — the caller
|
||||
// draws the gap.
|
||||
func MonitorSamples(instanceID, monitorID string, since time.Time) ([]models.MonitorSample, error) {
|
||||
ctx, cancel := monCtx()
|
||||
defer cancel()
|
||||
cur, err := db.Col("monitor_samples").Find(ctx,
|
||||
bson.M{"monitor_id": monitorID, "instance_id": instanceID, "at": bson.M{"$gte": since}},
|
||||
options.Find().SetSort(bson.M{"at": 1}).SetLimit(MaxMonitorSamples))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []models.MonitorSample
|
||||
if err := cur.All(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func IngestResult(instanceID, runner, monitorID string, res checker.Result) error {
|
||||
if instanceID == "" {
|
||||
return errors.New("instance id required")
|
||||
@@ -328,6 +354,17 @@ func ingestResult(instanceID, runner, monitorID string, res checker.Result) erro
|
||||
up = 1
|
||||
}
|
||||
|
||||
/* The sample is the same result at full resolution, expiring by TTL. It is
|
||||
written next to the rollup rather than instead of it: the rollup is what
|
||||
survives, the sample is what the sub-hour views read. */
|
||||
db.Col("monitor_samples").InsertOne(ctx, models.MonitorSample{
|
||||
InstanceID: m.InstanceID,
|
||||
MonitorID: monitorID,
|
||||
At: now,
|
||||
Up: res.Up,
|
||||
LatencyMs: res.LatencyMs,
|
||||
})
|
||||
|
||||
db.Col("monitor_rollups").UpdateOne(ctx,
|
||||
bson.M{"monitor_id": monitorID, "period_start": bucket},
|
||||
bson.M{
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/db"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo"
|
||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||
)
|
||||
|
||||
// MonitorSampleTTL is how long an individual check result is kept.
|
||||
//
|
||||
// It matches the longest range the chart draws from samples rather than the
|
||||
// longest range it draws at all: 24h and 48h come from the hourly rollups,
|
||||
// which are permanent. Keeping samples past the window that reads them would
|
||||
// only grow the collection.
|
||||
const MonitorSampleTTL = 48 * time.Hour
|
||||
|
||||
// EnsureMonitorSampleIndexes declares the sample range index and its TTL.
|
||||
//
|
||||
// Warn rather than fatal, like the other history indexes — but note the TTL is
|
||||
// not an optimisation: without it nothing ever removes a sample, and the
|
||||
// collection grows at the fleet's total check rate forever. A boot that logs
|
||||
// this warning needs following up.
|
||||
func EnsureMonitorSampleIndexes() error {
|
||||
ctx := context.Background()
|
||||
|
||||
idx := []mongo.IndexModel{
|
||||
// Every read is a range scan over this key.
|
||||
{Keys: bson.D{{Key: "instance_id", Value: 1}, {Key: "monitor_id", Value: 1}, {Key: "at", Value: 1}}},
|
||||
// Expiry is Mongo's job: a sweeper would be another leader-scoped loop
|
||||
// doing what the server already does for free.
|
||||
{Keys: bson.D{{Key: "at", Value: 1}}, Options: options.Index().SetExpireAfterSeconds(int32(MonitorSampleTTL.Seconds()))},
|
||||
}
|
||||
if _, err := db.Col("monitor_samples").Indexes().CreateMany(ctx, idx); err != nil {
|
||||
log.Printf("warning: monitor_samples indexes: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user