-
Notifications
You must be signed in to change notification settings - Fork 204
Expand file tree
/
Copy pathserde_metrics.rs
More file actions
163 lines (148 loc) · 4.54 KB
/
Copy pathserde_metrics.rs
File metadata and controls
163 lines (148 loc) · 4.54 KB
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Duration and size metrics for serialization and deserialization hot paths.
//!
//! These mirror the engine-side serde observability but follow rivetkit's
//! metric conventions: `rivetkit_`-prefixed names registered through a
//! `LazyLock` collector struct, and `crate::time::Instant` so the same code
//! compiles for the wasm runtime.
//!
//! The `format` label is the wire format (`bare`, `json`, `cbor`). The
//! `location` label identifies the call site and must be a bounded, code-defined
//! string, never user input.
use std::sync::LazyLock;
use std::time::Duration;
use rivet_metrics::{
MICRO_BUCKETS,
prometheus::{HistogramOpts, HistogramVec, Registry},
};
use crate::time::Instant;
const SERDE_LABELS: &[&str] = &["format", "location"];
/// Byte-size buckets shared by serialize and deserialize size histograms.
fn serde_size_buckets() -> Vec<f64> {
vec![
16.0, 32.0, 64.0, 128.0, 256.0, 1024.0, 4096.0, 16384.0, 65536.0, 262144.0, 1048576.0,
4194304.0, 16777216.0,
]
}
struct SerdeMetricCollectors {
serialize_size: HistogramVec,
deserialize_size: HistogramVec,
serialize_duration_seconds: HistogramVec,
deserialize_duration_seconds: HistogramVec,
}
static METRICS: LazyLock<SerdeMetricCollectors> = LazyLock::new(SerdeMetricCollectors::new);
impl SerdeMetricCollectors {
fn new() -> Self {
let serialize_size = HistogramVec::new(
HistogramOpts::new(
"rivetkit_serialize_size",
"size in bytes for any serialization",
)
.buckets(serde_size_buckets()),
SERDE_LABELS,
)
.expect("create rivetkit_serialize_size histogram");
let deserialize_size = HistogramVec::new(
HistogramOpts::new(
"rivetkit_deserialize_size",
"size in bytes for any deserialization",
)
.buckets(serde_size_buckets()),
SERDE_LABELS,
)
.expect("create rivetkit_deserialize_size histogram");
let serialize_duration_seconds = HistogramVec::new(
HistogramOpts::new(
"rivetkit_serialize_duration_seconds",
"duration in seconds for any serialization",
)
.buckets(MICRO_BUCKETS.to_vec()),
SERDE_LABELS,
)
.expect("create rivetkit_serialize_duration_seconds histogram");
let deserialize_duration_seconds = HistogramVec::new(
HistogramOpts::new(
"rivetkit_deserialize_duration_seconds",
"duration in seconds for any deserialization",
)
.buckets(MICRO_BUCKETS.to_vec()),
SERDE_LABELS,
)
.expect("create rivetkit_deserialize_duration_seconds histogram");
register_metric(&rivet_metrics::REGISTRY, serialize_size.clone());
register_metric(&rivet_metrics::REGISTRY, deserialize_size.clone());
register_metric(&rivet_metrics::REGISTRY, serialize_duration_seconds.clone());
register_metric(
&rivet_metrics::REGISTRY,
deserialize_duration_seconds.clone(),
);
Self {
serialize_size,
deserialize_size,
serialize_duration_seconds,
deserialize_duration_seconds,
}
}
}
/// Records the duration and output size of a serialization producing `Vec<u8>`.
///
/// The size is only recorded when the closure succeeds.
pub(crate) fn measure_serialize(
format: &str,
location: &str,
f: impl FnOnce() -> anyhow::Result<Vec<u8>>,
) -> anyhow::Result<Vec<u8>> {
let started = Instant::now();
let result = f();
observe(
&METRICS.serialize_duration_seconds,
format,
location,
started.elapsed(),
);
if let Ok(bytes) = &result {
observe_size(&METRICS.serialize_size, format, location, bytes.len());
}
result
}
/// Records the duration and input size of a deserialization.
///
/// The input size is recorded unconditionally because the bytes are available
/// regardless of whether decoding succeeds.
pub(crate) fn measure_deserialize<T>(
format: &str,
location: &str,
input_len: usize,
f: impl FnOnce() -> anyhow::Result<T>,
) -> anyhow::Result<T> {
observe_size(&METRICS.deserialize_size, format, location, input_len);
let started = Instant::now();
let result = f();
observe(
&METRICS.deserialize_duration_seconds,
format,
location,
started.elapsed(),
);
result
}
fn observe(metric: &HistogramVec, format: &str, location: &str, elapsed: Duration) {
metric
.with_label_values(&[format, location])
.observe(elapsed.as_secs_f64());
}
fn observe_size(metric: &HistogramVec, format: &str, location: &str, size: usize) {
metric
.with_label_values(&[format, location])
.observe(size as f64);
}
fn register_metric<M>(registry: &Registry, metric: M)
where
M: rivet_metrics::prometheus::core::Collector + Clone + Send + Sync + 'static,
{
if let Err(error) = registry.register(Box::new(metric)) {
tracing::warn!(
?error,
"serde metric registration failed, using existing collector"
);
}
}