Skip to content

Commit 3ce7ab9

Browse files
docs: document offset in the spec and check operators are served (#123)
spec.md had no mention of `offset`, so the MPL reference MetricsDB serves (and MCP's getMetricsSpec returns) never told anyone it exists. - add an Offset section to spec.md, plus offset and sample examples to examples::MPL (sample had no served example either) - add a language-server test that fails when a pipe operator in KEYWORDS is missing from spec.md or from every served example Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 2e076f6 commit 3ce7ab9

6 files changed

Lines changed: 83 additions & 2 deletions

File tree

‎extra/mpl-language-server/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ examples = ["mpl-lang/examples"]
3232
lsp-bin = ["dep:eyre", "dep:lsp-server", "dep:lsp-types", "dep:serde_json"]
3333

3434
[dev-dependencies]
35+
mpl-lang = { path = "../..", default-features = false, features = ["examples"] }
3536
test-case = "3"
3637
regex = "1"

‎extra/mpl-language-server/src/keywords.rs‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,31 @@ mod tests {
178178
assert!(keyword_info("nonsense").is_none());
179179
}
180180

181+
/// MetricsDB serves `SPEC` and `MPL` as the MPL reference, so every operator must appear in both.
182+
#[test]
183+
fn every_pipe_operator_is_in_the_served_spec() {
184+
// `filter` is a deprecated alias for `where`.
185+
const EXEMPT: &[&str] = &["filter"];
186+
let mut missing = Vec::new();
187+
for entry in KEYWORDS {
188+
let usage = format!("| {}", entry.label);
189+
if EXEMPT.contains(&entry.label) || !entry.syntax.is_some_and(|s| s.starts_with(&usage))
190+
{
191+
continue;
192+
}
193+
if !mpl_lang::examples::SPEC.contains(&usage) {
194+
missing.push(format!("spec.md does not show `{usage}`"));
195+
}
196+
if !mpl_lang::examples::MPL
197+
.iter()
198+
.any(|(_, example)| example.contains(&usage))
199+
{
200+
missing.push(format!("no example in `examples::MPL` uses `{usage}`"));
201+
}
202+
}
203+
assert!(missing.is_empty(), "{}", missing.join("\n"));
204+
}
205+
181206
#[test]
182207
fn descriptions_are_present() {
183208
for entry in KEYWORDS {

‎spec.md‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,40 @@ For example:
4747
`k8s-metrics-dev`:cpu_usage[2025-03-01T13:00:00Z..+1h]
4848
```
4949

50+
## Offset
51+
52+
`offset` reads the source from an earlier time. The query keeps its time range: each value is read from that far back and
53+
returned at the matching time in the query range. This makes it possible to compare a metric with its own past, for
54+
example this week with the same time last week:
55+
56+
```mpl
57+
// read the values from one hour earlier
58+
| offset -1h
59+
```
60+
61+
With `offset -1h` and a time range of 10:00 to 11:00, the source reads 09:00 to 10:00, and the value recorded at 09:15
62+
is returned at 10:15.
63+
64+
- The duration uses the [relative time](#time-range) units and must start with `-`. A negative offset moves back in
65+
time, which is the opposite sign of PromQL's `offset`. Moving forward in time is not supported yet.
66+
- The offset operator is only valid right after the source, before `sample`, and at most once per source.
67+
- Each source in a [computation](#computation) has its own offset; a source without one reads the query's time range.
68+
`offset` cannot follow `compute`.
69+
70+
```mpl
71+
// requests per second compared with the same time one week earlier
72+
(
73+
`k8s-metrics-dev`:http_requests_total
74+
| align to 5m using prom::rate
75+
| group using sum,
76+
`k8s-metrics-dev`:http_requests_total
77+
| offset -1w
78+
| align to 5m using prom::rate
79+
| group using sum
80+
)
81+
| compute week_over_week using /
82+
```
83+
5084
## Sampling
5185

5286
Before the filter it is possible to sample the data. This can be helpful when designing more complex queries or get a
@@ -58,7 +92,7 @@ Sampling by 0.9 means that 90% of the series will be kept. The syntax is:
5892
| sample 0.9
5993
```
6094

61-
The sampling operator is only valid right after the source.
95+
The sampling operator is only valid right after the source, or after its [offset](#offset).
6296

6397
## Filtering
6498

‎src/lib.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ pub mod examples {
454454
pub const SPEC: &str = include_str!("../spec.md");
455455

456456
/// MPL examples used in tests and documentation
457-
pub const MPL: [(&str, &str); 19] = [
457+
pub const MPL: [(&str, &str); 21] = [
458458
example!("align-rate"),
459459
example!("as"),
460460
// example!("enrich"),
@@ -469,9 +469,11 @@ pub mod examples {
469469
example!("map-gt"),
470470
example!("map-mul"),
471471
// example!("nested-enrich"),
472+
example!("offset"),
472473
example!("parser-error"),
473474
example!("rate"),
474475
// example!("replace_labels"),
476+
example!("sample"),
475477
example!("set"),
476478
example!("slo"),
477479
example!("slo-histogram"),

‎tests/examples/offset.mpl‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// sum(rate(http_requests_total[5m]))
2+
// /
3+
// sum(rate(http_requests_total[5m] offset 1w))
4+
5+
(
6+
test:http_requests_total
7+
| align to 5m using prom::rate
8+
| group using sum,
9+
test:http_requests_total
10+
| offset -1w
11+
| align to 5m using prom::rate
12+
| group using sum
13+
)
14+
| compute week_over_week using /

‎tests/examples/sample.mpl‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// look at a tenth of the series to get a feel for a large metric
2+
test:http_requests_total
3+
| sample 0.1
4+
| align to 5m using prom::rate
5+
| group by method using sum

0 commit comments

Comments
 (0)