PromQL Analyzer
PromQL Analyzer is a static analysis and explanation toolkit for PromQL.
It helps developers understand, review, and lint existing PromQL queries — without executing them against Prometheus.
Think of it as:
Ruff/Pylint-style analysis for PromQL, with deterministic explanations.
What it is
- A Python library (
analyze(...)) for structural analysis - A CLI (
promql-analyze) for terminals and CI logs - Analysis of Prometheus rule YAML/YML files (
exprfields) - A small set of heuristic lint rules (
PQL001–PQL005) - A transparent structural complexity score (0–100)
- Deterministic human-readable query explanations
What it is NOT
- Not a PromQL query engine
- Not a Prometheus replacement
- Not a runtime performance profiler
- Not an AI/LLM service
Static findings are heuristics. They never claim a query will definitely be slow.
Installation
pip install promql-analyzer
For local development:
pip install -e ".[dev]"
Python usage
from promql_analyzer import analyze, AnalyzerConfig
result = analyze(
'sum by(namespace)(rate(http_requests_total{status=~"5.."}[5m]))'
)
print(result.structure.metrics)
print(result.structure.functions)
print(result.findings)
print(result.complexity.score, result.complexity.level)
print(result.explain()) # concise
print(result.explain("detailed")) # detailed
print(result.to_dict()) # JSON-serializable dict
Analyze Prometheus rule YAML
from promql_analyzer import analyze_file
report = analyze_file("alerts.yaml") # or alerts.yml
for item in report.queries:
print(item.source.rule_kind, item.source.rule_name, item.source.expr)
if item.error:
print("parse error:", item.error)
else:
print(item.result.findings)
analyze_file() also accepts plain .promql text files (single query).
YAML support looks for string values under expr keys, covering:
- Prometheus rule files (
groups[].rules[].expr) - Prometheus Operator
PrometheusRuleresources (spec.groups...)
Configuration
config = AnalyzerConfig(
scrape_interval_seconds=15,
max_grouping_labels=3,
max_nesting_depth=4,
disabled_rules=("PQL002",),
)
result = analyze(query, config=config)
Invalid PromQL
from promql_analyzer import analyze, InvalidPromQL
try:
analyze("sum(")
except InvalidPromQL as exc:
print(exc.message)
print(exc.location) # may be None
print(exc.parser_detail) # raw detail available; shown in str(exc) only with debug=True
InvalidPromQL is an alias of PromQLSyntaxError.
CLI usage
Analyze an inline query
promql-analyze analyze 'sum(rate(http_requests_total[5m]))'
Explain a query
promql-analyze explain 'sum(rate(http_requests_total[5m]))'
promql-analyze explain --style detailed 'sum(rate(http_requests_total[5m]))'
Analyze a file
# Single PromQL query file
promql-analyze analyze-file query.promql
# Prometheus rules YAML / YML (all expr fields)
promql-analyze analyze-file alerts.yaml
promql-analyze analyze-file alerts.yml --format json
promql-analyze analyze-file alerts.yaml --fail-on warning
JSON output
promql-analyze analyze --format json 'sum(rate(http_requests_total[5m]))'
JSON keys are stable and suitable for CI tooling.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success (and findings below the fail threshold) |
| 1 | Findings met the --fail-on threshold |
| 2 | Invalid PromQL, missing file, or other CLI error |
Defaults:
--fail-on error— warnings do not fail the process--fail-on warning— WARNING and ERROR fail--fail-on never— always exit 0 after a successful parse/analysis
Examples:
promql-analyze analyze --fail-on warning 'avg(http_requests_total)'
promql-analyze analyze --fail-on never 'avg(http_requests_total)'
promql-analyze analyze --debug 'sum('
Useful flags
promql-analyze analyze \
--scrape-interval 15 \
--max-grouping-labels 3 \
--max-nesting-depth 4 \
--disable-rule PQL001 \
--style detailed \
--format text \
'sum(rate(http_requests_total[5m]))'
Rules
PQL001 — Suspicious counter usage
Detects: metrics ending in _total used without rate(), irate(), increase(), or resets().
Why it matters: counters are commonly rate-transformed before aggregation; direct use (for example avg(http_requests_total)) can be misleading.
False positives: naming is only a heuristic. A gauge can end in _total.
PQL002 — Broad regex matcher
Detects: label=~".*" (WARNING) and patterns beginning with .* (INFO).
Why it matters: broad regex matching may evaluate many series. Cost depends on cardinality.
False positives: some broad patterns are intentional and cheap on low-cardinality metrics.
PQL003 — Suspicious rate window
Detects: short rate() / irate() ranges relative to scrape_interval_seconds * rate_range_min_multiples.
Why it matters: short windows may not contain enough samples for reliable rates.
False positives: scrape intervals vary by environment; configure AnalyzerConfig / --scrape-interval.
PQL004 — Potential high-cardinality grouping
Detects: aggregations grouping by more than max_grouping_labels labels. Optionally emits INFO for commonly high-cardinality label names.
Why it matters: many grouping labels can retain many output series.
False positives: required high-dimension groupings are valid; raise the threshold when intentional.
PQL005 — Excessive nesting
Detects: function/aggregation nesting deeper than max_nesting_depth.
Why it matters: deep nesting hurts readability/reviewability. This is not a runtime cost score.
False positives: some nested forms are idiomatic (for example histogram quantiles).
Complexity
result.complexity.score is a structural score from 0–100 with a transparent factor breakdown.
Levels:
| Score | Level |
|---|---|
| 0–20 | simple |
| 21–45 | moderate |
| 46–70 | complex |
| 71–100 | very_complex |
This is not Prometheus execution cost. Runtime cost depends on series cardinality, Prometheus configuration, storage backend, recording rules, and actual metric data.
Limitations
- Analysis is static and offline
- Lint rules are heuristic and suppressible
- YAML support extracts
exprfields only (not Grafana dashboard panels / arbitrary YAML) - Parser compatibility follows
promql-parser(Prometheus ~v2.45-oriented upstream) - Explanations never invent metric business meaning from names alone
- No Grafana/Prometheus server integration in V1
Development
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest
ruff check src tests
Release / publishing (manual)
Do not publish automatically from CI without review.
-
Local install
pip install -e ".[dev]" promql-analyze --version
-
Build
python -m build
-
Test the distribution
python -m twine check dist/* python -m venv /tmp/promql-analyzer-dist-test /tmp/promql-analyzer-dist-test/bin/pip install dist/*.whl /tmp/promql-analyzer-dist-test/bin/promql-analyze analyze 'up'
-
Publish to TestPyPI
python -m twine upload --repository testpypi dist/* pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple promql-analyzer
-
Publish to PyPI
python -m twine upload dist/*
License
MIT
Metadata
Release files for promql-analyzer 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| promql_analyzer-0.1.0.tar.gz | 41.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| promql_analyzer-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 76.1 kB
Release files / promql_analyzer-0.1.0.tar.gz
| Download URL | promql_analyzer-0.1.0.tar.gz |
|---|---|
| Size | 41.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4216e87ea95321b47aac8285cccdfb9d7529c2796e63a35bb470ccc384d5d303
|
|
BLAKE2b-256 checksum How to use checksums |
3500ea81811388d7e8e571d7ef0f634351e4b5eaee40834ff987cf6dec1c20db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.7
|
Release files / promql_analyzer-0.1.0-py3-none-any.whl
| Download URL | promql_analyzer-0.1.0-py3-none-any.whl |
|---|---|
| Size | 34.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
88a1789e242ca28dcd0757c452b71154f68cfde4e9d21f5c8e26b801ddd3191b
|
|
BLAKE2b-256 checksum How to use checksums |
91091683c3cefd97c9afa48f12b732a1be485e1e705e1a33199ef4fa60ac1aa1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.7
|