From 857d488265f045c1417100ba6501a656b5c1955c Mon Sep 17 00:00:00 2001 From: chris gatto Date: Sat, 3 Jan 2026 12:29:14 -0500 Subject: [PATCH 1/4] quick-xml demangling --- Cargo.lock | 1 + Cargo.toml | 1 + src/lib.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dce2104..bcfd2cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,6 +505,7 @@ dependencies = [ "indicatif", "inferno", "opener", + "quick-xml", "rustc-demangle", "shlex", "signal-hook", diff --git a/Cargo.toml b/Cargo.toml index 692c837..0ee176c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ inferno = { version = "0.12.2", default-features = false, features = ["multithre opener = "0.8.1" shlex = "1.1.0" rustc-demangle = { version = "0.1", features = ["std"] } +quick-xml = { version = "0.37", default-features = false } [target.'cfg(unix)'.dependencies] signal-hook = "0.4.1" diff --git a/src/lib.rs b/src/lib.rs index 3557fce..9b7a4dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,8 @@ use std::{ + borrow::Cow, env, fs::File, - io::{BufReader, BufWriter, Cursor, Read, Write}, + io::{BufRead, BufReader, BufWriter, Cursor, Error, ErrorKind, Read, Write}, path::PathBuf, process::{exit, Command, ExitStatus, Stdio}, str::FromStr, @@ -28,7 +29,7 @@ use clap::{ Args, }; use inferno::{collapse::Collapse, flamegraph::color::Palette, flamegraph::from_reader}; -use rustc_demangle::demangle_stream; +use rustc_demangle::try_demangle; pub enum Workload { Command(Vec), @@ -490,6 +491,57 @@ fn print_command(cmd: &Command, verbose: bool) { } } +fn demangle_stream(input: &mut R, output: &mut W) -> std::io::Result<()> { + use quick_xml::events::Event; + + let mut reader = quick_xml::Reader::from_reader(input); + let mut writer = quick_xml::Writer::new(output); + let mut buf = Vec::new(); + + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Eof) => break, + Ok(Event::Start(el)) => writer.write_event(Event::Start(demangle_element(el)?))?, + Ok(Event::Empty(el)) => writer.write_event(Event::Empty(demangle_element(el)?))?, + Ok(el) => writer.write_event(el)?, + Err(err) => return Err(Error::new(ErrorKind::InvalidData, err)), + } + buf.clear(); + } + + Ok(()) +} + +fn demangle_element( + element: quick_xml::events::BytesStart, +) -> std::io::Result { + let mut new_element = element.clone(); + new_element.clear_attributes(); + + for attr in element.attributes() { + let mut attr = attr.map_err(|err| Error::new(ErrorKind::InvalidData, err))?; + demangle_attribute(&mut attr); + new_element.push_attribute(attr); + } + + Ok(new_element) +} + +fn demangle_attribute(attribute: &mut quick_xml::events::attributes::Attribute) { + let Ok(mangled) = str::from_utf8(&attribute.value) else { + return; + }; + + if let Ok(demangled) = try_demangle(mangled) { + let demangled = format!("{:#}", demangled); + + attribute.value = match quick_xml::escape::escape(demangled) { + Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()), + Cow::Owned(s) => Cow::Owned(s.into_bytes()), + }; + } +} + pub fn generate_flamegraph_for_workload(workload: Workload, opts: Options) -> anyhow::Result<()> { // Handle SIGINT with an empty handler. This has the // implicit effect of allowing the signal to reach the @@ -530,7 +582,7 @@ pub fn generate_flamegraph_for_workload(workload: Workload, opts: Options) -> an let mut demangled_output = vec![]; - demangle_stream(&mut Cursor::new(output), &mut demangled_output, false) + demangle_stream(&mut Cursor::new(output), &mut demangled_output) .context("unable to demangle")?; let perf_reader = BufReader::new(&*demangled_output); From 8afae53f83b511ab9d834fbe098557d146d09e21 Mon Sep 17 00:00:00 2001 From: paomian Date: Mon, 26 Jan 2026 18:04:11 +0800 Subject: [PATCH 2/4] fix: fix demangling per target by gating macOS helpers and wiring rustc_demangle::demangle_stream on other platforms --- src/lib.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9b7a4dc..27ae505 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,9 @@ use clap::{ Args, }; use inferno::{collapse::Collapse, flamegraph::color::Palette, flamegraph::from_reader}; +#[cfg(not(target_os = "macos"))] +use rustc_demangle::demangle_stream; +#[cfg(target_os = "macos")] use rustc_demangle::try_demangle; pub enum Workload { @@ -491,6 +494,7 @@ fn print_command(cmd: &Command, verbose: bool) { } } +#[cfg(target_os = "macos")] fn demangle_stream(input: &mut R, output: &mut W) -> std::io::Result<()> { use quick_xml::events::Event; @@ -512,6 +516,8 @@ fn demangle_stream(input: &mut R, output: &mut W) -> std:: Ok(()) } +#[cfg(target_os = "macos")] +#[inline] fn demangle_element( element: quick_xml::events::BytesStart, ) -> std::io::Result { @@ -527,6 +533,8 @@ fn demangle_element( Ok(new_element) } +#[cfg(target_os = "macos")] +#[inline] fn demangle_attribute(attribute: &mut quick_xml::events::attributes::Attribute) { let Ok(mangled) = str::from_utf8(&attribute.value) else { return; @@ -582,8 +590,12 @@ pub fn generate_flamegraph_for_workload(workload: Workload, opts: Options) -> an let mut demangled_output = vec![]; - demangle_stream(&mut Cursor::new(output), &mut demangled_output) - .context("unable to demangle")?; + #[cfg(not(target_os = "macos"))] + let demangle_result = demangle_stream(&mut Cursor::new(output), &mut demangled_output, false); + #[cfg(target_os = "macos")] + let demangle_result = demangle_stream(&mut Cursor::new(output), &mut demangled_output); + + demangle_result.context("unable to demangle")?; let perf_reader = BufReader::new(&*demangled_output); From 5a39377ec3d03f35f4a7219f3bd4e40797b1c24b Mon Sep 17 00:00:00 2001 From: paomian Date: Sat, 28 Feb 2026 18:07:13 +0800 Subject: [PATCH 3/4] fix: update demangling logic for attributes and adjust quick-xml dependency for macOS --- Cargo.toml | 5 ++++- src/lib.rs | 28 +++++++++++----------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0ee176c..a3a042f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ inferno = { version = "0.12.2", default-features = false, features = ["multithre opener = "0.8.1" shlex = "1.1.0" rustc-demangle = { version = "0.1", features = ["std"] } -quick-xml = { version = "0.37", default-features = false } + [target.'cfg(unix)'.dependencies] signal-hook = "0.4.1" @@ -36,5 +36,8 @@ signal-hook = "0.4.1" [target.'cfg(windows)'.dependencies] blondie = "0.5.2" +[target.'cfg(target_os = "macos")'.dependencies] +quick-xml = { version = "0.37", default-features = false } + [profile.release.build-override] opt-level = 0 diff --git a/src/lib.rs b/src/lib.rs index 27ae505..5b7ce1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -526,28 +526,22 @@ fn demangle_element( for attr in element.attributes() { let mut attr = attr.map_err(|err| Error::new(ErrorKind::InvalidData, err))?; - demangle_attribute(&mut attr); - new_element.push_attribute(attr); - } - Ok(new_element) -} + let mangled = String::from_utf8_lossy(attr.value.as_ref()); -#[cfg(target_os = "macos")] -#[inline] -fn demangle_attribute(attribute: &mut quick_xml::events::attributes::Attribute) { - let Ok(mangled) = str::from_utf8(&attribute.value) else { - return; - }; + if let Ok(demangled) = try_demangle(&mangled) { + let demangled = format!("{:#}", demangled); - if let Ok(demangled) = try_demangle(mangled) { - let demangled = format!("{:#}", demangled); + attr.value = match quick_xml::escape::escape(demangled) { + Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()), + Cow::Owned(s) => Cow::Owned(s.into_bytes()), + }; + } - attribute.value = match quick_xml::escape::escape(demangled) { - Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()), - Cow::Owned(s) => Cow::Owned(s.into_bytes()), - }; + new_element.push_attribute(attr); } + + Ok(new_element) } pub fn generate_flamegraph_for_workload(workload: Workload, opts: Options) -> anyhow::Result<()> { From fb7a9385c004312a853b5778f3e9f515d2cf8ffe Mon Sep 17 00:00:00 2001 From: paomian Date: Sat, 28 Feb 2026 18:20:33 +0800 Subject: [PATCH 4/4] (chore): make clippy happy --- src/lib.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5b7ce1f..37b1aeb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,7 @@ use std::{ - borrow::Cow, env, fs::File, - io::{BufRead, BufReader, BufWriter, Cursor, Error, ErrorKind, Read, Write}, + io::{BufReader, BufWriter, Cursor, Read, Write}, path::PathBuf, process::{exit, Command, ExitStatus, Stdio}, str::FromStr, @@ -15,7 +14,17 @@ use std::os::unix::process::ExitStatusExt; use inferno::collapse::perf::{Folder, Options as CollapseOptions}; #[cfg(target_os = "macos")] -use inferno::collapse::xctrace::Folder; +use { + inferno::collapse::xctrace::Folder, + rustc_demangle::try_demangle, + std::{ + borrow::Cow, + io::{BufRead, Error, ErrorKind}, + }, +}; + +#[cfg(not(target_os = "macos"))] +use rustc_demangle::demangle_stream; #[cfg(not(any(target_os = "linux", target_os = "macos")))] use inferno::collapse::dtrace::{Folder, Options as CollapseOptions}; @@ -29,10 +38,6 @@ use clap::{ Args, }; use inferno::{collapse::Collapse, flamegraph::color::Palette, flamegraph::from_reader}; -#[cfg(not(target_os = "macos"))] -use rustc_demangle::demangle_stream; -#[cfg(target_os = "macos")] -use rustc_demangle::try_demangle; pub enum Workload { Command(Vec),