use std::sync::LazyLock;
use std::sync::atomic::{AtomicU16, AtomicU32, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Backends(u16);
impl Backends {
pub const OFF: Self = Self(0);
pub const INSTANT: Self = Self(1 << 0);
pub const FASTRACE: Self = Self(1 << 1);
pub const WEB: Self = Self(1 << 2);
pub const PUFFIN: Self = Self(1 << 3);
pub const TRACY: Self = Self(1 << 4);
pub const SUPERLUMINAL: Self = Self(1 << 6);
pub const TRACING: Self = Self(1 << 7);
#[must_use]
pub const fn empty() -> Self {
Self::OFF
}
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub fn from_env_value(s: &str) -> Option<Self> {
let mut out = Self::OFF;
let mut saw_off = false;
let mut saw_other = false;
for part in s.split(',') {
let name = part.trim().to_ascii_lowercase();
if name == "off" {
saw_off = true;
continue;
}
let info = BACKENDS_INFO.iter().find(|info| info.name == name)?;
out |= info.bit;
saw_other = true;
}
if saw_off && saw_other {
return None;
}
Some(out)
}
}
pub(crate) struct BackendInfo {
pub bit: Backends,
pub name: &'static str,
pub feature: &'static str,
pub available: bool,
}
pub(crate) const BACKENDS_INFO: &[BackendInfo] = &[
BackendInfo {
bit: Backends::INSTANT,
name: "instant",
feature: "instant",
available: crate::profiling::instant_wrap::AVAILABLE,
},
BackendInfo {
bit: Backends::FASTRACE,
name: "fastrace",
feature: "fastrace",
available: crate::profiling::fastrace_wrap::AVAILABLE,
},
BackendInfo {
bit: Backends::WEB,
name: "web",
feature: "web",
available: crate::profiling::instant_wrap::AVAILABLE,
},
BackendInfo {
bit: Backends::PUFFIN,
name: "puffin",
feature: "profile-with-puffin",
available: crate::profiling::puffin_wrap::AVAILABLE,
},
BackendInfo {
bit: Backends::TRACY,
name: "tracy",
feature: "profile-with-tracy",
available: crate::profiling::tracy_wrap::AVAILABLE,
},
BackendInfo {
bit: Backends::SUPERLUMINAL,
name: "superluminal",
feature: "profile-with-superluminal",
available: crate::profiling::superluminal_wrap::AVAILABLE,
},
BackendInfo {
bit: Backends::TRACING,
name: "tracing",
feature: "profile-with-tracing",
available: crate::profiling::tracing_wrap::AVAILABLE,
},
];
impl std::ops::BitOr for Backends {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for Backends {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
pub struct ObserveConfig {
backends: AtomicU16,
error_hook_throttle: AtomicU32,
}
impl ObserveConfig {
#[must_use]
pub const fn new() -> Self {
Self {
backends: AtomicU16::new(Backends::FASTRACE.0),
error_hook_throttle: AtomicU32::new(0),
}
}
pub fn set_backends(&self, backends: Backends) {
let previous = Backends(self.backends.swap(backends.0, Ordering::AcqRel));
Self::warn_unavailable(backends);
if backends.contains(Backends::PUFFIN)
&& !previous.contains(Backends::PUFFIN)
&& crate::profiling::puffin_wrap::AVAILABLE
{
crate::profiling::puffin_wrap::on_enable();
}
}
#[must_use]
pub fn backends(&self) -> Backends {
Backends(self.backends.load(Ordering::Relaxed))
}
fn warn_unavailable(requested: Backends) {
static WARNED: AtomicU16 = AtomicU16::new(0);
for info in BACKENDS_INFO {
if info.available || !requested.contains(info.bit) {
continue;
}
if WARNED.fetch_or(info.bit.0, Ordering::AcqRel) & info.bit.0 == 0 {
log::warn!(
target: crate::log_targets::CONFIG,
"profiling backend '{}' requested but not compiled in — enable cargo feature `{}`",
info.name,
info.feature
);
}
}
}
pub fn set_error_hook_throttle(&self, max_per_second: u32) {
self.error_hook_throttle
.store(max_per_second, Ordering::Release);
}
#[must_use]
pub fn error_hook_throttle(&self) -> u32 {
self.error_hook_throttle.load(Ordering::Relaxed)
}
}
impl Default for ObserveConfig {
fn default() -> Self {
Self::new()
}
}
static CONFIG: LazyLock<ObserveConfig> = LazyLock::new(|| {
let cfg = ObserveConfig::new();
if let Ok(value) = std::env::var(crate::env_vars::OBSERVE_PROFILE) {
match Backends::from_env_value(&value) {
Some(backends) => cfg.set_backends(backends),
None => log::warn!(
target: crate::log_targets::CONFIG,
"invalid OBSERVE_PROFILE={value:?}; expected comma-separated \
off|instant|fastrace|web|puffin|tracy|superluminal|tracing — keeping default"
),
}
}
if let Ok(value) = std::env::var(crate::env_vars::OBSERVE_ERROR_THROTTLE) {
match value.trim().parse::<u32>() {
Ok(n) => cfg.set_error_hook_throttle(n),
Err(_) => log::warn!(
target: crate::log_targets::CONFIG,
"invalid OBSERVE_ERROR_THROTTLE={value:?}; expected a u32 — keeping default (0 = unlimited)"
),
}
}
cfg
});
#[must_use]
pub fn config() -> &'static ObserveConfig {
&CONFIG
}
pub(crate) fn env_enum<T: Copy>(
var: &str,
parse: impl Fn(&str) -> Option<T>,
default: T,
expected: &str,
) -> T {
let Ok(value) = std::env::var(var) else {
return default;
};
if let Some(parsed) = parse(value.trim().to_ascii_lowercase().as_str()) {
return parsed;
}
log::warn!(
target: crate::log_targets::CONFIG,
"invalid {var}={value:?}; expected {expected} — keeping default"
);
default
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ReportMode {
#[default]
Off,
Text,
Json,
}
static REPORT_MODE: LazyLock<ReportMode> = LazyLock::new(|| {
env_enum(
crate::env_vars::OBSERVE_REPORT,
|name| match name {
"off" | "" => Some(ReportMode::Off),
"text" | "1" | "true" => Some(ReportMode::Text),
"json" => {
if cfg!(not(feature = "serde")) {
log::warn!(
target: crate::log_targets::CONFIG,
"OBSERVE_REPORT=json requested but cargo feature `serde` is not compiled in — falling back to text"
);
Some(ReportMode::Text)
} else {
Some(ReportMode::Json)
}
}
_ => None,
},
ReportMode::Off,
"off|text|json",
)
});
#[must_use]
pub fn report_mode() -> ReportMode {
*REPORT_MODE
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ColorMode {
#[default]
Auto,
Always,
Never,
}
static COLOR_MODE: LazyLock<ColorMode> = LazyLock::new(|| {
env_enum(
crate::env_vars::OBSERVE_COLOR,
|name| match name {
"always" | "1" | "true" => Some(ColorMode::Always),
"never" | "0" | "false" => Some(ColorMode::Never),
_ => None,
},
ColorMode::Auto,
"auto|always|never",
)
});
#[must_use]
pub fn color_mode() -> ColorMode {
*COLOR_MODE
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_bits_roundtrip() {
let cfg = ObserveConfig::new();
cfg.backends.store(99, Ordering::Release);
assert_eq!(cfg.backends(), Backends(99));
}
#[test]
fn set_backends_stores_unavailable_bits() {
let cfg = ObserveConfig::new();
cfg.set_backends(Backends::FASTRACE | Backends::TRACY);
assert_eq!(cfg.backends(), Backends::FASTRACE | Backends::TRACY);
}
#[test]
fn parse_env_values() {
assert_eq!(Backends::from_env_value("off"), Some(Backends::OFF));
assert_eq!(Backends::from_env_value("OFF"), Some(Backends::OFF));
assert_eq!(Backends::from_env_value("INSTANT"), Some(Backends::INSTANT));
assert_eq!(
Backends::from_env_value(" fastrace "),
Some(Backends::FASTRACE)
);
assert_eq!(Backends::from_env_value("web"), Some(Backends::WEB));
assert_eq!(
Backends::from_env_value("fastrace,tracy"),
Some(Backends::FASTRACE | Backends::TRACY)
);
assert_eq!(
Backends::from_env_value("Instant, PUFFIN ,tracing"),
Some(Backends::INSTANT | Backends::PUFFIN | Backends::TRACING)
);
assert_eq!(
Backends::from_env_value("superluminal"),
Some(Backends::SUPERLUMINAL)
);
assert_eq!(Backends::from_env_value("tracing"), Some(Backends::TRACING));
assert_eq!(Backends::from_env_value("off,fastrace"), None);
assert_eq!(Backends::from_env_value("bogus"), None);
assert_eq!(Backends::from_env_value(""), None);
}
}