Hi, I really like your project, but encountered some issue: I want to use --perfdata to analyze a file recorded on a different machine, but it still requires perf to be locally installed. Copilot has figured out the reason below. I may implement it, if I find time for it. Otherwise feel free to implement.
The reason is that --perfdata only skips perf record, but it does not skip perf script.
In src/bin/flamegraph.rs, --perfdata sets the workload to Workload::ReadPerf(perf_file) and then calls:
flamegraph::generate_flamegraph_for_workload(workload, opt.graph)
|
return Ok(()); |
|
} |
|
|
|
opt.graph.check()?; |
|
|
|
let workload = if let Some(perf_file) = opt.perf_file { |
|
Workload::ReadPerf(perf_file) |
|
} else { |
|
match (opt.pid.is_empty(), opt.trailing_arguments.is_empty()) { |
|
(false, true) => Workload::Pid(opt.pid), |
|
(true, false) => Workload::Command(opt.trailing_arguments.clone()), |
|
(false, false) => return Err(anyhow!("cannot pass in command with --pid")), |
|
(true, true) => return Err(anyhow!("no workload given to generate a flamegraph for")), |
|
} |
|
}; |
|
flamegraph::generate_flamegraph_for_workload(workload, opt.graph) |
Inside generate_flamegraph_for_workload (src/lib.rs):
-
If the workload is ReadPerf, it does:
let perf_output = if let Workload::ReadPerf(perf_file) = workload {
Some(perf_file)
} else {
arch::initial_command(...)?
};
So it does not call arch::initial_command, and therefore it does not run the perf --help “is perf installed?” check that exists in the Linux initial_command.
|
let perf_output = if let Workload::ReadPerf(perf_file) = workload { |
|
Some(perf_file) |
|
} else { |
|
#[cfg(target_os = "linux")] |
|
let compression = opts.compression_level; |
|
#[cfg(not(target_os = "linux"))] |
|
let compression = None; |
|
|
|
arch::initial_command( |
|
workload, |
|
sudo, |
|
opts.frequency(), |
|
compression, |
|
opts.custom_cmd, |
|
opts.verbose, |
|
opts.ignore_status, |
|
)? |
|
}; |
-
But right after that, it always does:
let output = arch::output(perf_output, opts.script_no_inline, sudo)?;
|
let output = arch::output(perf_output, opts.script_no_inline, sudo)?; |
On Linux, arch::output(...) is implemented to invoke perf script, using either $PERF or "perf":
let perf = env::var("PERF").unwrap_or_else(|_| "perf".to_string());
let mut command = sudo_command(&perf, sudo);
command.arg("script");
command.arg("--force");
if let Some(perf_output) = perf_output {
command.arg("-i");
command.arg(perf_output);
}
let result = command.output().context("unable to call perf script");
So even when you pass an existing perf.data file, it still needs a local perf binary to run perf script -i <file> to turn the binary perf.data into textual stacks that inferno can collapse.
|
pub fn output( |
|
perf_output: Option<PathBuf>, |
|
script_no_inline: bool, |
|
sudo: Option<Option<&str>>, |
|
) -> anyhow::Result<Vec<u8>> { |
|
// We executed `perf record` with sudo, and will be executing `perf script` with sudo, |
|
// so that we can resolve privileged kernel symbols from /proc/kallsyms. |
|
let perf = env::var("PERF").unwrap_or_else(|_| "perf".to_string()); |
|
let mut command = sudo_command(&perf, sudo); |
|
|
|
command.arg("script"); |
|
|
|
// Force reading perf.data owned by another uid if it happened to be created earlier. |
|
command.arg("--force"); |
|
|
|
if script_no_inline { |
|
command.arg("--no-inline"); |
|
} |
|
|
|
if let Some(perf_output) = perf_output { |
|
command.arg("-i"); |
|
command.arg(perf_output); |
|
} |
|
|
|
// perf script can take a long time to run. Notify the user that it is running |
|
// by using a spinner. Note that if this function exits before calling |
|
// spinner.finish(), then the spinner will be completely removed from the terminal. |
|
let spinner = ProgressBar::new_spinner().with_prefix("Running perf script"); |
|
spinner.set_style( |
|
ProgressStyle::with_template("{prefix} [{elapsed}]: {spinner:.green}").unwrap(), |
|
); |
|
spinner.enable_steady_tick(Duration::from_millis(500)); |
|
|
|
let result = command.output().context("unable to call perf script"); |
|
spinner.finish(); |
|
let output = result?; |
|
if !output.status.success() { |
|
bail!( |
|
"unable to run 'perf script': ({}) {}", |
|
output.status, |
|
std::str::from_utf8(&output.stderr)? |
|
); |
|
} |
|
Ok(output.stdout) |
|
} |
Practical implication
If you don’t have perf installed locally, this version of flamegraph cannot analyze a raw perf.data because it depends on perf script for decoding.
Workarounds
- On the machine that has
perf: run perf script -i perf.data > out.perfscript and move that text output.
- Then use a workflow/tooling that accepts the script output (or “folded stacks”) directly. (This repo’s
--perfdata path currently expects the binary perf.data, not already-scripted text.)
Code-search results may be incomplete due to tool limits; you can browse more in GitHub here:
https://github.com/flamegraph-rs/flamegraph/search?q=perf+script&type=code
Hi, I really like your project, but encountered some issue: I want to use
--perfdatato analyze a file recorded on a different machine, but it still requires perf to be locally installed. Copilot has figured out the reason below. I may implement it, if I find time for it. Otherwise feel free to implement.