1extern crate proc_macro;
2
3use self::proc_macro::TokenStream;
4
5use proc_macro2::TokenStream as TokenStream2;
6use quote::{format_ident, quote, ToTokens};
7use syn::parse::{Error, Parse, ParseStream, Result};
8use syn::{parse::discouraged::Speculative, Lit};
9use syn::{parse_macro_input, Expr, Token};
10
11#[cfg(test)]
12mod tests;
13
14enum Labels {
15 Existing(Expr),
16 Inline(Vec<(Expr, Expr)>),
17}
18
19struct WithoutExpression {
20 key: Expr,
21 labels: Option<Labels>,
22}
23
24struct WithExpression {
25 key: Expr,
26 op_value: Expr,
27 labels: Option<Labels>,
28}
29
30struct Description {
31 key: Expr,
32 unit: Option<Expr>,
33 description: Expr,
34}
35
36impl Parse for WithoutExpression {
37 fn parse(mut input: ParseStream) -> Result<Self> {
38 let key = input.parse::<Expr>()?;
39 let labels = parse_labels(&mut input)?;
40
41 Ok(WithoutExpression { key, labels })
42 }
43}
44
45impl Parse for WithExpression {
46 fn parse(mut input: ParseStream) -> Result<Self> {
47 let key = input.parse::<Expr>()?;
48
49 input.parse::<Token![,]>()?;
50 let op_value = input.parse::<Expr>()?;
51
52 let labels = parse_labels(&mut input)?;
53
54 Ok(WithExpression { key, op_value, labels })
55 }
56}
57
58impl Parse for Description {
59 fn parse(input: ParseStream) -> Result<Self> {
60 let key = input.parse::<Expr>()?;
61
62 let unit = input
71 .call(|s| {
72 let forked = s.fork();
73 forked.parse::<Token![,]>()?;
74
75 let output = if let Ok(Expr::Path(path)) = forked.parse::<Expr>() {
76 let qname = path
77 .path
78 .segments
79 .iter()
80 .map(|x| x.ident.to_string())
81 .collect::<Vec<_>>()
82 .join("::");
83 if qname.starts_with("::metrics::Unit")
84 || qname.starts_with("metrics::Unit")
85 || qname.starts_with("Unit")
86 {
87 Some(Expr::Path(path))
88 } else {
89 None
90 }
91 } else {
92 None
93 };
94
95 if output.is_some() {
96 s.advance_to(&forked);
97 }
98
99 Ok(output)
100 })
101 .ok()
102 .flatten();
103
104 input.parse::<Token![,]>()?;
105 let description = input.parse::<Expr>()?;
106
107 Ok(Description { key, unit, description })
108 }
109}
110
111#[proc_macro]
112pub fn describe_counter(input: TokenStream) -> TokenStream {
113 let Description { key, unit, description } = parse_macro_input!(input as Description);
114
115 get_describe_code("counter", key, unit, description).into()
116}
117
118#[proc_macro]
119pub fn describe_gauge(input: TokenStream) -> TokenStream {
120 let Description { key, unit, description } = parse_macro_input!(input as Description);
121
122 get_describe_code("gauge", key, unit, description).into()
123}
124
125#[proc_macro]
126pub fn describe_histogram(input: TokenStream) -> TokenStream {
127 let Description { key, unit, description } = parse_macro_input!(input as Description);
128
129 get_describe_code("histogram", key, unit, description).into()
130}
131
132#[proc_macro]
133pub fn register_counter(input: TokenStream) -> TokenStream {
134 let WithoutExpression { key, labels } = parse_macro_input!(input as WithoutExpression);
135
136 get_register_and_op_code::<bool>("counter", key, labels, None).into()
137}
138
139#[proc_macro]
140pub fn register_gauge(input: TokenStream) -> TokenStream {
141 let WithoutExpression { key, labels } = parse_macro_input!(input as WithoutExpression);
142
143 get_register_and_op_code::<bool>("gauge", key, labels, None).into()
144}
145
146#[proc_macro]
147pub fn register_histogram(input: TokenStream) -> TokenStream {
148 let WithoutExpression { key, labels } = parse_macro_input!(input as WithoutExpression);
149
150 get_register_and_op_code::<bool>("histogram", key, labels, None).into()
151}
152
153#[proc_macro]
154pub fn increment_counter(input: TokenStream) -> TokenStream {
155 let WithoutExpression { key, labels } = parse_macro_input!(input as WithoutExpression);
156
157 let op_value = quote! { 1 };
158
159 get_register_and_op_code("counter", key, labels, Some(("increment", op_value))).into()
160}
161
162#[proc_macro]
163pub fn counter(input: TokenStream) -> TokenStream {
164 let WithExpression { key, op_value, labels } = parse_macro_input!(input as WithExpression);
165
166 get_register_and_op_code("counter", key, labels, Some(("increment", op_value))).into()
167}
168
169#[proc_macro]
170pub fn absolute_counter(input: TokenStream) -> TokenStream {
171 let WithExpression { key, op_value, labels } = parse_macro_input!(input as WithExpression);
172
173 get_register_and_op_code("counter", key, labels, Some(("absolute", op_value))).into()
174}
175
176#[proc_macro]
177pub fn increment_gauge(input: TokenStream) -> TokenStream {
178 let WithExpression { key, op_value, labels } = parse_macro_input!(input as WithExpression);
179
180 get_register_and_op_code("gauge", key, labels, Some(("increment", op_value))).into()
181}
182
183#[proc_macro]
184pub fn decrement_gauge(input: TokenStream) -> TokenStream {
185 let WithExpression { key, op_value, labels } = parse_macro_input!(input as WithExpression);
186
187 get_register_and_op_code("gauge", key, labels, Some(("decrement", op_value))).into()
188}
189
190#[proc_macro]
191pub fn gauge(input: TokenStream) -> TokenStream {
192 let WithExpression { key, op_value, labels } = parse_macro_input!(input as WithExpression);
193
194 get_register_and_op_code("gauge", key, labels, Some(("set", op_value))).into()
195}
196
197#[proc_macro]
198pub fn histogram(input: TokenStream) -> TokenStream {
199 let WithExpression { key, op_value, labels } = parse_macro_input!(input as WithExpression);
200
201 get_register_and_op_code("histogram", key, labels, Some(("record", op_value))).into()
202}
203
204fn get_describe_code(
205 metric_type: &str,
206 name: Expr,
207 unit: Option<Expr>,
208 description: Expr,
209) -> TokenStream2 {
210 let describe_ident = format_ident!("describe_{}", metric_type);
211
212 let unit = match unit {
213 Some(e) => quote! { Some(#e) },
214 None => quote! { None },
215 };
216
217 quote! {
218 {
219 if let Some(recorder) = ::metrics::try_recorder() {
221 recorder.#describe_ident(#name.into(), #unit, #description.into());
222 }
223 }
224 }
225}
226
227fn get_register_and_op_code<V>(
228 metric_type: &str,
229 name: Expr,
230 labels: Option<Labels>,
231 op: Option<(&'static str, V)>,
232) -> TokenStream2
233where
234 V: ToTokens,
235{
236 let register_ident = format_ident!("register_{}", metric_type);
237 let statics = generate_statics(&name, &labels);
238 let (locals, metric_key) = generate_metric_key(&name, &labels);
239 match op {
240 Some((op_type, op_value)) => {
241 let op_ident = format_ident!("{}", op_type);
242 let op_value = if metric_type == "histogram" {
243 quote! { ::metrics::__into_f64(#op_value) }
244 } else {
245 quote! { #op_value }
246 };
247
248 quote! {
251 {
252 #statics
253 if let Some(recorder) = ::metrics::try_recorder() {
255 #locals
256 let handle = recorder.#register_ident(#metric_key);
257 handle.#op_ident(#op_value);
258 }
259 }
260 }
261 }
262 None => {
263 quote! {
265 {
266 #statics
267 #locals
268 ::metrics::recorder().#register_ident(#metric_key)
269 }
270 }
271 }
272 }
273}
274
275fn name_is_fast_path(name: &Expr) -> bool {
276 if let Expr::Lit(lit) = name {
277 return matches!(lit.lit, Lit::Str(_));
278 }
279
280 false
281}
282
283fn labels_are_fast_path(labels: &Labels) -> bool {
284 match labels {
285 Labels::Existing(_) => false,
286 Labels::Inline(pairs) => {
287 pairs.iter().all(|(k, v)| matches!((k, v), (Expr::Lit(_), Expr::Lit(_))))
288 }
289 }
290}
291
292fn generate_statics(name: &Expr, labels: &Option<Labels>) -> TokenStream2 {
293 let use_name_static = name_is_fast_path(name);
295 let name_static = if use_name_static {
296 quote! {
297 static METRIC_NAME: &'static str = #name;
298 }
299 } else {
300 quote! {}
301 };
302
303 let has_labels = labels.is_some();
305 let use_labels_static = match labels.as_ref() {
306 Some(labels) => labels_are_fast_path(labels),
307 None => true,
308 };
309
310 let labels_static = match labels.as_ref() {
311 Some(labels) => {
312 if labels_are_fast_path(labels) {
313 if let Labels::Inline(pairs) = labels {
314 let labels = pairs
315 .iter()
316 .map(
317 |(key, val)| quote! { ::metrics::Label::from_static_parts(#key, #val) },
318 )
319 .collect::<Vec<_>>();
320 let labels_len = labels.len();
321 let labels_len = quote! { #labels_len };
322
323 quote! {
324 static METRIC_LABELS: [::metrics::Label; #labels_len] = [#(#labels),*];
325 }
326 } else {
327 quote! {}
328 }
329 } else {
330 quote! {}
331 }
332 }
333 None => quote! {},
334 };
335
336 let key_static = if use_name_static && use_labels_static {
337 if has_labels {
338 quote! {
339 static METRIC_KEY: ::metrics::Key = ::metrics::Key::from_static_parts(METRIC_NAME, &METRIC_LABELS);
340 }
341 } else {
342 quote! {
343 static METRIC_KEY: ::metrics::Key = ::metrics::Key::from_static_name(METRIC_NAME);
344 }
345 }
346 } else {
347 quote! {}
348 };
349
350 quote! {
351 #name_static
352 #labels_static
353 #key_static
354 }
355}
356
357fn generate_metric_key(name: &Expr, labels: &Option<Labels>) -> (TokenStream2, TokenStream2) {
358 let use_name_static = name_is_fast_path(name);
359
360 let has_labels = labels.is_some();
361 let use_labels_static = match labels.as_ref() {
362 Some(labels) => labels_are_fast_path(labels),
363 None => true,
364 };
365
366 let mut key_name = quote! { &key };
367 let locals = if use_name_static && use_labels_static {
368 key_name = quote! { &METRIC_KEY };
371 quote! {}
372 } else if use_name_static && !use_labels_static {
373 let labels = labels.as_ref().unwrap();
376 let quoted_labels = labels_to_quoted(labels);
377 quote! {
378 let key = ::metrics::Key::from_parts(METRIC_NAME, #quoted_labels);
379 }
380 } else if !use_name_static && !use_labels_static {
381 let labels = labels.as_ref().unwrap();
384 let quoted_labels = labels_to_quoted(labels);
385 quote! {
386 let key = ::metrics::Key::from_parts(#name, #quoted_labels);
387 }
388 } else {
389 if has_labels {
393 quote! {
394 let key = ::metrics::Key::from_static_labels(#name, &METRIC_LABELS);
395 }
396 } else {
397 quote! {
398 let key = ::metrics::Key::from_name(#name);
399 }
400 }
401 };
402
403 (locals, key_name)
404}
405
406fn labels_to_quoted(labels: &Labels) -> proc_macro2::TokenStream {
407 match labels {
408 Labels::Inline(pairs) => {
409 let labels =
410 pairs.iter().map(|(key, val)| quote! { ::metrics::Label::new(#key, #val) });
411 quote! { vec![#(#labels),*] }
412 }
413 Labels::Existing(e) => quote! { #e },
414 }
415}
416
417fn parse_labels(input: &mut ParseStream) -> Result<Option<Labels>> {
418 if input.is_empty() {
419 return Ok(None);
420 }
421
422 if !input.peek(Token![,]) {
423 input
427 .parse::<Token![,]>()
428 .map_err(|e| Error::new(e.span(), "expected labels, but comma not found"))?;
429 }
430
431 if input.peek(Token![,]) && input.peek3(Token![=>]) {
438 let mut labels = Vec::new();
439 loop {
440 if input.is_empty() {
441 break;
442 }
443 input.parse::<Token![,]>()?;
444 if input.is_empty() {
445 break;
446 }
447
448 let k = input.parse::<Expr>()?;
449 input.parse::<Token![=>]>()?;
450 let v = input.parse::<Expr>()?;
451
452 labels.push((k, v));
453 }
454
455 return Ok(Some(Labels::Inline(labels)));
456 }
457
458 input.parse::<Token![,]>()?;
460
461 if input.is_empty() {
463 return Ok(None);
464 }
465
466 let existing = input.parse::<Expr>().map_err(|e| {
467 Error::new(e.span(), "expected labels expression, but expression not found")
468 })?;
469
470 if input.peek(Token![,]) {
472 input.parse::<Token![,]>()?;
473 }
474
475 Ok(Some(Labels::Existing(existing)))
476}