1use std::collections::HashMap;
2use std::collections::hash_map::Iter;
3use std::fmt::Write as _;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use torrust_clock::DurationSinceUnixEpoch;
7
8use super::counter::Counter;
9use super::gauge::Gauge;
10use super::label::LabelSet;
11use super::prometheus::PrometheusSerializable;
12use super::sample::Sample;
13use crate::sample::Measurement;
14
15#[derive(Debug, Clone, Default, PartialEq)]
16pub struct SampleCollection<T> {
17 samples: HashMap<LabelSet, Measurement<T>>,
18}
19
20impl<T> SampleCollection<T> {
21 pub fn new(samples: Vec<Sample<T>>) -> Result<Self, Error> {
28 let mut map: HashMap<LabelSet, Measurement<T>> = HashMap::with_capacity(samples.len());
29
30 for sample in samples {
31 let (label_set, sample_data): (LabelSet, Measurement<T>) = sample.into();
32
33 let label_set_clone = label_set.clone();
34
35 if let Some(_old_measurement) = map.insert(label_set, sample_data) {
36 return Err(Error::DuplicateLabelSetInList {
37 label_set: label_set_clone,
38 });
39 }
40 }
41
42 Ok(Self { samples: map })
43 }
44
45 #[must_use]
46 pub fn get(&self, label: &LabelSet) -> Option<&Measurement<T>> {
47 self.samples.get(label)
48 }
49
50 #[must_use]
51 pub fn len(&self) -> usize {
52 self.samples.len()
53 }
54
55 #[must_use]
56 pub fn is_empty(&self) -> bool {
57 self.samples.is_empty()
58 }
59
60 #[must_use]
61 #[allow(clippy::iter_without_into_iter)]
62 pub fn iter(&self) -> Iter<'_, LabelSet, Measurement<T>> {
63 self.samples.iter()
64 }
65}
66
67#[derive(thiserror::Error, Debug, Clone)]
68pub enum Error {
69 #[error("Found duplicate label set in list. Label set must be unique in a SampleCollection.")]
70 DuplicateLabelSetInList { label_set: LabelSet },
71}
72
73impl SampleCollection<Counter> {
74 pub fn increment(&mut self, label_set: &LabelSet, time: DurationSinceUnixEpoch) {
75 let sample = self
76 .samples
77 .entry(label_set.clone())
78 .or_insert_with(|| Measurement::new(Counter::default(), time));
79
80 sample.increment(time);
81 }
82
83 pub fn absolute(&mut self, label_set: &LabelSet, value: u64, time: DurationSinceUnixEpoch) {
84 let sample = self
85 .samples
86 .entry(label_set.clone())
87 .or_insert_with(|| Measurement::new(Counter::default(), time));
88
89 sample.absolute(value, time);
90 }
91}
92
93impl SampleCollection<Gauge> {
94 pub fn set(&mut self, label_set: &LabelSet, value: f64, time: DurationSinceUnixEpoch) {
95 let sample = self
96 .samples
97 .entry(label_set.clone())
98 .or_insert_with(|| Measurement::new(Gauge::default(), time));
99
100 sample.set(value, time);
101 }
102
103 pub fn increment(&mut self, label_set: &LabelSet, time: DurationSinceUnixEpoch) {
104 let sample = self
105 .samples
106 .entry(label_set.clone())
107 .or_insert_with(|| Measurement::new(Gauge::default(), time));
108
109 sample.increment(time);
110 }
111
112 pub fn decrement(&mut self, label_set: &LabelSet, time: DurationSinceUnixEpoch) {
113 let sample = self
114 .samples
115 .entry(label_set.clone())
116 .or_insert_with(|| Measurement::new(Gauge::default(), time));
117
118 sample.decrement(time);
119 }
120}
121
122impl<T: Serialize> Serialize for SampleCollection<T> {
123 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
124 where
125 S: Serializer,
126 {
127 let mut samples: Vec<Sample<&T>> = vec![];
128
129 for (label_set, sample_data) in &self.samples {
130 samples.push(Sample::new(sample_data.value(), sample_data.recorded_at(), label_set.clone()));
131 }
132
133 samples.serialize(serializer)
134 }
135}
136
137impl<'de, T> Deserialize<'de> for SampleCollection<T>
138where
139 T: Deserialize<'de>,
140{
141 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142 where
143 D: Deserializer<'de>,
144 {
145 let samples = Vec::<Sample<T>>::deserialize(deserializer)?;
146
147 let sample_collection = SampleCollection::new(samples).map_err(serde::de::Error::custom)?;
148
149 Ok(sample_collection)
150 }
151}
152
153impl<T: PrometheusSerializable> PrometheusSerializable for SampleCollection<T> {
154 fn to_prometheus(&self) -> String {
155 let mut output = String::new();
156
157 for (label_set, sample_data) in &self.samples {
158 if label_set.is_empty() {
159 let _ = write!(output, "{}", sample_data.to_prometheus());
160 } else {
161 let _ = write!(output, "{} {}", label_set.to_prometheus(), sample_data.to_prometheus());
162 }
163 }
164
165 output
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use torrust_clock::DurationSinceUnixEpoch;
172
173 use crate::counter::Counter;
174 use crate::label::LabelSet;
175 use crate::sample::Sample;
176 use crate::sample_collection::SampleCollection;
177
178 fn sample_update_time() -> DurationSinceUnixEpoch {
179 DurationSinceUnixEpoch::from_secs(1_743_552_000)
180 }
181
182 #[test]
183 fn it_should_fail_trying_to_create_a_sample_collection_with_duplicate_label_sets() {
184 let samples = vec![
185 Sample::new(Counter::default(), sample_update_time(), LabelSet::default()),
186 Sample::new(Counter::default(), sample_update_time(), LabelSet::default()),
187 ];
188
189 let result = SampleCollection::new(samples);
190
191 assert!(result.is_err());
192 }
193
194 #[test]
195 fn it_should_return_a_sample_searching_by_label_set_with_one_empty_label_set() {
196 let label_set = LabelSet::default();
197
198 let sample = Sample::new(Counter::default(), sample_update_time(), label_set.clone());
199
200 let collection = SampleCollection::new(vec![sample.clone()]).unwrap();
201
202 let retrieved = collection.get(&label_set);
203
204 assert_eq!(retrieved.unwrap(), sample.measurement());
205 }
206
207 #[test]
208 fn it_should_return_a_sample_searching_by_label_set_with_two_label_sets() {
209 let label_set_1 = LabelSet::from(vec![("label_name_1", "label value 1")]);
210 let label_set_2 = LabelSet::from(vec![("label_name_2", "label value 2")]);
211
212 let sample_1 = Sample::new(Counter::new(1), sample_update_time(), label_set_1.clone());
213 let sample_2 = Sample::new(Counter::new(2), sample_update_time(), label_set_2.clone());
214
215 let collection = SampleCollection::new(vec![sample_1.clone(), sample_2.clone()]).unwrap();
216
217 let retrieved = collection.get(&label_set_1);
218 assert_eq!(retrieved.unwrap(), sample_1.measurement());
219
220 let retrieved = collection.get(&label_set_2);
221 assert_eq!(retrieved.unwrap(), sample_2.measurement());
222 }
223
224 #[test]
225 fn it_should_return_the_number_of_samples_in_the_collection() {
226 let samples = vec![Sample::new(Counter::default(), sample_update_time(), LabelSet::default())];
227 let collection = SampleCollection::new(samples).unwrap();
228 assert_eq!(collection.len(), 1);
229 }
230
231 #[test]
232 fn it_should_return_zero_number_of_samples_when_empty() {
233 let empty = SampleCollection::<Counter>::default();
234 assert_eq!(empty.len(), 0);
235 }
236
237 #[test]
238 fn it_should_indicate_is_it_is_empty() {
239 let empty = SampleCollection::<Counter>::default();
240 assert!(empty.is_empty());
241
242 let samples = vec![Sample::new(Counter::default(), sample_update_time(), LabelSet::default())];
243 let collection = SampleCollection::new(samples).unwrap();
244 assert!(!collection.is_empty());
245 }
246
247 #[test]
248 fn it_should_allow_iterating_samples() {
249 let label_set = LabelSet::from(vec![("key", "val")]);
250 let sample = Sample::new(Counter::new(5), sample_update_time(), label_set.clone());
251 let collection = SampleCollection::new(vec![sample]).unwrap();
252
253 let mut count = 0;
254 for (ls, meas) in collection.iter() {
255 assert_eq!(ls, &label_set);
256 assert_eq!(meas.value(), &Counter::new(5));
257 count += 1;
258 }
259 assert_eq!(count, 1);
260 }
261
262 mod json_serialization {
263 use crate::counter::Counter;
264 use crate::label::LabelSet;
265 use crate::sample::Sample;
266 use crate::sample_collection::SampleCollection;
267 use crate::sample_collection::tests::sample_update_time;
268
269 #[test]
270 fn it_should_be_serializable_and_deserializable_for_json_format() {
271 let sample = Sample::new(Counter::default(), sample_update_time(), LabelSet::default());
272 let collection = SampleCollection::new(vec![sample]).unwrap();
273
274 let serialized = serde_json::to_string(&collection).unwrap();
275 let deserialized: SampleCollection<Counter> = serde_json::from_str(&serialized).unwrap();
276
277 assert_eq!(deserialized, collection);
278 }
279
280 #[test]
281 fn it_should_fail_deserializing_from_json_with_duplicate_label_sets() {
282 let samples = vec![
283 Sample::new(Counter::default(), sample_update_time(), LabelSet::default()),
284 Sample::new(Counter::default(), sample_update_time(), LabelSet::default()),
285 ];
286
287 let serialized = serde_json::to_string(&samples).unwrap();
288
289 let result: Result<SampleCollection<Counter>, _> = serde_json::from_str(&serialized);
290
291 assert!(result.is_err());
292 }
293 }
294
295 mod prometheus_serialization {
296 use crate::counter::Counter;
297 use crate::label::LabelSet;
298 use crate::prometheus::PrometheusSerializable;
299 use crate::sample::Sample;
300 use crate::sample_collection::SampleCollection;
301 use crate::sample_collection::tests::sample_update_time;
302 use crate::tests::format_prometheus_output;
303
304 #[test]
305 fn it_should_be_exportable_to_prometheus_format_when_empty() {
306 let sample = Sample::new(Counter::default(), sample_update_time(), LabelSet::default());
307 let collection = SampleCollection::new(vec![sample]).unwrap();
308
309 let prometheus_output = collection.to_prometheus();
310
311 assert!(!prometheus_output.is_empty());
312 }
313
314 #[test]
315 fn it_should_be_exportable_to_prometheus_format() {
316 let sample = Sample::new(
317 Counter::new(1),
318 sample_update_time(),
319 LabelSet::from(vec![("labe_name_1", "label value value 1")]),
320 );
321
322 let collection = SampleCollection::new(vec![sample]).unwrap();
323
324 let prometheus_output = collection.to_prometheus();
325
326 let expected_prometheus_output = format_prometheus_output("{labe_name_1=\"label value value 1\"} 1");
327
328 assert_eq!(prometheus_output, expected_prometheus_output);
329 }
330 }
331
332 #[cfg(test)]
333 mod for_counters {
334
335 use std::ops::Add;
336
337 use super::super::LabelSet;
338 use super::*;
339
340 #[test]
341 fn it_should_increment_the_counter_for_a_preexisting_label_set() {
342 let label_set = LabelSet::default();
343 let mut collection = SampleCollection::<Counter>::default();
344
345 collection.increment(&label_set, sample_update_time());
347
348 let sample = collection.get(&label_set).unwrap();
350 assert_eq!(sample.value(), &Counter::new(1));
351
352 collection.increment(&label_set, sample_update_time());
354 let sample = collection.get(&label_set).unwrap();
355 assert_eq!(*sample.value(), Counter::new(2));
356 }
357
358 #[test]
359 fn it_should_allow_increment_the_counter_for_a_non_existent_label_set() {
360 let label_set = LabelSet::default();
361 let mut collection = SampleCollection::<Counter>::default();
362
363 collection.increment(&label_set, sample_update_time());
365
366 assert!(collection.get(&label_set).is_some());
368 let sample = collection.get(&label_set).unwrap();
369 assert_eq!(*sample.value(), Counter::new(1));
370 }
371
372 #[test]
373 fn it_should_update_the_latest_update_time_when_incremented() {
374 let label_set = LabelSet::default();
375 let initial_time = sample_update_time();
376
377 let mut collection = SampleCollection::<Counter>::default();
378 collection.increment(&label_set, initial_time);
379
380 let new_time = initial_time.add(DurationSinceUnixEpoch::from_secs(1));
382 collection.increment(&label_set, new_time);
383
384 let sample = collection.get(&label_set).unwrap();
385 assert_eq!(sample.recorded_at(), new_time);
386 assert_eq!(*sample.value(), Counter::new(2));
387 }
388
389 #[test]
390 fn it_should_increment_the_counter_for_multiple_labels() {
391 let label1 = LabelSet::from([("name", "value1")]);
392 let label2 = LabelSet::from([("name", "value2")]);
393 let now = sample_update_time();
394
395 let mut collection = SampleCollection::<Counter>::default();
396
397 collection.increment(&label1, now);
398 collection.increment(&label2, now);
399
400 assert_eq!(collection.get(&label1).unwrap().value(), &Counter::new(1));
401 assert_eq!(collection.get(&label2).unwrap().value(), &Counter::new(1));
402 assert_eq!(collection.len(), 2);
403 }
404
405 #[test]
406 fn it_should_allow_setting_absolute_value_for_a_counter() {
407 let label_set = LabelSet::default();
408 let mut collection = SampleCollection::<Counter>::default();
409
410 collection.absolute(&label_set, 42, sample_update_time());
412
413 assert!(collection.get(&label_set).is_some());
415 let sample = collection.get(&label_set).unwrap();
416 assert_eq!(*sample.value(), Counter::new(42));
417 }
418
419 #[test]
420 fn it_should_allow_setting_absolute_value_for_existing_counter() {
421 let label_set = LabelSet::default();
422 let mut collection = SampleCollection::<Counter>::default();
423
424 collection.increment(&label_set, sample_update_time());
426
427 let sample = collection.get(&label_set).unwrap();
429 assert_eq!(sample.value(), &Counter::new(1));
430
431 collection.absolute(&label_set, 100, sample_update_time());
433 let sample = collection.get(&label_set).unwrap();
434 assert_eq!(*sample.value(), Counter::new(100));
435 }
436
437 #[test]
438 fn it_should_update_time_when_setting_absolute_value() {
439 let label_set = LabelSet::default();
440 let initial_time = sample_update_time();
441 let mut collection = SampleCollection::<Counter>::default();
442
443 collection.absolute(&label_set, 50, initial_time);
445
446 let new_time = initial_time.add(DurationSinceUnixEpoch::from_secs(1));
448 collection.absolute(&label_set, 75, new_time);
449
450 let sample = collection.get(&label_set).unwrap();
451 assert_eq!(sample.recorded_at(), new_time);
452 assert_eq!(*sample.value(), Counter::new(75));
453 }
454 }
455
456 #[cfg(test)]
457 mod for_gauges {
458
459 use std::ops::Add;
460
461 use super::super::LabelSet;
462 use super::*;
463 use crate::gauge::Gauge;
464
465 #[test]
466 fn it_should_allow_setting_the_gauge_for_a_preexisting_label_set() {
467 let label_set = LabelSet::default();
468 let mut collection = SampleCollection::<Gauge>::default();
469
470 collection.set(&label_set, 1.0, sample_update_time());
472
473 let sample = collection.get(&label_set).unwrap();
475 assert_eq!(sample.value(), &Gauge::new(1.0));
476
477 collection.set(&label_set, 2.0, sample_update_time());
479 let sample = collection.get(&label_set).unwrap();
480 assert_eq!(*sample.value(), Gauge::new(2.0));
481 }
482
483 #[test]
484 fn it_should_allow_setting_the_gauge_for_a_non_existent_label_set() {
485 let label_set = LabelSet::default();
486 let mut collection = SampleCollection::<Gauge>::default();
487
488 collection.set(&label_set, 1.0, sample_update_time());
490
491 assert!(collection.get(&label_set).is_some());
493 let sample = collection.get(&label_set).unwrap();
494 assert_eq!(*sample.value(), Gauge::new(1.0));
495 }
496
497 #[test]
498 fn it_should_update_the_latest_update_time_when_setting() {
499 let label_set = LabelSet::default();
500 let initial_time = sample_update_time();
501
502 let mut collection = SampleCollection::<Gauge>::default();
503 collection.set(&label_set, 1.0, initial_time);
504
505 let new_time = initial_time.add(DurationSinceUnixEpoch::from_secs(1));
507 collection.set(&label_set, 2.0, new_time);
508
509 let sample = collection.get(&label_set).unwrap();
510 assert_eq!(sample.recorded_at(), new_time);
511 assert_eq!(*sample.value(), Gauge::new(2.0));
512 }
513
514 #[test]
515 fn it_should_allow_setting_the_gauge_for_multiple_labels() {
516 let label1 = LabelSet::from([("name", "value1")]);
517 let label2 = LabelSet::from([("name", "value2")]);
518 let now = sample_update_time();
519
520 let mut collection = SampleCollection::<Gauge>::default();
521
522 collection.set(&label1, 1.0, now);
523 collection.set(&label2, 2.0, now);
524
525 assert_eq!(collection.get(&label1).unwrap().value(), &Gauge::new(1.0));
526 assert_eq!(collection.get(&label2).unwrap().value(), &Gauge::new(2.0));
527 assert_eq!(collection.len(), 2);
528 }
529
530 #[test]
531 fn it_should_allow_incrementing_the_gauge() {
532 let label_set = LabelSet::default();
533 let mut collection = SampleCollection::<Gauge>::default();
534
535 collection.set(&label_set, 1.0, sample_update_time());
537
538 collection.increment(&label_set, sample_update_time());
540 let sample = collection.get(&label_set).unwrap();
541 assert_eq!(*sample.value(), Gauge::new(2.0));
542 }
543
544 #[test]
545 fn it_should_allow_decrementing_the_gauge() {
546 let label_set = LabelSet::default();
547 let mut collection = SampleCollection::<Gauge>::default();
548
549 collection.set(&label_set, 1.0, sample_update_time());
551
552 collection.decrement(&label_set, sample_update_time());
554 let sample = collection.get(&label_set).unwrap();
555 assert_eq!(*sample.value(), Gauge::new(0.0));
556 }
557
558 #[test]
559 fn it_should_create_a_default_gauge_when_decrementing_a_nonexistent_label_set() {
560 let label_set = LabelSet::default();
561 let mut collection = SampleCollection::<Gauge>::default();
562
563 collection.decrement(&label_set, sample_update_time());
565 let sample = collection.get(&label_set).unwrap();
566 assert_eq!(*sample.value(), Gauge::new(-1.0));
567 }
568 }
569}