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:_bucketseries with anlelabel, plus_sumand_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 nestedctxobject),{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.irateandideltause 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_timefamily (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 exceptlabel_replace,label_join,sort,sort_desc,last_over_timeandfirst_over_time. -
Aggregations:
sum avg min max count group stddev stdvar quantile topk bottomk count_valueswithbyorwithout, before or after the parentheses.topkkeeps 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(…)andgroup_right(…)copying labels from the "one" side, set operators; comparisons filter unless givenbool; arithmetic drops the metric name. -
histogram_quantile: buckets grouped by every label except
le, sorted, equal bounds merged, counts forced monotonic; rank = φ × the+Infcount; linear interpolation inside the first bucket whose count reaches the rank (from 0 for the first bucket); a rank in the+Infbucket returns the highest finite bound; no+Infbucket gives NaN. -
Subqueries:
expr[range:step]evaluatesexprat 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 byand,oror,;| line_format "{{.label}}";| drop,| keep;| unwrap label(alsoduration(label),bytes(label)). Range functionscount_over_time,rate,bytes_over_time,bytes_rate,absent_over_time, and over unwrapped valuessum avg min max first last stddev stdvar quantile _over_time, withby/withoutafter 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; noholt_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_formatbeyond{{.label}}, line-filterorchains oroffset-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.
Related: OpenTelemetry trace viewer, Regex tester, Queueing (where those latencies come from), Load balancing, Statistics and server config builders.
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 |
|---|---|---|
|
a preset (list below) |
|
|
the query |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
end the range this many minutes before 12:00 UTC (0–180) |
|
|
sample data seed |
|
example= |
Shows |
|---|---|
|
5xx share per instance — api-3’s incident (open) |
|
p99 latency per instance with |
|
the three busiest nodes with |
|
api-2’s restart: |
|
the raw counter, its drop and the stale scrape (open) |
|
|
|
memory used per node by one-to-one matching (open) |
|
many-to-one matching with |
|
traffic now against an hour ago with |
|
the worst 1-minute error rate in each 15 minutes (open) |
|
LogQL: error lines per second per instance (open) |
|
LogQL: 5xx share from logfmt fields (open) |
|
LogQL: failed worker jobs per job from JSON (open) |
|
LogQL: p95 duration with |
|
LogQL: api-3’s failed requests reformatted with |
|
LogQL: 404s by path from plain lines with |
For example, ?example=p99&at=65 &type=instant#explain
works through the p99 during api-3’s incident, bucket by bucket.
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.