PromQL: Functions

This section documents the current Prometheus 3.x line (latest 3.14.0) as published at the Prometheus documentation and the current Grafana 13.x line (latest 13.2.1) as published at the Grafana documentation, which are the references these pages are written and verified against. No specific patch version is pinned. Some capabilities (Grafana Enterprise-only features, Grafana Cloud-hosted services, and long-term-storage systems like Thanos/Cortex/Mimir) are linked, not documented in depth.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since both projects iterate quickly.

This section’s bibliography lists the reference material consulted while preparing these pages.

PromQL: Basics & Selectors introduces range vectors and instant vectors, and PromQL: Operators & Aggregation covers sum by (…​) and friends — both are assumed below. This page works through the function calls that turn those building blocks into the queries actually used in dashboards and alerts: rate(), irate() and increase() for Counters; histogram_quantile() for turning a classic Histogram’s _bucket series into a percentile; predict_linear() for forecasting when a Gauge will cross a threshold; label_replace()/label_join() for reshaping labels with regular expressions; and the time/date functions. The full function reference is Functions, and every example below can be adapted from the worked queries at Query examples.

Counter functions: rate, irate, increase

A Counter only ever goes up (or resets to zero on restart, as covered in Data Model & Metric Types), so its raw value is rarely useful by itself — what dashboards and alerts want is how fast it is increasing. rate(), irate() and increase() all take a range vector, never an instant vector: a single scraped sample has no rate, so each of these needs a [5m]-style range selector to compare samples across a time window.

# Per-second average rate of increase over the last 5 minutes.
rate(http_requests_total{job="api"}[5m])
# https://prometheus.io/docs/prometheus/latest/querying/functions/

rate() looks at the first and last samples in the range, extrapolates slightly to the edges of the window, and divides by the elapsed seconds — the result is smoothed across the whole range, which is what makes it the right choice for graphs and for alerting expressions, where a single noisy scrape should not flap a rule. irate() instead uses only the two most recent samples in the range and ignores the rest, so it reacts instantly to a spike but is far noisier; use it only for interactive, high-resolution graphing of fast-moving counters, never in a recording or alerting rule. increase() is rate() multiplied by the number of seconds in the range — the extrapolated total increase over the window, not a per-second figure:

# Extrapolated request count over the last hour, not a rate.
increase(http_requests_total{job="api"}[1h])

All three detect a counter reset (the value drops between consecutive samples, typically a process restart) and correct for it automatically by treating the drop as if the counter had continued from where it left off, rather than reporting a spurious negative rate. This correction only works because the input is a genuine Counter; calling rate() on a Gauge produces a meaningless number. Because rate()/irate()/increase() consume a range vector and return an instant vector, wrap the result in sum by (…​) (…​) to aggregate across instances — summing first and taking the rate of the sum is a different (and usually wrong) query, since it would apply a single reset-correction across already-summed, cross-instance data:

# Correct: rate per series first, then sum across instances.
sum by (job) (rate(http_requests_total[5m]))

histogram_quantile: latency percentiles from a classic Histogram

A classic Histogram (see Data Model & Metric Types) exposes cumulative _bucket{le="…​"} Counter series alongside _sum and _count. histogram_quantile(φ, b) estimates the φ-quantile (0 ≤ φ ≤ 1; 0.99 for p99) by linear interpolation within the bucket where the cumulative count crosses φ of the total, given a vector b whose series carry an le label:

# p99 request latency over the last 5 minutes, aggregated across all instances of a job.
histogram_quantile(0.99,
  sum by (le, job) (rate(http_request_duration_seconds_bucket[5m]))
)
# https://prometheus.io/docs/prometheus/latest/querying/functions/

Read this from the inside out:

  1. rate(…​_bucket[5m]) converts each cumulative bucket Counter into a per-second rate, one series per le/instance/pod combination.

  2. sum by (le, job) (…​) adds those rates across instances, collapsing everything down to one series per le value per job — le must be kept in the by (…​) clause, since histogram_quantile() needs it to reconstruct the bucket boundaries.

  3. histogram_quantile(0.99, …​) walks the resulting le buckets for each job and interpolates the 0.99 quantile within whichever bucket the running count crosses that threshold.

The estimate is only as accurate as the bucket boundaries chosen at instrumentation time — a p99 that falls between two widely spaced le values is a rough interpolation, not an exact figure, which is why bucket boundary selection matters and is covered alongside instrumentation in Instrumenting Applications. Native histograms let histogram_quantile() operate on a single series instead of a _bucket family and avoid this interpolation error; see that same page for the trade-off. This is the query pattern Recording & Alerting Rules precomputes into a recording rule, since a histogram_quantile(rate(…​)) chain evaluated on every dashboard refresh (or every alert evaluation) is one of the more expensive query shapes to run live.

