pedantic.run
PromQL analysis · HTTP API

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:

POST · application/json
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:

?q= param
GET or POST query string
GET /api/analyze?q=rate(...)
query field
JSON or form body
{"query": "rate(...)"}
raw body
text/plain request body
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.

200 OK · application/json
{
  "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)

invalid

Doesn't type-check — won't run.

slow

Expensive whatever your data.

runtime_dependent

Cost depends on cardinality.

moderate

Noticeable but usually fine.

fast

No concerns found.

status codes

200

Analyzed — findings returned.

400

Empty query — nothing to analyze.

422

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).