# slobudget

SLO, error budget and burn-rate alert calculator for SRE teams. Define an SLO as YAML, feed it a metric export (Prometheus range-query JSON or CSV), and get the error budget, burn rates, exhaustion projection and compliance history, a replay of multi-window multi-burn-rate alerts over your history (with precision, recall, detection and reset times), plus ready-to-load Prometheus rules and a Grafana dashboard. It follows the method of the Google SRE Workbook chapter "Alerting on SLOs".

## Features

- SLO spec as code: availability (good/total) and latency-threshold (histogram) SLIs, rolling or calendar (month/week) windows, custom alert policies.
- Inputs: Prometheus range-query JSON, CSV `timestamp,good,total`, or a deterministic synthetic generator with three incidents.
- Engine: exact window sums via prefix sums, error budget in minutes and percent, burn rate per window, exhaustion projection, compliance history.
- Alert replay: evaluates every policy at every tick and scores the alerts against budget-burning incidents found in the data.
- Generators: Prometheus recording and alerting rules, Grafana dashboard JSON, Markdown report.
- CLI: `report`, `simulate`, `rules`, `dashboard`, `whatif`, `generate`.

## Install

```bash
python3.12 -m venv .venv && .venv/bin/pip install -e '.[dev]'
```

Python 3.12+, dependencies: click, PyYAML.

## Quickstart

```bash
slobudget report    --slo examples/checkout-api.slo.yaml --data examples/synthetic-90d.csv
slobudget simulate  --slo examples/checkout-api.slo.yaml --synthetic
slobudget rules     --slo examples/checkout-api.slo.yaml -o slo-rules.yaml
slobudget dashboard --slo examples/checkout-api.slo.yaml -o dashboard.json
slobudget whatif    --objective 99.95
```

Every data command takes exactly one of `--data FILE` (`.json` is read as Prometheus, anything else as CSV) or `--synthetic`.

## SLO spec

```yaml
service: checkout-api
name: availability
objective: 99.9
sli:
  type: availability
  good: 'http_requests_total{job="checkout-api",code!~"5.."}'
  total: 'http_requests_total{job="checkout-api"}'
window:
  type: rolling
  days: 30
labels:
  team: payments
```

- `sli.type: availability` needs `good` and `total` selectors. `sli.type: latency` takes `histogram`, optional `labels` and `threshold_seconds` (good = `_bucket{le="<threshold>"}`, total = `_count`); the threshold must be an existing bucket boundary.
- `window`: `{type: rolling, days: N}` (or just `30d`), or `{type: calendar, period: month|week}`.
- `alerting.policies` is optional. Default is the workbook set: 14.4x (1h/5m, page), 6x (6h/30m, page), 3x (1d/2h, ticket), 1x (3d/6h, ticket). A custom example is in `examples/search-latency.slo.yaml`.

## Input formats

- CSV: header `timestamp,good,total`; timestamps are ISO 8601 or epoch seconds; each row is the event counts for the interval starting at the timestamp.
- Prometheus: a `status: success` matrix response whose series carry a label `sli` (or `series`) of `good`, `bad` or `total`, or a `__name__` ending in those words. Values are per-step increases, e.g. `sum(increase(http_requests_total{code!~"5.."}[5m]))` exported with `step=300`. See `examples/prom-export.json`.
- `slobudget generate [--days 90] [--step 300] [--seed 7] [--format csv|prom] -o FILE` writes the demo series used in `examples/`.

## Usage and real output

### report

`slobudget report --slo examples/checkout-api.slo.yaml --data examples/synthetic-90d.csv --history-days 30`

Options: `--at TIME` (evaluate in the past), `--history-days N`, `--with-simulation`, `--format markdown|json`, `-o FILE`.

# SLO report: checkout-api (availability)

Evaluated at **2026-09-29T00:00:00Z** over window **30d** (from 2026-08-30T00:00:00Z). Status: **WITHIN BUDGET**.

## Summary

| Metric | Value |
|---|---|
| Objective | 99.9% |
| SLI | 99.9600% |
| Good / total events | 23,574,201 / 23,583,645 |
| Bad events | 9,444 |

## Error budget

|  | Minutes | Share |
|---|---|---|
| Total | 43.2 | 100.0% |
| Consumed | 17.3 | 40.0% |
| Remaining | 25.9 | 60.0% |

