Chris Collins

Tools · infra

PromQL and LogQL playground

Write PromQL and LogQL against four hours of generated metrics and logs — counters with resets, histograms, a failing instance, a target that goes down — see the result as a graph or table, and walk through any query step by step, down to the arithmetic of rate()'s extrapolation and histogram_quantile()'s interpolation.

Added

How to use it

A sandbox for the two query languages most Grafana dashboards are written in. It runs a PromQL and LogQL engine in the page against sample data with things worth finding in it, so you can try a query, see why it returns what it does, and check your understanding of the parts that surprise people — rate() not giving whole numbers, a reset counter, a series that goes stale, a many-to-many matching error. Nothing is sent anywhere.

How to use it

Query. Type a query (or pick an example) and it runs as you type; Ctrl/⌘ + Enter runs it at once.

  • Auto / PromQL / LogQL: Auto treats a query with a log pipeline (|=, | logfmt, …) or a selector like {app="api"} as LogQL.

  • Range evaluates the query at every step over the last 15 minutes, 1 hour or 3 hours and draws it; Instant evaluates it once. Step is the resolution (auto: 15 s for 15 minutes, 30 s for an hour, 1 minute for 3 hours). at −min moves the end of the range back, e.g. 60 to look at api-3’s incident.

  • The graph breaks lines where a series has no value; tap a legend entry to hide a series, hover or touch the graph for the values at that step. Labels every series shares are shown once above the legend. The table lists each series' value at the end. Log queries show the matching lines, newest first, with a histogram. Full screen (F) gives the graph the whole screen.

  • Errors point at the place in the query, with Prometheus' wording where it has one (many-to-many matching not allowed, expected a range vector, …).

Explain. The query split into its steps, innermost first, each evaluated as an instant query at the end time: what the step does, its source text and its result. For rate(), increase() and delta() over a selector, Show the arithmetic lists the samples in the window and works through Prometheus' extrapolation for one series at a time; for histogram_quantile() it shows the buckets, the rank and the interpolation. For LogQL it follows sample lines through every pipeline stage.

Data. What is in the sample data, the metrics and log streams (tap one to query it), and a seed to generate a different but similar data set.

The sample data covers 4 hours up to 12:00 UTC on 17 October 2026, scraped every 15 s:

  • http_requests_total{job="api", instance, method, code} — counters; api-3 has an incident 75–60 minutes ago (25 % of requests fail, responses about 7× slower); api-2 restarts 20 minutes ago, so one scrape is missed (up = 0, staleness markers) and its counters reset.

  • http_request_duration_seconds — a classic histogram per instance: _bucket series with an le label, plus _sum and _count.

  • node_cpu_seconds_total{instance, cpu, mode}, node_memory_MemAvailable_bytes, node_memory_MemTotal_bytes, up — five nodes; node-5 is unreachable 25–15 minutes ago.

  • Logs: {app="api", instance} in logfmt, {app="worker"} in JSON (with a nested ctx object), {app="nginx"} in the plain combined log format.

What the engine does

