Prometheus Prometheus

Get Started with PromQL: Selecting, Rating, and Aggregating

Build up from a simple metric selector to a real query: per-path error rate, computed live.

12 min Intermediate

PromQL is the query language every part of Prometheus that reads data, dashboards, alerting rules, ad hoc debugging, is built on. This guide builds up a real query step by step: given a request counter split by path, method, and status, find every path whose error rate is above 5%.

Selecting time series

The simplest PromQL expression is just a metric name, which returns the current value of every time series with that name:

http_requests_total

Add label matchers in curly braces to narrow that down to a specific subset:

http_requests_total{status="500"}

This returns every series named http_requests_total whose status label is exactly "500", regardless of path or method. Matchers can also do regex matching (status=~"5.." for any 5xx status) and negation (status!="200"), among other operators.

From a counter to a rate

http_requests_total is a counter: it only goes up, so its raw value (some large, ever-growing number since the process started) isn’t what you actually want to look at. What you want is how fast it’s increasing, and that’s what the rate() function computes: the per-second average rate of increase over a given time window.

rate(http_requests_total{status="500"}[5m])

This returns, for every matching series, the average per-second rate of 500 errors over the trailing 5 minutes. A related function, irate(), computes an instantaneous rate using only the last two data points in the range, rather than averaging across the whole window. irate() reacts faster to sudden spikes, but is also noisier and more sensitive to a single unusual scrape; rate() is the recommended default for dashboards and especially for alerting, where a smoother, less spike-prone signal is usually what you actually want. Reach for irate() specifically when you’re looking at a fast-moving counter on a short timescale and want to see instantaneous changes rather than a smoothed trend.

Aggregating away labels you don’t care about

rate() still returns one result per original time series, one line per unique combination of path, method, and status. To get a single number per path, summed across whatever methods and statuses exist, use an aggregation operator:

sum by (path) (rate(http_requests_total{status="500"}[5m]))

sum by (path) (...) sums the rates together, grouping by the path label and discarding every other label in the result. This is the general shape of PromQL aggregation: pick an operator (sum, avg, max, min, count, and others), and a by (...) clause naming which labels survive into the result.

Combining queries with binary operators

To calculate an error rate as a percentage, you need two aggregated queries, error requests and total requests, divided against each other:

sum by (path) (rate(http_requests_total{status="500"}[5m]))
/
sum by (path) (rate(http_requests_total[5m]))
* 100

PromQL matches the two sides of a binary operation by their remaining labels, path in this case, computing the division for each matching pair rather than requiring you to align them manually. Multiplying by 100 turns the resulting ratio into a percentage.

Filtering the result itself

Wrap the whole expression to keep only the results that actually cross a threshold:

(
  sum by (path) (rate(http_requests_total{status="500"}[5m]))
  /
  sum by (path) (rate(http_requests_total[5m]))
  * 100
) > 5

This returns only the paths whose computed error percentage is currently above 5, which is exactly the shape of query you’d wrap in an alerting rule to page someone when it’s true.

Turning a query into an alert

An alerting rule is a PromQL expression plus metadata describing what to do when it evaluates to something:

groups:
  - name: example-alerts
    rules:
      - alert: HighErrorRatePerPath
        expr: |
          (
            sum by (path) (rate(http_requests_total{status="500"}[5m]))
            /
            sum by (path) (rate(http_requests_total[5m]))
            * 100
          ) > 5
        for: 10m
        labels:
          severity: page
        annotations:
          summary: "Error rate above 5% on {{ $labels.path }}"

for: 10m requires the condition to stay true for 10 minutes before the alert actually fires, which avoids paging on a brief, self-resolving blip. Prometheus evaluates rules like this continuously and hands anything currently firing to Alertmanager, which is the same PromQL you’d otherwise put directly into a Grafana panel, just wrapped with a name, a duration, and routing metadata.

Where to go from here

This covers instant vectors, rate calculations, aggregation, and binary operators, enough to read and write the large majority of queries you’ll encounter in existing dashboards and alerts. PromQL has more beyond this (range vector functions beyond rate, subqueries, more aggregation operators), well documented in Prometheus’s own query reference, which is worth keeping open while you write your first few real queries.

Popular Guides