A Jsonnet based DSL for writing PromQL queries.
This is useful when creating grafana dashboards using grafonnet
.
Instead of having to template strings manually, you can now use immutable builders to DRY out your PromQL queries.
This library generates jsonnet strings, which can be fed to the expr
field of the prometheus target in graffonet.
Here is a quick example that constructs a PromQL query for a counter:
local promql = import "promql.libsonnet";
promql.new("prometheus_http_requests_total")
.withLabels({
code: "200",
handler: "/api/v1/query",
})
.sum()
.delta(["5m","5m"])
.build()
// promql result: delta(sum(prometheus_http_requests_total{code="200",handler="/api/v1/query"})[5m:5m])
Usage is natural, if you know what you're looking to construct. The general steps are as follows:
- Import
promql
from thepromql.libsonnet
file. - Use
promql.new("...")
to start creating a query. - You can specify labels to filter with
.withLabels({...})
. - Call functions for any aggregations, transformations that you want to do.
- Finally don't forget to call
.build()
to get the generated PromQL query as a jsonnet string. - You can pass this to the
expr
field for theprometheus
target from grafonnet like this:local promql = import "promql.libsonnet"; local grafana = import "grafonnet.libsonnet"; local http_success_query = promql.new("prometheus_http_requests_total") .withLabels({code:"200"}) .sum() .delta(["$__interval", "$__interval"]) .build(); grafana.prometheus.target(expr=http_success_query, ...)
- There are two ways to specify label matchers.
- If you're using the
=
equals matcher, then you can use the shorter.withLabels({...})
function to specify multiple filters:promql.new("prometheus_http_requests_total") .withLabels({success:"true", handler:"/api/v1/query"}); .build() // output: prometheus_http_requests_total{success="true",handler="/api/v1/query"}
- If you need the other matchers like
!=
,=~
or!~
you need to use the.withLabel({key,op,value})
function.promql.new("prometheus_http_requests_total") .withLabel({key:"success", op: "=", value:"true"}); .withLabel({key:"handler", op: "=~", value:"/api/v1/"}); .withLabel({key:"method", op: "!=", value:"GET"}); .build() // output: prometheus_http_requests_total{success="true",handler=~"/api/v1/",method!="GET"}
- You can also mix and match the two as needed:
promql.new("prometheus_http_requests_total") .withLabels({success:"true", handler:"/api/v1/query"}); .withLabel({key:"method", op: "!=", value:"GET"}); .build() // output: prometheus_http_requests_total{success="true",handler="/api/v1/query",method!="GET"}
- Aggregation operators like
sum
,avg
etc. take instant vectors and return another instant vector. - These also support the following optional clauses with a list of labels:
by
clause -.topk(5, by=["host"])
without
clause -.avg(without=["handler"])
- Sample usage:
promql.new("prometheus_http_requests_total") .withLabels({code:"200"}) .sum(by=["handler", "instance"]) .build() // output: sum by (handler,instance) (prometheus_http_requests_total{code="200"})
- A PromQL Range Selector is represented in jsonnet by a 2-tuple of duration strings:
["1m","1m"]
. - The first one represents the actual range, while the second is the resolution.
- Since applying a range selector by itself is not of much use, this library couples it to all functions that expect a range-vector.
- Thus to construct
delta(prometheus_http_requests_total{status_code="200"}[1m:1m])
, the code would look like:promql.new("prometheus_http_requests_total") .withLabels({status_code:"200"}) .delta(["1m","1m"]) .build()
- When using Grafana, you want to let it fill appropriate durations for the range selectors using the
$__interval
variable. To support this, we don't perform any validation for the duration fields, thus code like this is allowed:promql.new("prometheus_http_requests_total") .withLabels({status_code:"200"}) .delta(["$__interval","$__interval"]) .build() // output: delta(prometheus_http_requests_total{status_code="200"}[$__interval:$__interval])
PromQL Function | Sample Jsonnet Usage | Support |
---|---|---|
Aggregation Functions | ||
sum(instant-vector) |
.sum() |
✔️ |
min(instant-vector) |
.min() |
✔️ |
max(instant-vector) |
.max() |
✔️ |
avg(instant-vector) |
.avg() |
✔️ |
group(instant-vector) |
.group() |
✔️ |
stddev(instant-vector) |
.stddev() |
✔️ |
stdvar(instant-vector) |
.stdvar() |
✔️ |
count(instant-vector) |
.count() |
✔️ |
count_values(string, instant-vector) |
.count_values("my-label-name") |
✔️ |
bottomk(scalar, instant-vector) |
.bottomk(5) |
✔️ |
topk(scalar, instant-vector) |
.topk(5) |
✔️ |
quantile(scalar, instant-vector) |
.quantile("0.99") |
✔️ |
Instant Vector Functions | ||
abs(instant-vector) |
.abs() |
✔️ |
absent(instant-vector) |
.absent() |
✔️ |
ceil(instant-vector) |
.ceil() |
✔️ |
clamp_max(instant-vector, scalar) |
.clamp_max(5) |
✔️ |
clamp_min(instant-vector, scalar) |
.clamp_min(5) |
✔️ |
exp(instant-vector) |
.exp() |
✔️ |
floor(instant-vector) |
.floor() |
✔️ |
histogram_quantile(scalar, instant-vector) |
.histogram_quantile("0.90") |
✔️ |
label_join(instant-vector, string, string, string...) |
🚧 | |
label_replace(instant-vector, string, string, string, string) |
🚧 | |
ln(instant-vector) |
.ln() |
✔️ |
log10(instant-vector) |
.log10() |
✔️ |
log2(instant-vector) |
.log2() |
✔️ |
round(instant-vector, scalar) |
.round(2) |
✔️ |
scalar(instant-vector) |
.scalar() |
✔️ |
sort(instant-vector) |
.sort() |
✔️ |
sort_desc(instant-vector) |
.sort_desc() |
✔️ |
sqrt(instant-vector) |
.sqrt() |
✔️ |
day_of_month(instant-vector) |
🚧 | |
day_of_week(instant-vector) |
🚧 | |
days_in_month(instant-vector) |
🚧 | |
hour(instant-vector) |
🚧 | |
minute(instant-vector) |
🚧 | |
month(instant-vector) |
🚧 | |
year(instant-vector) |
🚧 | |
timestamp(instant-vector) |
🚧 | |
Range Vector Functions | ||
changes(range-vector) |
.changes(["1m","1m"]) |
✔️ |
absent_over_time(range-vector) |
.absent_over_time(["1m","1m"]) |
✔️ |
delta(range-vector) |
.delta(["1m","1m"]) |
✔️ |
deriv(range-vector) |
.deriv(["1m","1m"]) |
✔️ |
holt_winters(range-vector, scalar, scalar) |
.holt_winters(["1m","1m"], "0.5", "0.5") |
✔️ |
idelta(range-vector) |
.idelta(["1m","1m"]) |
✔️ |
increase(range-vector) |
.increase(["1m","1m"]) |
✔️ |
irate(range-vector) |
.irate(["1m","1m"]) |
✔️ |
predict_linear(range-vector, scalar) |
.predict_linear(["1m","1m"], 60) |
✔️ |
rate(range-vector) |
.rate(["1m","1m"]) |
✔️ |
resets(range-vector) |
.resets(["1m","1m"]) |
✔️ |
avg_over_time(range-vector) |
.avg_over_time(["1m","1m"]) |
✔️ |
min_over_time(range-vector) |
.min_over_time(["1m","1m"]) |
✔️ |
max_over_time(range-vector) |
.max_over_time(["1m","1m"]) |
✔️ |
sum_over_time(range-vector) |
.sum_over_time(["1m","1m"]) |
✔️ |
count_over_time(range-vector) |
.count_over_time(["1m","1m"]) |
✔️ |
quantile_over_time(scalar, range-vector) |
.quantile_over_time("0.90", ["1m","1m"]) |
✔️ |
stddev_over_time(range-vector) |
.stddev_over_time(["1m","1m"]) |
✔️ |
stdvar_over_time(range-vector) |
.stdvar_over_time(["1m","1m"]) |
✔️ |
Miscellaneous Functions | ||
vector(scalar) |
🚧 | |
time() |
🚧 |
- Unit Tests:
jsonnet unit-tests.jsonnet
- Integration Tests
- Start a prometheus listening at
http://localhost:9090
withdocker run -p 127.0.0.1:9090:9090 --rm --name "promql-jsonnet-prometheus" -it prom/prometheus
- Run
./integration_tests.sh
- Start a prometheus listening at