PromQL Operators — Part I


In our last post, we learned 
how to shift queries in time using offset and @. We can now select the right series and read them at the right moment.

In this post, we will learn how to compute with those series. PromQL gives us three families of operators: arithmetic, comparison, and logical. Each one transforms or combines time series in a different way.

Our setup is a Prometheus server scraping a Linux host via Node Exporter. The target has these labels:

  • instance="172.31.33.131:9100"
  • job="node_exporter"


All examples will run in the Prometheus UI at http://<our-server>:9090.


Arithmetic Operators

Arithmetic operators perform math on time series values. PromQL supports +, -, *, /, % (modulo), and ^ (power).
We will use them constantly — raw Node Exporter metrics come in bytes, seconds, and counters. Arithmetic is how we turn those raw numbers into something human-readable.


Scalar arithmetic

The simplest case is operating on a single instant vector with a scalar number. Let's convert available memory from bytes to gigabytes:

node_memory_MemAvailable_bytes{instance="172.31.33.131:9100"} / (1024 * 1024 * 1024)


We'll run this in the Prometheus UI. The result is available memory expressed in GB — a number we can read at a glance instead of a nine-digit byte count.



Let's do the same for disk space. We'll convert available bytes on real filesystems to gigabytes:

node_filesystem_avail_bytes{
instance="172.31.33.131:9100",
fstype!~"tmpfs|squashfs|devtmpfs"
} / (1024 * 1024 * 1024)


We will see one result per filesystem, each value in GB.



Computing a percentage

Division between two instant vectors is how we compute ratios and percentages. PromQL matches series by their labels before performing the operation — every series in the numerator is divided by the series in the denominator that shares the same label set.

Let's compute disk usage as a percentage. We need used space divided by total space:

(
node_filesystem_size_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
-
node_filesystem_avail_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
)
/
node_filesystem_size_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
* 100


We will see one result per filesystem — the percentage of disk space currently used. This is one of the most practical queries we will put on a MuleSoft host dashboard.




Let's do the same for memory. Available memory divided by total memory gives us the free percentage:

node_memory_MemAvailable_bytes{instance="172.31.33.131:9100"}
/
node_memory_MemTotal_bytes{instance="172.31.33.131:9100"}
* 100


We will see the percentage of memory still available on our host.



Comparison Operators

Comparison operators evaluate a condition between a time series and a value. PromQL supports ==, !=, >, <, >=, and <=.
By default, a comparison operator filters the result — it drops any series where the condition is false and keeps the ones where it is true. This is the foundation of alerting rules.


Filtering with a threshold

Let's keep only the filesystems where disk usage exceeds 80%:

(
node_filesystem_size_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
-
node_filesystem_avail_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
)
/
node_filesystem_size_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
* 100 > 80


We'll run this. If no filesystem is above 80%, Prometheus returns an empty result. If one is above 80%, we will see exactly that series. This is how a Prometheus alert rule works — when the query returns results, the alert fires.



Let's apply the same pattern to memory. Let's flag when less than 20% of memory is free:

node_memory_MemAvailable_bytes{instance="172.31.33.131:9100"}
/
node_memory_MemTotal_bytes{instance="172.31.33.131:9100"}
* 100 < 20


If our MuleSoft runtime is consuming memory, this query will return a result. When it returns nothing, we are in a safe range.



The bool modifier

By default, a comparison filters series. With the bool modifier, it returns 1 (true) or 0 (false) for every series instead of dropping the ones that do not match.

(
node_filesystem_size_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
-
node_filesystem_avail_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
)
/
node_filesystem_size_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
* 100 > bool 80


We'll run this. Every filesystem now returns a value — 1 if usage is above 80%, 0 if it is not. We will use bool when we want to display a status indicator on a Grafana dashboard rather than filter out the healthy series.


Logical/Set Operators

Logical operators combine two instant vectors based on whether their label sets match. PromQL provides three: and, or, and unless.
Unlike arithmetic operators, logical operators do not compute new values — they select or drop series based on label matching.


and — Intersection

and returns the series from the left side that have a matching label set on the right side. It keeps the values from the left side.
Let's say we want to see network receive throughput, but only for the interfaces that also have transmit activity. We will combine two metrics:

node_network_receive_bytes_total{instance="172.31.33.131:9100", device!~"lo|veth.*"}
and
node_network_transmit_bytes_total{instance="172.31.33.131:9100", device!~"lo|veth.*"}


We'll run this. We will see receive bytes only for the network devices that appear in both metrics. In practice on our host, this will match the real interfaces and exclude any virtual device that only appears on one side.


or — Union

or returns all series from the left side plus any series from the right side that do not already appear on the left.
A practical use case: we want to monitor two different memory metrics and make sure we have a value even if one of them is missing in a given environment:

node_memory_MemAvailable_bytes{instance="172.31.33.131:9100"}
or
node_memory_MemFree_bytes{instance="172.31.33.131:9100"}


We'll run this. We will see both metrics returned — MemAvailable from the left side, and MemFree from the right side as a fallback series. We will use or in alerting rules that need to handle environments where a metric may not exist.


unless — Exclusion

unless returns the series from the left side that do not have a matching label set on the right side. It is the opposite of and.
Let's say we want to see all network interfaces receiving data, but we want to exclude any interface that is also listed as a known virtual device in another metric. A simpler everyday example: let's get filesystem metrics but exclude any mount point that also appears as a read-only filesystem:

node_filesystem_avail_bytes{instance="172.31.33.131:9100", fstype!~"tmpfs|squashfs|devtmpfs"}
unless
node_filesystem_readonly{instance="172.31.33.131:9100"} == 1


We'll run this. We will see available bytes only for filesystems that are not marked as read-only. Read-only mounts — like a CD-ROM or a squashfs snap — have no relevant free space anyway, so excluding them keeps the result clean.


Operator Quick Reference

Arithmetic

OperatorMeaning
+Addition
-Subtraction
*Multiplication
/Division
%Modulo
^Power


Comparison

OperatorMeaning
=Equal
!=Not equal
>Greater than
<Less than
>=Greater than or equal
<=Less than or equal

Add bool after any comparison operator to return 1/0 instead of filtering.

Logical / Set

OperatorMeaning
andKeep left series that match right
orAll left series plus unmatched right series
unlessKeep left series that do not match right


Summary

Arithmetic operators turn raw byte and second counts into readable percentages and human-scale units. Comparison operators filter series by a threshold — the basis of every alert rule we will write. The bool modifier switches comparison from filtering to scoring, which is useful for status panels. Logical operators combine or subtract instant vectors by their label sets without changing their values.

In the next post, we will learn about aggregation operators  sum(), avg(), max(), min(), and count() — and how to control their output with the by and without clauses.

Previous Post Next Post