use std::cell::RefCell;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::time::{Duration, Instant};
use rustc_hash::FxHashMap;
use super::allocation::{
ALLOCATION_TRACKING_ENABLED, ThreadAllocationCounters, current_thread_allocation,
pause_allocation_tracking, reset_allocation_counters,
};
use super::attribution::{AttributedSpanKey, SpanAttribution};
use super::metrics::{CounterMetrics, Metrics, SpanAllocationDelta};
pub(super) const PROFILER_SHARDS: usize = 32;
thread_local! {
static PROFILE_STACK: RefCell<std::vec::Vec<ProfileFrame>> = const { RefCell::new(std::vec::Vec::new()) };
}
#[derive(Debug)]
struct ProfileFrame {
name: &'static str,
attribution: SpanAttribution,
start: Instant,
child_duration: Duration,
alloc_start: ThreadAllocationCounters,
child_alloc: ThreadAllocationCounters,
}
#[derive(Debug)]
pub struct ProfileGuard {
profiler: &'static Profiler,
active: bool,
}
impl ProfileGuard {
#[inline]
fn start(
profiler: &'static Profiler,
name: &'static str,
attribution: SpanAttribution,
) -> Self {
let _allocation_tracking = pause_allocation_tracking();
let alloc_start = current_thread_allocation();
PROFILE_STACK.with(|stack| {
stack.borrow_mut().push(ProfileFrame {
name,
attribution,
start: Instant::now(),
child_duration: Duration::ZERO,
alloc_start,
child_alloc: ThreadAllocationCounters::ZERO,
});
});
Self {
profiler,
active: true,
}
}
}
impl Drop for ProfileGuard {
fn drop(&mut self) {
if !self.active {
return;
}
let alloc_now = current_thread_allocation();
PROFILE_STACK.with(|stack| {
let mut stack = stack.borrow_mut();
let Some(frame) = stack.pop() else {
return;
};
let duration = frame.start.elapsed();
let alloc_delta = alloc_now.since(frame.alloc_start);
if let Some(parent) = stack.last_mut() {
parent.child_duration += duration;
parent.child_alloc.calls =
parent.child_alloc.calls.saturating_add(alloc_delta.calls);
parent.child_alloc.bytes =
parent.child_alloc.bytes.saturating_add(alloc_delta.bytes);
}
self.profiler.record_span_sample_attributed_enabled(
frame.name,
frame.attribution,
duration,
frame.child_duration,
SpanAllocationDelta {
calls: alloc_delta.calls,
bytes: alloc_delta.bytes,
child_calls: frame.child_alloc.calls,
child_bytes: frame.child_alloc.bytes,
},
);
});
}
}
#[derive(Debug)]
pub struct Timer {
start: Instant,
name: &'static str,
}
impl Timer {
#[inline]
pub fn start(name: &'static str) -> Self {
Self {
start: Instant::now(),
name,
}
}
#[inline]
pub fn elapsed(&self) -> Duration {
self.start.elapsed()
}
#[inline]
pub fn stop(self) -> Duration {
self.elapsed()
}
#[inline]
pub fn record(self, profiler: &Profiler) {
profiler.record(self.name, self.elapsed());
}
}
#[derive(Debug)]
pub struct Profiler {
pub(super) metrics: [RwLock<FxHashMap<&'static str, Metrics>>; PROFILER_SHARDS],
pub(super) attributed: [RwLock<FxHashMap<AttributedSpanKey, Metrics>>; PROFILER_SHARDS],
pub(super) counters: [RwLock<FxHashMap<&'static str, CounterMetrics>>; PROFILER_SHARDS],
enabled: AtomicBool,
}
impl Profiler {
pub fn new() -> Self {
Self {
metrics: std::array::from_fn(|_| RwLock::new(FxHashMap::default())),
attributed: std::array::from_fn(|_| RwLock::new(FxHashMap::default())),
counters: std::array::from_fn(|_| RwLock::new(FxHashMap::default())),
enabled: AtomicBool::new(false),
}
}
pub fn enabled() -> Self {
let p = Self::new();
p.enable();
p
}
pub fn enable(&self) {
reset_allocation_counters();
ALLOCATION_TRACKING_ENABLED.store(true, Ordering::Relaxed);
self.enabled.store(true, Ordering::Relaxed);
}
pub fn disable(&self) {
self.enabled.store(false, Ordering::Relaxed);
ALLOCATION_TRACKING_ENABLED.store(false, Ordering::Relaxed);
}
#[inline]
pub fn is_enabled(&self) -> bool {
self.enabled.load(Ordering::Relaxed)
}
#[inline]
pub fn timer(&self, name: &'static str) -> Option<Timer> {
if self.is_enabled() {
Some(Timer::start(name))
} else {
None
}
}
pub fn record(&self, name: &'static str, duration: Duration) {
if !self.is_enabled() {
return;
}
self.record_enabled(name, duration);
}
#[doc(hidden)]
pub fn record_enabled(&self, name: &'static str, duration: Duration) {
self.record_sample_enabled(name, duration, Duration::ZERO);
}
#[inline]
pub fn global_span(&'static self, name: &'static str) -> Option<ProfileGuard> {
self.global_span_attributed(name, SpanAttribution::EMPTY)
}
#[inline]
pub fn global_span_attributed(
&'static self,
name: &'static str,
attribution: SpanAttribution,
) -> Option<ProfileGuard> {
if self.is_enabled() {
Some(ProfileGuard::start(self, name, attribution))
} else {
None
}
}
#[doc(hidden)]
pub fn record_sample_enabled(
&self,
name: &'static str,
duration: Duration,
child_duration: Duration,
) {
self.record_span_sample_attributed_enabled(
name,
SpanAttribution::EMPTY,
duration,
child_duration,
SpanAllocationDelta::ZERO,
);
}
pub fn record_attributed(
&self,
name: &'static str,
attribution: SpanAttribution,
duration: Duration,
) {
if !self.is_enabled() {
return;
}
self.record_span_sample_attributed_enabled(
name,
attribution,
duration,
Duration::ZERO,
SpanAllocationDelta::ZERO,
);
}
fn record_span_sample_attributed_enabled(
&self,
name: &'static str,
attribution: SpanAttribution,
duration: Duration,
child_duration: Duration,
allocations: SpanAllocationDelta,
) {
let _allocation_tracking = pause_allocation_tracking();
if attribution.is_empty() {
let mut metrics = self.metrics_write(Self::shard_index(name));
metrics.entry(name).or_default().record_span_sample(
duration,
child_duration,
allocations,
);
} else {
let mut metrics = self.attributed_write(Self::shard_index(name));
metrics
.entry(AttributedSpanKey { name, attribution })
.or_default()
.record_span_sample(duration, child_duration, allocations);
}
}
#[inline]
pub(super) fn metrics_read(
&self,
shard: usize,
) -> RwLockReadGuard<'_, FxHashMap<&'static str, Metrics>> {
super::shard::lock(&self.metrics, shard)
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[inline]
fn metrics_write(
&self,
shard: usize,
) -> RwLockWriteGuard<'_, FxHashMap<&'static str, Metrics>> {
super::shard::lock(&self.metrics, shard)
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[inline]
fn attributed_write(
&self,
shard: usize,
) -> RwLockWriteGuard<'_, FxHashMap<AttributedSpanKey, Metrics>> {
super::shard::lock(&self.attributed, shard)
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[inline]
pub(super) fn counters_write(
&self,
shard: usize,
) -> RwLockWriteGuard<'_, FxHashMap<&'static str, CounterMetrics>> {
super::shard::lock(&self.counters, shard)
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[inline]
pub(super) fn shard_index(name: &str) -> usize {
debug_assert!(PROFILER_SHARDS.is_power_of_two());
let mut hash = 0xcbf2_9ce4_8422_2325u64;
for byte in name.as_bytes() {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
(hash as usize) & (PROFILER_SHARDS - 1)
}
}
impl Default for Profiler {
fn default() -> Self {
Self::new()
}
}
static GLOBAL_PROFILER: once_cell::sync::Lazy<Profiler> = once_cell::sync::Lazy::new(Profiler::new);
#[inline]
pub fn global_profiler() -> &'static Profiler {
&GLOBAL_PROFILER
}