blob: 26f13b07055b213e7d1668686953a610d9652919 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
extern crate proc_macro;
use std::collections::BTreeSet;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, Ident, ItemFn, Lit, Meta, MetaNameValue, NestedMeta};
#[derive(Default)]
struct CustomAttributes {
category: Option<Lit>,
name: Option<Lit>,
thread_scope: bool,
process_scope: bool,
global_scope: bool,
anchor_begin: bool,
anchor_end: bool,
meta_idents: BTreeSet<String>,
}
impl CustomAttributes {
// Returns true if any scope has been defined.
fn is_scope_defined(&self) -> bool {
self.thread_scope | self.process_scope | self.global_scope
}
// Returns true if the scope gets defined, false otherwise. Panics if the scope gets set, but
// another scope was already defined.
fn set_scope(&mut self, scope: &Ident) -> syn::Result<bool> {
let was_defined = self.is_scope_defined();
match scope.to_string().as_str() {
"thread" => self.thread_scope = true,
"process" => self.process_scope = true,
"global" => self.global_scope = true,
_ => return Ok(false),
}
if was_defined {
return Err(syn::Error::new_spanned(
scope,
"scope has already been defined",
));
}
Ok(true)
}
// Returns true if any anchor has been defined.
fn is_anchor_defined(&self) -> bool {
self.anchor_begin | self.anchor_end
}
// Returns true if the anchor gets defined, false otherwise. Panics if the anchor gets set, but
// another anchor was already defined.
fn set_anchor(&mut self, anchor: &Ident) -> syn::Result<bool> {
let was_defined = self.is_anchor_defined();
match anchor.to_string().as_str() {
"begin" => self.anchor_begin = true,
"end" => self.anchor_end = true,
_ => return Ok(false),
}
if was_defined {
return Err(syn::Error::new_spanned(
anchor,
"anchor has already been defined",
));
}
Ok(true)
}
}
fn parse_attributes(nested_meta_list: Vec<NestedMeta>) -> syn::Result<CustomAttributes> {
let mut attributes = CustomAttributes::default();
for nested_meta in nested_meta_list {
match nested_meta {
NestedMeta::Meta(meta) => match meta {
Meta::NameValue(MetaNameValue { ident, lit, .. }) => {
match ident.to_string().as_str() {
"category" => {
if attributes.category.is_some() {
return Err(syn::Error::new_spanned(
ident,
"multiple values for `category`",
));
}
attributes.category = Some(lit);
}
"name" => {
if attributes.name.is_some() {
return Err(syn::Error::new_spanned(
ident,
"multiple values for `name`",
));
}
attributes.name = Some(lit);
}
_ => {
return Err(syn::Error::new_spanned(
ident,
"unrecognized attribute parameter",
));
}
}
}
Meta::Word(ident) => {
if !attributes.meta_idents.insert(ident.to_string()) {
return Err(syn::Error::new_spanned(
ident,
"attribute identifier repeated multiple times",
));
}
if attributes.set_scope(&ident)? {
continue;
}
if attributes.set_anchor(&ident)? {
continue;
}
return Err(syn::Error::new_spanned(
ident,
"unrecognized attribute identifier",
));
}
Meta::List(list) => {
return Err(syn::Error::new_spanned(list, "unrecognized meta list"));
}
},
NestedMeta::Literal(lit) => {
return Err(syn::Error::new_spanned(
lit,
"unexpected literal in attribute",
));
}
}
}
Ok(attributes)
}
/// Writes an instant trace event to the global tracer when the function is called.
///
/// By default, the category will be `module_path!()`, the name will be the name of the decorated
/// function, and the scope will be `Thread`. Use the `category` or `name` attribute parameters to
/// customize the trace event's label. Use the word `thread`, `process`, or `global` to explicitly
/// set the scope.
///
/// Use the word `end` in the attribute parameters to write the event when the function returns
/// instead of when it is called.
///
/// # Example
///
/// ```
/// use trace_events::instant;
///
/// #[instant]
/// fn instant_event() {}
///
/// #[instant(end)]
/// fn long_instant() {
/// // ... long running work
/// // trace event will be triggered after this line
/// }
///
/// #[instant(process)]
/// fn process_event() {}
///
/// #[instant(global)]
/// fn global_event() {}
///
/// #[instant(category = "foo", name = "bar")]
/// fn __weird_internal_name() {}
/// ```
#[proc_macro_attribute]
pub fn instant(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut function = parse_macro_input!(item as ItemFn);
let attributes = match parse_attributes(parse_macro_input!(attr as Vec<NestedMeta>)) {
Ok(a) => a,
Err(e) => return e.to_compile_error().into(),
};
let ident = function.ident.clone();
let name = match attributes.name {
Some(name) => quote!(#name),
None => quote!(stringify!(#ident)),
};
let category = match attributes.category {
Some(category) => quote!(#category),
None => quote!(module_path!()),
};
let scope = if attributes.thread_scope {
quote!(trace_events::Scope::Thread)
} else if attributes.process_scope {
quote!(trace_events::Scope::Process)
} else if attributes.global_scope {
quote!(trace_events::Scope::Global)
} else {
quote!(Default::default())
};
let trace_call = quote! {
if trace_events::is_category_enabled(#category) {
trace_events::__private_cold_trace_instant(
#category,
#name,
#scope,
file!(),
line!()
);
}
};
let body = function.block;
if attributes.anchor_end {
function.block = Box::new(parse_quote!({
struct __TraceEventsInstant;
impl Drop for __TraceEventsInstant {
fn drop(&mut self) {
#trace_call
}
}
let __trace_events_instant = __TraceEventsInstant;
#body
}))
} else {
function.block = Box::new(parse_quote!({
#trace_call
#body
}));
}
TokenStream::from(quote!(#function))
}
/// Writes a duration trace event to the global tracer when this function returns for the time this
/// function takes to run.
///
/// By default, the category will be `module_path!()` and the name will be the name of the decorated
/// function. Use the `category` or `name` attribute parameters to customize the trace event's
/// label.
///
/// # Example
///
/// ```
/// use trace_events::duration;
///
/// #[duration]
/// fn duration_event() {}
///
/// #[duration(category = "foo", name = "bar")]
/// fn __weird_internal_name() {}
/// ```
#[proc_macro_attribute]
pub fn duration(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut function = parse_macro_input!(item as ItemFn);
let attributes = match parse_attributes(parse_macro_input!(attr as Vec<NestedMeta>)) {
Ok(a) => a,
Err(e) => return e.to_compile_error().into(),
};
let ident = function.ident.clone();
let name = match attributes.name {
Some(name) => quote!(#name),
None => quote!(stringify!(#ident)),
};
let category = match attributes.category {
Some(category) => quote!(#category),
None => quote!(module_path!()),
};
let body = function.block;
function.block = Box::new(parse_quote!({
struct __TraceEventsDuration {
begin_ts: u64,
}
impl Drop for __TraceEventsDuration {
fn drop(&mut self) {
if trace_events::is_category_enabled(#category) {
trace_events::__private_cold_trace_duration(
#category,
#name,
self.begin_ts,
file!(),
line!()
);
}
}
}
let __trace_events_duration = __TraceEventsDuration {
begin_ts: trace_events::sys::get_monotonic()
};
#body
}));
TokenStream::from(quote!(#function))
}
/// Writes a counter trace event to the global tracer when the function is called.
///
/// By default, the category will be `module_path!()` and the name will be the name of the decorated
/// function. Use the `category` or `name` attribute parameters to customize the trace event's
/// label. The name of the counter will be `"calls"`. Only calls while the category is enabled are
/// counted.
///
/// Use the word `end` in the attribute parameters to write the event when the function returns
/// instead of when it is called.
///
/// # Example
///
/// ```
/// use trace_events::counter;
///
/// #[counter]
/// fn counter_event() {}
///
/// #[counter(end)]
/// fn long_counter() {
/// // ... long running work
/// // trace event will be triggered after this line
/// }
///
/// #[counter(category = "foo", name = "bar")]
/// fn __weird_internal_name() {}
/// ```
#[proc_macro_attribute]
pub fn counter(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut function = parse_macro_input!(item as ItemFn);
let attributes = match parse_attributes(parse_macro_input!(attr as Vec<NestedMeta>)) {
Ok(a) => a,
Err(e) => return e.to_compile_error().into(),
};
let ident = function.ident.clone();
let name = match attributes.name {
Some(name) => quote!(#name),
None => quote!(stringify!(#ident)),
};
let category = match attributes.category {
Some(category) => quote!(#category),
None => quote!(module_path!()),
};
let trace_counter = quote! {
static __TRACE_EVENTS_COUNTER: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
if trace_events::is_category_enabled(#category) {
trace_events::__private_cold_trace_counter(
#category,
#name,
"calls",
(__TRACE_EVENTS_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1) as u64
);
}
};
let body = function.block;
if attributes.anchor_end {
function.block = Box::new(parse_quote!({
struct __TraceEventsCounter;
impl Drop for __TraceEventsCounter {
fn drop(&mut self) {
#trace_counter
}
}
let __trace_events_instant = __TraceEventsCounter;
#body
}))
} else {
function.block = Box::new(parse_quote!({
#trace_counter
#body
}));
}
TokenStream::from(quote!(#function))
}