usdt_macro/
lib.rs

1//! Prototype proc-macro crate for parsing a DTrace provider definition into Rust code.
2
3// Copyright 2021 Oxide Computer Company
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17use std::iter::FromIterator;
18use std::{fs, path::Path};
19
20use quote::quote;
21use syn::{parse_macro_input, Lit};
22
23use usdt_impl::compile_provider_source;
24
25/// Generate DTrace probe macros from a provider definition file.
26///
27/// This macro parses a DTrace provider.d file, given as a single literal string path. It then
28/// generates a Rust macro for each of the DTrace probe definitions.
29///
30/// For example, let's say the file `"test.d"` has the following contents:
31///
32/// ```ignore
33/// provider test {
34///     probe start(uint8_t);
35///     probe stop(char *, uint8_t);
36/// };
37/// ```
38///
39/// In a Rust library or application, write:
40///
41/// ```ignore
42/// dtrace_provider!("test.d");
43/// ```
44///
45/// The macro looks for the file relative to the root of the package, so `"test.d"`
46/// in this case would be in the same directory as `"Cargo.toml"`.
47///
48/// By default probe macros are named `{provider}_{probe}!`. Arguments are passed
49/// via a closure that returns a tuple. Note that the provided closure is only
50/// evaluated when the probe is enabled. One can then add points of instrumentation
51/// by invoking the macro:
52///
53/// ```ignore
54/// fn do_stuff(count: u8, name: String) {
55///     // doing stuff
56///     test_stop!(|| (name, count));
57/// }
58/// ```
59///
60/// The probe macro names can be customized by adding `, format =
61/// my_prefix_{provider}_{probe}` to the macro invocation where `{provider}` and
62/// `{probe}` are optional and will be substituted with the actual provider and
63/// probe names:
64///
65/// ```ignore
66/// dtrace_provider!("test.d", format = "dtrace_{provider}_{probe}");
67/// ```
68///
69/// Note
70/// ----
71/// The only supported types are integers of specific bit-width (e.g., `uint16_t`),
72/// pointers to integers, and `char *`.
73#[proc_macro]
74pub fn dtrace_provider(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
75    let mut tokens = item.into_iter().collect::<Vec<proc_macro::TokenTree>>();
76
77    let comma_index = tokens
78        .iter()
79        .enumerate()
80        .find_map(|(i, token)| match token {
81            proc_macro::TokenTree::Punct(p) if p.as_char() == ',' => Some(i),
82            _ => None,
83        });
84
85    // Split off the tokens after the comma if there is one.
86    let rest = if let Some(index) = comma_index {
87        let mut rest = tokens.split_off(index);
88        let _ = rest.remove(0);
89        rest
90    } else {
91        Vec::new()
92    };
93
94    // Parse the config from the remaining tokens.
95    let config: usdt_impl::CompileProvidersConfig = serde_tokenstream::from_tokenstream(
96        &proc_macro2::TokenStream::from(proc_macro::TokenStream::from_iter(rest)),
97    )
98    .unwrap();
99
100    let first_item = proc_macro::TokenStream::from_iter(tokens);
101    let tok = parse_macro_input!(first_item as Lit);
102    let filename = match tok {
103        Lit::Str(f) => f.value(),
104        _ => panic!("DTrace provider must be a single literal string filename"),
105    };
106    let source = if filename.ends_with(".d") {
107        let dir = std::env::var("CARGO_MANIFEST_DIR").map_or_else(
108            |_| std::env::current_dir().unwrap(),
109            |s| Path::new(&s).to_path_buf(),
110        );
111
112        let path = dir.join(&filename);
113        fs::read_to_string(path).unwrap_or_else(|_| {
114            panic!(
115                "Could not read D source file \"{}\" in {:?}",
116                &filename, dir,
117            )
118        })
119    } else {
120        filename.clone()
121    };
122    match compile_provider_source(&source, &config) {
123        Ok(provider) => provider.into(),
124        Err(e) => {
125            let message = format!(
126                "Error building provider definition in \"{}\"\n\n{}",
127                filename, e
128            );
129            let out = quote! {
130                compile_error!(#message);
131            };
132            out.into()
133        }
134    }
135}