Skip to main content

rs_zero/observability/metrics/
sql.rs

1use std::{collections::BTreeMap, fmt::Write};
2
3use super::{DurationMetricValue, escape_label, write_duration_metric};
4
5/// Low-cardinality labels recorded for a SQL query.
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
7pub struct SqlMetricLabels {
8    /// Database kind, for example `sqlite`, `postgres` or `mysql`.
9    pub db_kind: String,
10    /// Repository or component name.
11    pub repository: String,
12    /// Repository method or operation owner.
13    pub method: String,
14    /// SQL operation category such as `select`, `insert` or `delete`.
15    pub operation: String,
16    /// Result category, usually `success` or `error`.
17    pub result: String,
18}
19
20impl SqlMetricLabels {
21    /// Creates a SQL label set without carrying SQL text or parameters.
22    pub fn new(
23        db_kind: impl Into<String>,
24        repository: impl Into<String>,
25        method: impl Into<String>,
26        operation: impl Into<String>,
27        result: impl Into<String>,
28    ) -> Self {
29        Self {
30            db_kind: db_kind.into(),
31            repository: repository.into(),
32            method: method.into(),
33            operation: operation.into(),
34            result: result.into(),
35        }
36    }
37}
38
39pub(crate) fn render(
40    output: &mut String,
41    metrics: &BTreeMap<SqlMetricLabels, DurationMetricValue>,
42) {
43    output.push_str("# HELP rs_zero_sql_queries_total Total number of SQL queries.\n");
44    output.push_str("# TYPE rs_zero_sql_queries_total counter\n");
45    for (labels, value) in metrics {
46        write!(output, "rs_zero_sql_queries_total{{").ok();
47        write_labels(output, labels, None);
48        writeln!(output, "}} {}", value.count).ok();
49    }
50
51    output.push_str("# HELP rs_zero_sql_query_errors_total Total number of failed SQL queries.\n");
52    output.push_str("# TYPE rs_zero_sql_query_errors_total counter\n");
53    for (labels, value) in metrics
54        .iter()
55        .filter(|(labels, _)| labels.result != "success")
56    {
57        write!(output, "rs_zero_sql_query_errors_total{{").ok();
58        write_labels(output, labels, None);
59        writeln!(output, "}} {}", value.count).ok();
60    }
61
62    output.push_str("# HELP rs_zero_sql_query_duration_seconds SQL query duration.\n");
63    output.push_str("# TYPE rs_zero_sql_query_duration_seconds histogram\n");
64    for (labels, value) in metrics {
65        write_duration_metric(
66            output,
67            "rs_zero_sql_query_duration_seconds",
68            labels,
69            value,
70            write_labels,
71        );
72    }
73}
74
75fn write_labels(output: &mut String, labels: &SqlMetricLabels, le: Option<&str>) {
76    write!(
77        output,
78        "db_kind=\"{}\",repository=\"{}\",method=\"{}\",operation=\"{}\",result=\"{}\"",
79        escape_label(&labels.db_kind),
80        escape_label(&labels.repository),
81        escape_label(&labels.method),
82        escape_label(&labels.operation),
83        escape_label(&labels.result)
84    )
85    .ok();
86    if let Some(le) = le {
87        write!(output, ",le=\"{}\"", escape_label(le)).ok();
88    }
89}