chrometracer_attributes/
lib.rs

1use std::collections::HashSet;
2
3use proc_macro::TokenStream;
4use proc_macro2::Span;
5use quote::ToTokens;
6use syn::ext::IdentExt;
7use syn::{
8    parse::{Parse, ParseStream, Parser},
9    parse_quote,
10    punctuated::Punctuated,
11    Expr, Ident, LitInt, LitStr, Path, Token,
12};
13
14#[derive(Default)]
15struct ChromeEventArgs {
16    level: Option<Level>,
17    target: Option<LitStr>,
18    event: Option<Event>,
19    fields: Fields,
20    skips: HashSet<Ident>,
21}
22
23#[derive(Default)]
24struct Fields(Punctuated<Field, Token![,]>);
25
26impl ToTokens for Fields {
27    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
28        self.0.to_tokens(tokens)
29    }
30}
31
32impl Parse for Fields {
33    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
34        let _ = input.parse::<kw::fields>();
35        let content;
36        let _ = syn::parenthesized!(content in input);
37        let fields: Punctuated<_, Token![,]> = content.parse_terminated(Field::parse)?;
38        Ok(Self(fields))
39    }
40}
41
42#[derive(Clone)]
43enum Level {
44    Str(LitStr),
45    Int(LitInt),
46    Path(Path),
47}
48
49impl Parse for Level {
50    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
51        let _ = input.parse::<kw::level>()?;
52        let _ = input.parse::<Token![=]>()?;
53        let lookahead = input.lookahead1();
54        if lookahead.peek(LitStr) {
55            Ok(Self::Str(input.parse()?))
56        } else if lookahead.peek(LitInt) {
57            Ok(Self::Int(input.parse()?))
58        } else if lookahead.peek(Ident) {
59            Ok(Self::Path(input.parse()?))
60        } else {
61            Err(lookahead.error())
62        }
63    }
64}
65
66struct Skips(HashSet<Ident>);
67
68impl Parse for Skips {
69    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
70        let _ = input.parse::<kw::skip>();
71        let content;
72        let _ = syn::parenthesized!(content in input);
73        let names: Punctuated<Ident, Token![,]> = content.parse_terminated(Ident::parse_any)?;
74        let mut skips = HashSet::new();
75        for name in names {
76            if skips.contains(&name) {
77                return Err(syn::Error::new(
78                    name.span(),
79                    "tried to skip the same field twice",
80                ));
81            } else {
82                skips.insert(name);
83            }
84        }
85        Ok(Self(skips))
86    }
87}
88
89impl ToTokens for ChromeEventArgs {
90    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
91        self.fields.to_tokens(tokens);
92    }
93}
94
95impl Parse for ChromeEventArgs {
96    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
97        let mut args = Self::default();
98
99        while !input.is_empty() {
100            let lookahead = input.lookahead1();
101            if lookahead.peek(kw::event) {
102                args.event = Some(Event::parse(input)?);
103            } else if lookahead.peek(kw::level) {
104                args.level = Some(Level::parse(input)?)
105            } else if lookahead.peek(kw::fields) {
106                args.fields = Fields::parse(input)?;
107            } else if lookahead.peek(kw::skip) {
108                let Skips(skips) = input.parse()?;
109                args.skips = skips;
110            } else if lookahead.peek(kw::target) {
111                let target = input.parse::<StrArg<kw::target>>()?.value;
112                args.target = Some(target);
113            } else if lookahead.peek(Token![,]) {
114                let _ = input.parse::<Token![,]>()?;
115            } else {
116                panic!(
117                    "Unknown fields, expected one of \"event\", \"level\", \"fields\", \"skip\", \"target\"",
118                )
119            }
120        }
121        Ok(args)
122    }
123}
124
125struct StrArg<T> {
126    value: LitStr,
127    _p: std::marker::PhantomData<T>,
128}
129
130impl<T: Parse> Parse for StrArg<T> {
131    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
132        let _ = input.parse::<T>()?;
133        let _ = input.parse::<Token![=]>()?;
134        let value = input.parse()?;
135        Ok(Self {
136            value,
137            _p: std::marker::PhantomData,
138        })
139    }
140}
141
142struct Event {
143    keyword: kw::event,
144    colon_token: Token![:],
145    event: LitStr,
146    comma_token: Token![,],
147}
148
149impl Parse for Event {
150    fn parse(input: ParseStream) -> syn::Result<Self> {
151        Ok(Event {
152            keyword: input.parse()?,
153            colon_token: input.parse()?,
154            event: input.parse()?,
155            comma_token: input.parse()?,
156        })
157    }
158}
159
160impl ToTokens for Event {
161    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
162        self.keyword.to_tokens(tokens);
163        self.colon_token.to_tokens(tokens);
164        self.event.to_tokens(tokens);
165        self.comma_token.to_tokens(tokens);
166    }
167}
168
169struct Field {
170    path: Path,
171    eq_token: Token![=],
172    value: Expr,
173}
174
175impl Parse for Field {
176    fn parse(input: ParseStream) -> syn::Result<Self> {
177        Ok(Field {
178            path: input.parse()?,
179            eq_token: input.parse()?,
180            value: input.parse()?,
181        })
182    }
183}
184
185impl ToTokens for Field {
186    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
187        self.path.to_tokens(tokens);
188        self.eq_token.to_tokens(tokens);
189        self.value.to_tokens(tokens);
190    }
191}
192
193#[proc_macro_attribute]
194pub fn instrument(attr: TokenStream, item: TokenStream) -> TokenStream {
195    let args = syn::parse_macro_input!(attr as ChromeEventArgs);
196
197    let event = args
198        .event
199        .map(|e| e.event)
200        .unwrap_or_else(|| LitStr::new("", Span::call_site()));
201
202    let fields = args.fields.0.iter();
203    let fields2 = args.fields.0.iter();
204    let fields3 = args.fields.0.iter();
205
206    let mut input = syn::Item::parse.parse(item).unwrap();
207
208    if let syn::Item::Fn(ref mut item) = input {
209        let original = &item.block;
210        item.block = Box::new(parse_quote! {{
211            let start = chrometracer::current(|tracer| tracer.map(|t| t.start));
212
213            if let Some(start) = start {
214                let event = match #event {
215                    "async" => Some((chrometracer::EventType::AsyncStart, chrometracer::EventType::AsyncEnd)),
216                    "" => None,
217                    _ => panic!("Unknown event, expected one of \"async\"")
218                };
219
220                let ret = if let Some(event) = event {
221                    chrometracer::event!(#(#fields,)* ph = event.0,
222                        ts = ::std::time::SystemTime::now().duration_since(start).unwrap().as_nanos() as f64 / 1000.0);
223                    let ret = #original;
224                    chrometracer::event!(#(#fields2,)* ph = event.1,
225                        ts = ::std::time::SystemTime::now().duration_since(start).unwrap().as_nanos() as f64 / 1000.0);
226                    ret
227                } else {
228                    let now = ::std::time::SystemTime::now();
229                    let ts = now.duration_since(start).unwrap().as_nanos() as f64 / 1000.0;
230                    let ret = #original;
231                    let dur = ::std::time::SystemTime::now().duration_since(now).unwrap().as_nanos() as f64 / 1000.0;
232
233                    chrometracer::event!(#(#fields3,)* ph = chrometracer::EventType::Complete, dur = dur, ts = ts);
234                    ret
235                };
236
237                ret
238            } else {
239                #original
240            }
241        }});
242    } else {
243        unreachable!()
244    }
245
246    input.into_token_stream().into()
247}
248
249mod kw {
250    syn::custom_keyword!(event);
251    syn::custom_keyword!(skip);
252    syn::custom_keyword!(fields);
253    syn::custom_keyword!(level);
254    syn::custom_keyword!(target);
255}