use serde::Deserialize;
use std::cell::RefCell;
use thiserror::Error;
#[cfg(any(usdt_backend_standard, usdt_backend_stapsdt, feature = "des"))]
pub mod record;
#[cfg_attr(usdt_backend_noop, path = "empty.rs")]
#[cfg_attr(usdt_backend_linker, path = "linker.rs")]
#[cfg_attr(usdt_backend_standard, path = "no-linker.rs")]
#[cfg_attr(usdt_backend_stapsdt, path = "stapsdt.rs")]
mod internal;
#[cfg_attr(usdt_backend_noop, allow(dead_code))]
mod common;
pub fn register_probes() -> Result<(), Error> {
crate::internal::register_probes()
}
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
ParseError(#[from] dtrace_parser::DTraceError),
#[error(transparent)]
IO(#[from] std::io::Error),
#[error(transparent)]
Env(#[from] std::env::VarError),
#[error("The file is not a valid object file")]
InvalidFile,
#[error("Failed to call DTrace subprocess")]
DTraceError,
#[error(transparent)]
Json(#[from] serde_json::Error),
}
#[derive(Default, Debug, Deserialize)]
pub struct CompileProvidersConfig {
pub provider: Option<String>,
pub probe_format: Option<String>,
pub module: Option<String>,
}
impl CompileProvidersConfig {
pub fn format_probe(&self, probe_name: &str) -> String {
if let Some(fmt) = &self.probe_format {
fmt.replace(
"{provider}",
self.provider
.as_ref()
.expect("Expected a provider name when formatting a rpobe"),
)
.replace("{probe}", probe_name)
} else {
String::from(probe_name)
}
}
pub fn probe_ident(&self, probe_name: &str) -> proc_macro2::Ident {
quote::format_ident!("{}", self.format_probe(probe_name))
}
pub fn module_ident(&self) -> proc_macro2::Ident {
let name = self.module.as_ref().unwrap_or_else(|| {
self.provider
.as_ref()
.expect("Expected a provider name when making a module ident")
});
quote::format_ident!("{}", name)
}
}
pub fn compile_provider_source(
source: &str,
config: &CompileProvidersConfig,
) -> Result<proc_macro2::TokenStream, Error> {
crate::internal::compile_provider_source(source, config)
}
pub fn compile_provider(
provider: &Provider,
config: &CompileProvidersConfig,
) -> proc_macro2::TokenStream {
crate::internal::compile_provider_from_definition(provider, config)
}
#[derive(Debug, Clone, PartialEq)]
pub enum DataType {
Native(dtrace_parser::DataType),
UniqueId,
Serializable(syn::Type),
}
impl DataType {
pub fn to_c_type(&self) -> String {
match self {
DataType::Native(ty) => ty.to_c_type(),
DataType::UniqueId => String::from("uint64_t"),
DataType::Serializable(_) => String::from("char*"),
}
}
pub fn to_rust_ffi_type(&self) -> syn::Type {
match self {
DataType::Native(ty) => syn::parse_str(&ty.to_rust_ffi_type()).unwrap(),
DataType::UniqueId => syn::parse_str("::std::os::raw::c_ulonglong").unwrap(),
DataType::Serializable(_) => syn::parse_str("*const ::std::os::raw::c_char").unwrap(),
}
}
pub fn to_rust_type(&self) -> syn::Type {
match self {
DataType::Native(ty) => syn::parse_str(&ty.to_rust_type()).unwrap(),
DataType::UniqueId => syn::parse_str("::usdt::UniqueId").unwrap(),
DataType::Serializable(ref inner) => inner.clone(),
}
}
}
impl From<dtrace_parser::DataType> for DataType {
fn from(ty: dtrace_parser::DataType) -> Self {
DataType::Native(ty)
}
}
impl From<&syn::Type> for DataType {
fn from(t: &syn::Type) -> Self {
DataType::Serializable(t.clone())
}
}
#[derive(Debug, Clone)]
pub struct Probe {
pub name: String,
pub types: Vec<DataType>,
}
impl From<dtrace_parser::Probe> for Probe {
fn from(p: dtrace_parser::Probe) -> Self {
Self {
name: p.name,
types: p.types.into_iter().map(DataType::from).collect(),
}
}
}
impl Probe {
pub fn to_d_source(&self) -> String {
let types = self
.types
.iter()
.map(|typ| typ.to_c_type())
.collect::<Vec<_>>()
.join(", ");
format!("probe {name}({types});", name = self.name, types = types)
}
}
#[derive(Debug, Clone)]
pub struct Provider {
pub name: String,
pub probes: Vec<Probe>,
pub use_statements: Vec<syn::ItemUse>,
}
impl Provider {
pub fn to_d_source(&self) -> String {
let probes = self
.probes
.iter()
.map(|probe| format!("\t{}", probe.to_d_source()))
.collect::<Vec<_>>()
.join("\n");
format!(
"provider {provider_name} {{\n{probes}\n}};",
provider_name = self.name,
probes = probes
)
}
}
impl From<dtrace_parser::Provider> for Provider {
fn from(p: dtrace_parser::Provider) -> Self {
Self {
name: p.name,
probes: p.probes.into_iter().map(Probe::from).collect(),
use_statements: vec![],
}
}
}
impl From<&dtrace_parser::Provider> for Provider {
fn from(p: &dtrace_parser::Provider) -> Self {
Self::from(p.clone())
}
}
pub fn to_json<T>(x: &T) -> Result<String, Error>
where
T: ?Sized + ::serde::Serialize,
{
::serde_json::to_string(x).map_err(Error::from)
}
thread_local! {
static CURRENT_ID: RefCell<u32> = const { RefCell::new(0) };
static THREAD_ID: RefCell<usize> = RefCell::new(thread_id::get());
}
#[derive(Debug)]
pub struct UniqueId {
id: RefCell<Option<u64>>,
}
impl UniqueId {
pub const fn new() -> Self {
Self {
id: RefCell::new(None),
}
}
fn materialize(&self) {
let mut inner = self.id.borrow_mut();
if inner.is_none() {
let id = CURRENT_ID.with(|id| {
let thread_id = THREAD_ID.with(|id| *id.borrow_mut() as u64);
let mut inner = id.borrow_mut();
*inner = inner.wrapping_add(1);
(thread_id << 32) | (*inner as u64)
});
inner.replace(id);
}
}
#[doc(hidden)]
pub fn as_u64(&self) -> u64 {
self.materialize();
self.id.borrow().unwrap()
}
}
impl Clone for UniqueId {
fn clone(&self) -> Self {
self.materialize();
Self {
id: self.id.clone(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use dtrace_parser::BitWidth;
use dtrace_parser::DataType as DType;
use dtrace_parser::Integer;
use dtrace_parser::Sign;
#[test]
fn test_probe_to_d_source() {
let probe = Probe {
name: String::from("my_probe"),
types: vec![DataType::Native(DType::Pointer(Integer {
sign: Sign::Unsigned,
width: BitWidth::Bit8,
}))],
};
assert_eq!(probe.to_d_source(), "probe my_probe(uint8_t*);");
}
#[test]
fn test_provider_to_d_source() {
let probe = Probe {
name: String::from("my_probe"),
types: vec![DataType::Native(DType::Integer(Integer {
sign: Sign::Unsigned,
width: BitWidth::Bit8,
}))],
};
let provider = Provider {
name: String::from("my_provider"),
probes: vec![probe],
use_statements: vec![],
};
assert_eq!(
provider.to_d_source(),
"provider my_provider {\n\tprobe my_probe(uint8_t);\n};"
);
}
#[test]
fn test_data_type() {
let ty = DataType::Native(DType::Pointer(Integer {
sign: Sign::Unsigned,
width: BitWidth::Bit8,
}));
assert_eq!(ty.to_rust_type(), syn::parse_str("*const u8").unwrap());
let ty = DataType::Native(dtrace_parser::DataType::String);
assert_eq!(ty.to_rust_type(), syn::parse_str("&str").unwrap());
let ty = DataType::UniqueId;
assert_eq!(
ty.to_rust_type(),
syn::parse_str("::usdt::UniqueId").unwrap()
);
}
#[test]
fn test_unique_id() {
let id = UniqueId::new();
assert!(id.id.borrow().is_none());
let x = id.as_u64();
assert_eq!(x & 0xFFFF_FFFF, 1);
assert_eq!(id.id.borrow().unwrap(), x);
}
#[test]
fn test_unique_id_clone() {
let id = UniqueId::new();
let id2 = id.clone();
assert!(id.id.borrow().is_some());
assert!(id2.id.borrow().is_some());
assert_eq!(id.id.borrow().unwrap(), id2.id.borrow().unwrap());
assert_ne!(&(id.id) as *const _, &(id2.id) as *const _);
assert_ne!(id.id.as_ptr(), id2.id.as_ptr());
}
#[test]
fn test_compile_providers_config() {
let config = CompileProvidersConfig {
provider: Some(String::from("prov")),
probe_format: Some(String::from("probe_{probe}")),
module: Some(String::from("not_prov")),
};
assert_eq!(config.format_probe("prob"), "probe_prob");
let module = config.module_ident();
assert_eq!(
quote::quote! { #module }.to_string(),
quote::quote! { not_prov }.to_string(),
);
}
}