The rules follow Prometheus 3 (promql/engine.go, functions.go, quantile.go) and Loki’s LogQL documentation.

  • Selectors: =, !=, =~, !~ with fully anchored regular expressions; a selector must have a name or a matcher that does not match the empty string. An instant selector takes the newest sample at most 5 minutes old (the lookback delta) and returns nothing if that sample is a staleness marker. Ranges are left-open, (t − range, t], as since Prometheus 3.0. offset (also negative) and @ (a Unix time, start(), end()) shift the evaluation time.

  • rate, increase, delta: the difference between the first and last sample in the window, plus the value before every counter reset, extrapolated to the window’s edges — but only half an average interval when the gap to an edge is more than 1.1 × the average interval (the series probably started or ended inside the window), and for counters never further back than where the counter would reach zero. That is why increase() is rarely an integer. irate and idelta use the last two samples.

  • Functions: abs ceil floor exp ln log2 log10 sqrt sgn round clamp clamp_min clamp_max, rate irate increase delta idelta deriv predict_linear changes resets, the *_over_time family (avg min max sum count last first present stddev stdvar quantile absent), histogram_quantile, label_replace label_join absent time vector scalar sort sort_desc timestamp. Functions drop the metric name except label_replace, label_join, sort, sort_desc, last_over_time and first_over_time.

  • Aggregations: sum avg min max count group stddev stdvar quantile topk bottomk count_values with by or without, before or after the parentheses. topk keeps the original labels and in a range query is evaluated at every step, so more than k series can appear on the graph.

  • Binary operators with Prometheus precedence (^ right-associative, then * / % atan2, + -, comparisons, and unless, or; unary minus binds below ^, so -2 ^ 2 = −4). Vector matching: one-to-one on all labels but the name, on(…), ignoring(…), group_left(…) and group_right(…) copying labels from the "one" side, set operators; comparisons filter unless given bool; arithmetic drops the metric name.

  • histogram_quantile: buckets grouped by every label except le, sorted, equal bounds merged, counts forced monotonic; rank = φ × the +Inf count; linear interpolation inside the first bucket whose count reaches the rank (from 0 for the first bucket); a rank in the +Inf bucket returns the highest finite bound; no +Inf bucket gives NaN.

  • Subqueries: expr[range:step] evaluates expr at multiples of the step inside the window.

  • LogQL: stream selectors; line filters |=, !=, |~, !~; parsers | json (nested keys joined with _), | logfmt, | regexp (Go (?P<name>…) groups), | pattern; label filters with strings, regexes, numbers and durations (duration > 100ms) joined by and, or or ,; | line_format "{{.label}}"; | drop, | keep; | unwrap label (also duration(label), bytes(label)). Range functions count_over_time, rate, bytes_over_time, bytes_rate, absent_over_time, and over unwrapped values sum avg min max first last stddev stdvar quantile _over_time, with by/without after the call; every PromQL aggregation and operator on top.

Limits

  • Float samples only: no native histograms, exemplars, recording rules or alerting rules; no @ modifier on LogQL; no holt_winters, double_exponential_smoothing, date functions, limitk, info() or experimental functions.

  • Regular expressions are JavaScript’s, with Go’s (?P<name>…) and (?i) translated; RE2 and JavaScript differ in a few corners (backreferences are accepted here, RE2 rejects them).

  • LogQL: no | label_format, | decolorize, | unpack, JSON parameters (| json a="b.c"), | line_format beyond {{.label}}, line-filter or chains or offset-free log queries over arbitrary times; a log query lists at most the newest 300 lines. Loki’s own query limits (max series, split intervals) are not modelled.

  • A range query evaluates every step independently (as Prometheus does); queries over 400,000 points stop with a warning.

Sources: the Prometheus documentation (Querying basics, Operators, Functions) and source (promql/functions.go extrapolatedRate, promql/quantile.go bucketQuantile and quantile, the 3.0 migration notes on left-open ranges), and Grafana Loki’s LogQL documentation (log queries, metric queries, label filter expressions). The tests check the extrapolation, quantile and matching rules against hand-worked examples from those sources.

Linking to an example

/tools/promql/?<parameters>#<tab> where the tab is query, explain or data. Share link builds one from what is on screen.

Parameter Meaning Example

example

a preset (list below)

p99

q

the query

up == 0

mode

auto, promql or logql

logql

type

range or instant

instant

range

15m, 1h or 3h

3h

step

auto, 15s, 30s, 1m or 5m

1m

at

end the range this many minutes before 12:00 UTC (0–180)

60

seed

sample data seed

7

example= Shows

error-ratio

5xx share per instance — api-3’s incident (open)

p99

p99 latency per instance with histogram_quantile (open)

top-cpu

the three busiest nodes with topk (open)

reset

api-2’s restart: rate() copes with the counter reset (open)

raw

the raw counter, its drop and the stale scrape (open)

down

up == 0: node-5 and the missed api-2 scrape (open)

memory

memory used per node by one-to-one matching (open)

group-left

many-to-one matching with group_left (open)

offset

traffic now against an hour ago with offset (open)

subquery

the worst 1-minute error rate in each 15 minutes (open)

logql-errors

LogQL: error lines per second per instance (open)

logql-ratio

LogQL: 5xx share from logfmt fields (open)

logql-json

LogQL: failed worker jobs per job from JSON (open)

logql-p95

LogQL: p95 duration with unwrap duration(…) (open)

logql-lines

LogQL: api-3’s failed requests reformatted with line_format (open)

logql-nginx

LogQL: 404s by path from plain lines with pattern (open)

For example, ?example=p99&at=65 &type=instant#explain works through the p99 during api-3’s incident, bucket by bucket.

Runs entirely in your browser; nothing you type leaves this page.