#![forbid(unsafe_code)]
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProfilingMode {
Cpu,
Allocation,
Syscall,
Workflow,
}
impl ProfilingMode {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::Cpu => "cpu",
Self::Allocation => "allocation",
Self::Syscall => "syscall",
Self::Workflow => "workflow",
}
}
#[must_use]
pub const fn typical_tool(&self) -> &'static str {
match self {
Self::Cpu => "perf/samply/flamegraph",
Self::Allocation => "DHAT/heaptrack",
Self::Syscall => "strace/perf-syscall",
Self::Workflow => "tracing spans",
}
}
pub const ALL: &'static [ProfilingMode] =
&[Self::Cpu, Self::Allocation, Self::Syscall, Self::Workflow];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum HotspotConfidence {
High,
Medium,
Low,
}
#[derive(Debug, Clone)]
pub struct Hotspot {
pub symbol: String,
pub mode: ProfilingMode,
pub contribution_pct: f64,
pub confidence: HotspotConfidence,
pub crate_name: String,
pub file: String,
pub line: u32,
pub likely_artifact: bool,
pub note: String,
}
impl Hotspot {
#[must_use]
pub fn new(symbol: &str, mode: ProfilingMode) -> Self {
Self {
symbol: symbol.to_string(),
mode,
contribution_pct: 0.0,
confidence: HotspotConfidence::Medium,
crate_name: String::new(),
file: String::new(),
line: 0,
likely_artifact: false,
note: String::new(),
}
}
#[must_use]
pub fn contribution_pct(mut self, pct: f64) -> Self {
self.contribution_pct = pct;
self
}
#[must_use]
pub fn location(mut self, crate_name: &str, file: &str, line: u32) -> Self {
self.crate_name = crate_name.to_string();
self.file = file.to_string();
self.line = line;
self
}
#[must_use]
pub fn confidence(mut self, confidence: HotspotConfidence) -> Self {
self.confidence = confidence;
self
}
#[must_use]
pub fn artifact(mut self) -> Self {
self.likely_artifact = true;
self
}
#[must_use]
pub fn note(mut self, note: &str) -> Self {
self.note = note.to_string();
self
}
#[must_use]
pub fn is_actionable(&self) -> bool {
!self.likely_artifact
&& self.confidence != HotspotConfidence::Low
&& self.contribution_pct >= 5.0
}
}
#[derive(Debug, Clone)]
pub struct HotspotTable {
pub fixture_id: String,
pub baseline_id: String,
pub hotspots: Vec<Hotspot>,
}
impl HotspotTable {
#[must_use]
pub fn new(fixture_id: &str, baseline_id: &str) -> Self {
Self {
fixture_id: fixture_id.to_string(),
baseline_id: baseline_id.to_string(),
hotspots: Vec::new(),
}
}
pub fn add(&mut self, hotspot: Hotspot) {
self.hotspots.push(hotspot);
}
#[must_use]
pub fn ranked(&self) -> Vec<&Hotspot> {
let mut sorted: Vec<&Hotspot> = self.hotspots.iter().collect();
sorted.sort_by(|a, b| {
b.contribution_pct
.partial_cmp(&a.contribution_pct)
.unwrap_or(std::cmp::Ordering::Equal)
});
sorted
}
#[must_use]
pub fn actionable(&self) -> Vec<&Hotspot> {
self.ranked()
.into_iter()
.filter(|h| h.is_actionable())
.collect()
}
#[must_use]
pub fn by_mode(&self, mode: ProfilingMode) -> Vec<&Hotspot> {
self.hotspots.iter().filter(|h| h.mode == mode).collect()
}
#[must_use]
pub fn total_contribution(&self, mode: ProfilingMode) -> f64 {
self.hotspots
.iter()
.filter(|h| h.mode == mode)
.map(|h| h.contribution_pct)
.sum()
}
#[must_use]
pub fn covered_modes(&self) -> HashSet<ProfilingMode> {
self.hotspots.iter().map(|h| h.mode).collect()
}
#[must_use]
pub fn to_json(&self) -> String {
let entries: Vec<String> = self
.hotspots
.iter()
.map(|h| {
format!(
r#" {{
"symbol": "{}",
"mode": "{}",
"contribution_pct": {:.1},
"confidence": "{}",
"crate": "{}",
"file": "{}",
"line": {},
"likely_artifact": {},
"actionable": {}
}}"#,
h.symbol,
h.mode.label(),
h.contribution_pct,
match h.confidence {
HotspotConfidence::High => "high",
HotspotConfidence::Medium => "medium",
HotspotConfidence::Low => "low",
},
h.crate_name,
h.file,
h.line,
h.likely_artifact,
h.is_actionable(),
)
})
.collect();
format!(
r#"{{
"fixture_id": "{}",
"baseline_id": "{}",
"hotspot_count": {},
"actionable_count": {},
"covered_modes": [{}],
"hotspots": [
{}
]
}}"#,
self.fixture_id,
self.baseline_id,
self.hotspots.len(),
self.actionable().len(),
self.covered_modes()
.iter()
.map(|m| format!("\"{}\"", m.label()))
.collect::<Vec<_>>()
.join(", "),
entries.join(",\n"),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn profiling_modes_all_have_labels() {
for mode in ProfilingMode::ALL {
assert!(!mode.label().is_empty());
assert!(!mode.typical_tool().is_empty());
}
}
#[test]
fn hotspot_builder() {
let h = Hotspot::new("ftui_render::diff::compute", ProfilingMode::Cpu)
.contribution_pct(34.2)
.location("ftui-render", "src/diff.rs", 142)
.confidence(HotspotConfidence::High)
.note("Inner loop dominates");
assert_eq!(h.symbol, "ftui_render::diff::compute");
assert_eq!(h.mode, ProfilingMode::Cpu);
assert!((h.contribution_pct - 34.2).abs() < 0.01);
assert_eq!(h.crate_name, "ftui-render");
assert_eq!(h.file, "src/diff.rs");
assert_eq!(h.line, 142);
assert_eq!(h.confidence, HotspotConfidence::High);
assert!(!h.likely_artifact);
assert!(h.is_actionable());
}
#[test]
fn hotspot_actionable_criteria() {
let actionable = Hotspot::new("f", ProfilingMode::Cpu)
.contribution_pct(10.0)
.confidence(HotspotConfidence::High);
assert!(actionable.is_actionable());
let low_conf = Hotspot::new("f", ProfilingMode::Cpu)
.contribution_pct(10.0)
.confidence(HotspotConfidence::Low);
assert!(!low_conf.is_actionable());
let artifact = Hotspot::new("f", ProfilingMode::Cpu)
.contribution_pct(10.0)
.artifact();
assert!(!artifact.is_actionable());
let small = Hotspot::new("f", ProfilingMode::Cpu)
.contribution_pct(2.0)
.confidence(HotspotConfidence::High);
assert!(!small.is_actionable());
}
#[test]
fn hotspot_table_ranking() {
let mut table = HotspotTable::new("fixture-1", "baseline-001");
table.add(Hotspot::new("a", ProfilingMode::Cpu).contribution_pct(10.0));
table.add(Hotspot::new("b", ProfilingMode::Cpu).contribution_pct(30.0));
table.add(Hotspot::new("c", ProfilingMode::Cpu).contribution_pct(20.0));
let ranked = table.ranked();
assert_eq!(ranked[0].symbol, "b"); assert_eq!(ranked[1].symbol, "c"); assert_eq!(ranked[2].symbol, "a"); }
#[test]
fn hotspot_table_by_mode() {
let mut table = HotspotTable::new("f", "b");
table.add(Hotspot::new("cpu1", ProfilingMode::Cpu).contribution_pct(30.0));
table.add(Hotspot::new("alloc1", ProfilingMode::Allocation).contribution_pct(20.0));
table.add(Hotspot::new("cpu2", ProfilingMode::Cpu).contribution_pct(15.0));
let cpu = table.by_mode(ProfilingMode::Cpu);
assert_eq!(cpu.len(), 2);
let alloc = table.by_mode(ProfilingMode::Allocation);
assert_eq!(alloc.len(), 1);
}
#[test]
fn hotspot_table_total_contribution() {
let mut table = HotspotTable::new("f", "b");
table.add(Hotspot::new("a", ProfilingMode::Cpu).contribution_pct(30.0));
table.add(Hotspot::new("b", ProfilingMode::Cpu).contribution_pct(25.0));
table.add(Hotspot::new("c", ProfilingMode::Allocation).contribution_pct(10.0));
let cpu_total = table.total_contribution(ProfilingMode::Cpu);
assert!((cpu_total - 55.0).abs() < 0.01);
let alloc_total = table.total_contribution(ProfilingMode::Allocation);
assert!((alloc_total - 10.0).abs() < 0.01);
}
#[test]
fn hotspot_table_covered_modes() {
let mut table = HotspotTable::new("f", "b");
table.add(Hotspot::new("a", ProfilingMode::Cpu).contribution_pct(30.0));
table.add(Hotspot::new("b", ProfilingMode::Syscall).contribution_pct(10.0));
let modes = table.covered_modes();
assert!(modes.contains(&ProfilingMode::Cpu));
assert!(modes.contains(&ProfilingMode::Syscall));
assert!(!modes.contains(&ProfilingMode::Allocation));
}
#[test]
fn hotspot_table_actionable_filters() {
let mut table = HotspotTable::new("f", "b");
table.add(
Hotspot::new("real", ProfilingMode::Cpu)
.contribution_pct(25.0)
.confidence(HotspotConfidence::High),
);
table.add(
Hotspot::new("noise", ProfilingMode::Cpu)
.contribution_pct(3.0)
.confidence(HotspotConfidence::Low),
);
table.add(
Hotspot::new("artifact", ProfilingMode::Cpu)
.contribution_pct(15.0)
.artifact(),
);
let actionable = table.actionable();
assert_eq!(actionable.len(), 1);
assert_eq!(actionable[0].symbol, "real");
}
#[test]
fn hotspot_table_to_json() {
let mut table = HotspotTable::new("render_80x24", "baseline-001");
table.add(
Hotspot::new("ftui_render::diff::compute", ProfilingMode::Cpu)
.contribution_pct(34.2)
.location("ftui-render", "src/diff.rs", 142)
.confidence(HotspotConfidence::High),
);
let json = table.to_json();
assert!(json.contains("\"fixture_id\": \"render_80x24\""));
assert!(json.contains("\"baseline_id\": \"baseline-001\""));
assert!(json.contains("\"hotspot_count\": 1"));
assert!(json.contains("\"symbol\": \"ftui_render::diff::compute\""));
assert!(json.contains("\"contribution_pct\": 34.2"));
assert!(json.contains("\"confidence\": \"high\""));
}
#[test]
fn empty_table() {
let table = HotspotTable::new("f", "b");
assert!(table.ranked().is_empty());
assert!(table.actionable().is_empty());
assert!(table.covered_modes().is_empty());
assert!((table.total_contribution(ProfilingMode::Cpu)).abs() < 0.01);
}
#[test]
fn confidence_ordering() {
assert!(HotspotConfidence::High < HotspotConfidence::Medium);
assert!(HotspotConfidence::Medium < HotspotConfidence::Low);
}
#[test]
fn all_profiling_modes_count() {
assert_eq!(ProfilingMode::ALL.len(), 4);
}
}