Skip to content

Commit 2ef86ae

Browse files
lstrojnyspoutn1k
authored andcommitted
Replace HashMap with BTreeMap to sort metrics deterministically
Signed-off-by: Lars Strojny <[email protected]>
1 parent 1d5824a commit 2ef86ae

File tree

7 files changed

+73
-31
lines changed

7 files changed

+73
-31
lines changed

examples/actix-web.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ use prometheus_client::metrics::counter::Counter;
88
use prometheus_client::metrics::family::Family;
99
use prometheus_client::registry::Registry;
1010

11-
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
11+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, EncodeLabelValue)]
1212
pub enum Method {
1313
Get,
1414
Post,
1515
}
1616

17-
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
17+
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, EncodeLabelSet)]
1818
pub struct MethodLabels {
1919
pub method: Method,
2020
}

examples/axum.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ use prometheus_client_derive_encode::{EncodeLabelSet, EncodeLabelValue};
1313
use std::sync::Arc;
1414
use tokio::sync::Mutex;
1515

16-
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
16+
#[derive(Clone, Debug, Ord, PartialOrd, PartialEq, Eq, EncodeLabelValue)]
1717
pub enum Method {
1818
Get,
1919
Post,
2020
}
2121

22-
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
22+
#[derive(Clone, Debug, Ord, PartialOrd, PartialEq, Eq, EncodeLabelSet)]
2323
pub struct MethodLabels {
2424
pub method: Method,
2525
}

examples/tide.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ async fn main() -> std::result::Result<(), std::io::Error> {
4444
Ok(())
4545
}
4646

47-
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
47+
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, EncodeLabelSet)]
4848
struct Labels {
4949
method: Method,
5050
path: String,
5151
}
5252

53-
#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
53+
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, EncodeLabelValue)]
5454
enum Method {
5555
Get,
5656
Put,

src/encoding/text.rs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -933,7 +933,7 @@ mod tests {
933933
Family::new_with_constructor(|| Histogram::new(exponential_buckets(1.0, 2.0, 10)));
934934
registry.register("my_histogram", "My histogram", family.clone());
935935

936-
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
936+
#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone)]
937937
struct EmptyLabels {}
938938

939939
impl EncodeLabelSet for EmptyLabels {
@@ -1117,7 +1117,7 @@ mod tests {
11171117

11181118
#[test]
11191119
fn label_sets_can_be_composed() {
1120-
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1120+
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
11211121
struct Color(&'static str);
11221122
impl EncodeLabelSet for Color {
11231123
fn encode(
@@ -1132,7 +1132,7 @@ mod tests {
11321132
}
11331133
}
11341134

1135-
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1135+
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
11361136
struct Size(&'static str);
11371137
impl EncodeLabelSet for Size {
11381138
fn encode(
@@ -1243,4 +1243,52 @@ def parse(input):
12431243
.unwrap();
12441244
})
12451245
}
1246+
1247+
#[test]
1248+
fn metrics_are_sorted_by_registration_order() {
1249+
let mut registry = Registry::default();
1250+
let counter: Counter = Counter::default();
1251+
let another_counter: Counter = Counter::default();
1252+
registry.register("my_counter", "My counter", counter);
1253+
registry.register("another_counter", "Another counter", another_counter);
1254+
1255+
let mut encoded = String::new();
1256+
encode(&mut encoded, &registry).unwrap();
1257+
1258+
let expected = "# HELP my_counter My counter.\n".to_owned()
1259+
+ "# TYPE my_counter counter\n"
1260+
+ "my_counter_total 0\n"
1261+
+ "# HELP another_counter Another counter.\n"
1262+
+ "# TYPE another_counter counter\n"
1263+
+ "another_counter_total 0\n"
1264+
+ "# EOF\n";
1265+
assert_eq!(expected, encoded);
1266+
}
1267+
1268+
#[test]
1269+
fn metric_family_is_sorted_by_registration_order() {
1270+
let mut registry = Registry::default();
1271+
let gauge = Family::<Vec<(String, String)>, Gauge>::default();
1272+
registry.register("my_gauge", "My gauge", gauge.clone());
1273+
1274+
{
1275+
let gauge0 = gauge.get_or_create(&vec![("label".to_string(), "0".to_string())]);
1276+
gauge0.set(0);
1277+
}
1278+
1279+
{
1280+
let gauge1 = gauge.get_or_create(&vec![("label".to_string(), "1".to_string())]);
1281+
gauge1.set(1);
1282+
}
1283+
1284+
let mut encoded = String::new();
1285+
encode(&mut encoded, &registry).unwrap();
1286+
1287+
let expected = "# HELP my_gauge My gauge.\n".to_owned()
1288+
+ "# TYPE my_gauge gauge\n"
1289+
+ "my_gauge{label=\"0\"} 0\n"
1290+
+ "my_gauge{label=\"1\"} 1\n"
1291+
+ "# EOF\n";
1292+
assert_eq!(expected, encoded);
1293+
}
12461294
}

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@
3131
//! //
3232
//! // You could as well use `(String, String)` to represent a label set,
3333
//! // instead of the custom type below.
34-
//! #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
34+
//! #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, EncodeLabelSet)]
3535
//! struct Labels {
3636
//! // Use your own enum types to represent label values.
3737
//! method: Method,
3838
//! // Or just a plain string.
3939
//! path: String,
4040
//! };
4141
//!
42-
//! #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
42+
//! #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, EncodeLabelValue)]
4343
//! enum Method {
4444
//! GET,
4545
//! PUT,

src/metrics/exemplar.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ pub struct Exemplar<S, V> {
4242
/// # use prometheus_client::metrics::histogram::exponential_buckets;
4343
/// # use prometheus_client::metrics::family::Family;
4444
/// # use prometheus_client_derive_encode::EncodeLabelSet;
45-
/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)]
45+
/// #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, EncodeLabelSet, Debug, Default)]
4646
/// pub struct ResultLabel {
4747
/// pub result: String,
4848
/// }
4949
///
50-
/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)]
50+
/// #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, EncodeLabelSet, Debug, Default)]
5151
/// pub struct TraceLabel {
5252
/// pub trace_id: String,
5353
/// }
@@ -183,12 +183,12 @@ where
183183
/// # use prometheus_client::metrics::histogram::exponential_buckets;
184184
/// # use prometheus_client::metrics::family::Family;
185185
/// # use prometheus_client::encoding::EncodeLabelSet;
186-
/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)]
186+
/// #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, EncodeLabelSet, Debug, Default)]
187187
/// pub struct ResultLabel {
188188
/// pub result: String,
189189
/// }
190190
///
191-
/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelSet, Debug, Default)]
191+
/// #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, EncodeLabelSet, Debug, Default)]
192192
/// pub struct TraceLabel {
193193
/// pub trace_id: String,
194194
/// }