## Burn rate

| Window | Burn rate |
|---|---|
| 5m | 0.00x |
| 30m | 0.00x |
| 1h | 0.26x |
| 2h | 0.21x |
| 6h | 0.21x |
| 1d | 0.23x |
| 3d | 0.23x |
| 30d | 0.40x |

Exhaustion projection: at the current 6h burn rate of 0.21x the remaining budget lasts **86d 6h 18m** (until 2026-12-24T06:18:46Z).

## Alerting policies

| Policy | Severity | Burn rate | Long / short | Budget burned at trigger | Budget gone in |
|---|---|---|---|---|---|
| page-fast | page | 14.4x | 1h / 5m | 2.0% | 2d 2h |
| page-slow | page | 6x | 6h / 30m | 5.0% | 5d |
| ticket-fast | ticket | 3x | 1d / 2h | 10.0% | 10d |
| ticket-slow | ticket | 1x | 3d / 6h | 10.0% | 30d |

## Compliance history (every 30d)

| As of | SLI | Budget remaining | Met |
|---|---|---|---|
| 2026-07-31T00:00:00Z | 99.9639% | 63.9% | yes |
| 2026-08-30T00:00:00Z | 99.9674% | 67.4% | yes |
| 2026-09-29T00:00:00Z | 99.9600% | 60.0% | yes |

### simulate

`slobudget simulate --slo examples/checkout-api.slo.yaml --data examples/synthetic-90d.csv`

Options: `--eval-step SECONDS`, `--incident-burn X` (burn rate that defines an incident, default 1), `--incident-min-budget PCT` (default 1).

# Alert replay: checkout-api (availability)

Incidents (ground truth, budget-burning periods): 3

| # | Start | End | Duration | Bad events | Budget | Peak 1h burn |
|---|---|---|---|---|---|---|
| 1 | 2026-07-21T03:15:00Z | 2026-07-21T04:00:00Z | 45m | 3,078 | 13.0% | 229.4x |
| 2 | 2026-08-17T14:00:00Z | 2026-08-17T20:00:00Z | 6h | 2,440 | 10.3% | 12.9x |
| 3 | 2026-09-10T06:05:00Z | 2026-09-14T05:00:00Z | 3d 22h 55m | 4,794 | 20.2% | 1.9x |

Alerts that would have fired:

| Policy | Severity | Fired | Resolved | Duration | Peak long-window burn |
|---|---|---|---|---|---|
| page-fast | page | 2026-07-21T03:20:00Z | 2026-07-21T04:05:00Z | 45m | 229.4x |
| page-slow | page | 2026-07-21T03:25:00Z | 2026-07-21T04:30:00Z | 1h 5m | 37.1x |
| ticket-fast | ticket | 2026-07-21T03:40:00Z | 2026-07-21T06:00:00Z | 2h 20m | 5.3x |
| ticket-slow | ticket | 2026-07-21T03:45:00Z | 2026-07-21T10:00:00Z | 6h 15m | 1.6x |
| page-slow | page | 2026-08-17T16:55:00Z | 2026-08-17T20:20:00Z | 3h 25m | 12.2x |
| ticket-fast | ticket | 2026-08-17T18:15:00Z | 2026-08-17T21:40:00Z | 3h 25m | 4.1x |
| ticket-slow | ticket | 2026-08-17T18:20:00Z | 2026-08-18T01:50:00Z | 7h 30m | 1.3x |
| ticket-slow | ticket | 2026-09-12T05:00:00Z | 2026-09-14T07:30:00Z | 2d 2h 30m | 1.4x |

Detection quality (precision = alerts overlapping an incident; recall = incidents with an alert; detection = incident start to first alert; reset = incident end to alert resolve):

| Policy | Alerts | Precision | Recall | Mean detection | Mean reset |
|---|---|---|---|---|---|
| page-fast | 1 | 100% | 33% | 5m | 5m |
| page-slow | 2 | 100% | 67% | 1h 32m | 25m |
| ticket-fast | 2 | 100% | 67% | 2h 20m | 1h 50m |
| ticket-slow | 3 | 100% | 100% | 17h 15m | 4h 46m |
| all | 8 | 100% | 100% | 16h 38m | 4h 46m |

