use std::fmt;
pub const ENABLED: bool = cfg!(feature = "perf-counters");
macro_rules! perf_counters {
($($(#[$meta:meta])* $name:ident => $label:literal,)*) => {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(usize)]
pub enum PerfCounter {
$($(#[$meta])* $name,)*
}
impl PerfCounter {
pub const ALL: &'static [PerfCounter] = &[$(PerfCounter::$name,)*];
pub const fn name(self) -> &'static str {
match self {
$(PerfCounter::$name => $label,)*
}
}
}
};
}
perf_counters! {
PropertyChangesRaised => "property changes raised",
PropertyChangesEffective => "property changes of an effective value",
PropertyChangesWithPropertyHandlers => "property changes with handlers of the property",
PropertyChangesWithObjectListeners => "property changes with listeners of the object",
InheritanceAncestorChanges => "inheritance ancestor changes",
InheritedValuesCompared => "inherited values compared on an ancestor change",
InheritedValuesDiffering => "inherited values differing on an ancestor change",
InheritedValueWalkVisits => "objects visited by inherited value walks",
LogicalTreeAttachments => "elements attached to a logical tree",
LogicalTreeDetachments => "elements detached from a logical tree",
ImplicitThemeLookups => "implicit theme lookups",
StyleHostsWalked => "style hosts walked",
StylesEvaluated => "styles evaluated",
StylesMatched => "styles matched",
ControlThemesEvaluated => "control themes evaluated",
ControlThemesMatched => "control themes matched",
StyleInstancesAttached => "style instances attached",
StyleInstancesCreated => "style instances created",
ResourceLookups => "resource lookups",
ResourceHostsProbed => "resource hosts probed",
BindingsInstanced => "bindings instanced",
BindingExpressionsCreated => "binding expressions created",
TemplateBindingExpressionsCreated => "template binding expressions created",
DynamicResourceExpressionsCreated => "dynamic resource expressions created",
BindingValuesPublished => "binding values published",
TextLayoutsCreated => "text layouts created",
TextLinesFormatted => "text lines formatted",
TextRunCacheHits => "text run cache hits",
TextRunCacheMisses => "text run cache misses",
TextRunsShaped => "text runs shaped",
ContainersRecycled => "containers recycled",
ContainersReused => "containers reused",
ContainersCreated => "containers created",
ContentPresenterChildrenReplaced => "content presenter children replaced",
}
#[cfg(feature = "perf-counters")]
const COUNT: usize = PerfCounter::ALL.len();
#[cfg(feature = "perf-counters")]
#[macro_export]
macro_rules! perf_count {
($counter:ident) => {
$crate::diagnostics::perf_counters::count($crate::diagnostics::perf_counters::PerfCounter::$counter)
};
}
#[cfg(not(feature = "perf-counters"))]
#[macro_export]
macro_rules! perf_count {
($counter:ident) => {{
let _ = $crate::diagnostics::perf_counters::PerfCounter::$counter;
}};
}
#[cfg(feature = "perf-counters")]
#[macro_export]
macro_rules! perf_count_virtual {
($member:expr) => {
$crate::diagnostics::perf_counters::count_virtual_call($member)
};
}
#[cfg(not(feature = "perf-counters"))]
#[macro_export]
macro_rules! perf_count_virtual {
($member:expr) => {
()
};
}
#[cfg(feature = "perf-counters")]
mod counting {
use super::{PerfCounter, COUNT};
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
crate::ferro_thread_local! {
static COUNTS: [Cell<u64>; COUNT] = const { [const { Cell::new(0) }; COUNT] };
static VIRTUAL_CALLS: RefCell<HashMap<&'static str, u64>> = RefCell::new(HashMap::new());
}
#[inline]
pub(super) fn count(counter: PerfCounter) {
let _ = COUNTS.try_with(|counts| {
let cell = &counts[counter as usize];
cell.set(cell.get() + 1);
});
}
pub(super) fn count_virtual_call(member: &'static str) {
let _ = VIRTUAL_CALLS.try_with(|calls| {
if let Ok(mut calls) = calls.try_borrow_mut() {
*calls.entry(member).or_insert(0) += 1;
}
});
}
pub(super) fn counts() -> [u64; COUNT] {
COUNTS.with(|counts| std::array::from_fn(|index| counts[index].get()))
}
pub(super) fn virtual_calls() -> Vec<(&'static str, u64)> {
let mut calls: Vec<(&'static str, u64)> =
VIRTUAL_CALLS.with(|calls| calls.borrow().iter().map(|(member, count)| (*member, *count)).collect());
super::sort_virtual_calls(&mut calls);
calls
}
pub(super) fn reset() {
COUNTS.with(|counts| {
for cell in counts.iter() {
cell.set(0);
}
});
VIRTUAL_CALLS.with(|calls| calls.borrow_mut().clear());
}
}
#[cfg(feature = "perf-counters")]
fn sort_virtual_calls(calls: &mut [(&'static str, u64)]) {
calls.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(b.0)));
}
#[cfg(feature = "perf-counters")]
#[inline]
pub fn count(counter: PerfCounter) {
counting::count(counter)
}
#[cfg(not(feature = "perf-counters"))]
#[inline(always)]
pub fn count(_counter: PerfCounter) {}
#[cfg(feature = "perf-counters")]
#[inline]
pub fn count_virtual_call(member: &'static str) {
counting::count_virtual_call(member)
}
#[cfg(not(feature = "perf-counters"))]
#[inline(always)]
pub fn count_virtual_call(_member: &'static str) {}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PerfCountersSnapshot {
#[cfg(feature = "perf-counters")]
counts: Option<[u64; COUNT]>,
#[cfg(feature = "perf-counters")]
virtual_calls: Vec<(&'static str, u64)>,
}
#[cfg(not(feature = "perf-counters"))]
const _: () = assert!(std::mem::size_of::<PerfCountersSnapshot>() == 0);
pub fn snapshot() -> PerfCountersSnapshot {
#[cfg(feature = "perf-counters")]
{
PerfCountersSnapshot { counts: Some(counting::counts()), virtual_calls: counting::virtual_calls() }
}
#[cfg(not(feature = "perf-counters"))]
{
PerfCountersSnapshot {}
}
}
pub fn reset() {
#[cfg(feature = "perf-counters")]
counting::reset();
}
impl PerfCountersSnapshot {
pub fn get(&self, counter: PerfCounter) -> u64 {
#[cfg(feature = "perf-counters")]
{
self.counts.map_or(0, |counts| counts[counter as usize])
}
#[cfg(not(feature = "perf-counters"))]
{
let _ = counter;
0
}
}
pub fn virtual_calls(&self) -> &[(&'static str, u64)] {
#[cfg(feature = "perf-counters")]
{
&self.virtual_calls
}
#[cfg(not(feature = "perf-counters"))]
{
&[]
}
}
pub fn virtual_calls_total(&self) -> u64 {
self.virtual_calls().iter().map(|(_, count)| *count).sum()
}
pub fn since(&self, earlier: &PerfCountersSnapshot) -> PerfCountersSnapshot {
#[cfg(feature = "perf-counters")]
{
let counts: [u64; COUNT] = std::array::from_fn(|index| {
let counter = PerfCounter::ALL[index];
self.get(counter).saturating_sub(earlier.get(counter))
});
let mut virtual_calls: Vec<(&'static str, u64)> = self
.virtual_calls
.iter()
.map(|(member, count)| {
let before =
earlier.virtual_calls.iter().find(|(other, _)| other == member).map_or(0, |(_, count)| *count);
(*member, count.saturating_sub(before))
})
.filter(|(_, count)| *count > 0)
.collect();
sort_virtual_calls(&mut virtual_calls);
PerfCountersSnapshot { counts: Some(counts), virtual_calls }
}
#[cfg(not(feature = "perf-counters"))]
{
let _ = earlier;
PerfCountersSnapshot {}
}
}
pub fn plus(&self, other: &PerfCountersSnapshot) -> PerfCountersSnapshot {
#[cfg(feature = "perf-counters")]
{
let counts: [u64; COUNT] = std::array::from_fn(|index| {
let counter = PerfCounter::ALL[index];
self.get(counter) + other.get(counter)
});
let mut virtual_calls = self.virtual_calls.clone();
for (member, count) in &other.virtual_calls {
match virtual_calls.iter_mut().find(|(existing, _)| *existing == *member) {
Some((_, total)) => *total += count,
None => virtual_calls.push((*member, *count)),
}
}
sort_virtual_calls(&mut virtual_calls);
PerfCountersSnapshot { counts: Some(counts), virtual_calls }
}
#[cfg(not(feature = "perf-counters"))]
{
let _ = other;
PerfCountersSnapshot {}
}
}
pub fn report(&self) -> String {
self.format(None)
}
pub fn report_per(&self, divisor: u64, unit: &str) -> String {
self.format(Some((divisor, unit)))
}
fn format(&self, per: Option<(u64, &str)>) -> String {
if !ENABLED {
return String::from("perf counters: not compiled in (the feature `perf-counters` is off)\n");
}
let line = |name: &str, count: u64| match per {
Some((divisor, unit)) if divisor > 0 => {
format!(" {name}: {count} ({:.2} per {unit})\n", count as f64 / divisor as f64)
}
_ => format!(" {name}: {count}\n"),
};
let mut text = String::new();
for counter in PerfCounter::ALL {
text.push_str(&line(counter.name(), self.get(*counter)));
}
text.push_str(&line("virtual calls", self.virtual_calls_total()));
for (member, count) in self.virtual_calls() {
text.push_str(&line(&format!(" {member}"), *count));
}
text
}
}
impl fmt::Display for PerfCountersSnapshot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.report())
}
}
#[cfg(not(feature = "perf-counters"))]
const _: () = {
crate::perf_count!(PropertyChangesRaised);
crate::perf_count_virtual!("FerroObject::on_property_changed")
};