Skip to content

Commit 5099e90

Browse files
committed
refactor: [#1403] extract Measurement strcut
To remove duplicate data. LabelSet is the HashMap key and it was also included in the HashMap value.
1 parent 017d977 commit 5099e90

4 files changed

Lines changed: 101 additions & 43 deletions

File tree

packages/metrics/src/metric/mod.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ use torrust_tracker_primitives::DurationSinceUnixEpoch;
77
use super::counter::Counter;
88
use super::label::LabelSet;
99
use super::prometheus::PrometheusSerializable;
10-
use super::sample::Sample;
1110
use super::sample_collection::SampleCollection;
1211
use crate::gauge::Gauge;
12+
use crate::sample::Measurement;
1313

1414
pub type MetricName = name::MetricName;
1515

@@ -36,7 +36,7 @@ impl<T> Metric<T> {
3636
}
3737

3838
#[must_use]
39-
pub fn get_sample(&self, label_set: &LabelSet) -> Option<&Sample<T>> {
39+
pub fn get_sample_data(&self, label_set: &LabelSet) -> Option<&Measurement<T>> {
4040
self.sample_collection.get(label_set)
4141
}
4242

@@ -68,11 +68,11 @@ impl<T: PrometheusSerializable> PrometheusSerializable for Metric<T> {
6868
let samples: Vec<String> = self
6969
.sample_collection
7070
.iter()
71-
.map(|(_label_set, sample)| {
71+
.map(|(label_set, sample)| {
7272
format!(
7373
"{}{} {}",
7474
self.name.to_prometheus(),
75-
sample.labels().to_prometheus(),
75+
label_set.to_prometheus(),
7676
sample.value().to_prometheus()
7777
)
7878
})
@@ -87,6 +87,7 @@ mod tests {
8787
use super::super::*;
8888
use crate::gauge::Gauge;
8989
use crate::label::{LabelName, LabelValue};
90+
use crate::sample::Sample;
9091

9192
#[test]
9293
fn it_should_be_empty_when_it_does_not_have_any_sample() {
@@ -132,6 +133,7 @@ mod tests {
132133
use super::super::*;
133134
use crate::counter::Counter;
134135
use crate::label::{LabelName, LabelValue};
136+
use crate::sample::Sample;
135137

136138
#[test]
137139
fn it_should_be_created_from_its_name_and_a_collection_of_samples() {
@@ -154,7 +156,7 @@ mod tests {
154156

155157
let metric = Metric::<Counter>::new(name.clone(), samples);
156158

157-
assert_eq!(metric.get_sample(&label_set).unwrap().value().value(), 1);
159+
assert_eq!(metric.get_sample_data(&label_set).unwrap().value().value(), 1);
158160
}
159161
}
160162

@@ -164,6 +166,7 @@ mod tests {
164166
use super::super::*;
165167
use crate::gauge::Gauge;
166168
use crate::label::{LabelName, LabelValue};
169+
use crate::sample::Sample;
167170

168171
#[test]
169172
fn it_should_be_created_from_its_name_and_a_collection_of_samples() {
@@ -186,7 +189,7 @@ mod tests {
186189

187190
let metric = Metric::<Gauge>::new(name.clone(), samples);
188191

189-
assert_relative_eq!(metric.get_sample(&label_set).unwrap().value().value(), 1.0);
192+
assert_relative_eq!(metric.get_sample_data(&label_set).unwrap().value().value(), 1.0);
190193
}
191194
}
192195
}

packages/metrics/src/metric_collection.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ impl MetricKindCollection<Counter> {
278278
pub fn get_value(&self, name: &MetricName, label_set: &LabelSet) -> Counter {
279279
self.metrics
280280
.get(name)
281-
.and_then(|metric| metric.get_sample(label_set))
281+
.and_then(|metric| metric.get_sample_data(label_set))
282282
.map_or(Counter::default(), |sample| sample.value().clone())
283283
}
284284
}
@@ -303,7 +303,7 @@ impl MetricKindCollection<Gauge> {
303303
pub fn get_value(&self, name: &MetricName, label_set: &LabelSet) -> Gauge {
304304
self.metrics
305305
.get(name)
306-
.and_then(|metric| metric.get_sample(label_set))
306+
.and_then(|metric| metric.get_sample_data(label_set))
307307
.map_or(Gauge::default(), |sample| sample.value().clone())
308308
}
309309
}

packages/metrics/src/sample.rs

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ use super::prometheus::PrometheusSerializable;
99

1010
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1111
pub struct Sample<T> {
12-
value: T,
13-
14-
#[serde(serialize_with = "serialize_duration", deserialize_with = "deserialize_duration")]
15-
update_at: DurationSinceUnixEpoch,
12+
#[serde(flatten)]
13+
measurement: Measurement<T>,
1614

1715
#[serde(rename = "labels")]
1816
label_set: LabelSet,
@@ -21,17 +19,68 @@ pub struct Sample<T> {
2119
impl<T> Sample<T> {
2220
#[must_use]
2321
pub fn new(value: T, update_at: DurationSinceUnixEpoch, label_set: LabelSet) -> Self {
22+
let data = Measurement { value, update_at };
23+
2424
Self {
25-
value,
26-
update_at,
25+
measurement: data,
2726
label_set,
2827
}
2928
}
3029

30+
#[must_use]
31+
pub fn measurement(&self) -> &Measurement<T> {
32+
&self.measurement
33+
}
34+
35+
#[must_use]
36+
pub fn value(&self) -> &T {
37+
&self.measurement.value
38+
}
39+
40+
#[must_use]
41+
pub fn update_at(&self) -> DurationSinceUnixEpoch {
42+
self.measurement.update_at
43+
}
44+
3145
#[must_use]
3246
pub fn labels(&self) -> &LabelSet {
3347
&self.label_set
3448
}
49+
}
50+
51+
impl<T: PrometheusSerializable> PrometheusSerializable for Sample<T> {
52+
fn to_prometheus(&self) -> String {
53+
format!("{} {}", self.label_set.to_prometheus(), self.measurement.to_prometheus())
54+
}
55+
}
56+
57+
impl Sample<Counter> {
58+
pub fn increment(&mut self, time: DurationSinceUnixEpoch) {
59+
self.measurement.increment(time);
60+
}
61+
}
62+
63+
impl Sample<Gauge> {
64+
pub fn set(&mut self, value: f64, time: DurationSinceUnixEpoch) {
65+
self.measurement.set(value, time);
66+
}
67+
}
68+
69+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
70+
pub struct Measurement<T> {
71+
/// The value of the sample.
72+
value: T,
73+
74+
/// The time when the sample was last updated.
75+
#[serde(serialize_with = "serialize_duration", deserialize_with = "deserialize_duration")]
76+
update_at: DurationSinceUnixEpoch,
77+
}
78+
79+
impl<T> Measurement<T> {
80+
#[must_use]
81+
pub fn new(value: T, update_at: DurationSinceUnixEpoch) -> Self {
82+
Self { value, update_at }
83+
}
3584

3685
#[must_use]
3786
pub fn value(&self) -> &T {
@@ -48,20 +97,26 @@ impl<T> Sample<T> {
4897
}
4998
}
5099

51-
impl<T: PrometheusSerializable> PrometheusSerializable for Sample<T> {
100+
impl<T> From<Sample<T>> for (LabelSet, Measurement<T>) {
101+
fn from(sample: Sample<T>) -> Self {
102+
(sample.label_set, sample.measurement)
103+
}
104+
}
105+
106+
impl<T: PrometheusSerializable> PrometheusSerializable for Measurement<T> {
52107
fn to_prometheus(&self) -> String {
53-
format!("{} {}", self.label_set.to_prometheus(), self.value.to_prometheus())
108+
self.value.to_prometheus()
54109
}
55110
}
56111

57-
impl Sample<Counter> {
112+
impl Measurement<Counter> {
58113
pub fn increment(&mut self, time: DurationSinceUnixEpoch) {
59114
self.value.increment(1);
60115
self.set_update_at(time);
61116
}
62117
}
63118

64-
impl Sample<Gauge> {
119+
impl Measurement<Gauge> {
65120
pub fn set(&mut self, value: f64, time: DurationSinceUnixEpoch) {
66121
self.value.set(value);
67122
self.set_update_at(time);
@@ -260,17 +315,13 @@ mod tests {
260315

261316
#[test]
262317
fn test_serialization_round_trip() {
263-
let original = Sample {
264-
value: 42,
265-
update_at: updated_at_time(),
266-
label_set: LabelSet::from(vec![("test", "serialization")]),
267-
};
318+
let original = Sample::new(42, updated_at_time(), LabelSet::from(vec![("test", "serialization")]));
268319

269320
let json = serde_json::to_string(&original).unwrap();
270321
let deserialized: Sample<i32> = serde_json::from_str(&json).unwrap();
271322

272-
assert_eq!(original.value, deserialized.value);
273-
assert_eq!(original.update_at, deserialized.update_at);
323+
assert_eq!(original.measurement.value, deserialized.measurement.value);
324+
assert_eq!(original.measurement.update_at, deserialized.measurement.update_at);
274325
assert_eq!(original.label_set, deserialized.label_set);
275326
}
276327

packages/metrics/src/sample_collection.rs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::hash_map::Iter;
22
use std::collections::{HashMap, HashSet};
3+
use std::fmt::Write as _;
34

45
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
56
use torrust_tracker_primitives::DurationSinceUnixEpoch;
@@ -9,28 +10,26 @@ use super::gauge::Gauge;
910
use super::label::LabelSet;
1011
use super::prometheus::PrometheusSerializable;
1112
use super::sample::Sample;
13+
use crate::sample::Measurement;
1214

1315
#[derive(Debug, Clone, Default, PartialEq)]
1416
pub struct SampleCollection<T> {
15-
samples: HashMap<LabelSet, Sample<T>>,
17+
samples: HashMap<LabelSet, Measurement<T>>,
1618
}
1719

1820
impl<T> SampleCollection<T> {
19-
// IMPORTANT: It should never allow mutation of the samples because it would
20-
// break the invariants. If the sample's `LabelSet` is changed, it can
21-
// create duplicate `LabelSet`s even if the `LabelSet` in the `HashMap` key
22-
// is unique.
23-
2421
/// # Panics
2522
///
2623
/// Panics if there are duplicate `LabelSets` in the provided samples.
2724
#[must_use]
2825
pub fn new(samples: Vec<Sample<T>>) -> Self {
29-
let mut map = HashMap::with_capacity(samples.len());
26+
let mut map: HashMap<LabelSet, Measurement<T>> = HashMap::with_capacity(samples.len());
3027

3128
for sample in samples {
29+
let (label_set, sample_data): (LabelSet, Measurement<T>) = sample.into();
30+
3231
assert!(
33-
map.insert(sample.labels().clone(), sample).is_none(),
32+
map.insert(label_set, sample_data).is_none(),
3433
"Duplicate LabelSet found in SampleCollection"
3534
);
3635
}
@@ -39,7 +38,7 @@ impl<T> SampleCollection<T> {
3938
}
4039

4140
#[must_use]
42-
pub fn get(&self, label: &LabelSet) -> Option<&Sample<T>> {
41+
pub fn get(&self, label: &LabelSet) -> Option<&Measurement<T>> {
4342
self.samples.get(label)
4443
}
4544

@@ -55,7 +54,7 @@ impl<T> SampleCollection<T> {
5554

5655
#[must_use]
5756
#[allow(clippy::iter_without_into_iter)]
58-
pub fn iter(&self) -> Iter<'_, LabelSet, Sample<T>> {
57+
pub fn iter(&self) -> Iter<'_, LabelSet, Measurement<T>> {
5958
self.samples.iter()
6059
}
6160
}
@@ -65,7 +64,7 @@ impl SampleCollection<Counter> {
6564
let sample = self
6665
.samples
6766
.entry(label_set.clone())
68-
.or_insert_with(|| Sample::new(Counter::default(), time, label_set.clone()));
67+
.or_insert_with(|| Measurement::new(Counter::default(), time));
6968

7069
sample.increment(time);
7170
}
@@ -76,7 +75,7 @@ impl SampleCollection<Gauge> {
7675
let sample = self
7776
.samples
7877
.entry(label_set.clone())
79-
.or_insert_with(|| Sample::new(Gauge::default(), time, label_set.clone()));
78+
.or_insert_with(|| Measurement::new(Gauge::default(), time));
8079

8180
sample.set(value, time);
8281
}
@@ -87,7 +86,12 @@ impl<T: Serialize> Serialize for SampleCollection<T> {
8786
where
8887
S: Serializer,
8988
{
90-
let samples: Vec<&Sample<T>> = self.samples.values().collect();
89+
let mut samples: Vec<Sample<&T>> = vec![];
90+
91+
for (label_set, sample_data) in &self.samples {
92+
samples.push(Sample::new(sample_data.value(), sample_data.update_at(), label_set.clone()));
93+
}
94+
9195
samples.serialize(serializer)
9296
}
9397
}
@@ -121,8 +125,8 @@ impl<T: PrometheusSerializable> PrometheusSerializable for SampleCollection<T> {
121125
fn to_prometheus(&self) -> String {
122126
let mut output = String::new();
123127

124-
for sample in self.samples.values() {
125-
output.push_str(&sample.to_prometheus());
128+
for (label_set, sample_data) in &self.samples {
129+
let _ = write!(output, "{} {}", label_set.to_prometheus(), sample_data.to_prometheus());
126130
}
127131

128132
output
@@ -165,7 +169,7 @@ mod tests {
165169

166170
let retrieved = collection.get(&label_set);
167171

168-
assert_eq!(retrieved.unwrap(), &sample);
172+
assert_eq!(retrieved.unwrap(), sample.measurement());
169173
}
170174

171175
#[test]
@@ -179,10 +183,10 @@ mod tests {
179183
let collection = SampleCollection::new(vec![sample_1.clone(), sample_2.clone()]);
180184

181185
let retrieved = collection.get(&label_set_1);
182-
assert_eq!(retrieved.unwrap(), &sample_1);
186+
assert_eq!(retrieved.unwrap(), sample_1.measurement());
183187

184188
let retrieved = collection.get(&label_set_2);
185-
assert_eq!(retrieved.unwrap(), &sample_2);
189+
assert_eq!(retrieved.unwrap(), sample_2.measurement());
186190
}
187191

188192
#[test]

0 commit comments

Comments
 (0)