laceBaseline¶
Response timing baseline monitoring and spike detection for Lace probes. Bundled with every executor as builtin:laceBaseline.
Purpose¶
laceBaseline tracks rolling averages of HTTP response timing metrics across probe runs. When a metric deviates significantly from its baseline average, the extension dispatches a structured() notification via laceNotifications.
This extension demonstrates extension composition -- it depends on laceNotifications and calls its exposed pushNotification() function to emit spike alerts. All spike data flows through actions.notifications; the extension does not declare its own action array.
Activation¶
# lace.config
[extensions.laceNotifications]
laceext = "builtin:laceNotifications"
[extensions.laceBaseline]
laceext = "builtin:laceBaseline"
Both extensions must be listed. laceBaseline declares require = ["laceNotifications"] and will fail startup if laceNotifications is absent.
Tracked metrics¶
All timing fields from the response object:
| Metric | Description |
|---|---|
responseTimeMs |
Total response time |
dnsMs |
DNS resolution |
connectMs |
TCP connection |
tlsMs |
TLS handshake (0 for plain HTTP) |
ttfbMs |
Time to first byte |
transferMs |
Body transfer |
sizeBytes |
Response body size |
Stats model¶
Stats are stored in result.runVars["laceBaseline.stats"] and carried across runs via the prev mechanism:
{
"count": 10,
"sums": {
"responseTimeMs": 1450.0,
"dnsMs": 120.0,
"connectMs": 340.0,
"tlsMs": 280.0,
"ttfbMs": 980.0,
"transferMs": 470.0,
"sizeBytes": 51200.0
}
}
The average for any metric is sums[metric] / count. The model stores count and sums rather than averages to allow incremental updates without precision loss.
The backend persists the full result after each run and passes it as prev on the next run. The extension reads prev.runVars["laceBaseline.stats"] for the accumulated baseline and emits updated stats at the end of each run.
Spike detection¶
On each successful call (on call hook):
- Read baseline stats from
prev.runVars["laceBaseline.stats"]. - If
count < min_entries, skip detection (not enough data yet). - For each tracked metric, compute
average = sum / count. - If
actual > average * spike_multiplierandaverage > 0, dispatch astructured()notification vialaceNotifications.pushNotification().
The average > 0 guard prevents false positives for metrics that are normally zero (e.g. tlsMs for plain HTTP).
Spike detection rule¶
when call.outcome eq "success"
when not is_null(call.response)
let $stats = prev.runVars["laceBaseline.stats"]
when not is_null($stats)
when not is_null($stats.count)
when $stats.count gte config.min_entries
let $mult = config.spike_multiplier
let $resp = call.response
check_spike($stats, "responseTimeMs", $resp.responseTimeMs, call.index, $mult)
check_spike($stats, "dnsMs", $resp.dnsMs, call.index, $mult)
check_spike($stats, "connectMs", $resp.connectMs, call.index, $mult)
// ... all 7 metrics
Stats accumulation¶
At the end of each run (on script hook):
- Read previous stats from
prev.runVars["laceBaseline.stats"](or initialize from zeros). - Iterate all calls from the current run.
- For each successful call with a response, add its timing values to the rolling sums and increment count.
- Emit updated stats to
result.runVars["laceBaseline.stats"].
The accumulate_stats function uses the set statement for in-loop accumulation:
let $count = is_null(prev_stats) ? 0 : prev_stats.count
let $s_rt = is_null(prev_stats) ? 0.0 : prev_stats.sums.responseTimeMs
// ... initialize all sums
for $call in calls:
when $call.outcome eq "success":
when not is_null($call.response):
let $skip = spike_action eq "skip" ? check_any_spike(prev_stats, $call.response, spike_multiplier) : false
when not $skip:
set $count = $count + 1
set $s_rt = $s_rt + $call.response.responseTimeMs
// ... accumulate all metrics
return { count: $count, sums: { responseTimeMs: $s_rt, ... } }
When spike_action = "skip", calls where any metric would spike are excluded from the rolling average, preventing one-off anomalies from polluting the baseline.
Configuration¶
| Key | Type | Default | Description |
|---|---|---|---|
min_entries |
int | 5 |
Minimum data points before spike detection activates |
spike_multiplier |
float | 3.0 |
Metric must exceed average x multiplier to be a spike |
spike_action |
string | "include" |
"include" or "skip" -- whether spiking calls are included in the rolling average |
Override in lace.config:
[extensions.laceBaseline]
laceext = "builtin:laceBaseline"
min_entries = 10
spike_multiplier = 2.5
spike_action = "skip"
Exposed functions¶
check_spike(stats, metric_name, actual, call_index, multiplier)¶
Checks a single metric against the baseline and dispatches a notification if the spike threshold is exceeded. Other extensions that require = ["laceBaseline"] can call this for custom baseline checks:
Returns true if a spike was detected and a notification dispatched, null otherwise.
Notification format¶
Spike notifications use structured() and are emitted into actions.notifications with trigger: "baseline_spike":
{
"callIndex": 0,
"conditionIndex": -1,
"trigger": "baseline_spike",
"scope": "responseTimeMs",
"notification": {
"tag": "structured",
"data": {
"metric": "responseTimeMs",
"actual": 1500,
"average": 145.0,
"threshold": 435.0,
"multiplier": 3.0
}
}
}
The backend receives these as regular notification events and can format, group, and deliver them alongside other notifications.