The three synthetic incidents are a 45 minute hard outage, a 6 hour 12x degradation and a four day 1.4x slow leak. Each policy catches what it is designed for: the fast page rule sees only the outage, the 6x rule also the degradation, and only the 1x ticket rule sees the slow leak.

### rules

`slobudget rules --slo examples/checkout-api.slo.yaml` (alert group shown; the recording group defines `slo:sli_error:ratio_rate<window>` for every window)

```yaml
- name: slobudget.checkout-api.availability.alerts
  interval: 30s
  rules:
  - alert: CheckoutApiAvailabilityErrorBudgetBurnPageFast
    expr: |-
      slo:sli_error:ratio_rate1h{service="checkout-api",slo="availability"} > 0.0144
      and
      slo:sli_error:ratio_rate5m{service="checkout-api",slo="availability"} > 0.0144
    labels:
      service: checkout-api
      slo: availability
      team: payments
      severity: page
      long_window: 1h
      short_window: 5m
    annotations:
      summary: checkout-api availability SLO is burning error budget at 14.4x
      description: Error ratio exceeds 14.4x the budget rate over 1h and 5m. At this rate 2% of the 30d
        error budget for a 99.9% objective is consumed in 1h.
  - alert: CheckoutApiAvailabilityErrorBudgetBurnPageSlow
    expr: |-
      slo:sli_error:ratio_rate6h{service="checkout-api",slo="availability"} > 0.006
      and
      slo:sli_error:ratio_rate30m{service="checkout-api",slo="availability"} > 0.006
```

### dashboard

`slobudget dashboard --slo FILE -o dashboard.json` produces a Grafana dashboard (import it and pick your Prometheus data source) with SLI and budget-remaining stats, an error budget burn-down, SLI over time and burn rate per window. It queries the recording rules, so load the output of `rules` first.

### whatif

`slobudget whatif --objective 99.95`

Allowed downtime (error budget) by objective; * = requested

| Objective | 7d (min) | 28d (min) | 30d (min) | 90d (min) |
|---|---|---|---|---|
| 90% | 1,008 | 4,032 | 4,320 | 12,960 |
| 95% | 504 | 2,016 | 2,160 | 6,480 |
| 99% | 101 | 403 | 432 | 1,296 |
| 99.5% | 50.40 | 202 | 216 | 648 |
| 99.9% | 10.08 | 40.32 | 43.20 | 130 |
| 99.95% * | 5.04 | 20.16 | 21.60 | 64.80 |
| 99.99% | 1.01 | 4.03 | 4.32 | 12.96 |
| 99.999% | 0.10 | 0.40 | 0.43 | 1.30 |

Add `--slo` and `--data`/`--synthetic` to also evaluate your history against the alternative objective.

## Configuration

There are no config files or environment variables; everything is in the SLO YAML and CLI flags. Errors in specs or data exit with status 1 and an `error:` message.

## Architecture

| Module | Role |
|---|---|
| `slobudget/spec.py` | SLO YAML parsing and validation, durations, alert policies |
| `slobudget/series.py` | Series with prefix sums, CSV and Prometheus loaders and writers, synthetic generator |
| `slobudget/engine.py` | Window bounds, budget, burn rate, projection, history, incident detection, alert replay and scoring |
| `slobudget/rules.py` | Prometheus recording and alerting rules |
| `slobudget/dashboard.py` | Grafana dashboard |
| `slobudget/report.py` | Markdown and JSON rendering, what-if table |
| `slobudget/cli.py` | Click commands |

Algorithms and worked examples are in [docs/methodology.md](docs/methodology.md).

## Tests

```bash
.venv/bin/pytest
```

The suite covers window arithmetic, the SRE workbook figures (2%, 5% and 10% budget at trigger), alert replay on synthetic incidents, PromQL sanity (a small tokenizer in `tests/promql_lint.py`), YAML/JSON validity and the CLI.

## Limitations

- Generated rules and dashboards approximate calendar windows with 30d (month) or 7d (week) rolling windows; the report and simulation use true calendar bounds.
- Windows shorter than the data step are widened to one step.
- Incident ground truth is derived from the data (hourly buckets burning at 1x or more, at least 1% of the budget), so precision and recall are relative to that definition.
- Exhaustion projection assumes the 6h burn rate persists and ignores old errors rolling out of the window.
- A single SLO per spec file; counters must be pre-differenced into per-step increases.
