1use proc_macro::TokenStream;
9use quote::{format_ident, quote};
10use syn::spanned::Spanned;
11use syn::{
12 Data, DeriveInput, Expr, Fields, GenericArgument, Lit, Meta, PathArguments, Type,
13 parse_macro_input,
14};
15
16enum MetricKind {
17 Counter,
18 Distribution,
19 DynamicCounter,
20 DynamicDistribution,
21 DynamicGauge,
22 DynamicGaugeI64,
23 DynamicHistogram,
24 Gauge,
25 GaugeF64,
26 Histogram,
27 SampledTimer,
28 MaxGauge,
29 MaxGaugeF64,
30 MinGauge,
31 MinGaugeF64,
32 LabeledCounter(Type),
33 LabeledGauge,
34 LabeledHistogram(Type),
35 LabeledSampledTimer(Type),
36}
37
38const PROM_BASE_FIELD_OVERHEAD_BYTES: usize = 48;
40const PROM_COMPLEX_METRIC_OVERHEAD_BYTES: usize = 128;
41const DOGSTATSD_SIMPLE_LINE_OVERHEAD_BYTES: usize = 24;
42const DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES: usize = 30;
43const DOGSTATSD_HISTOGRAM_LINES: usize = 2;
44const DOGSTATSD_SAMPLED_TIMER_LINES: usize = 3;
45const DOGSTATSD_TAG_PREFIX_BYTES: usize = 2; const DOGSTATSD_TAG_PAIR_OVERHEAD_BYTES: usize = 2; const DYNAMIC_LABELS_PER_SERIES_ESTIMATE: usize = 10;
48const DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES: usize = 16;
49const PROM_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES: usize = 64;
50const PROM_DYNAMIC_COMPLEX_SERIES_OVERHEAD_BYTES: usize = 160;
51const DOGSTATSD_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES: usize = 64;
52const DOGSTATSD_DYNAMIC_COMPLEX_SERIES_OVERHEAD_BYTES: usize = 160;
53
54fn metric_kind(ty: &Type) -> Option<MetricKind> {
55 let Type::Path(type_path) = ty else {
56 return None;
57 };
58 let segment = type_path.path.segments.last()?;
59 match segment.ident.to_string().as_str() {
60 "Counter" => Some(MetricKind::Counter),
61 "Distribution" => Some(MetricKind::Distribution),
62 "DynamicCounter" => Some(MetricKind::DynamicCounter),
63 "DynamicDistribution" => Some(MetricKind::DynamicDistribution),
64 "DynamicGauge" => Some(MetricKind::DynamicGauge),
65 "DynamicGaugeI64" => Some(MetricKind::DynamicGaugeI64),
66 "DynamicHistogram" => Some(MetricKind::DynamicHistogram),
67 "Gauge" => Some(MetricKind::Gauge),
68 "GaugeF64" => Some(MetricKind::GaugeF64),
69 "Histogram" => Some(MetricKind::Histogram),
70 "SampledTimer" => Some(MetricKind::SampledTimer),
71 "MaxGauge" => Some(MetricKind::MaxGauge),
72 "MaxGaugeF64" => Some(MetricKind::MaxGaugeF64),
73 "MinGauge" => Some(MetricKind::MinGauge),
74 "MinGaugeF64" => Some(MetricKind::MinGaugeF64),
75 "LabeledCounter" => {
76 let PathArguments::AngleBracketed(args) = &segment.arguments else {
77 return None;
78 };
79 let arg = args.args.first()?;
80 let GenericArgument::Type(label_ty) = arg else {
81 return None;
82 };
83 Some(MetricKind::LabeledCounter(label_ty.clone()))
84 }
85 "LabeledGauge" => {
86 let PathArguments::AngleBracketed(args) = &segment.arguments else {
87 return None;
88 };
89 let arg = args.args.first()?;
90 let GenericArgument::Type(_label_ty) = arg else {
91 return None;
92 };
93 Some(MetricKind::LabeledGauge)
94 }
95 "LabeledHistogram" => {
96 let PathArguments::AngleBracketed(args) = &segment.arguments else {
97 return None;
98 };
99 let arg = args.args.first()?;
100 let GenericArgument::Type(label_ty) = arg else {
101 return None;
102 };
103 Some(MetricKind::LabeledHistogram(label_ty.clone()))
104 }
105 "LabeledSampledTimer" => {
106 let PathArguments::AngleBracketed(args) = &segment.arguments else {
107 return None;
108 };
109 let arg = args.args.first()?;
110 let GenericArgument::Type(label_ty) = arg else {
111 return None;
112 };
113 Some(MetricKind::LabeledSampledTimer(label_ty.clone()))
114 }
115 _ => None,
116 }
117}
118
119fn visitor_meta(
120 name: &str,
121 help: &str,
122 kind: proc_macro2::TokenStream,
123) -> proc_macro2::TokenStream {
124 quote! {
125 fast_telemetry::MetricMeta {
126 name: #name,
127 help: #help,
128 kind: #kind,
129 unit: None,
130 }
131 }
132}
133
134fn visitor_label(label: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
135 quote! {
136 fast_telemetry::MetricLabel {
137 name: fast_telemetry::LabelEnum::label_name(&#label),
138 value: fast_telemetry::LabelEnum::variant_name(#label),
139 }
140 }
141}
142
143#[proc_macro_derive(ExportMetrics, attributes(metric_prefix, help, otlp, clickhouse))]
195pub fn derive_export_metrics(input: TokenStream) -> TokenStream {
196 let input = parse_macro_input!(input as DeriveInput);
197 match derive_export_metrics_impl(input) {
198 Ok(ts) => ts,
199 Err(err) => err.to_compile_error().into(),
200 }
201}
202
203fn derive_export_metrics_impl(input: DeriveInput) -> syn::Result<TokenStream> {
204 let name = &input.ident;
205 let vis = &input.vis;
206 let state_name = format_ident!("{}DogStatsDState", name);
207
208 let prefix = extract_metric_prefix(&input.attrs).unwrap_or_default();
210
211 let enable_otlp = input.attrs.iter().any(|attr| attr.path().is_ident("otlp"));
213 let enable_clickhouse = input
214 .attrs
215 .iter()
216 .any(|attr| attr.path().is_ident("clickhouse"));
217
218 let fields = match &input.data {
220 Data::Struct(data) => match &data.fields {
221 Fields::Named(fields) => &fields.named,
222 _ => {
223 return Err(syn::Error::new_spanned(
224 &data.fields,
225 "ExportMetrics only supports structs with named fields",
226 ));
227 }
228 },
229 _ => {
230 return Err(syn::Error::new_spanned(
231 &input,
232 "ExportMetrics only supports structs",
233 ));
234 }
235 };
236
237 let mut prometheus_exports = Vec::new();
238 let mut dogstatsd_exports = Vec::new();
239 let mut delta_exports = Vec::new();
240 let mut visitor_exports = Vec::new();
241 let mut otlp_exports = Vec::new();
242 let mut clickhouse_exports = Vec::new();
243 let mut state_fields = Vec::new();
244 let mut state_inits = Vec::new();
245 let mut state_label_count_exprs = Vec::new();
246 let mut prom_reserve_hint = 0usize;
247 let mut dogstatsd_reserve_hint = 0usize;
248 let mut dogstatsd_delta_reserve_hint = 0usize;
249 let mut dogstatsd_tag_line_hint = 0usize;
250 let mut dogstatsd_delta_tag_line_hint = 0usize;
251 let mut prom_dynamic_reserve_exprs = Vec::new();
252 let mut dogstatsd_dynamic_reserve_exprs = Vec::new();
253 let mut dogstatsd_delta_dynamic_reserve_exprs = Vec::new();
254 let mut dogstatsd_dynamic_tag_line_exprs = Vec::new();
255 let mut dogstatsd_delta_dynamic_tag_line_exprs = Vec::new();
256
257 for field in fields.iter() {
258 let field_name = field.ident.as_ref().ok_or_else(|| {
259 syn::Error::new(field.span(), "ExportMetrics only supports named fields")
260 })?;
261 let field_name_str = field_name.to_string();
262 let prom_metric_name = if prefix.is_empty() {
263 field_name_str.clone()
264 } else {
265 format!("{}_{}", prefix, field_name_str)
266 };
267 let statsd_metric_name = if prefix.is_empty() {
268 field_name_str.clone()
269 } else {
270 format!("{}.{}", prefix, field_name_str)
271 };
272 let help = extract_help(&field.attrs).unwrap_or_else(|| field_name_str.clone());
273
274 prometheus_exports.push(quote! {
275 fast_telemetry::PrometheusExport::export_prometheus(&self.#field_name, output, #prom_metric_name, #help);
276 });
277
278 dogstatsd_exports.push(quote! {
279 fast_telemetry::DogStatsDExport::export_dogstatsd(&self.#field_name, output, #statsd_metric_name, tags);
280 });
281
282 otlp_exports.push(quote! {
283 fast_telemetry::OtlpExport::export_otlp(&self.#field_name, metrics, #prom_metric_name, #help, time_unix_nano);
284 });
285 clickhouse_exports.push(quote! {
286 fast_telemetry::ClickHouseExport::export_clickhouse(&self.#field_name, batch, #prom_metric_name, #help, time_unix_nano);
287 });
288
289 let metric_kind = metric_kind(&field.ty).ok_or_else(|| {
290 syn::Error::new_spanned(
291 &field.ty,
292 format!(
293 "ExportMetrics does not support field '{}' with this type",
294 field_name_str
295 ),
296 )
297 })?;
298
299 prom_reserve_hint += prom_metric_name.len() + help.len() + PROM_BASE_FIELD_OVERHEAD_BYTES;
300 match &metric_kind {
301 MetricKind::Counter
302 | MetricKind::Gauge
303 | MetricKind::GaugeF64
304 | MetricKind::MaxGauge
305 | MetricKind::MaxGaugeF64
306 | MetricKind::MinGauge
307 | MetricKind::MinGaugeF64
308 | MetricKind::Distribution
309 | MetricKind::DynamicCounter
310 | MetricKind::DynamicGauge
311 | MetricKind::DynamicGaugeI64
312 | MetricKind::LabeledCounter(_)
313 | MetricKind::LabeledGauge => {
314 prom_reserve_hint += PROM_BASE_FIELD_OVERHEAD_BYTES;
315 }
316 MetricKind::Histogram
317 | MetricKind::SampledTimer
318 | MetricKind::DynamicHistogram
319 | MetricKind::DynamicDistribution
320 | MetricKind::LabeledHistogram(_)
321 | MetricKind::LabeledSampledTimer(_) => {
322 prom_reserve_hint += PROM_COMPLEX_METRIC_OVERHEAD_BYTES;
323 }
324 }
325
326 match &metric_kind {
327 MetricKind::Counter
328 | MetricKind::Gauge
329 | MetricKind::GaugeF64
330 | MetricKind::MaxGauge
331 | MetricKind::MaxGaugeF64
332 | MetricKind::MinGauge
333 | MetricKind::MinGaugeF64 => {
334 dogstatsd_reserve_hint +=
335 statsd_metric_name.len() + DOGSTATSD_SIMPLE_LINE_OVERHEAD_BYTES;
336 dogstatsd_delta_reserve_hint +=
337 statsd_metric_name.len() + DOGSTATSD_SIMPLE_LINE_OVERHEAD_BYTES;
338 dogstatsd_tag_line_hint += 1;
339 dogstatsd_delta_tag_line_hint += 1;
340 }
341 MetricKind::Histogram => {
342 dogstatsd_reserve_hint += (statsd_metric_name.len()
343 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
344 * DOGSTATSD_HISTOGRAM_LINES;
345 dogstatsd_delta_reserve_hint += (statsd_metric_name.len()
346 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
347 * DOGSTATSD_HISTOGRAM_LINES;
348 dogstatsd_tag_line_hint += DOGSTATSD_HISTOGRAM_LINES;
349 dogstatsd_delta_tag_line_hint += DOGSTATSD_HISTOGRAM_LINES;
350 }
351 MetricKind::SampledTimer => {
352 dogstatsd_reserve_hint += (statsd_metric_name.len()
353 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
354 * DOGSTATSD_SAMPLED_TIMER_LINES;
355 dogstatsd_delta_reserve_hint += (statsd_metric_name.len()
356 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
357 * DOGSTATSD_SAMPLED_TIMER_LINES;
358 dogstatsd_tag_line_hint += DOGSTATSD_SAMPLED_TIMER_LINES;
359 dogstatsd_delta_tag_line_hint += DOGSTATSD_SAMPLED_TIMER_LINES;
360 }
361 MetricKind::Distribution => {
362 dogstatsd_reserve_hint +=
363 statsd_metric_name.len() + DOGSTATSD_SIMPLE_LINE_OVERHEAD_BYTES;
364 dogstatsd_delta_reserve_hint +=
365 statsd_metric_name.len() + DOGSTATSD_SIMPLE_LINE_OVERHEAD_BYTES;
366 dogstatsd_tag_line_hint += 1;
367 dogstatsd_delta_tag_line_hint += 1;
368 }
369 MetricKind::DynamicCounter
370 | MetricKind::DynamicGauge
371 | MetricKind::DynamicGaugeI64
372 | MetricKind::DynamicDistribution => {
373 dogstatsd_reserve_hint +=
374 statsd_metric_name.len() + DOGSTATSD_SIMPLE_LINE_OVERHEAD_BYTES;
375 dogstatsd_delta_reserve_hint +=
376 statsd_metric_name.len() + DOGSTATSD_SIMPLE_LINE_OVERHEAD_BYTES;
377 }
378 MetricKind::DynamicHistogram => {
379 dogstatsd_reserve_hint += (statsd_metric_name.len()
380 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
381 * DOGSTATSD_HISTOGRAM_LINES;
382 dogstatsd_delta_reserve_hint += (statsd_metric_name.len()
383 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
384 * DOGSTATSD_HISTOGRAM_LINES;
385 }
386 MetricKind::LabeledCounter(_) | MetricKind::LabeledGauge => {
387 dogstatsd_reserve_hint +=
388 statsd_metric_name.len() + DOGSTATSD_SIMPLE_LINE_OVERHEAD_BYTES;
389 dogstatsd_delta_reserve_hint +=
390 statsd_metric_name.len() + DOGSTATSD_SIMPLE_LINE_OVERHEAD_BYTES;
391 }
392 MetricKind::LabeledHistogram(_) => {
393 dogstatsd_reserve_hint += (statsd_metric_name.len()
394 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
395 * DOGSTATSD_HISTOGRAM_LINES;
396 dogstatsd_delta_reserve_hint += (statsd_metric_name.len()
397 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
398 * DOGSTATSD_HISTOGRAM_LINES;
399 }
400 MetricKind::LabeledSampledTimer(_) => {
401 dogstatsd_reserve_hint += (statsd_metric_name.len()
402 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
403 * DOGSTATSD_SAMPLED_TIMER_LINES;
404 dogstatsd_delta_reserve_hint += (statsd_metric_name.len()
405 + DOGSTATSD_HISTOGRAM_LINE_OVERHEAD_BYTES)
406 * DOGSTATSD_SAMPLED_TIMER_LINES;
407 }
408 }
409
410 match metric_kind {
411 MetricKind::Counter => {
412 let meta = visitor_meta(
413 &prom_metric_name,
414 &help,
415 quote! { fast_telemetry::MetricKind::Counter },
416 );
417 visitor_exports.push(quote! {
418 visitor.counter(#meta, fast_telemetry::MetricLabels::none(), self.#field_name.sum() as i64);
419 });
420 state_label_count_exprs.push(quote! { 0usize });
421 state_fields.push(quote! { #field_name: isize, });
422 state_inits.push(quote! { #field_name: 0, });
423 delta_exports.push(quote! {
424 let current = self.#field_name.sum();
425 let delta = current - state.#field_name;
426 state.#field_name = current;
427 fast_telemetry::__macro_support::__write_dogstatsd(output, #statsd_metric_name, delta, "c", tags);
429 });
430 }
431 MetricKind::Distribution => {
432 let meta = visitor_meta(
433 &prom_metric_name,
434 &help,
435 quote! { fast_telemetry::MetricKind::Distribution },
436 );
437 visitor_exports.push(quote! {
438 let __ft_snapshot = self.#field_name.buckets_snapshot();
439 visitor.distribution(#meta, fast_telemetry::MetricLabels::none(), &__ft_snapshot);
440 });
441 let buckets_state_field = format_ident!("{}_buckets", field_name);
442 state_label_count_exprs.push(quote! { 0usize });
443 state_fields.push(quote! { #buckets_state_field: [u64; 65], });
444 state_inits.push(quote! { #buckets_state_field: [0u64; 65], });
445 delta_exports.push(quote! {
446 let snap = self.#field_name.buckets_snapshot();
447 fast_telemetry::__macro_support::__write_dogstatsd_distribution_delta(
448 output, #statsd_metric_name, &snap, &mut state.#buckets_state_field, tags
449 );
450 });
451 }
452 MetricKind::DynamicCounter => {
453 let meta = visitor_meta(
454 &prom_metric_name,
455 &help,
456 quote! { fast_telemetry::MetricKind::Counter },
457 );
458 visitor_exports.push(quote! {
459 let __ft_meta = #meta;
460 let __ft_overflow = self.#field_name.overflow_count();
461 if __ft_overflow > 0 {
462 visitor.dynamic_overflow(__ft_meta, __ft_overflow);
463 }
464 self.#field_name.visit_series(|labels, current| {
465 visitor.counter(
466 __ft_meta,
467 fast_telemetry::MetricLabels::dynamic_pairs(labels),
468 current as i64,
469 );
470 });
471 });
472 prom_dynamic_reserve_exprs.push(quote! {
473 self.#field_name.cardinality().saturating_mul(
474 #prom_metric_name.len()
475 + #PROM_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES
476 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
477 )
478 });
479 dogstatsd_dynamic_reserve_exprs.push(quote! {
480 self.#field_name.cardinality().saturating_mul(
481 #statsd_metric_name.len()
482 + #DOGSTATSD_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES
483 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
484 )
485 });
486 dogstatsd_delta_dynamic_reserve_exprs.push(quote! {
487 self.#field_name.cardinality().saturating_mul(
488 #statsd_metric_name.len()
489 + #DOGSTATSD_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES
490 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
491 )
492 });
493 dogstatsd_dynamic_tag_line_exprs.push(quote! { self.#field_name.cardinality() });
494 dogstatsd_delta_dynamic_tag_line_exprs
495 .push(quote! { self.#field_name.cardinality() });
496 state_label_count_exprs.push(quote! { self.#field_name.len() });
497 state_fields.push(quote! { #field_name: std::collections::HashMap<fast_telemetry::DynamicLabelSet, isize>, });
498 state_inits.push(quote! { #field_name: std::collections::HashMap::new(), });
499 delta_exports.push(quote! {
500 let overflow = self.#field_name.overflow_count();
501 if overflow > 0 {
502 log::warn!(
503 "fast-telemetry: {} hit cardinality cap, {} records routed to overflow",
504 #statsd_metric_name,
505 overflow
506 );
507 }
508 let mut current_keys = std::collections::HashSet::new();
509 self.#field_name.visit_series(|labels, current| {
510 let key = fast_telemetry::DynamicLabelSet::from_canonical_pairs(labels);
511 current_keys.insert(key.clone());
512 let previous = state.#field_name.get(&key).copied().unwrap_or(0);
513 let delta = current - previous;
514 state.#field_name.insert(key, current);
515 fast_telemetry::__macro_support::__write_dogstatsd_dynamic_pairs(
516 output,
517 #statsd_metric_name,
518 delta,
519 "c",
520 labels,
521 tags,
522 );
523 });
524 state.#field_name.retain(|k, _| current_keys.contains(k));
526 });
527 }
528 MetricKind::DynamicDistribution => {
529 let meta = visitor_meta(
530 &prom_metric_name,
531 &help,
532 quote! { fast_telemetry::MetricKind::Distribution },
533 );
534 let buckets_state_field = format_ident!("{}_buckets", field_name);
535 visitor_exports.push(quote! {
536 let __ft_meta = #meta;
537 let __ft_overflow = self.#field_name.overflow_count();
538 if __ft_overflow > 0 {
539 visitor.dynamic_overflow(__ft_meta, __ft_overflow);
540 }
541 self.#field_name.visit_series(|labels, _count, _sum, snapshot| {
542 visitor.distribution(
543 __ft_meta,
544 fast_telemetry::MetricLabels::dynamic_pairs(labels),
545 &snapshot,
546 );
547 });
548 });
549 prom_dynamic_reserve_exprs.push(quote! {
550 self.#field_name.cardinality().saturating_mul(
551 #prom_metric_name.len()
552 + #PROM_DYNAMIC_COMPLEX_SERIES_OVERHEAD_BYTES
553 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
554 )
555 });
556 dogstatsd_dynamic_reserve_exprs.push(quote! {
557 self.#field_name.cardinality().saturating_mul(
558 #statsd_metric_name.len()
559 + #DOGSTATSD_DYNAMIC_COMPLEX_SERIES_OVERHEAD_BYTES
560 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
561 )
562 });
563 dogstatsd_delta_dynamic_reserve_exprs.push(quote! {
564 self.#field_name.cardinality().saturating_mul(
565 #statsd_metric_name.len()
566 + #DOGSTATSD_DYNAMIC_COMPLEX_SERIES_OVERHEAD_BYTES
567 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
568 )
569 });
570 dogstatsd_dynamic_tag_line_exprs.push(quote! { self.#field_name.cardinality() });
571 dogstatsd_delta_dynamic_tag_line_exprs
572 .push(quote! { self.#field_name.cardinality() });
573 state_label_count_exprs.push(quote! {
574 self.#buckets_state_field.len()
575 });
576 state_fields.push(quote! { #buckets_state_field: std::collections::HashMap<fast_telemetry::DynamicLabelSet, [u64; 65]>, });
577 state_inits
578 .push(quote! { #buckets_state_field: std::collections::HashMap::new(), });
579 delta_exports.push(quote! {
580 let overflow = self.#field_name.overflow_count();
581 if overflow > 0 {
582 log::warn!(
583 "fast-telemetry: {} hit cardinality cap, {} records routed to overflow",
584 #statsd_metric_name,
585 overflow
586 );
587 }
588 let mut current_keys = std::collections::HashSet::new();
589 self.#field_name.visit_series(|labels, _count, _sum, snap| {
590 let key = fast_telemetry::DynamicLabelSet::from_canonical_pairs(labels);
591 current_keys.insert(key.clone());
592 let prev = state.#buckets_state_field.entry(key).or_insert([0u64; 65]);
593 fast_telemetry::__macro_support::__write_dogstatsd_distribution_delta_dynamic_pairs(
594 output, #statsd_metric_name, &snap, prev, labels, tags
595 );
596 });
597 state.#buckets_state_field.retain(|k, _| current_keys.contains(k));
599 });
600 }
601 MetricKind::DynamicGauge => {
602 let meta = visitor_meta(
603 &prom_metric_name,
604 &help,
605 quote! { fast_telemetry::MetricKind::Gauge },
606 );
607 visitor_exports.push(quote! {
608 let __ft_meta = #meta;
609 let __ft_overflow = self.#field_name.overflow_count();
610 if __ft_overflow > 0 {
611 visitor.dynamic_overflow(__ft_meta, __ft_overflow);
612 }
613 self.#field_name.visit_series(|labels, current| {
614 visitor.gauge_f64(
615 __ft_meta,
616 fast_telemetry::MetricLabels::dynamic_pairs(labels),
617 current,
618 );
619 });
620 });
621 prom_dynamic_reserve_exprs.push(quote! {
622 self.#field_name.cardinality().saturating_mul(
623 #prom_metric_name.len()
624 + #PROM_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES
625 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
626 )
627 });
628 dogstatsd_dynamic_reserve_exprs.push(quote! {
629 self.#field_name.cardinality().saturating_mul(
630 #statsd_metric_name.len()
631 + #DOGSTATSD_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES
632 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
633 )
634 });
635 dogstatsd_delta_dynamic_reserve_exprs.push(quote! {
636 self.#field_name.cardinality().saturating_mul(
637 #statsd_metric_name.len()
638 + #DOGSTATSD_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES
639 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
640 )
641 });
642 dogstatsd_dynamic_tag_line_exprs.push(quote! { self.#field_name.cardinality() });
643 dogstatsd_delta_dynamic_tag_line_exprs
644 .push(quote! { self.#field_name.cardinality() });
645 state_label_count_exprs.push(quote! { 0usize });
646 delta_exports.push(quote! {
648 let overflow = self.#field_name.overflow_count();
649 if overflow > 0 {
650 log::warn!(
651 "fast-telemetry: {} hit cardinality cap, {} records routed to overflow",
652 #statsd_metric_name,
653 overflow
654 );
655 }
656 fast_telemetry::DogStatsDExport::export_dogstatsd(&self.#field_name, output, #statsd_metric_name, tags);
657 });
658 }
659 MetricKind::DynamicGaugeI64 => {
660 let meta = visitor_meta(
661 &prom_metric_name,
662 &help,
663 quote! { fast_telemetry::MetricKind::Gauge },
664 );
665 visitor_exports.push(quote! {
666 let __ft_meta = #meta;
667 let __ft_overflow = self.#field_name.overflow_count();
668 if __ft_overflow > 0 {
669 visitor.dynamic_overflow(__ft_meta, __ft_overflow);
670 }
671 self.#field_name.visit_series(|labels, current| {
672 visitor.gauge_i64(
673 __ft_meta,
674 fast_telemetry::MetricLabels::dynamic_pairs(labels),
675 current,
676 );
677 });
678 });
679 prom_dynamic_reserve_exprs.push(quote! {
680 self.#field_name.cardinality().saturating_mul(
681 #prom_metric_name.len()
682 + #PROM_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES
683 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
684 )
685 });
686 dogstatsd_dynamic_reserve_exprs.push(quote! {
687 self.#field_name.cardinality().saturating_mul(
688 #statsd_metric_name.len()
689 + #DOGSTATSD_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES
690 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
691 )
692 });
693 dogstatsd_delta_dynamic_reserve_exprs.push(quote! {
694 self.#field_name.cardinality().saturating_mul(
695 #statsd_metric_name.len()
696 + #DOGSTATSD_DYNAMIC_SIMPLE_SERIES_OVERHEAD_BYTES
697 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
698 )
699 });
700 dogstatsd_dynamic_tag_line_exprs.push(quote! { self.#field_name.cardinality() });
701 dogstatsd_delta_dynamic_tag_line_exprs
702 .push(quote! { self.#field_name.cardinality() });
703 state_label_count_exprs.push(quote! { 0usize });
704 delta_exports.push(quote! {
706 let overflow = self.#field_name.overflow_count();
707 if overflow > 0 {
708 log::warn!(
709 "fast-telemetry: {} hit cardinality cap, {} records routed to overflow",
710 #statsd_metric_name,
711 overflow
712 );
713 }
714 fast_telemetry::DogStatsDExport::export_dogstatsd(&self.#field_name, output, #statsd_metric_name, tags);
715 });
716 }
717 MetricKind::DynamicHistogram => {
718 let meta = visitor_meta(
719 &prom_metric_name,
720 &help,
721 quote! { fast_telemetry::MetricKind::Histogram },
722 );
723 let count_state_field = format_ident!("{}_count", field_name);
724 let sum_state_field = format_ident!("{}_sum", field_name);
725 let count_metric_name = format!("{}.count", statsd_metric_name);
726 let sum_metric_name = format!("{}.sum", statsd_metric_name);
727 visitor_exports.push(quote! {
728 let __ft_meta = #meta;
729 let __ft_overflow = self.#field_name.overflow_count();
730 if __ft_overflow > 0 {
731 visitor.dynamic_overflow(__ft_meta, __ft_overflow);
732 }
733 self.#field_name.visit_series(|labels, series| {
734 visitor.histogram(
735 __ft_meta,
736 fast_telemetry::MetricLabels::dynamic_pairs(labels),
737 &series,
738 );
739 });
740 });
741 prom_dynamic_reserve_exprs.push(quote! {
742 self.#field_name.cardinality().saturating_mul(
743 #prom_metric_name.len()
744 + #PROM_DYNAMIC_COMPLEX_SERIES_OVERHEAD_BYTES
745 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
746 )
747 });
748 dogstatsd_dynamic_reserve_exprs.push(quote! {
749 self.#field_name.cardinality().saturating_mul(
750 #statsd_metric_name.len()
751 + #DOGSTATSD_DYNAMIC_COMPLEX_SERIES_OVERHEAD_BYTES
752 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
753 )
754 });
755 dogstatsd_delta_dynamic_reserve_exprs.push(quote! {
756 self.#field_name.cardinality().saturating_mul(
757 #statsd_metric_name.len()
758 + #DOGSTATSD_DYNAMIC_COMPLEX_SERIES_OVERHEAD_BYTES
759 + (#DYNAMIC_LABELS_PER_SERIES_ESTIMATE * #DYNAMIC_LABEL_PAIR_ESTIMATE_BYTES)
760 )
761 });
762 dogstatsd_dynamic_tag_line_exprs.push(quote! { self.#field_name.cardinality() });
763 dogstatsd_delta_dynamic_tag_line_exprs
764 .push(quote! { self.#field_name.cardinality() });
765 state_label_count_exprs.push(quote! {
766 core::cmp::max(self.#count_state_field.len(), self.#sum_state_field.len())
767 });
768 state_fields.push(quote! { #count_state_field: std::collections::HashMap<fast_telemetry::DynamicLabelSet, u64>, });
769 state_fields.push(quote! { #sum_state_field: std::collections::HashMap<fast_telemetry::DynamicLabelSet, u64>, });
770 state_inits.push(quote! { #count_state_field: std::collections::HashMap::new(), });
771 state_inits.push(quote! { #sum_state_field: std::collections::HashMap::new(), });
772 delta_exports.push(quote! {
773 let overflow = self.#field_name.overflow_count();
774 if overflow > 0 {
775 log::warn!(
776 "fast-telemetry: {} hit cardinality cap, {} records routed to overflow",
777 #statsd_metric_name,
778 overflow
779 );
780 }
781 let mut current_keys = std::collections::HashSet::new();
782 self.#field_name.visit_series(|labels, series| {
783 let key = fast_telemetry::DynamicLabelSet::from_canonical_pairs(labels);
784 current_keys.insert(key.clone());
785 let current_count = series.count();
786 let current_sum = series.sum();
787 let previous_count = state.#count_state_field.get(&key).copied().unwrap_or(0);
788 let previous_sum = state.#sum_state_field.get(&key).copied().unwrap_or(0);
789 let delta_count = if current_count >= previous_count {
790 current_count - previous_count
791 } else {
792 current_count
793 };
794 let delta_sum = if current_sum >= previous_sum {
795 current_sum - previous_sum
796 } else {
797 current_sum
798 };
799 state.#count_state_field.insert(key.clone(), current_count);
800 state.#sum_state_field.insert(key, current_sum);
801 fast_telemetry::__macro_support::__write_dogstatsd_dynamic_pairs(
802 output,
803 #count_metric_name,
804 delta_count,
805 "c",
806 labels,
807 tags,
808 );
809 fast_telemetry::__macro_support::__write_dogstatsd_dynamic_pairs(
810 output,
811 #sum_metric_name,
812 delta_sum,
813 "c",
814 labels,
815 tags,
816 );
817 });
818 state.#count_state_field.retain(|k, _| current_keys.contains(k));
820 state.#sum_state_field.retain(|k, _| current_keys.contains(k));
821 });
822 }
823 MetricKind::GaugeF64 | MetricKind::MaxGaugeF64 | MetricKind::MinGaugeF64 => {
824 let meta = visitor_meta(
825 &prom_metric_name,
826 &help,
827 quote! { fast_telemetry::MetricKind::Gauge },
828 );
829 visitor_exports.push(quote! {
830 visitor.gauge_f64(#meta, fast_telemetry::MetricLabels::none(), self.#field_name.get());
831 });
832 state_label_count_exprs.push(quote! { 0usize });
833 delta_exports.push(quote! {
835 fast_telemetry::DogStatsDExport::export_dogstatsd(&self.#field_name, output, #statsd_metric_name, tags);
836 });
837 }
838 MetricKind::Gauge | MetricKind::MaxGauge | MetricKind::MinGauge => {
839 let meta = visitor_meta(
840 &prom_metric_name,
841 &help,
842 quote! { fast_telemetry::MetricKind::Gauge },
843 );
844 visitor_exports.push(quote! {
845 visitor.gauge_i64(#meta, fast_telemetry::MetricLabels::none(), self.#field_name.get());
846 });
847 state_label_count_exprs.push(quote! { 0usize });
848 delta_exports.push(quote! {
850 fast_telemetry::DogStatsDExport::export_dogstatsd(&self.#field_name, output, #statsd_metric_name, tags);
851 });
852 }
853 MetricKind::Histogram => {
854 let meta = visitor_meta(
855 &prom_metric_name,
856 &help,
857 quote! { fast_telemetry::MetricKind::Histogram },
858 );
859 visitor_exports.push(quote! {
860 visitor.histogram(#meta, fast_telemetry::MetricLabels::none(), &self.#field_name);
861 });
862 let count_state_field = format_ident!("{}_count", field_name);
863 let sum_state_field = format_ident!("{}_sum", field_name);
864 let count_metric_name = format!("{}.count", statsd_metric_name);
865 let sum_metric_name = format!("{}.sum", statsd_metric_name);
866 state_label_count_exprs.push(quote! { 0usize });
867 state_fields.push(quote! { #count_state_field: u64, });
868 state_fields.push(quote! { #sum_state_field: u64, });
869 state_inits.push(quote! { #count_state_field: 0, });
870 state_inits.push(quote! { #sum_state_field: 0, });
871 delta_exports.push(quote! {
872 let current_count = self.#field_name.count();
873 let current_sum = self.#field_name.sum();
874 let delta_count = if current_count >= state.#count_state_field {
875 current_count - state.#count_state_field
876 } else {
877 current_count
878 };
879 let delta_sum = if current_sum >= state.#sum_state_field {
880 current_sum - state.#sum_state_field
881 } else {
882 current_sum
883 };
884 state.#count_state_field = current_count;
885 state.#sum_state_field = current_sum;
886 fast_telemetry::__macro_support::__write_dogstatsd(output, #count_metric_name, delta_count, "c", tags);
887 fast_telemetry::__macro_support::__write_dogstatsd(output, #sum_metric_name, delta_sum, "c", tags);
888 });
889 }
890 MetricKind::SampledTimer => {
891 let calls_metric_name = format!("{}_calls", prom_metric_name);
892 let samples_metric_name = format!("{}_samples", prom_metric_name);
893 let calls_help = format!("{} total calls", help);
894 let samples_help = format!("{} sampled latency in nanoseconds", help);
895 let calls_meta = visitor_meta(
896 &calls_metric_name,
897 &calls_help,
898 quote! { fast_telemetry::MetricKind::Counter },
899 );
900 let samples_meta = visitor_meta(
901 &samples_metric_name,
902 &samples_help,
903 quote! { fast_telemetry::MetricKind::Histogram },
904 );
905 let calls_state_field = format_ident!("{}_calls", field_name);
906 let count_state_field = format_ident!("{}_sample_count", field_name);
907 let sum_state_field = format_ident!("{}_sample_sum", field_name);
908 let calls_metric_name = format!("{}.calls", statsd_metric_name);
909 let count_metric_name = format!("{}.samples.count", statsd_metric_name);
910 let sum_metric_name = format!("{}.samples.sum", statsd_metric_name);
911 visitor_exports.push(quote! {
912 visitor.counter(#calls_meta, fast_telemetry::MetricLabels::none(), self.#field_name.calls() as i64);
913 visitor.histogram(#samples_meta, fast_telemetry::MetricLabels::none(), self.#field_name.histogram());
914 });
915 state_label_count_exprs.push(quote! { 0usize });
916 state_fields.push(quote! { #calls_state_field: u64, });
917 state_fields.push(quote! { #count_state_field: u64, });
918 state_fields.push(quote! { #sum_state_field: u64, });
919 state_inits.push(quote! { #calls_state_field: 0, });
920 state_inits.push(quote! { #count_state_field: 0, });
921 state_inits.push(quote! { #sum_state_field: 0, });
922 delta_exports.push(quote! {
923 let current_calls = self.#field_name.calls();
924 let current_count = self.#field_name.sample_count();
925 let current_sum = self.#field_name.sample_sum_nanos();
926 let delta_calls = if current_calls >= state.#calls_state_field {
927 current_calls - state.#calls_state_field
928 } else {
929 current_calls
930 };
931 let delta_count = if current_count >= state.#count_state_field {
932 current_count - state.#count_state_field
933 } else {
934 current_count
935 };
936 let delta_sum = if current_sum >= state.#sum_state_field {
937 current_sum - state.#sum_state_field
938 } else {
939 current_sum
940 };
941 state.#calls_state_field = current_calls;
942 state.#count_state_field = current_count;
943 state.#sum_state_field = current_sum;
944 fast_telemetry::__macro_support::__write_dogstatsd(output, #calls_metric_name, delta_calls, "c", tags);
945 fast_telemetry::__macro_support::__write_dogstatsd(output, #count_metric_name, delta_count, "c", tags);
946 fast_telemetry::__macro_support::__write_dogstatsd(output, #sum_metric_name, delta_sum, "c", tags);
947 });
948 }
949 MetricKind::LabeledCounter(label_ty) => {
950 let meta = visitor_meta(
951 &prom_metric_name,
952 &help,
953 quote! { fast_telemetry::MetricKind::Counter },
954 );
955 let label = visitor_label(quote! { label });
956 visitor_exports.push(quote! {
957 let __ft_meta = #meta;
958 for (label, value) in self.#field_name.iter() {
959 visitor.counter(
960 __ft_meta,
961 fast_telemetry::MetricLabels::one(#label),
962 value as i64,
963 );
964 }
965 });
966 state_label_count_exprs.push(quote! { 0usize });
967 state_fields.push(quote! { #field_name: Vec<isize>, });
968 state_inits.push(quote! {
969 #field_name: vec![0; <#label_ty as fast_telemetry::LabelEnum>::CARDINALITY],
970 });
971 delta_exports.push(quote! {
972 for idx in 0..<#label_ty as fast_telemetry::LabelEnum>::CARDINALITY {
973 let label = <#label_ty as fast_telemetry::LabelEnum>::from_index(idx);
974 let current = self.#field_name.get(label);
975 let delta = current - state.#field_name[idx];
976 state.#field_name[idx] = current;
977 fast_telemetry::__macro_support::__write_dogstatsd_with_label(
978 output,
979 #statsd_metric_name,
980 delta,
981 "c",
982 <#label_ty as fast_telemetry::LabelEnum>::LABEL_NAME,
983 <#label_ty as fast_telemetry::LabelEnum>::variant_name(label),
984 tags,
985 );
986 }
987 });
988 }
989 MetricKind::LabeledGauge => {
990 let meta = visitor_meta(
991 &prom_metric_name,
992 &help,
993 quote! { fast_telemetry::MetricKind::Gauge },
994 );
995 let label = visitor_label(quote! { label });
996 visitor_exports.push(quote! {
997 let __ft_meta = #meta;
998 for (label, value) in self.#field_name.iter() {
999 visitor.gauge_i64(
1000 __ft_meta,
1001 fast_telemetry::MetricLabels::one(#label),
1002 value,
1003 );
1004 }
1005 });
1006 state_label_count_exprs.push(quote! { 0usize });
1007 delta_exports.push(quote! {
1008 fast_telemetry::DogStatsDExport::export_dogstatsd(&self.#field_name, output, #statsd_metric_name, tags);
1009 });
1010 }
1011 MetricKind::LabeledHistogram(label_ty) => {
1012 let meta = visitor_meta(
1013 &prom_metric_name,
1014 &help,
1015 quote! { fast_telemetry::MetricKind::Histogram },
1016 );
1017 let count_state_field = format_ident!("{}_count", field_name);
1018 let sum_state_field = format_ident!("{}_sum", field_name);
1019 let count_metric_name = format!("{}.count", statsd_metric_name);
1020 let sum_metric_name = format!("{}.sum", statsd_metric_name);
1021 let label = visitor_label(quote! { label });
1022 visitor_exports.push(quote! {
1023 let __ft_meta = #meta;
1024 for (label, histogram) in self.#field_name.iter() {
1025 visitor.histogram(
1026 __ft_meta,
1027 fast_telemetry::MetricLabels::one(#label),
1028 histogram,
1029 );
1030 }
1031 });
1032 state_label_count_exprs.push(quote! { 0usize });
1033 state_fields.push(quote! { #count_state_field: Vec<u64>, });
1034 state_fields.push(quote! { #sum_state_field: Vec<u64>, });
1035 state_inits.push(quote! {
1036 #count_state_field: vec![0; <#label_ty as fast_telemetry::LabelEnum>::CARDINALITY],
1037 });
1038 state_inits.push(quote! {
1039 #sum_state_field: vec![0; <#label_ty as fast_telemetry::LabelEnum>::CARDINALITY],
1040 });
1041 delta_exports.push(quote! {
1042 for idx in 0..<#label_ty as fast_telemetry::LabelEnum>::CARDINALITY {
1043 let label = <#label_ty as fast_telemetry::LabelEnum>::from_index(idx);
1044 let current_count = self.#field_name.get(label).count();
1045 let current_sum = self.#field_name.get(label).sum();
1046 let delta_count = if current_count >= state.#count_state_field[idx] {
1047 current_count - state.#count_state_field[idx]
1048 } else {
1049 current_count
1050 };
1051 let delta_sum = if current_sum >= state.#sum_state_field[idx] {
1052 current_sum - state.#sum_state_field[idx]
1053 } else {
1054 current_sum
1055 };
1056 state.#count_state_field[idx] = current_count;
1057 state.#sum_state_field[idx] = current_sum;
1058 fast_telemetry::__macro_support::__write_dogstatsd_with_label(
1059 output,
1060 #count_metric_name,
1061 delta_count,
1062 "c",
1063 <#label_ty as fast_telemetry::LabelEnum>::LABEL_NAME,
1064 <#label_ty as fast_telemetry::LabelEnum>::variant_name(label),
1065 tags,
1066 );
1067 fast_telemetry::__macro_support::__write_dogstatsd_with_label(
1068 output,
1069 #sum_metric_name,
1070 delta_sum,
1071 "c",
1072 <#label_ty as fast_telemetry::LabelEnum>::LABEL_NAME,
1073 <#label_ty as fast_telemetry::LabelEnum>::variant_name(label),
1074 tags,
1075 );
1076 }
1077 });
1078 }
1079 MetricKind::LabeledSampledTimer(label_ty) => {
1080 let calls_prom_metric_name = format!("{}_calls", prom_metric_name);
1081 let samples_prom_metric_name = format!("{}_samples", prom_metric_name);
1082 let calls_help = format!("{} total calls", help);
1083 let samples_help = format!("{} sampled latency in nanoseconds", help);
1084 let calls_meta = visitor_meta(
1085 &calls_prom_metric_name,
1086 &calls_help,
1087 quote! { fast_telemetry::MetricKind::Counter },
1088 );
1089 let samples_meta = visitor_meta(
1090 &samples_prom_metric_name,
1091 &samples_help,
1092 quote! { fast_telemetry::MetricKind::Histogram },
1093 );
1094 let calls_state_field = format_ident!("{}_calls", field_name);
1095 let count_state_field = format_ident!("{}_sample_count", field_name);
1096 let sum_state_field = format_ident!("{}_sample_sum", field_name);
1097 let calls_metric_name = format!("{}.calls", statsd_metric_name);
1098 let count_metric_name = format!("{}.samples.count", statsd_metric_name);
1099 let sum_metric_name = format!("{}.samples.sum", statsd_metric_name);
1100 let label = visitor_label(quote! { label });
1101 visitor_exports.push(quote! {
1102 let __ft_calls_meta = #calls_meta;
1103 let __ft_samples_meta = #samples_meta;
1104 for (label, calls, histogram) in self.#field_name.iter() {
1105 let __ft_labels = fast_telemetry::MetricLabels::one(#label);
1106 visitor.counter(__ft_calls_meta, __ft_labels, calls.sum() as i64);
1107 visitor.histogram(__ft_samples_meta, __ft_labels, histogram);
1108 }
1109 });
1110 state_label_count_exprs.push(quote! { 0usize });
1111 state_fields.push(quote! { #calls_state_field: Vec<u64>, });
1112 state_fields.push(quote! { #count_state_field: Vec<u64>, });
1113 state_fields.push(quote! { #sum_state_field: Vec<u64>, });
1114 state_inits.push(quote! {
1115 #calls_state_field: vec![0; <#label_ty as fast_telemetry::LabelEnum>::CARDINALITY],
1116 });
1117 state_inits.push(quote! {
1118 #count_state_field: vec![0; <#label_ty as fast_telemetry::LabelEnum>::CARDINALITY],
1119 });
1120 state_inits.push(quote! {
1121 #sum_state_field: vec![0; <#label_ty as fast_telemetry::LabelEnum>::CARDINALITY],
1122 });
1123 delta_exports.push(quote! {
1124 for idx in 0..<#label_ty as fast_telemetry::LabelEnum>::CARDINALITY {
1125 let label = <#label_ty as fast_telemetry::LabelEnum>::from_index(idx);
1126 let current_calls = self.#field_name.calls(label);
1127 let current_count = self.#field_name.sample_count(label);
1128 let current_sum = self.#field_name.sample_sum_nanos(label);
1129 let delta_calls = if current_calls >= state.#calls_state_field[idx] {
1130 current_calls - state.#calls_state_field[idx]
1131 } else {
1132 current_calls
1133 };
1134 let delta_count = if current_count >= state.#count_state_field[idx] {
1135 current_count - state.#count_state_field[idx]
1136 } else {
1137 current_count
1138 };
1139 let delta_sum = if current_sum >= state.#sum_state_field[idx] {
1140 current_sum - state.#sum_state_field[idx]
1141 } else {
1142 current_sum
1143 };
1144 state.#calls_state_field[idx] = current_calls;
1145 state.#count_state_field[idx] = current_count;
1146 state.#sum_state_field[idx] = current_sum;
1147 fast_telemetry::__macro_support::__write_dogstatsd_with_label(
1148 output,
1149 #calls_metric_name,
1150 delta_calls,
1151 "c",
1152 <#label_ty as fast_telemetry::LabelEnum>::LABEL_NAME,
1153 <#label_ty as fast_telemetry::LabelEnum>::variant_name(label),
1154 tags,
1155 );
1156 fast_telemetry::__macro_support::__write_dogstatsd_with_label(
1157 output,
1158 #count_metric_name,
1159 delta_count,
1160 "c",
1161 <#label_ty as fast_telemetry::LabelEnum>::LABEL_NAME,
1162 <#label_ty as fast_telemetry::LabelEnum>::variant_name(label),
1163 tags,
1164 );
1165 fast_telemetry::__macro_support::__write_dogstatsd_with_label(
1166 output,
1167 #sum_metric_name,
1168 delta_sum,
1169 "c",
1170 <#label_ty as fast_telemetry::LabelEnum>::LABEL_NAME,
1171 <#label_ty as fast_telemetry::LabelEnum>::variant_name(label),
1172 tags,
1173 );
1174 }
1175 });
1176 }
1177 }
1178 }
1179
1180 let otlp_method = if enable_otlp {
1181 quote! {
1182 pub fn export_otlp(&self, metrics: &mut Vec<fast_telemetry::otlp::pb::Metric>, time_unix_nano: u64) {
1189 #(#otlp_exports)*
1190 }
1191 }
1192 } else {
1193 quote! {}
1194 };
1195
1196 let clickhouse_method = if enable_clickhouse {
1197 quote! {
1198 pub fn export_clickhouse(
1203 &self,
1204 batch: &mut fast_telemetry::clickhouse::ClickHouseMetricBatch,
1205 time_unix_nano: u64,
1206 ) {
1207 #(#clickhouse_exports)*
1208 }
1209 }
1210 } else {
1211 quote! {}
1212 };
1213
1214 let expanded = quote! {
1215 #vis struct #state_name {
1217 #(#state_fields)*
1218 }
1219
1220 impl #state_name {
1221 pub fn new() -> Self {
1222 Self {
1223 #(#state_inits)*
1224 }
1225 }
1226
1227 pub fn tracked_label_sets(&self) -> usize {
1229 0usize #(+ #state_label_count_exprs)*
1230 }
1231 }
1232
1233 impl Default for #state_name {
1234 fn default() -> Self {
1235 Self::new()
1236 }
1237 }
1238
1239 impl #name {
1240 pub fn export_prometheus(&self, output: &mut String) {
1242 let __ft_prom_dynamic_reserve = 0usize #(+ #prom_dynamic_reserve_exprs)*;
1243 output.reserve(#prom_reserve_hint + __ft_prom_dynamic_reserve);
1244 #(#prometheus_exports)*
1245 }
1246
1247 pub fn export_dogstatsd(&self, output: &mut String, tags: &[(&str, &str)]) {
1252 let __ft_tag_bytes = if tags.is_empty() {
1253 0usize
1254 } else {
1255 #DOGSTATSD_TAG_PREFIX_BYTES
1256 + tags.iter().map(|(k, v)| k.len() + v.len() + #DOGSTATSD_TAG_PAIR_OVERHEAD_BYTES).sum::<usize>()
1257 };
1258 let __ft_dynamic_reserve = 0usize #(+ #dogstatsd_dynamic_reserve_exprs)*;
1259 let __ft_dynamic_tag_lines = 0usize #(+ #dogstatsd_dynamic_tag_line_exprs)*;
1260 output.reserve(
1261 #dogstatsd_reserve_hint
1262 + __ft_dynamic_reserve
1263 + __ft_tag_bytes.saturating_mul(#dogstatsd_tag_line_hint + __ft_dynamic_tag_lines)
1264 );
1265 #(#dogstatsd_exports)*
1266 }
1267
1268 pub fn export_dogstatsd_delta(
1272 &self,
1273 output: &mut String,
1274 tags: &[(&str, &str)],
1275 state: &mut #state_name,
1276 ) {
1277 let __ft_tag_bytes = if tags.is_empty() {
1278 0usize
1279 } else {
1280 #DOGSTATSD_TAG_PREFIX_BYTES
1281 + tags.iter().map(|(k, v)| k.len() + v.len() + #DOGSTATSD_TAG_PAIR_OVERHEAD_BYTES).sum::<usize>()
1282 };
1283 let __ft_dynamic_reserve = 0usize #(+ #dogstatsd_delta_dynamic_reserve_exprs)*;
1284 let __ft_dynamic_tag_lines = 0usize #(+ #dogstatsd_delta_dynamic_tag_line_exprs)*;
1285 output.reserve(
1286 #dogstatsd_delta_reserve_hint
1287 + __ft_dynamic_reserve
1288 + __ft_tag_bytes.saturating_mul(#dogstatsd_delta_tag_line_hint + __ft_dynamic_tag_lines)
1289 );
1290 #(#delta_exports)*
1291 }
1292
1293 pub fn export_dogstatsd_with_temporality(
1295 &self,
1296 output: &mut String,
1297 tags: &[(&str, &str)],
1298 temporality: fast_telemetry::Temporality,
1299 state: &mut #state_name,
1300 ) {
1301 match temporality {
1302 fast_telemetry::Temporality::Cumulative => self.export_dogstatsd(output, tags),
1303 fast_telemetry::Temporality::Delta => self.export_dogstatsd_delta(output, tags, state),
1304 }
1305 }
1306
1307 pub fn visit_metrics<V: fast_telemetry::MetricVisitor + ?Sized>(&self, visitor: &mut V) {
1309 #(#visitor_exports)*
1310 }
1311
1312 #otlp_method
1313 #clickhouse_method
1314 }
1315 };
1316
1317 Ok(TokenStream::from(expanded))
1318}
1319
1320#[proc_macro_derive(LabelEnum, attributes(label_name, label))]
1354pub fn derive_label_enum(input: TokenStream) -> TokenStream {
1355 let input = parse_macro_input!(input as DeriveInput);
1356 match derive_label_enum_impl(input) {
1357 Ok(ts) => ts,
1358 Err(err) => err.to_compile_error().into(),
1359 }
1360}
1361
1362fn derive_label_enum_impl(input: DeriveInput) -> syn::Result<TokenStream> {
1363 let name = &input.ident;
1364
1365 let label_name = extract_label_name(&input.attrs).ok_or_else(|| {
1367 syn::Error::new_spanned(
1368 name,
1369 "LabelEnum requires #[label_name = \"...\"] attribute on the enum",
1370 )
1371 })?;
1372
1373 let variants = match &input.data {
1375 Data::Enum(data) => &data.variants,
1376 _ => {
1377 return Err(syn::Error::new_spanned(
1378 &input,
1379 "LabelEnum can only be derived for enums",
1380 ));
1381 }
1382 };
1383 if variants.is_empty() {
1384 return Err(syn::Error::new_spanned(
1385 name,
1386 "LabelEnum requires at least one variant",
1387 ));
1388 }
1389
1390 let cardinality = variants.len();
1391
1392 let as_index_arms: Vec<_> = variants
1394 .iter()
1395 .enumerate()
1396 .map(|(idx, variant)| {
1397 let variant_ident = &variant.ident;
1398 quote! { Self::#variant_ident => #idx, }
1399 })
1400 .collect();
1401
1402 let from_index_arms: Vec<_> = variants
1404 .iter()
1405 .enumerate()
1406 .map(|(idx, variant)| {
1407 let variant_ident = &variant.ident;
1408 quote! { #idx => Self::#variant_ident, }
1409 })
1410 .collect();
1411
1412 let last_variant = &variants[variants.len() - 1].ident;
1414
1415 let variant_name_arms: Vec<_> = variants
1417 .iter()
1418 .map(|variant| {
1419 let variant_ident = &variant.ident;
1420 let label_value = extract_label_override(&variant.attrs)
1421 .unwrap_or_else(|| to_snake_case(&variant_ident.to_string()));
1422 quote! { Self::#variant_ident => #label_value, }
1423 })
1424 .collect();
1425
1426 let expanded = quote! {
1427 impl fast_telemetry::LabelEnum for #name {
1428 const CARDINALITY: usize = #cardinality;
1429 const LABEL_NAME: &'static str = #label_name;
1430
1431 fn as_index(self) -> usize {
1432 match self {
1433 #(#as_index_arms)*
1434 }
1435 }
1436
1437 fn from_index(index: usize) -> Self {
1438 match index {
1439 #(#from_index_arms)*
1440 _ => Self::#last_variant,
1441 }
1442 }
1443
1444 fn variant_name(self) -> &'static str {
1445 match self {
1446 #(#variant_name_arms)*
1447 }
1448 }
1449 }
1450 };
1451
1452 Ok(TokenStream::from(expanded))
1453}
1454
1455fn extract_label_name(attrs: &[syn::Attribute]) -> Option<String> {
1457 for attr in attrs {
1458 if attr.path().is_ident("label_name")
1459 && let Meta::NameValue(nv) = &attr.meta
1460 && let Expr::Lit(expr_lit) = &nv.value
1461 && let Lit::Str(lit) = &expr_lit.lit
1462 {
1463 return Some(lit.value());
1464 }
1465 }
1466 None
1467}
1468
1469fn extract_label_override(attrs: &[syn::Attribute]) -> Option<String> {
1471 for attr in attrs {
1472 if attr.path().is_ident("label")
1473 && let Meta::NameValue(nv) = &attr.meta
1474 && let Expr::Lit(expr_lit) = &nv.value
1475 && let Lit::Str(lit) = &expr_lit.lit
1476 {
1477 return Some(lit.value());
1478 }
1479 }
1480 None
1481}
1482
1483fn to_snake_case(s: &str) -> String {
1485 let mut result = String::new();
1486 for (i, c) in s.chars().enumerate() {
1487 if c.is_uppercase() {
1488 if i > 0 {
1489 result.push('_');
1490 }
1491 for lower in c.to_lowercase() {
1492 result.push(lower);
1493 }
1494 } else {
1495 result.push(c);
1496 }
1497 }
1498 result
1499}
1500
1501fn extract_metric_prefix(attrs: &[syn::Attribute]) -> Option<String> {
1502 for attr in attrs {
1503 if attr.path().is_ident("metric_prefix")
1504 && let Meta::NameValue(nv) = &attr.meta
1505 && let Expr::Lit(expr_lit) = &nv.value
1506 && let Lit::Str(lit) = &expr_lit.lit
1507 {
1508 return Some(lit.value());
1509 }
1510 }
1511 None
1512}
1513
1514fn extract_help(attrs: &[syn::Attribute]) -> Option<String> {
1515 for attr in attrs {
1516 if attr.path().is_ident("help")
1517 && let Meta::NameValue(nv) = &attr.meta
1518 && let Expr::Lit(expr_lit) = &nv.value
1519 && let Lit::Str(lit) = &expr_lit.lit
1520 {
1521 return Some(lit.value());
1522 }
1523 }
1524 None
1525}