Skip to content

Commit 72bc78d

Browse files
committed
feat: add retain() to Family to allow metrics filtering
Signed-off-by: John Howard <john.howard@solo.io>
1 parent 9a74e99 commit 72bc78d

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ http-body-util = "0.1.1"
4444
[build-dependencies]
4545
prost-build = { version = "0.12.0", optional = true }
4646

47+
[[example]]
48+
name = "prune"
49+
4750
[[bench]]
4851
name = "baseline"
4952
harness = false

examples/prune.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use prometheus_client::encoding::{text::encode, EncodeMetric, MetricEncoder};
2+
use prometheus_client::metrics::counter::Atomic;
3+
use prometheus_client::metrics::family::Family;
4+
use prometheus_client::metrics::{MetricType, TypedMetric};
5+
use prometheus_client::registry::Registry;
6+
use prometheus_client_derive_encode::EncodeLabelSet;
7+
use std::fmt::Error;
8+
use std::sync::atomic::AtomicU64;
9+
use std::sync::{Arc, Mutex};
10+
use std::thread;
11+
use std::time::Duration;
12+
use tokio::time::Instant;
13+
14+
#[derive(Default, Debug)]
15+
struct MyCounter {
16+
value: Arc<AtomicU64>,
17+
last_access: Arc<Mutex<Option<Instant>>>,
18+
}
19+
20+
impl TypedMetric for MyCounter {
21+
const TYPE: MetricType = MetricType::Counter;
22+
}
23+
24+
impl MyCounter {
25+
pub fn get(&self) -> u64 {
26+
self.value.get()
27+
}
28+
pub fn inc(&self) -> u64 {
29+
let mut last = self.last_access.lock().unwrap();
30+
*last = Some(Instant::now());
31+
self.value.inc()
32+
}
33+
}
34+
35+
impl EncodeMetric for MyCounter {
36+
fn encode(&self, mut encoder: MetricEncoder) -> Result<(), Error> {
37+
encoder.encode_counter::<prometheus_client::encoding::NoLabelSet, _, u64>(&self.get(), None)
38+
}
39+
40+
fn metric_type(&self) -> MetricType {
41+
todo!()
42+
}
43+
}
44+
45+
#[derive(Clone, Hash, Default, Debug, PartialEq, Eq, EncodeLabelSet)]
46+
struct Labels {
47+
name: String,
48+
}
49+
50+
fn main() {
51+
let mut registry = Registry::default();
52+
53+
let metric: Family<Labels, MyCounter> = Family::default();
54+
registry.register("my_custom_metric", "test", metric.clone());
55+
metric
56+
.get_or_create(&Labels {
57+
name: "apple".to_string(),
58+
})
59+
.inc();
60+
metric
61+
.get_or_create(&Labels {
62+
name: "banana".to_string(),
63+
})
64+
.inc();
65+
66+
let mut encoded = String::new();
67+
encode(&mut encoded, &registry).unwrap();
68+
69+
println!("Scrape output:\n{}", encoded);
70+
thread::sleep(Duration::from_secs(1));
71+
metric
72+
.get_or_create(&Labels {
73+
name: "banana".to_string(),
74+
})
75+
.inc();
76+
let now = Instant::now();
77+
metric.retain(|_labels, counter| {
78+
let last = counter.last_access.lock().unwrap().unwrap();
79+
now.saturating_duration_since(last) > Duration::from_secs(1)
80+
});
81+
82+
let mut encoded = String::new();
83+
encode(&mut encoded, &registry).unwrap();
84+
85+
println!("Scrape output:\n{}", encoded);
86+
}

src/metrics/family.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,33 @@ impl<S: Clone + std::hash::Hash + Eq, M, C: MetricConstructor<M>> Family<S, M, C
303303
self.metrics.write().clear()
304304
}
305305

306+
/// Retains only the label sets specified by the predicate.
307+
///
308+
/// ```
309+
/// # use prometheus_client::metrics::counter::{Atomic, Counter};
310+
/// # use prometheus_client::metrics::family::Family;
311+
/// #
312+
/// let family = Family::<Vec<(String, String)>, Counter>::default();
313+
///
314+
/// // Will create the metric with label `method="GET"` on first call and
315+
/// // return a reference.
316+
/// family.get_or_create(&vec![("method".to_owned(), "GET".to_owned())]).inc_by(10);
317+
/// family.get_or_create(&vec![("method".to_owned(), "DELETE".to_owned())]).inc();
318+
/// family.get_or_create(&vec![("method".to_owned(), "POST".to_owned())]).inc();
319+
///
320+
/// // Retain only label sets where the counter is less than 10, or method="POST"
321+
/// // This will leave the method="POST" and method="DELETE" label sets.
322+
/// family.retain(|labels, counter| {
323+
/// counter.get() < 5 || labels.contains(&("method".to_owned(), "POST".to_owned()))
324+
/// });
325+
/// ```
326+
pub fn retain<F>(&self, f: F)
327+
where
328+
F: FnMut(&S, &mut M) -> bool,
329+
{
330+
self.metrics.write().retain(f)
331+
}
332+
306333
pub(crate) fn read(&self) -> RwLockReadGuard<HashMap<S, M>> {
307334
self.metrics.read()
308335
}

0 commit comments

Comments
 (0)