Skip to content

Commit f0194db

Browse files
committed
Add unprefixed flag to #[metrics] macro
This change is twofold. First, it refactors the registry selection in `metrics::internal::Registries` to use boolean flags rather than separate methods to select a registry to use. As part of this, we add a combination of flags to return a registry without the global service name prefix that is normally present. Second, we add a new `unprefixed` argument to the `metrics` proc-macro. If present, this argument causes all metrics in the associated module to be registered in the registry mentioned above. This works for both the "main" and the "optional" registry. I've added both a proc-macro expansion test and an integration test to validate the flag's functionality.
1 parent da1adb9 commit f0194db

File tree

4 files changed

+189
-69
lines changed

4 files changed

+189
-69
lines changed

foundations-macros/src/metrics/mod.rs

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
use darling::util::Flag;
12
use darling::FromMeta;
23
use proc_macro::TokenStream;
34
use proc_macro2::Span;
4-
use quote::{quote, ToTokens};
5+
use quote::{format_ident, quote, ToTokens};
56
use syn::punctuated::Punctuated;
67
use syn::{
78
parse_macro_input, parse_quote, Attribute, ExprStruct, Ident, LitStr, Path, Token, Type,
@@ -13,13 +14,15 @@ mod validation;
1314

1415
#[derive(FromMeta)]
1516
struct MacroArgs {
17+
unprefixed: Flag,
1618
#[darling(default = "Self::default_crate_path")]
1719
crate_path: Path,
1820
}
1921

2022
impl Default for MacroArgs {
2123
fn default() -> Self {
2224
Self {
25+
unprefixed: Flag::default(),
2326
crate_path: Self::default_crate_path(),
2427
}
2528
}
@@ -86,8 +89,11 @@ pub(crate) fn expand(args: TokenStream, item: TokenStream) -> TokenStream {
8689

8790
fn expand_from_parsed(args: MacroArgs, extern_: Mod) -> proc_macro2::TokenStream {
8891
let MacroArgs {
92+
unprefixed,
8993
crate_path: foundations,
9094
} = &args;
95+
let with_service_prefix =
96+
format_ident!("{}", !unprefixed.is_present(), span = unprefixed.span());
9197

9298
let Mod {
9399
attrs: mod_attrs,
@@ -107,24 +113,26 @@ fn expand_from_parsed(args: MacroArgs, extern_: Mod) -> proc_macro2::TokenStream
107113
.iter()
108114
.filter_map(|fn_| label_set_struct(foundations, fn_));
109115

110-
let registry_init = |var: &str, kind: &str| {
116+
let registry_init = |var: &str, opt: bool| {
111117
let var = Ident::new(var, Span::call_site());
112-
let method = Ident::new(&format!("get_{kind}_subsystem"), Span::call_site());
118+
let optional = format_ident!("{opt}");
113119

114120
quote! {
115-
let #var = &mut *#foundations::telemetry::metrics::internal::Registries::#method(stringify!(#mod_name));
121+
let #var = &mut *#foundations::telemetry::metrics::internal::Registries::get_subsystem(
122+
stringify!(#mod_name), #optional, #with_service_prefix
123+
);
116124
}
117125
};
118126

119127
let init_registry = fns
120128
.iter()
121129
.any(|fn_| !fn_.attrs.optional)
122-
.then(|| registry_init("registry", "main"));
130+
.then(|| registry_init("registry", false));
123131

124132
let init_opt_registry = fns
125133
.iter()
126134
.any(|fn_| fn_.attrs.optional)
127-
.then(|| registry_init("opt_registry", "opt"));
135+
.then(|| registry_init("opt_registry", true));
128136

129137
let metric_inits = fns.iter().map(|fn_| metric_init(foundations, fn_));
130138

@@ -453,7 +461,7 @@ mod tests {
453461
#[allow(non_upper_case_globals)]
454462
static __oxy_Metrics: tarmac::reexports_for_macros::once_cell::sync::Lazy<__oxy_Metrics> =
455463
tarmac::reexports_for_macros::once_cell::sync::Lazy::new(|| {
456-
let registry = &mut *tarmac::telemetry::metrics::internal::Registries::get_main_subsystem(stringify!(oxy));
464+
let registry = &mut *tarmac::telemetry::metrics::internal::Registries::get_subsystem(stringify!(oxy), false, true);
457465

458466
__oxy_Metrics {
459467
connections_total: {
@@ -510,9 +518,83 @@ mod tests {
510518
#[allow(non_upper_case_globals)]
511519
static __oxy_Metrics: ::foundations::reexports_for_macros::once_cell::sync::Lazy<__oxy_Metrics> =
512520
::foundations::reexports_for_macros::once_cell::sync::Lazy::new(|| {
513-
let opt_registry = &mut *::foundations::telemetry::metrics::internal::Registries::get_opt_subsystem(stringify!(oxy));
521+
let opt_registry = &mut *::foundations::telemetry::metrics::internal::Registries::get_subsystem(stringify!(oxy), true, true);
522+
523+
__oxy_Metrics {
524+
connections_total: {
525+
let metric = ::std::default::Default::default();
526+
527+
::foundations::reexports_for_macros::prometheus_client::registry::Registry::register(
528+
opt_registry,
529+
::std::stringify!(connections_total),
530+
str::trim(" Total number of connections"),
531+
::std::boxed::Box::new(::std::clone::Clone::clone(&metric))
532+
);
533+
534+
metric
535+
},
536+
}
537+
});
538+
539+
#[doc = " Total number of connections"]
540+
#[must_use]
541+
pub(crate) fn connections_total() -> Counter {
542+
::std::clone::Clone::clone(&__oxy_Metrics.connections_total)
543+
}
544+
}
545+
};
546+
547+
assert_eq!(actual, expected);
548+
}
549+
550+
#[test]
551+
fn expand_unprefixed_mixed() {
552+
let attr = parse_attr! {
553+
#[metrics(unprefixed)]
554+
};
555+
556+
let src = parse_quote! {
557+
pub(crate) mod oxy {
558+
/// Total number of requests
559+
pub(crate) fn requests_total() -> Counter;
560+
561+
/// Total number of connections
562+
#[optional]
563+
pub(crate) fn connections_total() -> Counter;
564+
}
565+
};
566+
567+
let actual = expand_from_parsed(attr, src).to_string();
568+
569+
let expected = code_str! {
570+
pub(crate) mod oxy {
571+
use super::*;
572+
573+
#[allow(non_camel_case_types)]
574+
struct __oxy_Metrics {
575+
requests_total: Counter,
576+
connections_total: Counter,
577+
}
578+
579+
#[allow(non_upper_case_globals)]
580+
static __oxy_Metrics: ::foundations::reexports_for_macros::once_cell::sync::Lazy<__oxy_Metrics> =
581+
::foundations::reexports_for_macros::once_cell::sync::Lazy::new(|| {
582+
let registry = &mut *::foundations::telemetry::metrics::internal::Registries::get_subsystem(stringify!(oxy), false, false);
583+
let opt_registry = &mut *::foundations::telemetry::metrics::internal::Registries::get_subsystem(stringify!(oxy), true, false);
514584

515585
__oxy_Metrics {
586+
requests_total: {
587+
let metric = ::std::default::Default::default();
588+
589+
::foundations::reexports_for_macros::prometheus_client::registry::Registry::register(
590+
registry,
591+
::std::stringify!(requests_total),
592+
str::trim(" Total number of requests"),
593+
::std::boxed::Box::new(::std::clone::Clone::clone(&metric))
594+
);
595+
596+
metric
597+
},
516598
connections_total: {
517599
let metric = ::std::default::Default::default();
518600

@@ -528,6 +610,12 @@ mod tests {
528610
}
529611
});
530612

613+
#[doc = " Total number of requests"]
614+
#[must_use]
615+
pub(crate) fn requests_total() -> Counter {
616+
::std::clone::Clone::clone(&__oxy_Metrics.requests_total)
617+
}
618+
531619
#[doc = " Total number of connections"]
532620
#[must_use]
533621
pub(crate) fn connections_total() -> Counter {
@@ -596,7 +684,7 @@ mod tests {
596684
#[allow(non_upper_case_globals)]
597685
static __oxy_Metrics: ::foundations::reexports_for_macros::once_cell::sync::Lazy<__oxy_Metrics> =
598686
::foundations::reexports_for_macros::once_cell::sync::Lazy::new(|| {
599-
let registry = &mut *::foundations::telemetry::metrics::internal::Registries::get_main_subsystem(stringify!(oxy));
687+
let registry = &mut *::foundations::telemetry::metrics::internal::Registries::get_subsystem(stringify!(oxy), false, true);
600688

601689
__oxy_Metrics {
602690
connections_errors_total: {
@@ -691,7 +779,7 @@ mod tests {
691779
#[allow(non_upper_case_globals)]
692780
static __oxy_Metrics: ::foundations::reexports_for_macros::once_cell::sync::Lazy<__oxy_Metrics> =
693781
::foundations::reexports_for_macros::once_cell::sync::Lazy::new(|| {
694-
let registry = &mut *::foundations::telemetry::metrics::internal::Registries::get_main_subsystem(stringify!(oxy));
782+
let registry = &mut *::foundations::telemetry::metrics::internal::Registries::get_subsystem(stringify!(oxy), false, true);
695783

696784
__oxy_Metrics {
697785
connections_latency: {

foundations/src/telemetry/metrics/internal.rs

Lines changed: 52 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,53 @@ use prometheus_client::encoding::text::{encode, EncodeMetric};
66
use prometheus_client::registry::Registry;
77
use prometools::serde::InfoGauge;
88
use std::any::TypeId;
9+
use std::borrow::Cow;
910
use std::collections::HashMap;
1011
use std::ops::DerefMut;
1112

1213
static REGISTRIES: OnceCell<Registries> = OnceCell::new();
1314

15+
enum MetricsServiceName {
16+
Prefix(String),
17+
Label(String, String),
18+
}
19+
20+
impl MetricsServiceName {
21+
fn new(name: &str, format: ServiceNameFormat) -> Self {
22+
let name = name.to_owned();
23+
match format {
24+
ServiceNameFormat::MetricPrefix => Self::Prefix(name),
25+
ServiceNameFormat::LabelWithName(label) => Self::Label(label, name),
26+
}
27+
}
28+
}
29+
1430
#[doc(hidden)]
1531
pub struct Registries {
1632
// NOTE: we intentionally use a lock without poisoning here to not
1733
// panic the threads if they just share telemetry with failed thread.
1834
main: parking_lot::RwLock<Registry>,
1935
opt: parking_lot::RwLock<Registry>,
2036
pub(super) info: parking_lot::RwLock<HashMap<TypeId, Box<dyn ErasedInfoMetric>>>,
21-
extra_label: Option<(String, String)>,
37+
service_name: MetricsServiceName,
2238
extra_producers: parking_lot::RwLock<Vec<Box<dyn ExtraProducer>>>,
2339
}
2440

2541
impl Registries {
2642
pub(super) fn init(service_info: &ServiceInfo, settings: &MetricsSettings) {
27-
let extra_label = match &settings.service_name_format {
28-
ServiceNameFormat::MetricPrefix => None,
29-
ServiceNameFormat::LabelWithName(name) => {
30-
Some((name.clone(), service_info.name_in_metrics.clone()))
31-
}
32-
};
43+
let service_name = MetricsServiceName::new(
44+
&service_info.name_in_metrics,
45+
settings.service_name_format.clone(),
46+
);
3347

48+
// FIXME(nox): Due to prometheus-client 0.18 not supporting the creation of
49+
// registries with specific label values, we use `MetricsServiceName::Label`
50+
// directly in `Registries::get_subsystem`.
3451
REGISTRIES.get_or_init(|| Registries {
35-
main: new_registry(&service_info.name_in_metrics, &settings.service_name_format),
36-
opt: new_registry(&service_info.name_in_metrics, &settings.service_name_format),
52+
main: Default::default(),
53+
opt: Default::default(),
3754
info: Default::default(),
38-
extra_label,
55+
service_name,
3956
extra_producers: Default::default(),
4057
});
4158
}
@@ -72,24 +89,31 @@ impl Registries {
7289
encode_registry(buffer, &registry)
7390
}
7491

75-
pub fn get_main_subsystem(subsystem: &str) -> impl DerefMut<Target = Registry> + '_ {
92+
pub fn get_subsystem(
93+
subsystem: &str,
94+
optional: bool,
95+
with_service_prefix: bool,
96+
) -> impl DerefMut<Target = Registry> + 'static {
7697
let registries = Self::get();
98+
let registry = if optional {
99+
&registries.opt
100+
} else {
101+
&registries.main
102+
};
77103

78-
get_subsystem(
79-
Self::get().main.write(),
80-
subsystem,
81-
registries.extra_label.clone(),
82-
)
83-
}
84-
85-
pub fn get_opt_subsystem(subsystem: &str) -> impl DerefMut<Target = Registry> + '_ {
86-
let registries = Self::get();
104+
let mut prefix = Cow::Borrowed(subsystem);
105+
if with_service_prefix {
106+
if let MetricsServiceName::Prefix(service) = &registries.service_name {
107+
prefix = format!("{service}_{subsystem}").into();
108+
}
109+
}
87110

88-
get_subsystem(
89-
Self::get().opt.write(),
90-
subsystem,
91-
registries.extra_label.clone(),
92-
)
111+
parking_lot::RwLockWriteGuard::map(registry.write(), move |mut reg| {
112+
if let MetricsServiceName::Label(name, val) = &registries.service_name {
113+
reg = reg.sub_registry_with_label((name.into(), val.into()));
114+
}
115+
reg.sub_registry_with_prefix(prefix)
116+
})
93117
}
94118

95119
pub fn add_extra_producer(&self, producer: Box<dyn ExtraProducer>) {
@@ -98,45 +122,15 @@ impl Registries {
98122

99123
pub(super) fn get() -> &'static Registries {
100124
REGISTRIES.get_or_init(|| Registries {
101-
main: new_registry("undefined", &ServiceNameFormat::MetricPrefix),
102-
opt: new_registry("undefined", &ServiceNameFormat::MetricPrefix),
125+
main: Default::default(),
126+
opt: Default::default(),
103127
info: Default::default(),
104-
extra_label: None,
128+
service_name: MetricsServiceName::Prefix("undefined".to_owned()),
105129
extra_producers: Default::default(),
106130
})
107131
}
108132
}
109133

110-
fn new_registry(
111-
service_name_in_metrics: &str,
112-
service_name_format: &ServiceNameFormat,
113-
) -> parking_lot::RwLock<Registry> {
114-
parking_lot::RwLock::new(match service_name_format {
115-
ServiceNameFormat::MetricPrefix => match service_name_in_metrics {
116-
"" => Registry::default(),
117-
_ => Registry::with_prefix(service_name_in_metrics),
118-
},
119-
// FIXME(nox): Due to prometheus-client 0.18 not supporting the creation of
120-
// registries with specific label values, we use this service identifier
121-
// format directly in `Registries::get_main` and `Registries::get_optional`.
122-
ServiceNameFormat::LabelWithName(_) => Registry::default(),
123-
})
124-
}
125-
126-
fn get_subsystem<'a>(
127-
registry: parking_lot::RwLockWriteGuard<'a, Registry>,
128-
subsystem: &str,
129-
extra_label: Option<(String, String)>,
130-
) -> impl DerefMut<Target = Registry> + 'a {
131-
parking_lot::RwLockWriteGuard::map(registry, move |mut registry| {
132-
if let Some((name, value)) = extra_label {
133-
registry = registry.sub_registry_with_label((name.into(), value.into()));
134-
}
135-
136-
registry.sub_registry_with_prefix(subsystem)
137-
})
138-
}
139-
140134
/// Build and version information
141135
#[info_metric(crate_path = "crate")]
142136
pub(super) struct BuildInfo {

foundations/src/telemetry/metrics/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ pub fn collect(settings: &MetricsSettings) -> Result<String> {
5454
/// name becomes `<global prefix>_<module name>_<bodyless function name>`and function's
5555
/// Rust doc comment is reported as metric description to Prometheus.
5656
///
57+
/// The `<global_prefix>` can be disabled by passing the `unprefixed` flag to the macro
58+
/// invocation, like `#[metrics(unprefixed)]`. The module name is a mandatory prefix.
59+
///
5760
/// # Labels
5861
/// Arguments of the bodyless functions become labels for that metric.
5962
///
@@ -85,7 +88,6 @@ pub fn collect(settings: &MetricsSettings) -> Result<String> {
8588
/// `collect_optional` argument of [`collect`] is set to `true`, or, in case the [telemetry server]
8689
/// is used, if [`MetricsSettings::report_optional`] is set to `true`.
8790
///
88-
///
8991
/// Can be used for heavy-weight metrics (e.g. with high cardinality) that don't need to be reported
9092
/// on a regular basis.
9193
///

0 commit comments

Comments
 (0)