mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(metrics): derive with dynamic scope (#785)
This commit is contained in:
@ -2,8 +2,8 @@ use once_cell::sync::Lazy;
|
||||
use quote::{quote, ToTokens};
|
||||
use regex::Regex;
|
||||
use syn::{
|
||||
punctuated::Punctuated, Attribute, Data, DeriveInput, Error, Lit, LitStr, MetaNameValue,
|
||||
Result, Token,
|
||||
punctuated::Punctuated, Attribute, Data, DeriveInput, Error, Lit, LitBool, LitStr,
|
||||
MetaNameValue, Result, Token,
|
||||
};
|
||||
|
||||
use crate::{metric::Metric, with_attrs::WithAttrs};
|
||||
@ -15,63 +15,141 @@ static METRIC_NAME_RE: Lazy<Regex> =
|
||||
|
||||
pub(crate) fn derive(node: &DeriveInput) -> Result<proc_macro2::TokenStream> {
|
||||
let ty = &node.ident;
|
||||
let vis = &node.vis;
|
||||
let ident_name = ty.to_string();
|
||||
|
||||
let metrics_attr = parse_metrics_attr(node)?;
|
||||
let metric_fields = parse_metric_fields(node)?;
|
||||
|
||||
let default_fields = metric_fields
|
||||
.iter()
|
||||
.map(|metric| {
|
||||
let field_name = &metric.field.ident;
|
||||
let register_stmt = metric.register_stmt(&metrics_attr)?;
|
||||
Ok(quote! {
|
||||
#field_name: #register_stmt,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
let describe_doc = quote! {
|
||||
/// Describe all exposed metrics. Internally calls `describe_*` macros from
|
||||
/// the metrics crate according to the metric type.
|
||||
/// Ref: https://docs.rs/metrics/0.20.1/metrics/index.html#macros
|
||||
};
|
||||
let register_and_describe = match &metrics_attr.scope {
|
||||
MetricsScope::Static(scope) => {
|
||||
let (defaults, describes): (Vec<_>, Vec<_>) = metric_fields
|
||||
.iter()
|
||||
.map(|metric| {
|
||||
let field_name = &metric.field.ident;
|
||||
let metric_name =
|
||||
format!("{}{}{}", scope.value(), metrics_attr.separator(), metric.name());
|
||||
let registrar = metric.register_stmt()?;
|
||||
let describe = metric.describe_stmt()?;
|
||||
let description = &metric.description;
|
||||
Ok((
|
||||
quote! {
|
||||
#field_name: #registrar(#metric_name),
|
||||
},
|
||||
quote! {
|
||||
#describe(#metric_name, #description);
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.unzip();
|
||||
|
||||
let describe_stmts = metric_fields
|
||||
.iter()
|
||||
.map(|metric| metric.describe_stmt(&metrics_attr))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
quote! {
|
||||
impl Default for #ty {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#(#defaults)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
impl Default for #ty {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#(#default_fields)*
|
||||
impl #ty {
|
||||
#describe_doc
|
||||
#vis fn describe() {
|
||||
#(#describes)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MetricsScope::Dynamic => {
|
||||
let (defaults, describes): (Vec<_>, Vec<_>) = metric_fields
|
||||
.iter()
|
||||
.map(|metric| {
|
||||
let name = metric.name();
|
||||
let separator = metrics_attr.separator();
|
||||
let metric_name = quote! {
|
||||
format!("{}{}{}", scope, #separator, #name)
|
||||
};
|
||||
let field_name = &metric.field.ident;
|
||||
|
||||
let registrar = metric.register_stmt()?;
|
||||
let describe = metric.describe_stmt()?;
|
||||
let description = &metric.description;
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#field_name: #registrar(#metric_name),
|
||||
},
|
||||
quote! {
|
||||
#describe(#metric_name, #description);
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.unzip();
|
||||
|
||||
quote! {
|
||||
impl #ty {
|
||||
/// Create new instance of metrics with provided scope.
|
||||
#vis fn new(scope: &str) -> Self {
|
||||
Self {
|
||||
#(#defaults)*
|
||||
}
|
||||
}
|
||||
|
||||
#describe_doc
|
||||
#vis fn describe(scope: &str) {
|
||||
#(#describes)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#register_and_describe
|
||||
|
||||
impl std::fmt::Debug for #ty {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct(#ident_name).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl #ty {
|
||||
/// Describe all exposed metrics. Internally calls `describe_*` macros from
|
||||
/// the metrics crate according to the metric type.
|
||||
/// Ref: https://docs.rs/metrics/0.20.1/metrics/index.html#macros
|
||||
pub fn describe() {
|
||||
#(#describe_stmts;)*
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) struct MetricsAttr {
|
||||
pub(crate) scope: LitStr,
|
||||
pub(crate) scope: MetricsScope,
|
||||
pub(crate) separator: Option<LitStr>,
|
||||
}
|
||||
|
||||
impl MetricsAttr {
|
||||
const DEFAULT_SEPARATOR: &str = "_";
|
||||
|
||||
fn separator(&self) -> String {
|
||||
match &self.separator {
|
||||
Some(sep) => sep.value(),
|
||||
None => MetricsAttr::DEFAULT_SEPARATOR.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum MetricsScope {
|
||||
Static(LitStr),
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
fn parse_metrics_attr(node: &DeriveInput) -> Result<MetricsAttr> {
|
||||
let metrics_attr = parse_single_required_attr(node, "metrics")?;
|
||||
let parsed =
|
||||
metrics_attr.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)?;
|
||||
let (mut scope, mut separator) = (None, None);
|
||||
let (mut scope, mut separator, mut dynamic) = (None, None, None);
|
||||
for kv in parsed {
|
||||
if kv.path.is_ident("scope") {
|
||||
if scope.is_some() {
|
||||
@ -92,12 +170,30 @@ fn parse_metrics_attr(node: &DeriveInput) -> Result<MetricsAttr> {
|
||||
))
|
||||
}
|
||||
separator = Some(separator_lit);
|
||||
} else if kv.path.is_ident("dynamic") {
|
||||
if dynamic.is_some() {
|
||||
return Err(Error::new_spanned(kv, "Duplicate `dynamic` flag provided."))
|
||||
}
|
||||
dynamic = Some(parse_bool_lit(&kv.lit)?.value);
|
||||
} else {
|
||||
return Err(Error::new_spanned(kv, "Unsupported attribute entry."))
|
||||
}
|
||||
}
|
||||
|
||||
let scope = scope.ok_or_else(|| Error::new_spanned(node, "`scope = ..` must be set."))?;
|
||||
let scope = match (scope, dynamic) {
|
||||
(Some(scope), None) | (Some(scope), Some(false)) => MetricsScope::Static(scope),
|
||||
(None, Some(true)) => MetricsScope::Dynamic,
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(Error::new_spanned(node, "`scope = ..` conflicts with `dynamic = true`."))
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
node,
|
||||
"Either `scope = ..` or `dynamic = true` must be set.",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(MetricsAttr { scope, separator })
|
||||
}
|
||||
|
||||
@ -213,3 +309,10 @@ fn parse_str_lit(lit: &Lit) -> Result<LitStr> {
|
||||
_ => Err(Error::new_spanned(lit, "Value **must** be a string literal.")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_bool_lit(lit: &Lit) -> Result<LitBool> {
|
||||
match lit {
|
||||
Lit::Bool(lit_bool) => Ok(lit_bool.to_owned()),
|
||||
_ => Err(Error::new_spanned(lit, "Value **must** be a string literal.")),
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ mod with_attrs;
|
||||
/// creates a [Default] implementation for the struct registering all of
|
||||
/// the metrics.
|
||||
///
|
||||
/// Additionally, it creates a `describe()` method on the struct, which
|
||||
/// Additionally, it creates a `describe` method on the struct, which
|
||||
/// internally calls the describe statements for all metric fields.
|
||||
///
|
||||
/// Sample usage:
|
||||
@ -31,7 +31,7 @@ mod with_attrs;
|
||||
///
|
||||
/// #[derive(Metrics)]
|
||||
/// #[metrics(scope = "metrics_custom")]
|
||||
/// struct CustomMetrics {
|
||||
/// pub struct CustomMetrics {
|
||||
/// /// A gauge with doc comment description.
|
||||
/// gauge: Gauge,
|
||||
/// #[metric(rename = "second_gauge", describe = "A gauge with metric attribute description.")]
|
||||
@ -47,7 +47,7 @@ mod with_attrs;
|
||||
///
|
||||
/// The example above will be expanded to:
|
||||
/// ```
|
||||
/// struct CustomMetrics {
|
||||
/// pub struct CustomMetrics {
|
||||
/// /// A gauge with doc comment description.
|
||||
/// gauge: metrics::Gauge,
|
||||
/// gauge2: metrics::Gauge,
|
||||
@ -68,12 +68,6 @@ mod with_attrs;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl std::fmt::Debug for CustomMetrics {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// f.debug_struct("CustomMetrics").finish()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl CustomMetrics {
|
||||
/// /// Describe all exposed metrics
|
||||
/// pub fn describe() {
|
||||
@ -83,6 +77,52 @@ mod with_attrs;
|
||||
/// metrics::describe_histogram!("metrics_custom_histogram", "A renamed histogram.");
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl std::fmt::Debug for CustomMetrics {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// f.debug_struct("CustomMetrics").finish()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Similarly, you can derive metrics with "dynamic" scope,
|
||||
/// meaning their scope can be set at the time of instantiation.
|
||||
/// For example:
|
||||
/// ```
|
||||
/// use reth_metrics_derive::Metrics;
|
||||
///
|
||||
/// #[derive(Metrics)]
|
||||
/// #[metrics(dynamic = true)]
|
||||
/// pub struct DynamicScopeMetrics {
|
||||
/// /// A gauge with doc comment description.
|
||||
/// gauge: metrics::Gauge,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The example with dynamic scope will expand to
|
||||
/// ```
|
||||
/// pub struct DynamicScopeMetrics {
|
||||
/// /// A gauge with doc comment description.
|
||||
/// gauge: metrics::Gauge,
|
||||
/// }
|
||||
///
|
||||
/// impl DynamicScopeMetrics {
|
||||
/// pub fn new(scope: &str) -> Self {
|
||||
/// Self {
|
||||
/// gauge: metrics::register_gauge!(format!("{}{}{}", scope, "_", "gauge"))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// pub fn describe(scope: &str) {
|
||||
/// metrics::describe_gauge!(format!("{}{}{}", scope, "_", "gauge"), "A gauge with doc comment description.");
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl std::fmt::Debug for DynamicScopeMetrics {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// f.debug_struct("DynamicScopeMetrics").finish()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(Metrics, attributes(metrics, metric))]
|
||||
pub fn derive_metrics(input: TokenStream) -> TokenStream {
|
||||
|
||||
@ -1,40 +1,29 @@
|
||||
use quote::quote;
|
||||
use syn::{Error, Field, LitStr, Result, Type};
|
||||
|
||||
use crate::expand::MetricsAttr;
|
||||
|
||||
const COUNTER_TY: &str = "Counter";
|
||||
const HISTOGRAM_TY: &str = "Histogram";
|
||||
const GAUGE_TY: &str = "Gauge";
|
||||
|
||||
pub(crate) struct Metric<'a> {
|
||||
pub(crate) field: &'a Field,
|
||||
description: String,
|
||||
pub(crate) description: String,
|
||||
rename: Option<LitStr>,
|
||||
}
|
||||
|
||||
impl<'a> Metric<'a> {
|
||||
const DEFAULT_SEPARATOR: &str = "_";
|
||||
|
||||
pub(crate) fn new(field: &'a Field, description: String, rename: Option<LitStr>) -> Self {
|
||||
Self { field, description, rename }
|
||||
}
|
||||
|
||||
pub(crate) fn metric_name(&self, config: &MetricsAttr) -> String {
|
||||
let scope = config.scope.value();
|
||||
let metric = match self.rename.as_ref() {
|
||||
pub(crate) fn name(&self) -> String {
|
||||
match self.rename.as_ref() {
|
||||
Some(name) => name.value(),
|
||||
None => self.field.ident.as_ref().map(ToString::to_string).unwrap_or_default(),
|
||||
};
|
||||
match config.separator.as_ref() {
|
||||
Some(separator) => format!("{scope}{}{metric}", separator.value()),
|
||||
None => format!("{scope}{}{metric}", Metric::DEFAULT_SEPARATOR),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn register_stmt(&self, config: &MetricsAttr) -> Result<proc_macro2::TokenStream> {
|
||||
let metric_name = self.metric_name(config);
|
||||
|
||||
pub(crate) fn register_stmt(&self) -> Result<proc_macro2::TokenStream> {
|
||||
if let Type::Path(ref path_ty) = self.field.ty {
|
||||
if let Some(last) = path_ty.path.segments.last() {
|
||||
let registrar = match last.ident.to_string().as_str() {
|
||||
@ -44,17 +33,14 @@ impl<'a> Metric<'a> {
|
||||
_ => return Err(Error::new_spanned(path_ty, "Unsupported metric type")),
|
||||
};
|
||||
|
||||
return Ok(quote! { #registrar(#metric_name) })
|
||||
return Ok(quote! { #registrar })
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::new_spanned(&self.field.ty, "Unsupported metric type"))
|
||||
}
|
||||
|
||||
pub(crate) fn describe_stmt(&self, config: &MetricsAttr) -> Result<proc_macro2::TokenStream> {
|
||||
let metric_name = self.metric_name(config);
|
||||
let description = &self.description;
|
||||
|
||||
pub(crate) fn describe_stmt(&self) -> Result<proc_macro2::TokenStream> {
|
||||
if let Type::Path(ref path_ty) = self.field.ty {
|
||||
if let Some(last) = path_ty.path.segments.last() {
|
||||
let descriptor = match last.ident.to_string().as_str() {
|
||||
@ -64,7 +50,7 @@ impl<'a> Metric<'a> {
|
||||
_ => return Err(Error::new_spanned(path_ty, "Unsupported metric type")),
|
||||
};
|
||||
|
||||
return Ok(quote! { #descriptor(#metric_name, #description) })
|
||||
return Ok(quote! { #descriptor })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -50,3 +50,7 @@ struct CustomMetrics11;
|
||||
#[derive(Metrics)]
|
||||
#[metrics(random = "value")]
|
||||
struct CustomMetrics12;
|
||||
|
||||
#[derive(Metrics)]
|
||||
#[metrics(scope = "scope", dynamic = true)]
|
||||
struct CustomMetrics13;
|
||||
|
||||
@ -10,7 +10,7 @@ error: Duplicate `#[metrics(..)]` attribute provided.
|
||||
11 | #[metrics()]
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: `scope = ..` must be set.
|
||||
error: Either `scope = ..` or `dynamic = true` must be set.
|
||||
--> tests/compile-fail/metrics_attr.rs:15:1
|
||||
|
|
||||
15 | / #[metrics()]
|
||||
@ -70,3 +70,10 @@ error: Unsupported attribute entry.
|
||||
|
|
||||
51 | #[metrics(random = "value")]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error: `scope = ..` conflicts with `dynamic = true`.
|
||||
--> tests/compile-fail/metrics_attr.rs:55:1
|
||||
|
|
||||
55 | / #[metrics(scope = "scope", dynamic = true)]
|
||||
56 | | struct CustomMetrics13;
|
||||
| |_______________________^
|
||||
|
||||
Reference in New Issue
Block a user