sui_metric_checker/
query.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
use crate::unix_seconds_to_timestamp_string;
use anyhow::anyhow;
use base64::{engine::general_purpose, Engine};
use prometheus_http_query::Client;
use reqwest::header::{HeaderValue, AUTHORIZATION};
use tracing::{debug, info};

pub async fn instant_query(
    auth_header: &str,
    client: Client,
    query: &str,
) -> Result<f64, anyhow::Error> {
    debug!("Executing {query}");
    let response = client
        .query(query)
        .header(
            AUTHORIZATION,
            HeaderValue::from_str(&format!(
                "Basic {}",
                general_purpose::STANDARD.encode(auth_header)
            ))?,
        )
        .get()
        .await?;

    let result = response
        .data()
        .as_vector()
        .unwrap_or_else(|| panic!("Expected result of type vector for {query}"));

    if !result.is_empty() {
        let first = result.first().unwrap();
        debug!("Got value {}", first.sample().value());
        Ok(first.sample().value())
    } else {
        Err(anyhow!(
            "Did not get expected response from server for {query}"
        ))
    }
}

// This will return the median value of the queried metric over the given time range.
pub async fn range_query(
    auth_header: &str,
    client: Client,
    query: &str,
    start: i64,
    end: i64,
    step: f64,
    percentile: u8,
) -> Result<f64, anyhow::Error> {
    debug!("Executing {query}");
    let response = client
        .query_range(query, start, end, step)
        .header(
            AUTHORIZATION,
            HeaderValue::from_str(&format!(
                "Basic {}",
                general_purpose::STANDARD.encode(auth_header)
            ))?,
        )
        .get()
        .await?;

    let result = response
        .data()
        .as_matrix()
        .unwrap_or_else(|| panic!("Expected result of type matrix for {query}"));

    if result.is_empty() {
        return Err(anyhow!(
            "Did not get expected response from server for {query}"
        ));
    }

    let mut samples: Vec<f64> = result
        .first()
        .unwrap()
        .samples()
        .iter()
        .filter_map(|sample| {
            let v = sample.value();
            if v.is_nan() {
                None
            } else {
                Some(v)
            }
        })
        .collect();
    if samples.is_empty() {
        return Err(anyhow!("Query returned zero data point! {query}"));
    }
    samples.sort_by(|a, b| a.partial_cmp(b).unwrap());

    assert!(
        (1..=100).contains(&percentile),
        "Invalid percentile {percentile}"
    );
    let index = samples.len() * percentile as usize / 100;
    let result = samples[index];
    info!(
        "{query}: got p{percentile} value {result}, over {} data points in time range {} - {}",
        samples.len(),
        unix_seconds_to_timestamp_string(start),
        unix_seconds_to_timestamp_string(end)
    );
    Ok(result)
}