Skip to main content

Macros for PromQL

Everything you need to know to start harnessing power of PromQL-based Macros.

Why Macros

Today, if you want to set a 1-day request SLO for your servicefoo, this is what your expression would like:

sum(rate(http_requests_to tal{handler!~"/metrics", method="GET, service="foo", code=~"2.*"}[1d])) / sum(rate(http_requests_total{handler!~"/metrics", method="GET, service="foo"}[1d]))

Now:

  • You want to set up a 7-day SLO as well
  • You want to ensure the same SLO is used across different services for their primary indicators.

You can see how quickly things will get out of hand, and you’ll start running into the following issues:

  • Repetition of code
  • Reduced abstractions and readability
  • Error-prone large text blobs
  • Modifications are hard and tiresome

To implement this fix across all consumers, dashboards, and alerts, it will take:

  • Weeks of migrations and trial and error to roll out
  • A week of distraction away from feature development

The solution to this is Macros.

They work in a way that is similar to how SQL developers use stored procedures. Macros take full advantage of the time-tested best practices of functions, abstractions, and reusability to replace cumbersome and error-prone methods.

Configuration Template and Examples

Macros are written as functions to which a prop is passed, can contain variables, and returns a PromQL.

function macro_name(props) {
...
return promql
}

Continuing with the above Availability PromQL example, here’s what an abstracted macro for it would look like:

function availability (metric, window, service) {
let exclude = "/metrics"
let good_codes = "2.*"
let all_codes = "2.*|5.*"

return sum(rate(metric{handler!~exclude, method="GET", service=service, code=~good_codes}[window])) / sum(rate(metric{handler!~exclude, method="GET, service=service, code=~all_codes}[window]))
}

Now you can use it as.availability(http_requests_total,7d,foo) wherever needed.

Setting Up Macros

Setting up PromQL Macros

  1. Access the UI by navigating: Cluster → Settings → Macros.
  2. Define the macro function(s). See the Configuration Template and Examples section for reference.
  3. On clicking Save, we’ll check to ensure no syntax errors.
tip

Please allow up to 5 mins before the updated macros are available to query. Similarly, after deleting a macro, it may still be available for querying up to 5 mins.

Calling the Macro

Once the macro is defined, you can call it just like a function call by passing values.

availability(http_requests_total,7d,foo)

Note that the Macro evaluation engine will replace all occurrences of variables inside the body of the Macro definition with the values passed when the Macro was called. This can be tricky when you use by clause inside the Macro definition uses the same variable names as the ones defined in the Macro definition.

function availability (metric, window, service) {
let exclude = "/metrics"
let good_codes = "2.*"
let all_codes = "2.*|5.*"

return sum(rate(metric{handler!~exclude, method="GET", service=service, code=~good_codes}[window]) by (metric)) / sum(rate(metric{handler!~exclude, method="GET, service=service, code=~all_codes}[window]))
}

In this case, the Macro engine will evaluate this to

function availability (metric, window, service) {
let exclude = "/metrics"
let good_codes = "2.*"
let all_codes = "2.*|5.*"

return sum(rate(metric{handler!~exclude, method="GET", service=service, code=~good_codes}[window]) by ('http_requests_total')) / sum(rate(metric{handler!~exclude, method="GET, service=service, code=~all_codes}[window]))
}

To mitigate this, you can rename the variables either in by clause or in the Macro definition.

function availability (metric1, window, service) {
let exclude = "/metrics"
let good_codes = "2.*"
let all_codes = "2.*|5.*"

return sum(rate(metric{handler!~exclude, method="GET", service=service, code=~good_codes}[window]) by (metric)) / sum(rate(metric{handler!~exclude, method="GET, service=service, code=~all_codes}[window]))
}

Troubleshooting

Please get in touch with us on Discord or Email if you have any questions.