Behaviour norms
normsLearn what normal looks like for the app (traffic, errors, latency, CPU, loop delay, memory) by hour of week, deterministically and with no model, and emit an anomaly signal when it leaves the range.
normsFree in your app, Apache-2.0Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
norms lets your app learn what normal looks like for itself, by hour of week. When a metric leaves that range and stays out, it emits one anomaly.detected signal.
At a glance
norms:
season: hour-of-week
tz: Asia/Kolkata
learn: 7d
min-samples: 20 # observations a bucket needs before it speaks
persist: 5m # how often the model is stored, 1s to 24himport { norms } from "mainsdk";
const n = await norms.get("requests");
console.log(n.bucket, n.p50, n.p99, n.learning);Drivers
1 drivers. Select one to see its line in main.yaml.
norms:
driver: builtinChange this one line to swap providers; your calls stay the same.Built-in and local drivers integration
Codes
| Code | Where it appears | Meaning |
|---|---|---|
MAIN-NORMS-0001 | Checkmainsdk check, before deploy | A norms value is invalid: every outside 1m to 15m, an unknown season, metric or time zone, learn over 28d, min-samples under 10, z under 2, or margin outside 0% to 200% |
MAIN-NORMS-0002 | RuntimeError responses and logs | See the spec |
MAIN-NORMS-0003 | RuntimeError responses and logs | See the spec |
MAIN-NORMS-0004 | RuntimeError responses and logs | See the spec |
Features
Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.
| Feature | Phase | Milestone | Spec ticket |
|---|---|---|---|
| EWMA and decayed histogram model | Phase 1 | 0.4 | norms#model |
| Seasonal buckets | Phase 1 | 0.4 | norms#season |
| Auth failures, access denials and refused egress as inputs | Phase 1 | 0.4 | norms#inputs-security |
| Anomaly signals | Phase 1 | 0.4 | norms#anomaly |
| Model persisted in the runtime store | Phase 1 | 0.4 | norms#persist |
Quick start
norms:
season: hour-of-week
tz: Asia/Kolkata
learn: 7dimport { norms } from "mainsdk";
const n = await norms.get("requests");
console.log(n.bucket, n.p50, n.p99, n.learning); // "mon-10" 118 240 trueAdding a norms section turns learning on. Without it nothing runs and norms.get throws MAIN-CONFIG-0003.
Configuration
norms:
every: 1m # one value per metric per window; 1m to 15m
season: hour-of-week # hour-of-week (168 buckets) | hour-of-day (24) | none (1)
tz: Asia/Kolkata # time zone for buckets; default UTC
learn: 7d # anomalies are info and learning: true until the model is this old; 0 to 28d
min-samples: 30 # values a bucket needs before it is used; else hour-of-day, else global
half-life: 14d # how fast old behaviour fades, counted in visits to a bucket
metrics: [cpu, loop, memory, requests, errors, latency]
sensitivity: { z: 4, quantile: p99, margin: 20%, hold: 3 } # hold: consecutive windows out of range
persist: 5m| Key | What it does |
|---|---|
every | The window. Windows are aligned to the clock, so instances agree on buckets. |
season | How values are bucketed. Scoring uses the finest bucket with min-samples values. |
learn | While the model is younger than this, anomalies are info with learning: true. |
half-life | Old behaviour fades by half after this many visits to a bucket. |
sensitivity | A value is out of range high when it is above quantile * (1 + margin) and above mean + z * sd. Only requests is also checked low. hold windows in a row raise the signal. |
persist | How often the model is written to the runtime store, so a restart or another instance keeps what was learned. |
| Metric | One value per window |
|---|---|
cpu, loop, memory | Mean CPU share, highest loop delay p99 and last memory share from resources. Skipped on serverless. |
requests | Requests per minute from the request counters. |
errors, latency | 5xx share and p95 in ms; skipped under 20 requests in the window. |
auth-failures | Refused sign-ins per minute, counted from auth.failed security events when the app has a securityEvents feed; skipped with MAIN-NORMS-0002 without one. |
denials, egress-refused | Access denials per minute from mainsdk:access.decision and enforced refusals per minute from mainsdk:network.decision. Counts only: no user, tenant or address is kept. |
Calls
| Call | What it does |
|---|---|
norms.get(metric, { at? }) | The bucket for now, or for a time: count, mean, sd, p50, p95, p99, learning and the last value. |
norms.list({ cursor?, limit? }) | One view per configured metric, for now. |
norms.delete(metric, { reason }) | Forget one metric's model, or "all". Needs norms.manage and is audited. |
Signals
anomaly.detected(subjectmetric:<name>):value,expected(mean,sd,p50,p99),bucket,score,direction(highorlow),learning. Severity ishighfrom a score of 8,mediumfrom 6, elselow, andinfowhile learning. It is sent again each minute while the anomaly holds.anomaly.clearedafterholdwindows back in range.pressure.contentionwhen loop delay is high while your own CPU is at or under its median: the instance is waiting on something else on the machine.
Helpers for wiring and tests
| Call | What it does |
|---|---|
new Norms(opts) | The learner of one app instance, for tests and custom wiring. |
parseNorms(section?) | Read the norms section of main.yaml into settings. |
normsIssues(section) | List what is wrong in the norms section, without throwing. |
What you get
- A learner that is plain arithmetic: a decayed mean and variance and a decayed 48-bin histogram per hour of the week.
- A value out of range raises one signal with the value, the expected range and a score, and a playbook, a person or the advisor can then act.
- It is free.
- The same input gives the same model and the same signals.
- No dependency, no model and no service.
- Holds numbers only, never a person, a tenant or an address.
- Reads counters the SDK already keeps once a window, off the request path. A store that does not answer leaves the model in memory.
Errors and checks
MAIN-NORMS-0001: anormsvalue is invalid (mainsdk check).MAIN-NORMS-0002: a metric's source is not available; it is skipped, logged once.MAIN-NORMS-0003: the stored model was another version or corrupt; it was discarded and learning restarted.MAIN-NORMS-0004:norms.deleterefused, the caller lacksnorms.manage(403).
Each code's cause and fix is in check codes.
Swapping drivers
norms has no drivers: the learner is built in, and persist sets how often the model is written to the runtime store.
Testing
createSim in @mainsdk/testing plays hours of traffic and CPU curves against an app in a second, on a fake clock, so you can assert what norms noticed. See testing.
npx mainsdk norms # each metric's bucket, count, p50, p99 and learning state
npx mainsdk info --json # the same under "norms"Next
- Signals: where
anomaly.detectedgoes. - Playbooks: act on an anomaly.
- Testing kit:
createSimand the fake clock. - Check codes: each code's cause and fix.
Was this page helpful?