feat(metrics-derive): ability to specify labels during instantiation (#893)

This commit is contained in:
Andrew Kirillov
2023-01-19 16:25:20 -08:00
committed by GitHub
parent cfef666886
commit 2ec490a13d
2 changed files with 221 additions and 46 deletions

View File

@ -28,7 +28,7 @@ pub(crate) fn derive(node: &DeriveInput) -> Result<proc_macro2::TokenStream> {
};
let register_and_describe = match &metrics_attr.scope {
MetricsScope::Static(scope) => {
let (defaults, describes): (Vec<_>, Vec<_>) = metric_fields
let (defaults, labeled_defaults, describes): (Vec<_>, Vec<_>, Vec<_>) = metric_fields
.iter()
.map(|metric| {
let field_name = &metric.field.ident;
@ -41,6 +41,9 @@ pub(crate) fn derive(node: &DeriveInput) -> Result<proc_macro2::TokenStream> {
quote! {
#field_name: #registrar(#metric_name),
},
quote! {
#field_name: #registrar(#metric_name, labels.clone()),
},
quote! {
#describe(#metric_name, #description);
},
@ -48,7 +51,12 @@ pub(crate) fn derive(node: &DeriveInput) -> Result<proc_macro2::TokenStream> {
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.unzip();
.fold((vec![], vec![], vec![]), |mut acc, x| {
acc.0.push(x.0);
acc.1.push(x.1);
acc.2.push(x.2);
acc
});
quote! {
impl Default for #ty {
@ -60,6 +68,13 @@ pub(crate) fn derive(node: &DeriveInput) -> Result<proc_macro2::TokenStream> {
}
impl #ty {
/// Create new instance of metrics with provided labels.
#vis fn new_with_labels(labels: impl metrics::IntoLabels + Clone) -> Self {
Self {
#(#labeled_defaults)*
}
}
#describe_doc
#vis fn describe() {
#(#describes)*
@ -68,7 +83,7 @@ pub(crate) fn derive(node: &DeriveInput) -> Result<proc_macro2::TokenStream> {
}
}
MetricsScope::Dynamic => {
let (defaults, describes): (Vec<_>, Vec<_>) = metric_fields
let (defaults, labeled_defaults, describes): (Vec<_>, Vec<_>, Vec<_>) = metric_fields
.iter()
.map(|metric| {
let name = metric.name();
@ -86,6 +101,9 @@ pub(crate) fn derive(node: &DeriveInput) -> Result<proc_macro2::TokenStream> {
quote! {
#field_name: #registrar(#metric_name),
},
quote! {
#field_name: #registrar(#metric_name, labels.clone()),
},
quote! {
#describe(#metric_name, #description);
},
@ -93,7 +111,12 @@ pub(crate) fn derive(node: &DeriveInput) -> Result<proc_macro2::TokenStream> {
})
.collect::<Result<Vec<_>>>()?
.into_iter()
.unzip();
.fold((vec![], vec![], vec![]), |mut acc, x| {
acc.0.push(x.0);
acc.1.push(x.1);
acc.2.push(x.2);
acc
});
quote! {
impl #ty {
@ -104,6 +127,13 @@ pub(crate) fn derive(node: &DeriveInput) -> Result<proc_macro2::TokenStream> {
}
}
/// Create new instance of metrics with provided labels.
#vis fn new_with_labels(scope: &str, labels: impl metrics::IntoLabels + Clone) -> Self {
Self {
#(#labeled_defaults)*
}
}
#describe_doc
#vis fn describe(scope: &str) {
#(#describes)*

View File

@ -1,5 +1,5 @@
use metrics::{
set_recorder, Counter, Gauge, Histogram, Key, KeyName, Recorder, SharedString, Unit,
set_recorder, Counter, Gauge, Histogram, Key, KeyName, Label, Recorder, SharedString, Unit,
};
use once_cell::sync::Lazy;
use reth_metrics_derive::Metrics;
@ -22,8 +22,74 @@ struct CustomMetrics {
histo: Histogram,
}
#[allow(dead_code)]
#[derive(Metrics)]
#[metrics(dynamic = true)]
struct DynamicScopeMetrics {
/// A gauge with doc comment description.
gauge: Gauge,
#[metric(rename = "second_gauge", describe = "A gauge with metric attribute description.")]
gauge2: Gauge,
/// Some doc comment
#[metric(describe = "Metric attribute description will be preferred over doc comment.")]
counter: Counter,
/// A renamed histogram.
#[metric(rename = "histogram")]
histo: Histogram,
}
static RECORDER: Lazy<TestRecorder> = Lazy::new(TestRecorder::new);
fn test_describe(scope: &str) {
assert_eq!(RECORDER.metrics_len(), 4);
let gauge = RECORDER.get_metric(&format!("{scope}_gauge"));
assert!(gauge.is_some());
assert_eq!(
gauge.unwrap(),
TestMetric {
ty: TestMetricTy::Gauge,
description: Some("A gauge with doc comment description.".to_owned()),
labels: None,
}
);
let second_gauge = RECORDER.get_metric(&format!("{scope}_second_gauge"));
assert!(second_gauge.is_some());
assert_eq!(
second_gauge.unwrap(),
TestMetric {
ty: TestMetricTy::Gauge,
description: Some("A gauge with metric attribute description.".to_owned()),
labels: None,
}
);
let counter = RECORDER.get_metric(&format!("{scope}_counter"));
assert!(counter.is_some());
assert_eq!(
counter.unwrap(),
TestMetric {
ty: TestMetricTy::Counter,
description: Some(
"Metric attribute description will be preferred over doc comment.".to_owned()
),
labels: None,
}
);
let histogram = RECORDER.get_metric(&format!("{scope}_histogram"));
assert!(histogram.is_some());
assert_eq!(
histogram.unwrap(),
TestMetric {
ty: TestMetricTy::Histogram,
description: Some("A renamed histogram.".to_owned()),
labels: None,
}
);
}
#[test]
#[serial]
fn describe_metrics() {
@ -31,51 +97,55 @@ fn describe_metrics() {
CustomMetrics::describe();
test_describe("metrics_custom");
RECORDER.clear();
}
#[test]
#[serial]
fn describe_dynamic_metrics() {
let _ = set_recorder(&*RECORDER as &dyn Recorder); // ignore error
let scope = "local_scope";
DynamicScopeMetrics::describe(scope);
test_describe(scope);
RECORDER.clear();
}
fn test_register(scope: &str) {
assert_eq!(RECORDER.metrics_len(), 4);
let gauge = RECORDER.get_metric("metrics_custom_gauge");
let gauge = RECORDER.get_metric(&format!("{scope}_gauge"));
assert!(gauge.is_some());
assert_eq!(
gauge.unwrap(),
TestMetric {
ty: TestMetricTy::Gauge,
description: Some("A gauge with doc comment description.".to_owned())
}
TestMetric { ty: TestMetricTy::Gauge, description: None, labels: None }
);
let second_gauge = RECORDER.get_metric("metrics_custom_second_gauge");
let second_gauge = RECORDER.get_metric(&format!("{scope}_second_gauge"));
assert!(second_gauge.is_some());
assert_eq!(
second_gauge.unwrap(),
TestMetric {
ty: TestMetricTy::Gauge,
description: Some("A gauge with metric attribute description.".to_owned())
}
TestMetric { ty: TestMetricTy::Gauge, description: None, labels: None }
);
let counter = RECORDER.get_metric("metrics_custom_counter");
let counter = RECORDER.get_metric(&format!("{scope}_counter"));
assert!(counter.is_some());
assert_eq!(
counter.unwrap(),
TestMetric {
ty: TestMetricTy::Counter,
description: Some(
"Metric attribute description will be preferred over doc comment.".to_owned()
)
}
TestMetric { ty: TestMetricTy::Counter, description: None, labels: None }
);
let histogram = RECORDER.get_metric("metrics_custom_histogram");
let histogram = RECORDER.get_metric(&format!("{scope}_histogram"));
assert!(histogram.is_some());
assert_eq!(
histogram.unwrap(),
TestMetric {
ty: TestMetricTy::Histogram,
description: Some("A renamed histogram.".to_owned())
}
TestMetric { ty: TestMetricTy::Histogram, description: None, labels: None }
);
RECORDER.clear();
}
#[test]
@ -85,23 +155,75 @@ fn register_metrics() {
let _metrics = CustomMetrics::default();
assert_eq!(RECORDER.metrics_len(), 4);
test_register("metrics_custom");
let gauge = RECORDER.get_metric("metrics_custom_gauge");
RECORDER.clear();
}
#[test]
#[serial]
fn register_dynamic_metrics() {
let _ = set_recorder(&*RECORDER as &dyn Recorder); // ignore error
let scope = "local_scope";
let _metrics = DynamicScopeMetrics::new(scope);
test_register(scope);
RECORDER.clear();
}
fn test_labels(scope: &str) {
let test_labels = vec![Label::new("key", "value")];
let gauge = RECORDER.get_metric(&format!("{scope}_gauge"));
assert!(gauge.is_some());
assert_eq!(gauge.unwrap(), TestMetric { ty: TestMetricTy::Gauge, description: None });
let labels = gauge.unwrap().labels;
assert!(labels.is_some());
assert_eq!(labels.unwrap(), test_labels.clone(),);
let second_gauge = RECORDER.get_metric("metrics_custom_second_gauge");
let second_gauge = RECORDER.get_metric(&format!("{scope}_second_gauge"));
assert!(second_gauge.is_some());
assert_eq!(second_gauge.unwrap(), TestMetric { ty: TestMetricTy::Gauge, description: None });
let labels = second_gauge.unwrap().labels;
assert!(labels.is_some());
assert_eq!(labels.unwrap(), test_labels.clone(),);
let counter = RECORDER.get_metric("metrics_custom_counter");
let counter = RECORDER.get_metric(&format!("{scope}_counter"));
assert!(counter.is_some());
assert_eq!(counter.unwrap(), TestMetric { ty: TestMetricTy::Counter, description: None });
let labels = counter.unwrap().labels;
assert!(labels.is_some());
assert_eq!(labels.unwrap(), test_labels.clone(),);
let histogram = RECORDER.get_metric("metrics_custom_histogram");
let histogram = RECORDER.get_metric(&format!("{scope}_histogram"));
assert!(histogram.is_some());
assert_eq!(histogram.unwrap(), TestMetric { ty: TestMetricTy::Histogram, description: None });
let labels = histogram.unwrap().labels;
assert!(labels.is_some());
assert_eq!(labels.unwrap(), test_labels,);
}
#[test]
#[serial]
fn label_metrics() {
let _ = set_recorder(&*RECORDER as &dyn Recorder); // ignore error
let _metrics = CustomMetrics::new_with_labels(&[("key", "value")]);
test_labels("metrics_custom");
RECORDER.clear();
}
#[test]
#[serial]
fn dynamic_label_metrics() {
let _ = set_recorder(&*RECORDER as &dyn Recorder); // ignore error
let scope = "local_scope";
let _metrics = DynamicScopeMetrics::new_with_labels(scope, &[("key", "value")]);
test_labels(scope);
RECORDER.clear();
}
@ -122,6 +244,7 @@ enum TestMetricTy {
struct TestMetric {
ty: TestMetricTy,
description: Option<String>,
labels: Option<Vec<Label>>,
}
impl TestRecorder {
@ -137,11 +260,17 @@ impl TestRecorder {
self.metrics.lock().expect("failed to lock metrics").get(key).cloned()
}
fn record_metric(&self, key: &str, ty: TestMetricTy, description: Option<String>) {
fn record_metric(
&self,
key: &str,
ty: TestMetricTy,
description: Option<String>,
labels: Option<Vec<Label>>,
) {
self.metrics
.lock()
.expect("failed to lock metrics")
.insert(key.to_owned(), TestMetric { ty, description });
.insert(key.to_owned(), TestMetric { ty, description, labels });
}
fn clear(&self) {
@ -151,29 +280,45 @@ impl TestRecorder {
impl Recorder for TestRecorder {
fn describe_counter(&self, key: KeyName, _unit: Option<Unit>, description: SharedString) {
self.record_metric(key.as_str(), TestMetricTy::Counter, Some(description.into_owned()))
self.record_metric(
key.as_str(),
TestMetricTy::Counter,
Some(description.into_owned()),
None,
)
}
fn describe_gauge(&self, key: KeyName, _unit: Option<Unit>, description: SharedString) {
self.record_metric(key.as_str(), TestMetricTy::Gauge, Some(description.into_owned()))
self.record_metric(key.as_str(), TestMetricTy::Gauge, Some(description.into_owned()), None)
}
fn describe_histogram(&self, key: KeyName, _unit: Option<Unit>, description: SharedString) {
self.record_metric(key.as_str(), TestMetricTy::Histogram, Some(description.into_owned()))
self.record_metric(
key.as_str(),
TestMetricTy::Histogram,
Some(description.into_owned()),
None,
)
}
fn register_counter(&self, key: &Key) -> Counter {
self.record_metric(key.name(), TestMetricTy::Counter, None);
let labels_vec: Vec<Label> = key.labels().cloned().collect();
let labels = if labels_vec.is_empty() { None } else { Some(labels_vec) };
self.record_metric(key.name(), TestMetricTy::Counter, None, labels);
Counter::noop()
}
fn register_gauge(&self, key: &Key) -> Gauge {
self.record_metric(key.name(), TestMetricTy::Gauge, None);
let labels_vec: Vec<Label> = key.labels().cloned().collect();
let labels = if labels_vec.is_empty() { None } else { Some(labels_vec) };
self.record_metric(key.name(), TestMetricTy::Gauge, None, labels);
Gauge::noop()
}
fn register_histogram(&self, key: &Key) -> Histogram {
self.record_metric(key.name(), TestMetricTy::Histogram, None);
let labels_vec: Vec<Label> = key.labels().cloned().collect();
let labels = if labels_vec.is_empty() { None } else { Some(labels_vec) };
self.record_metric(key.name(), TestMetricTy::Histogram, None, labels);
Histogram::noop()
}
}