src/metrics/family.rs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder};
66

77
use super::{MetricType, TypedMetric};
88
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
9-
use std::collections::HashMap;
9+
use std::collections::BTreeMap;
1010
use std::sync::Arc;
1111

1212
/// Representation of the OpenMetrics *MetricFamily* data type.
@@ -68,12 +68,12 @@ use std::sync::Arc;
6868
/// # use std::io::Write;
6969
/// #
7070
/// # let mut registry = Registry::default();
71-
/// #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
71+
/// #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, EncodeLabelSet)]
7272
/// struct Labels {
7373
/// method: Method,
7474
/// };
7575
///
76-
/// #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)]
76+
/// #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, EncodeLabelValue)]
7777
/// enum Method {
7878
/// GET,
7979
/// PUT,
@@ -99,9 +99,8 @@ use std::sync::Arc;
9999
/// # "# EOF\n";
100100
/// # assert_eq!(expected, buffer);
101101
/// ```
102-
// TODO: Consider exposing hash algorithm.
103102
pub struct Family<S, M, C = fn() -> M> {
104-
metrics: Arc<RwLock<HashMap<S, M>>>,
103+
metrics: Arc<RwLock<BTreeMap<S, M>>>,
105104
/// Function that when called constructs a new metric.
106105
///
107106
/// For most metric types this would simply be its [`Default`]
@@ -168,7 +167,7 @@ impl<M, F: Fn() -> M> MetricConstructor<M> for F {
168167
}
169168
}
170169

171-
impl<S: Clone + std::hash::Hash + Eq, M: Default> Default for Family<S, M> {
170+
impl<S: Clone + Eq, M: Default> Default for Family<S, M> {
172171
fn default() -> Self {
173172
Self {
174173
metrics: Arc::new(RwLock::new(Default::default())),
@@ -177,7 +176,7 @@ impl<S: Clone + std::hash::Hash + Eq, M: Default> Default for Family<S, M> {
177176
}
178177
}
179178

180-
impl<S: Clone + std::hash::Hash + Eq, M, C> Family<S, M, C> {
179+
impl<S: Clone + Eq, M, C> Family<S, M, C> {
181180
/// Create a metric family using a custom constructor to construct new
182181
/// metrics.
183182
///
@@ -207,12 +206,7 @@ impl<S: Clone + std::hash::Hash + Eq, M, C> Family<S, M, C> {
207206
}
208207
}
209208

210-
impl<S: Clone + std::hash::Hash + Eq, M: Clone, C: MetricConstructor<M>> Family<S, M, C>
211-
where
212-
S: Clone + std::hash::Hash + Eq,
213-
M: Clone,
214-
C: MetricConstructor<M>,
215-
{
209+
impl<S: Clone + Eq + Ord, M: Clone, C: MetricConstructor<M>> Family<S, M, C> {
216210
/// Access a metric with the given label set, creating it if one does not yet exist.
217211
///
218212
/// ```
@@ -241,7 +235,7 @@ where
241235
}
242236
}
243237

244-
impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C> {
238+
impl<S: Clone + Eq + Ord, M, C: MetricConstructor<M>> Family<S, M, C> {
245239
/// Access a metric with the given label set, creating it if one does not
246240
/// yet exist.
247241
///
@@ -341,7 +335,7 @@ impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C
341335
self.metrics.write().clear()
342336
}
343337

344-
pub(crate) fn read(&self) -> RwLockReadGuard<'_, HashMap<S, M>> {
338+
pub(crate) fn read(&self) -> RwLockReadGuard<'_, BTreeMap<S, M>> {
345339
self.metrics.read()
346340
}
347341
}
@@ -361,7 +355,7 @@ impl<S, M: TypedMetric, C> TypedMetric for Family<S, M, C> {
361355

362356
impl<S, M, C> EncodeMetric for Family<S, M, C>
363357
where
364-
S: Clone + std::hash::Hash + Eq + EncodeLabelSet,
358+
S: Clone + Eq + Ord + EncodeLabelSet,
365359
M: EncodeMetric + TypedMetric,
366360
C: MetricConstructor<M>,
367361
{

0 commit comments

Comments
 (0)