Macros
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
- Access the UI by navigating: Cluster → Settings → Macros
- Define the macro function(s). See the Configuration Template and Examples section for reference
- On clicking Save, we’ll check to ensure no syntax errors
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.