Skip to content

Commit

Permalink
Move profiling processing to the record step
Browse files Browse the repository at this point in the history
Currently, users are unable to generate reports, of an aperf run with
--profile, on a system that is not the SUT. Move the processing step for
perf_profile and flamegraph to the record stage.
  • Loading branch information
janaknat committed Jul 22, 2024
1 parent ef2dd06 commit 146bcc9
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 132 deletions.
152 changes: 73 additions & 79 deletions src/data/flamegraphs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,30 @@ extern crate ctor;

use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData};
use crate::visualizer::{DataVisualizer, GetData, ReportParams};
use crate::{get_file_name, PERFORMANCE_DATA, VISUALIZATION_DATA};
use crate::{get_file_name, PDError, PERFORMANCE_DATA, VISUALIZATION_DATA};
use anyhow::Result;
use ctor::ctor;
use inferno::collapse::perf::Folder;
use inferno::collapse::Collapse;
use inferno::flamegraph::{self, Options};
use log::{error, trace};
use log::{error, info, trace};
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{ErrorKind, Write};
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;

pub static FLAMEGRAPHS_FILE_NAME: &str = "flamegraph";

fn write_msg_to_svg(mut file: File, msg: String) -> Result<()> {
write!(
file,
"<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" height=\"100%\"><text x=\"0%\" y=\"1%\">{}</text></svg>",
msg
)?;
Ok(())
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FlamegraphRaw {
pub data: String,
Expand All @@ -31,39 +40,67 @@ impl FlamegraphRaw {
}

impl CollectData for FlamegraphRaw {
fn prepare_data_collector(&mut self, _params: CollectorParams) -> Result<()> {
match Command::new("perf").args(["--version"]).output() {
Err(e) => Err(PDError::DependencyError(format!("'perf' command failed. {}", e)).into()),
_ => Ok(()),
}
}

fn after_data_collection(&mut self, params: CollectorParams) -> Result<()> {
let data_dir = PathBuf::from(params.data_dir.clone());
let data_dir = PathBuf::from(&params.data_dir);

let mut file_pathbuf = data_dir.clone();
file_pathbuf.push(get_file_name(
params.data_dir.clone(),
"perf_profile".to_string(),
)?);
let file_pathbuf =
data_dir.join(get_file_name(params.data_dir, "perf_profile".to_string())?);

let mut perf_jit_loc = data_dir.clone();
perf_jit_loc.push(format!("{}-perf.data.jit", params.run_name));
let perf_jit_loc = data_dir.join("perf.data.jit");

println!("Running Perf inject...");
trace!("Running Perf inject...");
let out_jit = Command::new("perf")
.args([
"inject",
"-j",
"-i",
&file_pathbuf.to_str().unwrap(),
"-o",
perf_jit_loc.clone().to_str().unwrap(),
perf_jit_loc.to_str().unwrap(),
])
.status();

let fg_out = File::create(data_dir.join(format!("{}-flamegraph.svg", params.run_name)))?;

match out_jit {
Err(e) => {
if e.kind() == ErrorKind::NotFound {
error!("'perf' command not found.");
} else {
error!("Unknown error: {}", e);
let out = format!("Skip processing profiling data due to: {}", e);
error!("{}", out);
write_msg_to_svg(fg_out, out)?;
}
Ok(_) => {
info!("Creating flamegraph...");
let script_loc = data_dir.join("script.out");
let out = Command::new("perf")
.stdout(File::create(&script_loc)?)
.args(["script", "-f", "-i", perf_jit_loc.to_str().unwrap()])
.output();
match out {
Err(e) => {
let out = format!("Did not process profiling data due to: {}", e);
error!("{}", out);
write_msg_to_svg(fg_out, out)?;
}
Ok(_) => {
let collapse_loc = data_dir.join("collapse.out");

Folder::default()
.collapse_file(Some(script_loc), File::create(&collapse_loc)?)?;
flamegraph::from_files(
&mut Options::default(),
&[collapse_loc.to_path_buf()],
fg_out,
)?;
}
}
error!("Skip processing profiling data.");
}
Ok(_) => trace!("Perf inject successful."),
}
Ok(())
}
Expand All @@ -84,68 +121,25 @@ impl Flamegraph {

impl GetData for Flamegraph {
fn custom_raw_data_parser(&mut self, params: ReportParams) -> Result<Vec<ProcessedData>> {
/* Get the perf_profile file */
let mut file_pathbuf = PathBuf::from(params.data_dir.clone());
file_pathbuf.push(get_file_name(
params.data_dir.clone(),
"perf_profile".to_string(),
)?);

let _file_name = file_pathbuf.to_str().unwrap();
let profile = Flamegraph::new();

let mut perf_jit_loc = PathBuf::from(params.data_dir.clone());
perf_jit_loc.push(format!("{}-perf.data.jit", params.run_name));

/* Use APERF_TMP to generate intermediate perf files */
let tmp_path = PathBuf::from(params.tmp_dir);

let mut script_loc = tmp_path.clone();
script_loc.push(format!("{}-script.out", params.run_name));

let mut collapse_loc = tmp_path.clone();
collapse_loc.push(format!("{}-collapse.out", params.run_name));

let mut fg_loc = params.report_dir.clone();
fg_loc.push(format!("data/js/{}-flamegraph.svg", params.run_name));

let mut script_out = File::create(script_loc.clone())?;
let collapse_out = File::create(collapse_loc.clone())?;
let mut fg_out = File::create(fg_loc.clone())?;

if perf_jit_loc.exists() {
let out = Command::new("perf")
.args(["script", "-f", "-i", perf_jit_loc.to_str().unwrap()])
.output();
match out {
Err(e) => {
if e.kind() == ErrorKind::NotFound {
error!("'perf' command not found.");
} else {
error!("Unknown error: {}", e);
}
error!("Skip processing profiling data.");
write!(fg_out, "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" height=\"100%\"><text x=\"0%\" y=\"1%\">Did not process profiling data</text></svg>")?;
}
Ok(v) => {
write!(script_out, "{}", std::str::from_utf8(&v.stdout)?)?;
Folder::default().collapse_file(Some(script_loc), collapse_out)?;
fg_out = std::fs::OpenOptions::new()
.read(true)
.write(true)
.truncate(true)
.open(fg_loc)?;
flamegraph::from_files(
&mut Options::default(),
&[collapse_loc.to_path_buf()],
fg_out,
)?;
}
}
let processed_data = vec![ProcessedData::Flamegraph(Flamegraph::new())];

let file_name = format!("{}-flamegraph.svg", params.run_name);
let fg_loc = params.data_dir.join(&file_name);
let fg_out = params.report_dir.join("data/js/".to_owned() + &file_name);

/* Copy the flamegraph to the report dir */
if fg_loc.exists() {
std::fs::copy(fg_loc, fg_out)?;
} else {
write!(fg_out, "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" height=\"100%\"><text x=\"0%\" y=\"1%\">No data collected</text></svg>")?;
write_msg_to_svg(
std::fs::OpenOptions::new()
.create_new(true)
.read(true)
.write(true)
.open(fg_out)?,
"No data collected".to_string(),
)?;
}
let processed_data = vec![ProcessedData::Flamegraph(profile)];
Ok(processed_data)
}

Expand Down
27 changes: 20 additions & 7 deletions src/data/java_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ pub struct JavaProfileRaw {
process_map: HashMap<String, Vec<String>>,
}

impl Default for JavaProfileRaw {
fn default() -> Self {
Self::new()
}
}

impl JavaProfileRaw {
pub fn new() -> Self {
JavaProfileRaw {
Expand All @@ -51,7 +57,7 @@ impl JavaProfileRaw {
Err(e) => {
return Err(PDError::DependencyError(format!(
"'asprof' command failed. {}",
e.to_string()
e
))
.into());
}
Expand Down Expand Up @@ -105,7 +111,7 @@ impl JavaProfileRaw {
}
Err(e) => Err(PDError::DependencyError(format!(
"Jps command failed. {}",
e.to_string()
e
))),
}
}
Expand All @@ -122,7 +128,7 @@ impl JavaProfileRaw {
}
Err(e) => Err(PDError::DependencyError(format!(
"pgrep command failed. {}",
e.to_string()
e
))),
}
}
Expand Down Expand Up @@ -233,15 +239,22 @@ impl JavaProfile {
}
}

impl Default for JavaProfile {
fn default() -> Self {
Self::new()
}
}

impl GetData for JavaProfile {
fn custom_raw_data_parser(
&mut self,
params: crate::visualizer::ReportParams,
) -> Result<Vec<ProcessedData>> {
let mut processes_loc = PathBuf::from(params.data_dir.clone());
processes_loc.push(format!("{}-jps-map.json", params.run_name));
let processes_loc = params
.data_dir
.join(format!("{}-jps-map.json", params.run_name));
let processes_json =
fs::read_to_string(processes_loc.to_str().unwrap()).unwrap_or(String::new());
fs::read_to_string(processes_loc.to_str().unwrap()).unwrap_or_default();
let mut process_map: HashMap<String, Vec<String>> =
serde_json::from_str(&processes_json).unwrap_or(HashMap::new());
let process_list: Vec<String> = process_map.clone().into_keys().collect();
Expand All @@ -252,7 +265,7 @@ impl GetData for JavaProfile {
"data/js/{}-java-flamegraph-{}.html",
params.run_name, process
));
let mut html_loc = PathBuf::from(params.data_dir.clone());
let mut html_loc = params.data_dir.clone();
html_loc.push(format!(
"{}-java-flamegraph-{}.html",
params.run_name, process
Expand Down
Loading

0 comments on commit 146bcc9

Please sign in to comment.