Lint your PromQL over a single HTTP call.
pedantic.run parses a query and returns the patterns that get expensive at runtime — negative-regex matchers, bare selectors, high-cardinality groupings. Send a query, get back JSON findings. No auth, no state.
Send a query
Post the query as JSON to /api/analyze:
curl -s https://pedantic.run/api/analyze \
-H 'content-type: application/json' \
-d '{"query":"topk(5, rate(container_memory_usage_bytes{pod=~\".*\"}[10m]))"}'
The query is read from the first of these that is present:
GET /api/analyze?q=rate(...)
{"query": "rate(...)"}
POST body: rate(...)
What you get back
A JSON object with the original query,
a summary
tally of verdicts, and the
flat list of findings.
{ "query": "topk(5, rate(container_memory_usage_bytes{pod=~\".*\"}[10m]))", "summary": { "slow": 1, "fast": 1 }, "findings": [ { "selector": "container_memory_usage_bytes{pod=~\".*\"}", "category": "matchers", "verdict": "slow", "codes": [ { "code": "CATCHALL_MATCHER", "title": "Catch-all matcher", "description": "A .* / .+ value matches everything — no real filtering." } ] }, { "category": "range_vectors", "verdict": "fast", "codes": [] } ] }
verdict — how worried to be (worst first)
Doesn't type-check — won't run.
Expensive whatever your data.
Cost depends on cardinality.
Noticeable but usually fine.
No concerns found.
status codes
Analyzed — findings returned.
Empty query — nothing to analyze.
Could not parse or analyze the query.
Finding codes
Each finding carries a
category
and one or more codes. Here's what every code means.
matchers
-
BARE_SELECTOR - Selector with no label filters — scans every series for the metric.
-
REGEX_MATCHER - Uses =~ / !~ instead of equality; harder to index.
-
NEGATIVE_REGEX - !~ must scan all series just to exclude some.
-
CATCHALL_MATCHER - A .* / .+ value matches everything — no real filtering.
-
UNANCHORED_REGEX - Regex not anchored at both ends; can't shortcut the lookup.
range_vectors
-
LONG_RANGE_VECTOR - Window long enough to load a lot of samples (> 59m / > 2h).
-
SHORT_RANGE_VECTOR - Sub-minute window that may under-sample the metric.
-
NO_AGGREGATION_HIGH_CARD - A high-cardinality selector returned raw, with no aggregation.
-
SUBQUERY - A subquery re-evaluates the inner query at every step.
-
NESTED_SUBQUERY - A subquery inside a subquery multiplies that work.
aggregation
-
HIGH_CARD_GROUPING - Grouping by an unbounded label (request_id, trace_id, pod, …).
-
SORT_AGGREGATION - topk / bottomk / quantile must sort the full result set.
structure & type_error
-
VECTOR_JOIN - A binary op with empty on() matching.
-
REDUNDANT_SUBEXPR - The same sub-expression is computed more than once.
-
ARITY_MISMATCH - A function called with the wrong number of arguments.
-
ARG_TYPE_MISMATCH - An argument has the wrong type (e.g. instant where a range is required).