Skip to main content

rs_zero/observability/metrics/
redis.rs

1use std::{collections::BTreeMap, fmt::Write};
2
3use super::{DurationMetricValue, escape_label, write_duration_metric};
4
5/// Low-cardinality labels recorded for a Redis command.
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
7pub struct RedisMetricLabels {
8    /// Redis command category such as `GET`, `SETEX` or `DEL`.
9    pub command: String,
10    /// Redis shard name or `default`.
11    pub shard: String,
12    /// Result category, usually `success`, `error` or `timeout`.
13    pub result: String,
14}
15
16impl RedisMetricLabels {
17    /// Creates a Redis command label set without carrying Redis keys.
18    pub fn new(
19        command: impl Into<String>,
20        shard: impl Into<String>,
21        result: impl Into<String>,
22    ) -> Self {
23        Self {
24            command: command.into(),
25            shard: shard.into(),
26            result: result.into(),
27        }
28    }
29}
30
31/// Low-cardinality labels recorded for Redis events such as pool, redirect or script cache.
32#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
33pub struct RedisEventLabels {
34    /// Event category.
35    pub event: String,
36    /// Redis shard name or `default`.
37    pub shard: String,
38    /// Result category.
39    pub result: String,
40}
41
42impl RedisEventLabels {
43    /// Creates a Redis event label set without carrying Redis keys.
44    pub fn new(
45        event: impl Into<String>,
46        shard: impl Into<String>,
47        result: impl Into<String>,
48    ) -> Self {
49        Self {
50            event: event.into(),
51            shard: shard.into(),
52            result: result.into(),
53        }
54    }
55}
56
57/// Low-cardinality labels recorded when Redis behavior is explicitly degraded.
58#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
59pub struct RedisDegradationLabels {
60    /// Cache operation.
61    pub operation: String,
62    /// Degradation action.
63    pub action: String,
64    /// Redis shard name or `default`.
65    pub shard: String,
66}
67
68impl RedisDegradationLabels {
69    /// Creates a Redis degradation label set without carrying Redis keys.
70    pub fn new(
71        operation: impl Into<String>,
72        action: impl Into<String>,
73        shard: impl Into<String>,
74    ) -> Self {
75        Self {
76            operation: operation.into(),
77            action: action.into(),
78            shard: shard.into(),
79        }
80    }
81}
82
83pub(crate) fn render(
84    output: &mut String,
85    metrics: &BTreeMap<RedisMetricLabels, DurationMetricValue>,
86    events: &BTreeMap<RedisEventLabels, u64>,
87    degradations: &BTreeMap<RedisDegradationLabels, u64>,
88) {
89    output.push_str("# HELP rs_zero_redis_commands_total Total number of Redis commands.\n");
90    output.push_str("# TYPE rs_zero_redis_commands_total counter\n");
91    for (labels, value) in metrics {
92        write!(output, "rs_zero_redis_commands_total{{").ok();
93        write_labels(output, labels, None);
94        writeln!(output, "}} {}", value.count).ok();
95    }
96
97    output.push_str(
98        "# HELP rs_zero_redis_command_errors_total Total number of failed Redis commands.\n",
99    );
100    output.push_str("# TYPE rs_zero_redis_command_errors_total counter\n");
101    for (labels, value) in metrics
102        .iter()
103        .filter(|(labels, _)| labels.result != "success")
104    {
105        write!(output, "rs_zero_redis_command_errors_total{{").ok();
106        write_labels(output, labels, None);
107        writeln!(output, "}} {}", value.count).ok();
108    }
109
110    output.push_str("# HELP rs_zero_redis_command_duration_seconds Redis command duration.\n");
111    output.push_str("# TYPE rs_zero_redis_command_duration_seconds histogram\n");
112    for (labels, value) in metrics {
113        write_duration_metric(
114            output,
115            "rs_zero_redis_command_duration_seconds",
116            labels,
117            value,
118            write_labels,
119        );
120    }
121
122    output.push_str("# HELP rs_zero_redis_events_total Total number of Redis pool, redirect, script and degradation events.\n");
123    output.push_str("# TYPE rs_zero_redis_events_total counter\n");
124    for (labels, value) in events {
125        write!(output, "rs_zero_redis_events_total{{").ok();
126        write_event_labels(output, labels);
127        writeln!(output, "}} {}", value).ok();
128    }
129
130    output.push_str("# HELP rs_zero_redis_degradations_total Total number of explicitly degraded Redis operations.\n");
131    output.push_str("# TYPE rs_zero_redis_degradations_total counter\n");
132    for (labels, value) in degradations {
133        write!(output, "rs_zero_redis_degradations_total{{").ok();
134        write_degradation_labels(output, labels);
135        writeln!(output, "}} {}", value).ok();
136    }
137}
138
139fn write_labels(output: &mut String, labels: &RedisMetricLabels, le: Option<&str>) {
140    write!(
141        output,
142        "command=\"{}\",shard=\"{}\",result=\"{}\"",
143        escape_label(&labels.command),
144        escape_label(&labels.shard),
145        escape_label(&labels.result)
146    )
147    .ok();
148    if let Some(le) = le {
149        write!(output, ",le=\"{}\"", escape_label(le)).ok();
150    }
151}
152
153fn write_event_labels(output: &mut String, labels: &RedisEventLabels) {
154    write!(
155        output,
156        "event=\"{}\",shard=\"{}\",result=\"{}\"",
157        escape_label(&labels.event),
158        escape_label(&labels.shard),
159        escape_label(&labels.result)
160    )
161    .ok();
162}
163
164fn write_degradation_labels(output: &mut String, labels: &RedisDegradationLabels) {
165    write!(
166        output,
167        "operation=\"{}\",action=\"{}\",shard=\"{}\"",
168        escape_label(&labels.operation),
169        escape_label(&labels.action),
170        escape_label(&labels.shard)
171    )
172    .ok();
173}