Skip to content

Commit f9b4a97

Browse files
committed
Replace HashMap with BTreeMap to sort metrics deterministically
1 parent 9141c11 commit f9b4a97

File tree

6 files changed

+67
-20
lines changed

6 files changed

+67
-20
lines changed

examples/actix-web.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ use prometheus_client::metrics::counter::Counter;
77
use prometheus_client::metrics::family::Family;
88
use prometheus_client::registry::Registry;
99

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

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

examples/tide.rs

+2-2
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

+48
Original file line numberDiff line numberDiff line change
@@ -751,4 +751,52 @@ def parse(input):
751751
.unwrap();
752752
})
753753
}
754+
755+
#[test]
756+
fn metrics_are_sorted_by_registration_order() {
757+
let mut registry = Registry::default();
758+
let counter: Counter = Counter::default();
759+
let another_counter: Counter = Counter::default();
760+
registry.register("my_counter", "My counter", counter);
761+
registry.register("another_counter", "Another counter", another_counter);
762+
763+
let mut encoded = String::new();
764+
encode(&mut encoded, &registry).unwrap();
765+
766+
let expected = "# HELP my_counter My counter.\n".to_owned()
767+
+ "# TYPE my_counter counter\n"
768+
+ "my_counter_total 0\n"
769+
+ "# HELP another_counter Another counter.\n"
770+
+ "# TYPE another_counter counter\n"
771+
+ "another_counter_total 0\n"
772+
+ "# EOF\n";
773+
assert_eq!(expected, encoded);
774+
}
775+
776+
#[test]
777+
fn metric_family_is_sorted_by_registration_order() {
778+
let mut registry = Registry::default();
779+
let gauge = Family::<Vec<(String, String)>, Gauge>::default();
780+
registry.register("my_gauge", "My gauge", gauge.clone());
781+
782+
{
783+
let gauge0 = gauge.get_or_create(&vec![("label".to_string(), "0".to_string())]);
784+
gauge0.set(0);
785+
}
786+
787+
{
788+
let gauge1 = gauge.get_or_create(&vec![("label".to_string(), "1".to_string())]);
789+
gauge1.set(1);
790+
}
791+
792+
let mut encoded = String::new();
793+
encode(&mut encoded, &registry).unwrap();
794+
795+
let expected = "# HELP my_gauge My gauge.\n".to_owned()
796+
+ "# TYPE my_gauge gauge\n"
797+
+ "my_gauge{label=\"0\"} 0\n"
798+
+ "my_gauge{label=\"1\"} 1\n"
799+
+ "# EOF\n";
800+
assert_eq!(expected, encoded);
801+
}
754802
}

src/lib.rs

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

src/metrics/exemplar.rs

+4-4
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

+9-10
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::encoding::{EncodeLabelSet, EncodeMetric, MetricEncoder};
77
use super::{MetricType, TypedMetric};
88
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
99
use std::cell::RefCell;
10-
use std::collections::HashMap;
10+
use std::collections::BTreeMap;
1111
use std::sync::Arc;
1212

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

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

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

211-
impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C> {
210+
impl<S: Clone + Eq + Ord, M, C: MetricConstructor<M>> Family<S, M, C> {
212211
/// Access a metric with the given label set, creating it if one does not
213212
/// yet exist.
214213
///
@@ -289,7 +288,7 @@ impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C
289288
self.metrics.write().clear()
290289
}
291290

292-
pub(crate) fn read(&self) -> RwLockReadGuard<HashMap<S, M>> {
291+
pub(crate) fn read(&self) -> RwLockReadGuard<BTreeMap<S, M>> {
293292
self.metrics.read()
294293
}
295294
}
@@ -309,7 +308,7 @@ impl<S, M: TypedMetric, C> TypedMetric for Family<S, M, C> {
309308

310309
impl<S, M, C> EncodeMetric for Family<S, M, C>
311310
where
312-
S: Clone + std::hash::Hash + Eq + EncodeLabelSet,
311+
S: Clone + Eq + Ord + EncodeLabelSet,
313312
M: EncodeMetric + TypedMetric,
314313
C: MetricConstructor<M>,
315314
{

0 commit comments

Comments
 (0)