The predefined rules are too constraining: an ISP cannot say "CPU over 80% on servers for 15 minutes" or "uplink without a description", and every new need meant editing config.yaml. Admins now build their own rules; the shipped ones stay as read-only built-in macros with their names unchanged, so v1.1 policies and alert history keep working. Custom rules live in a new alert_rules table (migration 13) and are builder-only. A metric rule names a catalogue metric (every pollable profile metric plus push-ingested names) with a transform (value, rate, percent_of), window, function, aggregate, comparison and threshold; the pure internal/alertrule package compiles it to MetricsQL from fixed templates, so no user text reaches a query. An inventory rule ANDs one to five conditions over whitelisted device or port fields and is evaluated in Go over store rows. The evaluator takes its rule set from a function each cycle, so edits apply live, and both kinds use the existing state machine, policies, suppression and notifications. Comparisons now cover > >= < <= == != everywhere. utilisation is not offered: port speed is not a stored series. config.yaml rules still work but are deprecated. API (additive): rule list gains id/kind/editable/spec/expr, plus get, create, update, delete, catalogue, test (viewer, read-only) and copy of a built-in (cpu_high, ups_on_battery, printer_supply_empty) to a custom rule. Deleting or renaming a rule that a policy names is a 409. The UI gets an Alert rules tab with a builder dialog, a test button and the generated query, and the policy dialog lists custom rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T4PdJUPrakM8fHvN8jYv2x
50 lines
1.4 KiB
Go
50 lines
1.4 KiB
Go
package alert
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// Reading is one entity's measured value, as a Source reports it.
|
|
type Reading struct {
|
|
Device, Iface, Component string
|
|
Value float64
|
|
}
|
|
|
|
// Source supplies a rule's readings from somewhere other than the metric store. The app implements it for
|
|
// inventory rules, which are evaluated in Go over device and interface rows. Like a query result it must
|
|
// include entities that do not breach (value below the threshold), because a missing entity is "no data", not
|
|
// "resolved".
|
|
type Source interface {
|
|
Read(ctx context.Context, now time.Time) ([]Reading, error)
|
|
}
|
|
|
|
// ProbeReading is a reading and whether it breaches the rule's own threshold.
|
|
type ProbeReading struct {
|
|
Reading
|
|
Breach bool
|
|
}
|
|
|
|
// Probe runs one rule once, without touching alert state or any policy, and reports what it sees. The API's
|
|
// rule test uses it to show an admin what a draft rule would do.
|
|
func Probe(ctx context.Context, q Querier, storeName string, r Rule, now time.Time) ([]ProbeReading, error) {
|
|
c, err := compile(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rds []Reading
|
|
if r.Source != nil {
|
|
rds, err = r.Source.Read(ctx, now)
|
|
} else {
|
|
rds, err = readMetric(ctx, q, storeName, c, now)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]ProbeReading, 0, len(rds))
|
|
for _, rd := range rds {
|
|
out = append(out, ProbeReading{Reading: rd, Breach: c.breach(rd.Value, r.Threshold)})
|
|
}
|
|
return out, nil
|
|
}
|