Skip to main content

servers/exemplar/
query.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Prometheus exemplar selectors and millisecond API timestamps.
16
17use std::collections::BTreeMap;
18
19use promql_parser::label::{METRIC_NAME, MatchOp, Matcher};
20use promql_parser::parser::{Expr, VectorSelector};
21use promql_parser::util::{ExprVisitor, walk_expr};
22use query::parser::QueryLanguageParser;
23use query::promql::label_values::signed_millis_since_epoch;
24use serde::{Deserialize, Serialize};
25use snafu::OptionExt;
26
27use crate::error::{InvalidQuerySnafu, Result};
28use crate::prom_store::{FIELD_NAME_LABEL, is_database_selection_label};
29use crate::prometheus_handler::resolve_schema_from_matchers;
30
31#[derive(Debug, Clone)]
32pub struct Selector {
33    pub schema: String,
34    pub matchers: Vec<Matcher>,
35}
36
37#[derive(Debug)]
38pub struct Query {
39    pub expression: String,
40    pub selectors: Vec<Selector>,
41    /// Inclusive nanosecond scan bounds covering the requested milliseconds.
42    pub bounds: (i64, i64),
43}
44
45impl Query {
46    pub fn parse(expression: String, start: &str, end: &str, schema: &str) -> Result<Self> {
47        let parse = |input| {
48            let time = QueryLanguageParser::parse_promql_timestamp(input).map_err(|error| {
49                InvalidQuerySnafu {
50                    reason: error.to_string(),
51                }
52                .build()
53            })?;
54            let millis = signed_millis_since_epoch(time).map_err(|error| {
55                InvalidQuerySnafu {
56                    reason: error.to_string(),
57                }
58                .build()
59            })?;
60            millis.checked_mul(1_000_000).context(InvalidQuerySnafu {
61                reason: "timestamp exceeds signed nanosecond range",
62            })
63        };
64        let start = parse(start)?;
65        let end = parse(end)?;
66        if start > end {
67            return invalid("start must not exceed end");
68        }
69        let expr = promql_parser::parser::parse(&expression)
70            .map_err(|reason| InvalidQuerySnafu { reason }.build())?;
71        struct Collector<'a> {
72            schema: &'a str,
73            selectors: Vec<Selector>,
74        }
75        impl ExprVisitor for Collector<'_> {
76            type Error = crate::error::Error;
77            fn pre_visit(&mut self, expr: &Expr) -> Result<bool> {
78                let selector = match expr {
79                    Expr::VectorSelector(vs) => vs,
80                    Expr::MatrixSelector(ms) => &ms.vs,
81                    Expr::Aggregate(aggregate) => {
82                        // promql-parser 0.10's walker omits aggregation parameters.
83                        if let Some(param) = &aggregate.param {
84                            walk_expr(self, param)?;
85                        }
86                        return Ok(true);
87                    }
88                    _ => return Ok(true),
89                };
90                self.selectors.extend(resolve(selector, self.schema)?);
91                Ok(true)
92            }
93        }
94        let mut collector = Collector {
95            schema,
96            selectors: Vec::new(),
97        };
98        walk_expr(&mut collector, &expr)?;
99        Ok(Self {
100            expression,
101            selectors: collector.selectors,
102            // Include the whole final millisecond without casting the scanned column.
103            bounds: (start, end.saturating_add(999_999)),
104        })
105    }
106}
107
108fn resolve(selector: &VectorSelector, default_schema: &str) -> Result<Vec<Selector>> {
109    let branches = if selector.matchers.or_matchers.is_empty() {
110        vec![&selector.matchers.matchers]
111    } else {
112        selector.matchers.or_matchers.iter().collect()
113    };
114    branches
115        .into_iter()
116        .map(|branch| {
117            if branch
118                .iter()
119                .any(|matcher| matcher.name == FIELD_NAME_LABEL)
120            {
121                return invalid("__field__ is not supported in exemplar queries");
122            }
123            let schema =
124                resolve_schema_from_matchers(branch)?.unwrap_or_else(|| default_schema.to_string());
125            let mut matchers = branch
126                .iter()
127                .filter(|matcher| !is_database_selection_label(&matcher.name))
128                .cloned()
129                .collect::<Vec<_>>();
130            if let Some(name) = &selector.name {
131                matchers.push(Matcher::new(MatchOp::Equal, METRIC_NAME, name));
132            }
133            Ok(Selector { schema, matchers })
134        })
135        .collect()
136}
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub struct Series {
140    #[serde(rename = "seriesLabels")]
141    pub series_labels: BTreeMap<String, String>,
142    pub exemplars: Vec<Observation>,
143}
144
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146pub struct Observation {
147    pub labels: BTreeMap<String, String>,
148    pub value: String,
149    /// Unix seconds at millisecond precision.
150    pub timestamp: f64,
151}
152
153fn invalid<T>(reason: &str) -> Result<T> {
154    InvalidQuerySnafu { reason }.fail()
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn query_bounds_use_prometheus_milliseconds() {
163        for (input, millis) in [
164            ("1.234567891", 1235),
165            ("1.234e0", 1234),
166            ("1720000000.123456789", 1720000000123),
167            ("-0.0006", -1),
168            ("-0.0004", 0),
169            ("-1.0005", -1000),
170            ("1970-01-01T00:00:00.0019Z", 1),
171            ("1969-12-31T23:59:59.999999999Z", -1),
172            ("1970-01-01T01:00:00.0019+01:00", 1),
173        ] {
174            assert_eq!(
175                Query::parse("up".into(), input, input, "public")
176                    .unwrap()
177                    .bounds,
178                (millis * 1_000_000, millis * 1_000_000 + 999_999),
179                "{input}",
180            );
181        }
182        assert_eq!(
183            Query::parse("up".into(), "0.0012", "0.0011", "public")
184                .unwrap()
185                .bounds,
186            (1_000_000, 1_999_999),
187        );
188        assert_eq!(
189            Query::parse(
190                "up".into(),
191                "2262-04-11T23:47:16.854Z",
192                "2262-04-11T23:47:16.854Z",
193                "public"
194            )
195            .unwrap()
196            .bounds,
197            (9_223_372_036_854_000_000, i64::MAX),
198        );
199        assert!(Query::parse("up".into(), "0.002", "0.001", "public").is_err());
200        for input in [
201            "NaN",
202            "inf",
203            "",
204            "1e100",
205            "9223372036.855",
206            "-9223372036.855",
207        ] {
208            assert!(
209                Query::parse("up".into(), input, input, "public").is_err(),
210                "{input}"
211            );
212        }
213        assert!(
214            Query::parse("1".into(), "0", "1", "public")
215                .unwrap()
216                .selectors
217                .is_empty()
218        );
219    }
220
221    #[test]
222    fn aggregation_parameter_selectors() {
223        for (expression, expected) in [
224            (
225                "topk(scalar(limit_metric), requests)",
226                vec!["limit_metric", "requests"],
227            ),
228            (
229                "bottomk(scalar(limit_metric), requests)",
230                vec!["limit_metric", "requests"],
231            ),
232            (
233                "quantile(scalar(limit_metric), requests)",
234                vec!["limit_metric", "requests"],
235            ),
236            (
237                "topk(scalar(topk(scalar(inner_limit), limit_metric)), requests)",
238                vec!["inner_limit", "limit_metric", "requests"],
239            ),
240            ("topk(2, requests)", vec!["requests"]),
241            ("count_values(\"kind\", requests)", vec!["requests"]),
242            (
243                "topk(scalar(requests), requests)",
244                vec!["requests", "requests"],
245            ),
246        ] {
247            let query = Query::parse(expression.into(), "0", "1", "public").unwrap();
248            let mut names = query
249                .selectors
250                .iter()
251                .flat_map(|selector| &selector.matchers)
252                .filter(|matcher| matcher.name == METRIC_NAME)
253                .map(|matcher| matcher.value.as_str())
254                .collect::<Vec<_>>();
255            names.sort_unstable();
256            assert_eq!(names, expected, "{expression}");
257        }
258    }
259
260    #[test]
261    fn range_vectors_and_routing_controls() {
262        let query = Query::parse(
263            "topk(scalar(sum(rate(requests{__schema__=\"a\"}[5m]))), errors{__database__=\"b\"})"
264                .into(),
265            "0",
266            "1",
267            "public",
268        )
269        .unwrap();
270        assert_eq!(
271            query
272                .selectors
273                .iter()
274                .map(|s| s.schema.as_str())
275                .collect::<Vec<_>>(),
276            ["a", "b"]
277        );
278        for query in [
279            "up{__schema__=\"\"}",
280            "up{__schema__!=\"a\"}",
281            "up{__field__=\"x\"}",
282            "topk(scalar(up{__schema__=\"\"}), requests)",
283            "topk(scalar(up{__schema__!=\"a\"}), requests)",
284            "topk(scalar(up{__field__=\"x\"}), requests)",
285        ] {
286            assert!(Query::parse(query.into(), "0", "1", "public").is_err());
287        }
288    }
289}