Performance budgets: absolute limits for Rust functions, queries and routes

A performance budget is a hard limit on one function, query, route or other profiled entry: "this function never allocates more than 1 KB per call", "this query runs at most once per request". The regression policy compares a pull request with its baseline, so slow drift over many pull requests passes it. A budget is checked on every report on its own, so it holds whatever the baseline did, and it works on the first pull request and on pushes to the default branch.

Budgets live in the same file as the regression policy rules, hotpath/policy.toml. A broken budget fails the verdict like a regression.

Rules

One [[<resource>.budgets]] table per rule, under the resource it limits.

Functions

Allocations, timing and call count of instrumented functions.

[[functions.budgets]]
match = "my_crate::parser::parse"
alloc = { avg = "1 KB", total = "10 MB" }
timing = { avg = "250 us", p95 = "1 ms" }
calls = { min = 1000, max = 1000 }
message = "parse must stay allocation-light"

alloc takes bytes and timing durations, for avg, total and percentiles. match is the function name.

SQL queries

Timing and execution count of SQL queries.

[[sql.budgets]]
match = "SELECT * FROM users WHERE id = ?*"
timing = { avg = "2 ms", total = "50 ms" }
count = { max = 100 }

match runs against query | route | source, so a pattern on the query alone, ending with *, matches it wherever it ran.

HTTP requests

Timing, errors and request count of outbound HTTP requests.

[[http.budgets]]
match = "*api.stripe.com*"
timing = { avg = "300 ms", errors = 0 }
count = { max = 20 }

match runs against endpoint | route | source.

Server routes

Allocations, response time and downstream calls of each served route.

[[server.budgets]]
match = "GET /users/{id}"
alloc = { bytes_per_request = 65536, allocs_per_request = 200 }
timing = { p95 = "50 ms", status_5xx = 0, sql_per_request = 1 }

timing takes avg, total and percentiles as durations, status_4xx and status_5xx as counts, sql_per_request and http_per_request as numbers. alloc takes avg, total and percentiles as bytes, bytes_per_request and allocs_per_request as numbers. match is the route as METHOD template.

Mutexes

Wait and hold time of mutexes.

[[mutexes.budgets]]
match = "app_state"
timing = { wait_avg = "100 us", acquire_avg = "1 ms" }

match is the lock’s label.

RwLocks

Wait and hold time of read-write locks, for reads and writes.

[[rw_locks.budgets]]
match = "config_cache"
timing = { read_wait_avg = "50 us", write_acquire_avg = "1 ms" }

match is the lock’s label. count is reads and writes together.

Channels

Delay, backlog and throughput of channels.

[[channels.budgets]]
match = "events"
flow = { delay_avg = "1 ms", max_queue_size = 1000, received_per_sec = 5000 }
sent_count = { min = 10000 }

sent_per_sec and received_per_sec are minimums: the channel must move at least that many messages per second. The rest are maximums. The count is sent_count.

I/O

Latency, throughput and errors of I/O streams.

[[io.budgets]]
match = "upload_socket*"
flow = { avg = "5 ms", errors = 0, bytes_per_sec = "10 MB" }

bytes_per_sec is a minimum, the rest are maximums. match runs against label | operation | -, so a pattern on the label alone matches every operation on it.

Options

Rule keys

Every rule takes:

KeyMeaning
matchThe entry the rule applies to. * matches any run of characters, and the pattern must match the whole name. Every entry it matches is checked on its own.
calls / count / sent_count{ min, max } on how many times the entry ran: calls for functions, sent_count for channels, count for the rest.
alloc, timing, flowLimits on the resource’s metrics, as in Rules.
messageOptional text shown next to a broken rule in the comment, up to 256 bytes.
fail_checkWhether a broken rule fails the pull request check. true by default; false reports the rule without failing the check.

Every rule checks that its entry ran. Without a min, the count must be at least 1, so a match with a typo or a renamed function breaks the budget instead of passing silently. Write calls = { min = 0 } for an entry that may not run.

ignore, judged, min_percent_change, min_calls and min_percent_total do not apply to budgets: a rule names its entry explicitly. A resource takes up to 64 rules.

Units

Values carry their unit:

KindWritten as
durations"250 ns", "250 us", "5 ms", "1.5 s"
bytes"512 B", "1 KB", "10 MB" (1 KB = 1024 B)
bytes per second"10 MB", "500 B"
countsa whole number: 0, 1000
per-request numbersa number: 1, 2.5

A percentile name with a dot is quoted: timing = { "p99.9" = "5 ms" }.

Broken budgets

A broken budget is listed under “Budgets” in the pull request comment, with the value, the limit and the rule’s message, and the heading counts it. Under pr_check = "fail" a rule with fail_check (the default) says it fails the check:

⛔ my_crate::parser::parse: alloc.avg 1.4 KB, over budget 1.0 KB: parse must stay allocation-light · fails the check
⛔ my_crate::flush: not called · fails the check