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"}
* 100We 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"}
* 100We 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 > 80We'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 < 20If 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 80We'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"} == 1We'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
| Operator | Meaning |
|---|---|
+ | Addition |
- | Subtraction |
* | Multiplication |
/ | Division |
% | Modulo |
^ | Power |
Comparison
| Operator | Meaning |
|---|---|
| = | 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
| Operator | Meaning |
|---|---|
and | Keep left series that match right |
or | All left series plus unmatched right series |
unless | Keep 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.