predict_linear: capacity forecasting

predict_linear(v range-vector, t scalar) fits a simple linear regression to the samples in the range vector and extrapolates t seconds into the future, returning the predicted value. It is built for slow, roughly linear Gauges — disk usage, queue backlog — where "will this cross a threshold soon" matters more than the exact current value:

# Will free disk space go negative within the next 4 hours, based on the last 6h trend?
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 4 * 3600) < 0
# https://prometheus.io/docs/prometheus/latest/querying/functions/

The range ([6h] above) controls how much history the regression is fit against — long enough to smooth out noise, short enough to still reflect the current trend rather than a stale one from days ago. predict_linear() assumes a straight-line trend; a workload with strong daily seasonality (a disk that fills on business days and drains overnight) is a poor fit and will over- or under-predict depending on where in the cycle the range window lands. This expression is a common alerting-rule body in Recording & Alerting Rules — "page before it actually runs out" rather than "page once it already has."

label_replace and label_join: reshaping labels with regexes

Both functions return the input vector with one label added or overwritten, without touching sample values — useful for normalizing labels that differ across jobs before an aggregation or a join-style vector match (see PromQL: Operators & Aggregation).

label_replace(v, dst, replacement, src, regex) matches regex (a fully anchored RE2 pattern) against the src label’s value and writes the result of substituting capture groups ($1, $2, …​) into replacement, storing it in dst:

# Pull the numeric instance id out of a pod name like "api-7" into its own label.
label_replace(
  up{job="api"},
  "instance_id", "$1",
  "pod", "api-(.*)"
)
# https://prometheus.io/docs/prometheus/latest/querying/functions/

Every series where pod matches api-(.*) gains an instance_id label holding whatever the capture group matched ("7" for pod="api-7"); a series that does not match regex at all is passed through with dst left unset. This is the standard way to align label names that differ only in spelling between two metrics before matching them together with on(…​)/ignoring(…​), and it is exactly what Service Discovery & Relabeling does at scrape time with relabel_configs — label_replace() is the PromQL-query-time equivalent of that same regex-capture-and-rewrite idiom, applied per query instead of once at scrape.

label_join(v, dst, separator, src_label_1, src_label_2, …​) concatenates one or more existing labels into dst with separator between them — no regex, just a join:

# Build a single "namespace/pod" identity label out of two existing labels.
label_join(kube_pod_info, "pod_identity", "/", "namespace", "pod")

Both functions are pure-vector transforms with no aggregation, so they compose directly with sum by (…​), on(…​) joins, or another label_replace() call in the same expression.

Time and date functions

A small family of functions ignores its (optional) vector argument entirely and instead returns the evaluation timestamp itself, broken down however is useful — most often to gate a rule to a particular window or day:

time()                              # evaluation time as a Unix timestamp, seconds
day_of_week(vector(time()))         # 0 (Sunday) .. 6 (Saturday), in UTC
day_of_month(vector(time()))        # 1..31
hour(vector(time()))                # 0..23, UTC
minute(vector(time()))              # 0..59
month(vector(time()))               # 1..12
year(vector(time()))                # e.g. 2026
# https://prometheus.io/docs/prometheus/latest/querying/functions/

vector(time()) turns the scalar time() into a one-sample instant vector so the other functions (which expect a vector argument, defaulting to the current time when omitted) have something to operate on; calling hour() with no argument is equivalent and more common in practice. A frequent use is silencing a noisy rule outside business hours directly in the expression:

# Only fire during weekday business hours (UTC): Mon-Fri, 08:00-18:00.
(
  rate(http_requests_total{status=~"5.."}[5m]) > 0.05
)
and on() (day_of_week() >= 1 and day_of_week() <= 5)
and on() (hour() >= 8 and hour() < 18)

These functions are all evaluated in UTC; converting to a local timezone for a display label (rather than a threshold) is normally left to Grafana, covered in Grafana: Dashboards & Visualization, rather than done inside PromQL.

Where these land in real rules

Every function above shows up, more or less verbatim, once queries stop being typed ad hoc into the Prometheus expression browser and get promoted to recording rules (precomputing an expensive histogram_quantile(sum(rate(…​)))) or alerting rules (a predict_linear() or counter-reset-aware rate() threshold). See Recording & Alerting Rules for that promotion, and Prometheus & Monitoring for the rest of this section.