#![doc = include_str!("../README.md")]
use std::collections::BTreeMap;
use std::path::PathBuf;
use tracing::Level;
pub use tracing;
#[cfg(feature = "serde")]
use serde::Serialize;
#[doc(hidden)]
#[cfg(feature = "git-version")]
pub use git_version::git_version;
#[cfg(not(feature = "git-version"))]
#[macro_export]
macro_rules! git_version {
(
$( args = [ $( $arg:literal ),* $(,)? ])?
) => {
"rummage-rs built without the 'git-version' feature"
};
}
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Clone, Debug)]
pub struct CrateInfo {
pub git_commit_hash: String,
pub is_git_repo_dirty: bool,
pub bin_name: String,
pub crate_name: String,
pub crate_version: String,
}
impl CrateInfo {
#[doc(hidden)]
pub fn new(git_version: &str, crate_name: &str, crate_version: &str, bin_name: &str) -> Self {
let dirty = git_version.ends_with("-dirty");
let hash = git_version.trim_end_matches("-dirty");
Self {
git_commit_hash: hash.to_string(),
is_git_repo_dirty: dirty,
crate_name: crate_name.to_string(),
crate_version: crate_version.to_string(),
bin_name: bin_name.to_string(),
}
}
pub fn iter_props(&self) -> impl Iterator<Item = (&'static str, String)> {
[
("git_commit_hash", self.git_commit_hash.clone()),
("git_repo_dirty", self.is_git_repo_dirty.to_string()),
("crate_name", self.crate_name.clone()),
("crate_version", self.crate_version.clone()),
("bin_name", self.bin_name.clone()),
]
.into_iter()
}
fn log_debug(&self) {
tracing::event!(
Level::DEBUG,
git_commit_hash = self.git_commit_hash,
git_repo_dirty = self.is_git_repo_dirty,
crate_name = self.crate_name,
crate_version = self.crate_version,
bin_name = self.bin_name,
"Crate information:"
);
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! _crate_info {
() => {{
::rummage::CrateInfo::new(
::rummage::git_version!(
args = [
"--always",
"--abbrev=0",
"--match",
"NOT A TAG",
"--dirty=-dirty"
]
),
option_env!("CARGO_CRATE_NAME").unwrap_or("<failed to scrape>"),
option_env!("CARGO_PKG_VERSION").unwrap_or("<failed to scrape>"),
option_env!("CARGO_BIN_NAME").unwrap_or("<failed to scrape>"),
)
}};
}
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Clone, Debug)]
pub struct CargoTarget {
pub profile: String,
pub host: String,
pub target: String,
pub family: String,
pub os: String,
pub arch: String,
pub pointer_width: String,
pub endian: String,
pub features: String,
}
impl CargoTarget {
fn gather() -> Self {
Self {
profile: env!("RUMMAGE_PROFILE").to_string(),
host: env!("RUMMAGE_HOST").to_string(),
target: env!("RUMMAGE_TARGET").to_string(),
family: env!("RUMMAGE_CARGO_CFG_TARGET_FAMILY").to_string(),
os: env!("RUMMAGE_CARGO_CFG_TARGET_OS").to_string(),
arch: env!("RUMMAGE_CARGO_CFG_TARGET_ARCH").to_string(),
pointer_width: env!("RUMMAGE_CARGO_CFG_TARGET_POINTER_WIDTH").to_string(),
endian: env!("RUMMAGE_CARGO_CFG_TARGET_ENDIAN").to_string(),
features: env!("RUMMAGE_CARGO_CFG_TARGET_FEATURE").to_string(),
}
}
pub fn iter_props(&self) -> impl Iterator<Item = (&'static str, String)> {
[
("profile", self.profile.clone()),
("host", self.host.clone()),
("target", self.target.clone()),
("family", self.family.clone()),
("os", self.os.clone()),
("arch", self.arch.clone()),
("pointer_width", self.pointer_width.clone()),
("endian", self.endian.clone()),
("features", self.features.clone()),
]
.into_iter()
}
pub fn log_debug(&self) {
tracing::event!(
Level::DEBUG,
profile = self.profile,
host = self.host,
target = self.target,
family = self.family,
os = self.os,
arch = self.arch,
pointer_width = self.pointer_width,
endian = self.endian,
features = self.features,
"Cargo target information:"
)
}
}
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Clone, Debug)]
pub struct RustcVersion {
pub rustc_semver: String,
pub commit_hash: String,
pub commit_date: String,
pub llvm_version: String,
}
impl RustcVersion {
fn gather() -> Self {
let major = env!("RUMMAGE_RUSTC_VERSION_MAJOR");
let minor = env!("RUMMAGE_RUSTC_VERSION_MINOR");
let patch = env!("RUMMAGE_RUSTC_VERSION_PATCH");
let pre = env!("RUMMAGE_RUSTC_VERSION_PRE");
let build = env!("RUMMAGE_RUSTC_VERSION_BUILD");
let mut rustc_semver = format!("{major}.{minor}.{patch}");
if !pre.is_empty() {
rustc_semver.push('-');
rustc_semver.push_str(pre);
}
if !build.is_empty() {
rustc_semver.push('+');
rustc_semver.push_str(build);
}
Self {
rustc_semver,
commit_hash: env!("RUMMAGE_RUSTC_VERSION_COMMIT_HASH").to_string(),
commit_date: env!("RUMMAGE_RUSTC_VERSION_COMMIT_DATE").to_string(),
llvm_version: env!("RUMMAGE_RUSTC_VERSION_LLVM_VERSION").to_string(),
}
}
pub fn iter_props(&self) -> impl Iterator<Item = (&'static str, String)> {
[
("rustc_semver", self.rustc_semver.clone()),
("commit_hash", self.commit_hash.clone()),
("commit_date", self.commit_date.clone()),
("llvm_version", self.llvm_version.clone()),
]
.into_iter()
}
pub fn log_debug(&self) {
tracing::event!(
Level::DEBUG,
rustc_semver = self.rustc_semver,
commit_hash = self.commit_hash,
commit_date = self.commit_date,
llvm_version = self.llvm_version,
"Rustc information:"
);
}
}
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Clone, Debug)]
pub struct CompileInfo {
pub target: CargoTarget,
pub rustc: RustcVersion,
}
impl CompileInfo {
#[doc(hidden)]
pub fn gather() -> Self {
Self {
target: CargoTarget::gather(),
rustc: RustcVersion::gather(),
}
}
pub fn log_debug(&self) {
self.target.log_debug();
self.rustc.log_debug();
}
}
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Clone, Debug)]
pub struct SystemInfo {
pub hostname: Option<String>,
pub os: String,
pub linux_distro: Option<String>,
pub cpu_vendor: Option<String>,
pub cpu_brand_string: Option<String>,
pub num_cpus: usize,
pub num_physical_cpus: usize,
}
impl SystemInfo {
#[doc(hidden)]
pub fn gather() -> Self {
let os = sys_info::os_type()
.and_then(|ty| sys_info::os_release().map(|rel| (ty, rel)))
.map(|(ty, rel)| format!("{ty} {rel}"))
.unwrap_or("<failed to query OS information>".to_string());
let linux_distro = sys_info::linux_os_release()
.ok()
.and_then(|r| r.pretty_name);
let cpu_vendor;
let cpu_brand_string;
#[cfg(target_arch = "x86_64")]
{
let cpuid = raw_cpuid::CpuId::new();
cpu_vendor = cpuid.get_vendor_info().map(|v| v.as_str().to_string());
cpu_brand_string = cpuid
.get_processor_brand_string()
.map(|s| s.as_str().to_string());
}
#[cfg(not(target_arch = "x86_64"))]
{
cpu_vendor = None;
cpu_brand_string = None;
}
let num_cpus = num_cpus::get();
let num_physical_cpus = num_cpus::get_physical();
Self {
hostname: sys_info::hostname().ok(),
os,
linux_distro,
cpu_vendor,
cpu_brand_string,
num_cpus,
num_physical_cpus,
}
}
pub fn iter_props(&self) -> impl Iterator<Item = (&'static str, String)> {
[
("hostname", self.hostname.clone().unwrap_or_default()),
("os", self.os.clone()),
(
"linux_distro",
self.linux_distro.clone().unwrap_or_default(),
),
("cpu_vendor", self.cpu_vendor.clone().unwrap_or_default()),
(
"cpu_brand_string",
self.cpu_brand_string.clone().unwrap_or_default(),
),
("num_cpus", self.num_cpus.to_string()),
("num_physical_cpus", self.num_physical_cpus.to_string()),
]
.into_iter()
}
pub fn log_debug(&self) {
fn map_optional_string(s: &Option<String>) -> &str {
s.as_ref().map(|s| s.as_str()).unwrap_or("<failed to get>")
}
tracing::event!(
Level::DEBUG,
hostname = map_optional_string(&self.hostname),
os = self.os,
linux_distro = map_optional_string(&self.linux_distro),
cpu_vendor = map_optional_string(&self.cpu_vendor),
cpu_brand_string = map_optional_string(&self.cpu_brand_string),
num_cpus = self.num_cpus,
num_physical_cpus = self.num_physical_cpus,
"System information:"
)
}
}
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Clone, Debug)]
pub struct RuntimeEnvironment {
pub command_line: Vec<String>,
pub working_dir: Option<PathBuf>,
pub core_affinity: Option<Vec<usize>>,
pub envvars: BTreeMap<String, Option<String>>,
}
pub fn core_affinity_string(cores: &[usize]) -> String {
let max = *cores.iter().max().unwrap_or(&0) + 1;
let max = (max + 7) & !7;
let mut bitset = vec![false; max];
for id in cores {
bitset[max - *id - 1] = true;
}
let mut string = String::with_capacity(max + max / 4);
for nibble in bitset.chunks(4) {
for bit in nibble.iter() {
string.push(if *bit { '1' } else { '0' });
}
string.push(' ');
}
string.pop();
string
}
impl RuntimeEnvironment {
pub fn new() -> Self {
Self {
command_line: std::env::args().collect(),
working_dir: std::env::current_dir().ok(),
core_affinity: core_affinity::get_core_ids()
.map(|ids| ids.into_iter().map(|id| id.id).collect()),
envvars: BTreeMap::new(),
}
}
pub fn with_envvar(mut self, name: impl AsRef<str>) -> Self {
let name = name.as_ref().to_string();
let value = std::env::var(&name).ok();
self.envvars.insert(name, value);
self
}
pub fn with_envvars(mut self, names: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
for name in names {
self = self.with_envvar(name);
}
self
}
pub fn iter_props(&self) -> impl Iterator<Item = (&'static str, String)> + '_ {
let working_dir = self
.working_dir
.as_ref()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or("<failed to get>".to_string());
let core_affinity_string = match &self.core_affinity {
Some(cores) => core_affinity_string(cores),
None => "<not set>".to_string(),
};
[
("working_dir", working_dir),
("command_line_tokenized", format!("{:?}", self.command_line)),
("command_line_raw", self.command_line.join(" ")),
("core_affinity", core_affinity_string),
]
.into_iter()
}
pub fn log_debug(&self) {
tracing::event!(
Level::DEBUG,
args = format!("{:?}", self.command_line),
"Command line args:"
);
tracing::event!(
Level::DEBUG,
working_dir = format!("{:?}", self.working_dir),
"Working directory:"
);
let core_affinity_string = match &self.core_affinity {
Some(cores) => core_affinity_string(cores),
None => "<not set>".to_string(),
};
tracing::event!(
Level::DEBUG,
core_affinity = core_affinity_string,
"Core affinity:"
);
tracing::event!(
Level::DEBUG,
args = format!("{:?}", self.envvars),
"Environment variables:"
);
}
}
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Clone, Debug)]
pub struct RummageInfo {
pub crate_info: CrateInfo,
pub compile_info: CompileInfo,
pub system_info: SystemInfo,
pub runtime_env: RuntimeEnvironment,
}
impl RummageInfo {
pub fn with_envvar(mut self, name: impl AsRef<str>) -> Self {
self.runtime_env = self.runtime_env.with_envvar(name);
self
}
pub fn with_envvars(mut self, names: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
self.runtime_env = self.runtime_env.with_envvars(names);
self
}
pub fn log_debug(&self) {
self.crate_info.log_debug();
self.compile_info.log_debug();
self.system_info.log_debug();
self.runtime_env.log_debug();
}
}
#[macro_export]
macro_rules! info {
() => {{
::rummage::RummageInfo {
crate_info: ::rummage::_crate_info!(),
compile_info: ::rummage::CompileInfo::gather(),
system_info: ::rummage::SystemInfo::gather(),
runtime_env: ::rummage::RuntimeEnvironment::new(),
}
}};
}
#[macro_export]
macro_rules! log_all {
($info:expr) => {
$crate::log_all!($info, level = $crate::tracing::Level::INFO)
};
($info:expr, level=$level:expr) => {{
use $crate::tracing::event;
event!($level, "Collected rummage info:");
event!($level, " Crate info:");
for (prop, value) in $info.crate_info.iter_props() {
event!($level, " {:>25}: {}", prop, value);
}
event!($level, "");
event!($level, " Compilation target:");
for (prop, value) in $info.compile_info.target.iter_props() {
event!($level, " {:>25}: {}", prop, value);
}
event!($level, "");
event!($level, " Rustc version:");
for (prop, value) in $info.compile_info.rustc.iter_props() {
event!($level, " {:>25}: {}", prop, value);
}
event!($level, "");
event!($level, " System info:");
for (prop, value) in $info.system_info.iter_props() {
event!($level, " {:>25}: {}", prop, value);
}
event!($level, "");
event!($level, " Runtime environment:");
for (prop, value) in $info.runtime_env.iter_props() {
event!($level, " {:>25}: {}", prop, value);
}
event!($level, "");
event!($level, " Environment variables:");
for (name, value) in $info.runtime_env.envvars.iter() {
event!(
$level,
" {}={}",
name,
value.as_deref().unwrap_or("")
);
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpu_affinity_string() {
assert_eq!(core_affinity_string(&[0]), "0000 0001");
assert_eq!(core_affinity_string(&[1]), "0000 0010");
assert_eq!(core_affinity_string(&[7]), "1000 0000");
assert_eq!(core_affinity_string(&[0, 1]), "0000 0011");
assert_eq!(core_affinity_string(&[0, 1, 2, 3]), "0000 1111");
assert_eq!(core_affinity_string(&[0, 1, 2, 3, 4, 5, 6, 7]), "1111 1111");
assert_eq!(core_affinity_string(&[8]), "0000 0001 0000 0000");
}
}