API reference¶
Models¶
Typed domain models for SLOs.
These models are the vocabulary of the whole library: an :class:SLO bundles
an :class:SLI (how we measure success), a :class:Target (how reliable we
promise to be), and a :class:Window (over what rolling period).
Everything downstream — the budget engine, burn-rate math, and alert-rule generation — consumes these models, so they are intentionally small and strict.
AlertingConfig
¶
Bases: BaseModel
Optional per-SLO alerting policy overriding the built-in defaults.
Source code in src/slo_kit/models.py
151 152 153 154 155 156 | |
BurnCondition
¶
Bases: BaseModel
One row of a multi-window multi-burn-rate alert policy.
A condition fires only when the burn rate over both the long and short
windows exceeds threshold. The long window gives sensitivity to the
right amount of budget burn; the short "for real / still ongoing" window
makes the alert resolve quickly once the incident ends.
Source code in src/slo_kit/models.py
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | |
SLI
¶
Bases: BaseModel
A Service-Level Indicator expressed as a good / total event ratio.
Both queries are source-native expressions (PromQL for the Prometheus
source). They may contain a {window} placeholder which the source
substitutes when it evaluates the SLI over a specific window::
good_query: 'sum(rate(http_requests_total{code!~"5.."}[{window}]))'
total_query: 'sum(rate(http_requests_total[{window}]))'
Source code in src/slo_kit/models.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | |
SLO
¶
Bases: BaseModel
A Service-Level Objective: an SLI held to a Target over a Window.
Source code in src/slo_kit/models.py
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | |
evaluate(source, window=None)
¶
Evaluate the SLI over window and return the compliance ratio.
Source code in src/slo_kit/models.py
194 195 196 | |
sample(source, window=None)
¶
Sample good/total event counts for this SLI over window.
The {window} placeholder in the SLI queries is substituted with the
given window (defaulting to the SLO's own window) before querying.
Source code in src/slo_kit/models.py
181 182 183 184 185 186 187 188 189 190 191 192 | |
Target
¶
Bases: BaseModel
The reliability objective, e.g. 0.999 (three nines).
Source code in src/slo_kit/models.py
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
error_budget
property
¶
The fraction of events allowed to fail: 1 - objective.
Window
¶
Bases: BaseModel
A rolling time window, e.g. the 30-day SLO window or a 5m alert window.
Source code in src/slo_kit/models.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
parse_duration(value)
¶
Parse a Prometheus-style duration string into seconds.
parse_duration("5m") 300.0 parse_duration("30d") 2592000.0
Source code in src/slo_kit/models.py
37 38 39 40 41 42 43 44 45 46 47 48 49 | |
Spec loading¶
Load and validate SLO specs from YAML (or plain Python dicts).
The YAML schema mirrors the models but uses friendly scalar fields so specs read naturally::
apiVersion: slo-kit/v1
name: checkout-availability
service: checkout
description: Checkout API availability
objective: 0.999
window: 30d
sli:
good_query: 'sum(rate(http_requests_total{job="checkout",code!~"5.."}[{window}]))'
total_query: 'sum(rate(http_requests_total{job="checkout"}[{window}]))'
labels:
team: payments
alerting:
conditions:
- name: fast_burn
long_window: 1h
short_window: 5m
threshold: 14.4
severity: page
SpecError
¶
Bases: ValueError
Raised when a spec is structurally invalid.
Source code in src/slo_kit/spec.py
41 42 | |
dump_spec(slo)
¶
Serialize an :class:SLO back to canonical YAML.
Source code in src/slo_kit/spec.py
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | |
load_spec(path)
¶
Load a single SLO spec from a YAML file.
Source code in src/slo_kit/spec.py
107 108 109 110 111 112 113 114 115 116 117 118 | |
load_spec_dict(data)
¶
Build a validated :class:SLO from an already-parsed mapping.
Source code in src/slo_kit/spec.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
load_specs(path)
¶
Load one or more SLO specs from a YAML file (supports multi-doc YAML).
Source code in src/slo_kit/spec.py
121 122 123 124 125 126 127 128 129 130 131 132 133 | |
Metric sources¶
The MetricSource protocol shared by all backends.
A metric source knows one thing: given a backend-native query, return a single scalar value. Everything else — SLI evaluation, budgets, burn rates — is built on top of that primitive, so adding a backend (Prometheus, OTel, a fake for tests) means implementing a single method.
MetricSource
¶
Bases: Protocol
A backend capable of resolving a query to a single float.
The query may contain a {window} placeholder; the caller substitutes a
concrete duration before calling :meth:scalar.
Source code in src/slo_kit/sources/base.py
37 38 39 40 41 42 43 44 45 46 47 | |
scalar(query)
¶
Evaluate query and return its scalar result.
Source code in src/slo_kit/sources/base.py
45 46 47 | |
SLISample
dataclass
¶
Good/total counts for an SLI over some window.
Source code in src/slo_kit/sources/base.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
ratio
property
¶
The good ratio (SLI value); 1.0 when there is no traffic.
Prometheus metric source.
Talks to the Prometheus HTTP API (/api/v1/query) and reduces the result to
a single scalar. Instant vectors with a single sample and scalar results are
both supported; multi-sample vectors are summed so that a bare selector like
sum(rate(...)) and an un-aggregated one behave sensibly.
PrometheusError
¶
Bases: RuntimeError
Raised when Prometheus returns an error or an unusable response.
Source code in src/slo_kit/sources/prometheus.py
18 19 | |
PrometheusSource
¶
A :class:~slo_kit.sources.base.MetricSource backed by Prometheus.
Source code in src/slo_kit/sources/prometheus.py
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
scalar(query)
¶
Run an instant query and collapse the result to a single float.
Source code in src/slo_kit/sources/prometheus.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
OpenTelemetry metric source.
Most OTel metrics pipelines land in a backend that speaks PromQL (the OTLP
metrics data model maps cleanly onto Prometheus, and the Collector's
prometheus / prometheusremotewrite exporters are the common path). So
the OTel source is a thin specialization of the Prometheus source that applies
OTLP naming conventions to the query, keeping the same MetricSource
protocol and identical evaluation behaviour.
If your OTel metrics are queried through a backend with a different API, this
class is the seam to override :meth:scalar for that API.
OTelSource
¶
Bases: PrometheusSource
OTLP-metrics source backed by a PromQL-compatible query endpoint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_url
|
str
|
PromQL-compatible query endpoint (e.g. a Prometheus, Mimir, or Thanos frontend that ingests OTLP metrics). |
required |
normalize_names
|
bool
|
When true, apply the OTLP -> Prometheus name
translation ( |
True
|
Source code in src/slo_kit/sources/otel.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | |
Error budget¶
Error-budget engine.
Given the good/total event counts observed over the SLO window and the objective, this computes how much of the error budget has been consumed, how much remains, and — combined with a current burn rate — an estimated time to exhaustion.
All quantities are derived from three numbers (good, total,
objective) so the results are trivially checkable by hand, which is
exactly how the tests validate them.
ErrorBudget
dataclass
¶
A point-in-time view of an SLO's error budget over its window.
Attributes:
| Name | Type | Description |
|---|---|---|
objective |
float
|
The SLO objective, e.g. |
window_seconds |
float
|
Length of the SLO window in seconds. |
good |
float
|
Observed good events over the window. |
total |
float
|
Observed total (valid) events over the window. |
Source code in src/slo_kit/budget/error_budget.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | |
allowed_bad_events
property
¶
Absolute budget: how many events are allowed to fail over the window.
bad_events
property
¶
Observed bad events (clamped to [0, total]).
consumed
property
¶
Absolute number of budgeted failures already spent.
consumed_pct
property
¶
Fraction of the budget consumed in [0, 1+].
Values above 1.0 mean the budget is overspent (SLO violated). If the budget is zero (objective of 1.0 is disallowed, but total==0 yields a zero budget), any failure counts as fully consumed.
current_burn_rate
property
¶
Burn rate implied by the full-window error rate.
error_budget_fraction
property
¶
The allowed error fraction: 1 - objective.
is_exhausted
property
¶
True once consumed failures meet or exceed the allowed budget.
remaining
property
¶
Absolute budget remaining (never negative).
remaining_pct
property
¶
Fraction of budget remaining in [0, 1] (clamped).
sli
property
¶
The observed SLI (good ratio) over the window.
time_to_exhaustion(burn_rate=None)
¶
Estimate time until the remaining budget is exhausted.
At a constant burn rate B the entire budget empties in
window / B; the remaining fraction therefore empties in
remaining_pct * window / B.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
burn_rate
|
float | None
|
Burn rate to project forward. Defaults to the
full-window :attr: |
None
|
Returns:
| Type | Description |
|---|---|
timedelta | None
|
A |
timedelta | None
|
the burn rate is non-positive (never exhausts). |
Source code in src/slo_kit/budget/error_budget.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | |
compute_error_budget(*, objective, window_seconds, good, total)
¶
Construct an :class:ErrorBudget (keyword-only for call-site clarity).
Source code in src/slo_kit/budget/error_budget.py
127 128 129 130 131 132 133 134 135 136 137 | |
Burn rate¶
Burn-rate math — the beating heart of SLO alerting.
Definition. The burn rate is how fast we are consuming the error budget relative to the pace that would exactly exhaust it over the SLO window:
burn_rate = observed_error_rate / error_budget
= observed_error_rate / (1 - objective)
where observed_error_rate is the fraction of bad events measured over
some (usually short) window.
Why divide by the error budget? The error budget is the maximum error rate you can sustain for the entire SLO window and still exactly meet the objective. So:
* burn_rate == 1 -> you are on track to spend 100% of the budget over the
whole window (right at the objective).
* burn_rate == 2 -> you would exhaust the whole budget in half the window.
* burn_rate == 14.4 over 30 days -> the whole budget gone in ~50 hours,
which is why 14.4 is the classic fast-page threshold.
The general identity: at a constant burn rate B, the full budget is
consumed in window / B. Every threshold in the multi-window tables is just
a choice of "how much of the budget are we willing to let a single incident
burn before we page?" — see :mod:slo_kit.alerts.multiwindow.
This module is pure arithmetic on counts/ratios: no I/O, fully deterministic, and exhaustively unit-tested against hand-computed values.
budget_consumed_fraction(threshold, alert_window_seconds, slo_window_seconds)
¶
Fraction of the total budget consumed if a burn of threshold runs
for exactly alert_window_seconds.
This is the "budget consumed before alert" column in the SRE workbook
tables: threshold * alert_window / slo_window.
Source code in src/slo_kit/budget/burn_rate.py
85 86 87 88 89 90 91 92 93 94 95 96 | |
burn_rate(error_rate, objective)
¶
Burn rate from an already-computed error rate and the objective.
burn_rate = error_rate / (1 - objective).
Source code in src/slo_kit/budget/burn_rate.py
56 57 58 59 60 61 62 63 64 | |
burn_rate_from_counts(good, total, objective)
¶
Burn rate computed directly from good/total event counts.
Source code in src/slo_kit/budget/burn_rate.py
67 68 69 | |
error_rate_from_counts(good, total)
¶
Fraction of bad events: (total - good) / total.
A total of zero means "no traffic", which we treat as a 0.0 error rate
(you cannot fail requests you never received). good is clamped to
[0, total] so noisy inputs cannot produce a negative or >1 error rate.
Source code in src/slo_kit/budget/burn_rate.py
43 44 45 46 47 48 49 50 51 52 53 | |
threshold_for_budget_fraction(budget_fraction, alert_window_seconds, slo_window_seconds)
¶
Inverse of :func:budget_consumed_fraction.
Given the fraction of budget you're willing to burn within an alert window, return the burn-rate threshold that corresponds to it. Handy for deriving custom multi-window tables for non-standard SLO windows.
Source code in src/slo_kit/budget/burn_rate.py
99 100 101 102 103 104 105 106 107 108 109 110 | |
time_to_full_burn_seconds(current_burn_rate, window_seconds)
¶
Seconds to exhaust a full, fresh budget at current_burn_rate.
Returns None when the burn rate is non-positive (budget never
exhausts). Note this is time to burn the entire budget; to account for a
budget that is already partly consumed, use
:meth:slo_kit.budget.error_budget.ErrorBudget.time_to_exhaustion.
Source code in src/slo_kit/budget/burn_rate.py
72 73 74 75 76 77 78 79 80 81 82 | |
Multi-window alerting¶
Multi-window, multi-burn-rate alerting — the differentiating core.
This implements the alerting strategy from the Google SRE Workbook ("Alerting on SLOs", multiwindow multi-burn-rate). The problem it solves:
- Alert on a single long window (e.g. burn rate over 1h) and you detect slow burns but page slowly and keep paging long after an incident ends (poor reset time).
- Alert on a single short window and you page fast but with lots of false positives from brief blips.
The fix is to require two conditions simultaneously: a long window (the one that defines how much budget is being burned) AND a short window (a fraction of the long one — typically 1/12) that must also be burning. The short window is the "is this still happening right now?" gate: it makes the alert fire quickly, and — crucially — stop firing quickly once errors subside, because the short window recovers long before the long window does.
We then stack several such (long, short, threshold) conditions at different severities so a catastrophic burn pages immediately while a slow leak opens a ticket:
| severity | long | short | burn rate | budget burned before firing |
|----------|------|-------|-----------|-----------------------------|
| page | 1h | 5m | 14.4 | 2% (over a 30d window) |
| page | 6h | 30m | 6 | 5% |
| ticket | 24h | 2h | 3 | 10% |
A :class:MultiWindowPolicy fires when any of its conditions fire, and each
condition fires only when the burn rate over both its windows exceeds its
threshold.
The module is pure logic over a mapping of window -> burn_rate; it does no
I/O and is validated against an explicit truth table in the tests.
ConditionResult
dataclass
¶
Outcome of evaluating a single :class:BurnCondition.
Source code in src/slo_kit/alerts/multiwindow.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
MultiWindowPolicy
¶
A set of multi-window burn-rate conditions evaluated together.
Source code in src/slo_kit/alerts/multiwindow.py
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | |
required_windows
property
¶
All distinct window durations this policy needs burn rates for.
evaluate(burn_rates)
¶
Evaluate the policy against a mapping of window -> burn_rate.
Each condition fires iff the burn rate over both its long and short windows is strictly greater than the condition's threshold.
Source code in src/slo_kit/alerts/multiwindow.py
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
PolicyResult
dataclass
¶
Outcome of evaluating a whole :class:MultiWindowPolicy.
Source code in src/slo_kit/alerts/multiwindow.py
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
severity
property
¶
Highest-priority severity currently firing, or None.
page outranks ticket; unknown severities sort last.
default_conditions()
¶
The canonical SRE-workbook conditions for a 30-day SLO window.
Two paging conditions (fast + medium burn) and one ticket condition
(slow burn). Callers can override these per-SLO via AlertingConfig.
Source code in src/slo_kit/alerts/multiwindow.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | |
Prometheus rule generation¶
Generate Prometheus recording + alerting rules from an SLO.
Given an SLO and its multi-window policy we emit a Prometheus rule group with:
- one recording rule per distinct window, recording the SLI error
ratio over that window as
slo:sli_error:ratio_rate<window>. Recording rules keep the alert expressions cheap and readable. - one alerting rule per burn condition. Because
burn_rate = error_ratio / (1 - objective), the conditionburn_rate > thresholdis equivalent toerror_ratio > threshold * (1 - objective)— so the alert compares the recorded error ratios againstthreshold * error_budgetdirectly, with no division in the alert expression.
The output is a plain dict that serializes to the exact YAML Prometheus
expects under groups:. Golden-file tests pin the rendered output.
build_rule_group(slo)
¶
Build the Prometheus rule group (as a dict) for a single SLO.
Source code in src/slo_kit/alerts/prometheus_rules.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
generate_rules(slo)
¶
Return the full {'groups': [...]} structure for one SLO.
Source code in src/slo_kit/alerts/prometheus_rules.py
128 129 130 | |
generate_rules_yaml(slo)
¶
Render the Prometheus rules for an SLO to a YAML string.
Source code in src/slo_kit/alerts/prometheus_rules.py
133 134 135 | |
recording_rule_name(window_duration)
¶
Metric name of the recorded error ratio for a window, e.g. ...rate5m.
Source code in src/slo_kit/alerts/prometheus_rules.py
37 38 39 | |
Status¶
Programmatic SLO status: budget + burn rates + alert policy, one call.
:func:evaluate_status samples an SLO against a metric source and returns a
:class:SLOStatus bundling everything a human or dashboard needs: the current
SLI, the error-budget breakdown, per-window burn rates, projected time to
exhaustion, and whether the multi-window alert policy is firing. It also
serializes to JSON for the CLI and Grafana.
SLOStatus
dataclass
¶
A complete point-in-time status report for one SLO.
Source code in src/slo_kit/report/status.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
burn_rate(window)
¶
Burn rate over a given window duration (must have been sampled).
Source code in src/slo_kit/report/status.py
61 62 63 64 65 66 67 | |
to_dict()
¶
JSON-serializable status structure.
Source code in src/slo_kit/report/status.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
evaluate_status(slo, source)
¶
Sample slo against source and build a full :class:SLOStatus.
Source code in src/slo_kit/report/status.py
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |
Deploy gate¶
CI deploy gate: block deploys when the error budget is spent.
The pattern: run slo-kit gate in a deploy pipeline. If the SLO's error
budget is exhausted (or has fallen below a configured floor, or an alert is
already firing), the gate exits non-zero and the deploy is blocked — you don't
ship risky changes while you're already burning reliability you don't have.
GateDecision
dataclass
¶
The outcome of a deploy-gate evaluation.
Source code in src/slo_kit/gate.py
20 21 22 23 24 25 26 27 28 29 30 31 | |
exit_code
property
¶
Process exit code: 0 = allow deploy, 1 = block.
evaluate_gate(slo, source, *, min_budget_pct=0.0, block_on_firing=False)
¶
Decide whether a deploy should proceed for slo.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
slo
|
SLO
|
The SLO to gate on. |
required |
source
|
MetricSource
|
Metric source to sample. |
required |
min_budget_pct
|
float
|
Minimum remaining budget fraction ( |
0.0
|
block_on_firing
|
bool
|
When true, also block if the multi-window alert policy is currently firing, even if budget remains. |
False
|
Source code in src/slo_kit/gate.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |