use failure::Fallible;
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
const MAX_ARITY: usize = 12; const MAX_QUICKCHECK_ARITY: usize = 8;
fn main() -> Fallible<()> {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("probe_args.rs");
let dest_tests_path = Path::new(&out_dir).join("probe_args_tests.rs");
let mut f = File::create(&dest_path)?;
let mut f_tests = File::create(&dest_tests_path)?;
for arity in 0..=MAX_ARITY {
let type_params = get_type_param_names(arity);
f.write_all(generate_probe_args_impl(&type_params).as_bytes())?;
}
f.write_all(generate_unsafe_provider_probe_impl_trait().as_bytes())?;
f.write_all(generate_unsafe_provider_probe_native_impl_trait().as_bytes())?;
f_tests.write_all(generate_tests().as_bytes())?;
Ok(())
}
fn get_type_param_names(args: usize) -> Vec<String> {
(0..args).map(|x| format!("T{}", x)).collect()
}
fn make_tuple_type(type_params: &Vec<String>) -> String {
if type_params.is_empty() {
"()".to_string()
} else {
format!("({},)", type_params.join(","))
}
}
fn xform_types<F: FnMut(&String) -> String>(type_params: &Vec<String>, mut f: F) -> Vec<String> {
type_params.iter().map(|x| f(x)).collect::<Vec<String>>()
}
fn xform_types_i<F: FnMut(usize, &String) -> String>(
type_params: &Vec<String>,
mut f: F,
) -> Vec<String> {
type_params
.iter()
.enumerate()
.map(|(i, x)| f(i, x))
.collect::<Vec<String>>()
}
fn generate_probe_args_impl(type_params: &Vec<String>) -> String {
let probe_args = xform_types_i(&type_params, |i, _| format!("self.{}", i));
format!(
r#"
/// Supports the use of a {arg_count}-arity tuple type of probe arguments.
///
/// Note that all of the elements must have a type `T` for which `ProbeArgType<T>` is defined.
impl<{type_list}> ProbeArgs<{tuple_type}> for {tuple_type} where {args_where_clause} {{
const ARG_COUNT: usize = {arg_count};
fn arg_types() -> Vec<CType> {{
vec![{ctypes}]
}}
fn fire_probe<ImplT: UnsafeProviderProbeImpl>(self, probe: &ImplT) {{
unsafe {{ probe.fire{arg_count}({probe_args}) }}
}}
}}
"#,
type_list = type_params.join(","),
tuple_type = make_tuple_type(&type_params),
args_where_clause = xform_types(&type_params, |x| format!("{t}: ProbeArgType<{t}>", t=x)).join(","),
arg_count = type_params.len(),
ctypes = xform_types(&type_params, |x| format!("get_ctype::<{}>()", x)).join(","),
probe_args = probe_args.join(",")
)
}
fn generate_unsafe_provider_probe_impl_trait() -> String {
let mut decl= r#"
/// This trait is implemented by tracing providers that operate on probe arguments in their Rust represention, not
/// their C native representation. Most implementations will not implement this trait directly but rather its subtrait
/// `UnsafeProviderProbeNativeImpl`, which provides an implementation of this trait which wraps all parameters in their
/// wrapper types and calls its own trait methods with native C representations of each argument.
///
/// This trait and its subtrait `UnsafeProviderProbeNativeImpl` are both unsafe because the provider cannot necessarily
/// verify that the types and argument counts for the probe match those when the probe was first created. This is partially for
/// performance reasons and also a practical limitation of the `var-arg` based implementations most commonly used in C tracing
/// libraries.
///
/// The implementor of this API for a specific tracing library need only implement all 13
/// possible `fire` methods, one for each number of args from 0 to 12.
#[allow(clippy::too_many_arguments)]
pub trait UnsafeProviderProbeImpl
{
/// Tests if this probe is enabled or not. This should be a very fast test, ideally just a memory
/// access. The Rust compiler should be able to inline this implementation for maxmimum performance.
fn is_enabled(&self) -> bool;
unsafe fn fire0(&self);
"#.to_string();
for arity in 1..=MAX_ARITY {
let type_params = get_type_param_names(arity);
decl += &format!(
r##"
unsafe fn fire{arg_count}<{type_list}>(&self, {args})
where {where_clause};
"##,
arg_count = type_params.len(),
type_list = type_params.join(","),
args = xform_types_i(&type_params, |i, x| format!("arg{}: {}", i, x)).join(","),
where_clause =
xform_types(&type_params, |x| format!("{t}: ProbeArgType<{t}>", t = x)).join(",")
);
}
decl.push_str("}");
decl
}
fn generate_unsafe_provider_probe_native_impl_trait() -> String {
let mut decl= r#"
/// See `UnsafeProviderProbeImpl` for additional details. This subtrait provides an implementation of
/// `UnsafeProviderProbeImpl` which wraps each of the Rust types into a `ProbeArgWrapper` and presents to the
/// implementor of this trait the C representation of each probe argument. It is presumbed that implmeentations of
/// `UnsafeProviderProbeNativeImpl` are passing these parameters directly into C APIs.
///
/// The implementor of this API for a specific tracing library need only implement all 13
/// possible `fire` methods, one for each number of args from 0 to 12.
///
/// *IMPORTANT NOTE TO IMPLEMENTORS*: Each of the `fireN` methods take arguments which may be either
/// integers or possibly pointers to strings or other memory. The caller guarantees that these are valid
/// addresses *only* for the duration of the call. Immediatley after the `fireN` method returns this memory may
/// be freed. Thus it's imperative that the probing implementation process probes synchronously. Otherwise
/// invalid memory accesses are inevitable.
#[allow(clippy::too_many_arguments)]
pub trait UnsafeProviderProbeNativeImpl
{
/// Tests if this probe is enabled or not. This should be a very fast test, ideally just a memory
/// access. The Rust compiler should be able to inline this implementation for maxmimum performance.
fn is_enabled(&self) -> bool;
/// This is actually identical to `fire0` but is provided for consistency with the other arities
unsafe fn c_fire0(&self);
"#.to_string();
for arity in 1..=MAX_ARITY {
let type_params = get_type_param_names(arity);
decl += &format!(
r##"
unsafe fn c_fire{arg_count}<{type_list}>(&self, {args})
where {where_clause};
"##,
arg_count = type_params.len(),
type_list = type_params.join(","),
args = xform_types_i(&type_params, |i, x| format!("arg{}: {}", i, x)).join(","),
where_clause = xform_types(&type_params, |x| format!(
"{t}: ProbeArgNativeType<{t}>",
t = x
))
.join(",")
);
}
decl += "}\n";
decl += r#"
impl<T: UnsafeProviderProbeNativeImpl> UnsafeProviderProbeImpl for T
{
fn is_enabled(&self) -> bool {
T::is_enabled(self)
}
unsafe fn fire0(&self) { self.c_fire0() }
"#;
for arity in 1..=MAX_ARITY {
let type_params = get_type_param_names(arity);
let wrapper_decls = xform_types_i(&type_params, |i, _| {
format!("let wrapper{0} = wrap(arg{0})", i)
})
.join(";\n");
let probe_args =
xform_types_i(&type_params, |i, _| format!("wrapper{}.as_c_type()", i)).join(",");
decl += &format!(
r##"
unsafe fn fire{arg_count}<{type_list}>(&self, {args})
where {where_clause} {{
{wrapper_decls};
self.c_fire{arg_count}({probe_args});
}}
"##,
type_list = type_params.join(","),
arg_count = type_params.len(),
args = xform_types_i(&type_params, |i, x| format!("arg{}: {}", i, x)).join(","),
where_clause =
xform_types(&type_params, |x| format!("{t}: ProbeArgType<{t}>", t = x)).join(","),
wrapper_decls = wrapper_decls,
probe_args = probe_args,
);
}
decl += "}\n";
decl
}
fn generate_tests() -> String {
[
generate_test_unsafe_probe_impl(),
"\n".to_string(),
generate_probe_tests(),
]
.concat()
}
fn generate_test_unsafe_probe_impl() -> String {
let mut decl = r#"
#[cfg(test)]
#[cfg(unix)]
#[allow(clippy::too_many_arguments)]
impl UnsafeProviderProbeNativeImpl for TestingProviderProbeImpl {
fn is_enabled(&self) -> bool {
self.is_enabled
}
unsafe fn c_fire0(&self) {
{
let buffer = self.buffer.lock().unwrap();
libc::snprintf(buffer.as_ptr() as *mut c_char, BUFFER_SIZE, self.format_string.as_ptr());
}
self.log_call();
}
"#
.to_string();
for arity in 1..=MAX_ARITY {
let type_params = get_type_param_names(arity);
decl += &format!(
r##"
unsafe fn c_fire{arg_count}<{type_list}>(&self, {args})
where {where_clause} {{
{{
let buffer = self.buffer.lock().unwrap();
libc::snprintf(buffer.as_ptr() as *mut c_char,
BUFFER_SIZE,
self.format_string.as_ptr(),
{probe_args});
}}
self.log_call();
}}
"##,
arg_count = type_params.len(),
type_list = type_params.join(","),
args = xform_types_i(&type_params, |i, x| format!("arg{}: {}", i, x)).join(","),
where_clause = xform_types(&type_params, |x| format!(
"{t}: ProbeArgNativeType<{t}>",
t = x
))
.join(","),
probe_args = if type_params.is_empty() {
"".to_string()
} else {
xform_types_i(&type_params, |i, _| format!("arg{}", i)).join(",")
}
);
}
decl.push_str("}");
decl
}
fn generate_probe_tests() -> String {
const STRING_ARG_INDEX: usize = 4;
const ARG_TYPES: &[(&str, &str)] = &[
("u64", "%u"),
("u32", "%u"),
("u16", "%u"),
("u8", "%u"),
("String", "%s"),
("bool", "%u"),
("i64", "%d"),
("i32", "%d"),
("i16", "%d"),
("i8", "%d"),
];
fn choose_arg_for_n(n: usize) -> (String, String, String, bool) {
let (type_name, format_specifier) = ARG_TYPES[n % ARG_TYPES.len()];
let byref = type_name == "String" || type_name.starts_with("Option");
(
format!("arg{}", n),
type_name.to_string(),
format_specifier.to_string(),
byref,
)
}
let mut decl = "".to_string();
for arity in 1..=MAX_ARITY {
let type_params = get_type_param_names(arity);
let quickcheck_arg_count = if arity > MAX_QUICKCHECK_ARITY {
MAX_QUICKCHECK_ARITY
} else {
arity
};
let additional_arg_count = arity - quickcheck_arg_count;
let args = (1..=arity)
.rev()
.map(|n| {
if n <= MAX_QUICKCHECK_ARITY {
choose_arg_for_n(n)
} else {
let mut arg = choose_arg_for_n(STRING_ARG_INDEX);
arg.0 = format!("arg{}", n);
arg
}
})
.collect::<Vec<(String, String, String, bool)>>();
let args_declaration: Vec<String> = args
.iter()
.skip(additional_arg_count)
.map(|(name, typ, _, _)| format!("{}: {}", name, typ))
.collect();
let additional_args_declaration: Vec<String> = args
.iter()
.take(additional_arg_count)
.map(|(name, _, _, _)| format!("let {name} = \"{name}\".to_string()", name = name))
.collect();
let expected_arg_values: Vec<String> = args
.iter()
.map(|(name, typ, _, _)| {
if typ == "String" {
format!("c_and_back_again(&{})", name)
} else if typ == "bool" {
format!("u8::from({})", name)
} else {
name.to_string()
}
})
.collect();
let args_tuple = make_tuple_type(
&args
.iter()
.map(|(name, _, _, byref)| {
if *byref {
format!("&{}", name)
} else {
format!("{}", name)
}
})
.collect(),
);
let c_format_string = args
.iter()
.map(|(_, _, fmt, _)| fmt.to_string())
.collect::<Vec<String>>()
.join(" ");
let rust_format_string = std::iter::repeat("{}".to_string())
.take(arity)
.collect::<Vec<String>>()
.join(" ");
decl += &format!(
r##"
#[quickcheck]
#[cfg(unix)]
fn test_fire{arg_count}({args_declaration}) -> bool {{
let unsafe_impl = TestingProviderProbeImpl::new("{c_format_string}".to_string());
let probe_impl = ProviderProbe::new(&unsafe_impl);
{additional_args_declaration};
let probe_args={args_tuple};
probe_impl.fire(probe_args);
assert_eq!(probe_impl.unsafe_probe_impl.get_calls(),
vec![format!("{rust_format_string}", {expected_arg_values})]);
true
}}
"##,
arg_count = type_params.len(),
args_declaration = args_declaration.join(", "),
additional_args_declaration = additional_args_declaration.join(";\n"),
c_format_string = c_format_string,
args_tuple = args_tuple,
rust_format_string = rust_format_string,
expected_arg_values = expected_arg_values.join(", ")
);
}
decl
}