src/pmu.rs (309 lines of code) (raw):
#[cfg(target_arch = "aarch64")]
use crate::data::perf_stat::arm64_perf_list;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use crate::data::perf_stat::{form_events_map, x86_perf_list};
use crate::data::perf_stat::{to_events, NamedCtr, NamedTypeCtr, PerfType};
use crate::PDError;
use anyhow::Result;
use clap::Args;
use inquire::{
list_option::ListOption, required, validator::Validation, Confirm, MultiSelect, Select, Text,
};
use std::path::PathBuf;
#[derive(Args, Debug)]
pub struct CustomPMU {
/// Name of the file for the custom PMU configuration.
#[clap(short, long, value_parser)]
pub pmu_file: Option<String>,
/// Verify the supplied pmu file.
#[clap(long, value_parser)]
pub verify: bool,
}
pub fn get_ctrs(opt_str: &str) -> Result<Vec<NamedTypeCtr>> {
println!(" \"{opt_str}\": [");
let mut ret: Vec<NamedTypeCtr> = Vec::new();
loop {
println!(" {{");
println!(" \"perf_type\": RAW");
let perf_type = PerfType::RAW;
let name = Text::new(" \"name\":")
.with_validator(|s: &str| {
if s.chars().all(char::is_alphanumeric) {
Ok(Validation::Valid)
} else {
Ok(Validation::Invalid(
"Name must contain only alphanumeric characters.".into(),
))
}
})
.with_validator(required!())
.prompt()?;
let raw_config = Text::new(" \"config\":")
.with_validator(|s: &str| {
if s.starts_with("0x") {
Ok(Validation::Valid)
} else {
Ok(Validation::Invalid(
"Config must be hexadecimal and start with 0x.".into(),
))
}
})
.with_validator(|s: &str| {
let no_prefix = s.trim_start_matches("0x");
match u64::from_str_radix(no_prefix, 16) {
Ok(_) => Ok(Validation::Valid),
Err(_) => Ok(Validation::Invalid("Invalid hexadecimal value.".into())),
}
})
.prompt()?;
let no_prefix = raw_config.trim_start_matches("0x");
let config = u64::from_str_radix(no_prefix, 16)?;
println!(" }}");
ret.push(NamedTypeCtr {
perf_type,
name,
config,
});
if !Confirm::new(format!("Add more {opt_str}:").as_str())
.with_default(false)
.prompt()?
{
println!("\n ]");
break;
}
}
Ok(ret)
}
pub fn add_events(existing_events: Vec<NamedCtr>) -> Result<Vec<NamedCtr>> {
let mut events: Vec<NamedCtr> = Vec::new();
let mut event_names = Vec::new();
for event in existing_events {
event_names.push(event.name);
}
loop {
println!("{{");
let event_names_tmp = event_names.clone();
let name = Text::new(" \"name\":")
.with_validator(|s: &str| {
if s.chars().all(char::is_alphanumeric) {
Ok(Validation::Valid)
} else {
Ok(Validation::Invalid(
"Name must contain only alphanumeric characters.".into(),
))
}
})
.with_validator(move |s: &str| {
if !event_names_tmp.contains(&s.to_string()) {
Ok(Validation::Valid)
} else {
Ok(Validation::Invalid(
"Event with the same name exists.".into(),
))
}
})
.with_validator(required!())
.prompt()?;
event_names.push(name.clone());
let nrs = get_ctrs("nrs")?;
let drs = get_ctrs("drs")?;
let scale_text = Text::new(" \"scale\":")
.with_validator(required!())
.with_validator(|s: &str| match s.parse::<u64>() {
Ok(v) => {
if v == 0 {
Ok(Validation::Invalid("Scaling value cannot be 0.".into()))
} else {
Ok(Validation::Valid)
}
}
Err(_) => Ok(Validation::Invalid(
"Scaling value should be a valid number.".into(),
)),
})
.prompt()?;
let scale = scale_text.parse::<u64>()?;
events.push(NamedCtr {
name,
nrs,
drs,
scale,
});
println!(" }}");
if !Confirm::new("Add more events:")
.with_default(false)
.prompt()?
{
break;
}
}
Ok(events)
}
pub fn delete_events(mut perf_list: Vec<NamedCtr>) -> Result<Vec<NamedCtr>> {
loop {
let mut ev_list = Vec::new();
for event in &perf_list {
ev_list.push(event.name.clone());
}
if ev_list.is_empty() {
println!("Cannot delete any more events.");
return Ok(Vec::new());
}
let delete_list = MultiSelect::new("Select event(s) to delete:", ev_list)
.with_validator(|a: &[ListOption<&String>]| {
if a.is_empty() {
Ok(Validation::Invalid("Must choose at least 1 option.".into()))
} else {
Ok(Validation::Valid)
}
})
.prompt()?;
for name in delete_list {
let index = perf_list.iter().position(|ev| ev.name == name).unwrap();
let out = format!(
"\n{}\nDelete?",
serde_json::to_string_pretty(&perf_list[index])?
);
if Confirm::new(&out).with_default(true).prompt()? {
perf_list.remove(index);
}
}
if !Confirm::new("Remove more?").with_default(false).prompt()? {
break;
}
}
Ok(perf_list.to_vec())
}
pub fn create_pmu_config(cpmu: &CustomPMU) -> Result<()> {
let mut pmu_file = PathBuf::from("aperf_custom_pmu.json");
if let Some(f) = &cpmu.pmu_file {
pmu_file = PathBuf::from(f);
}
#[cfg(target_arch = "aarch64")]
let events: Vec<NamedCtr> = serde_json::from_slice(arm64_perf_list::GRV_EVENTS)?;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let events: Vec<NamedCtr> = serde_json::from_slice(x86_perf_list::INTEL_EVENTS)?;
#[cfg(target_arch = "aarch64")]
let platform = "Graviton";
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let platform = "Intel";
println!(
"\nAperf PMU Event structure (Ex: {})\n{}",
platform,
serde_json::to_string_pretty(&events[0])?
);
println!(
"\nPlease enter your custom PMU details. Only hex values (0x) for 'config' will work.\n"
);
let events = add_events(Vec::new())?;
let f = std::fs::File::create(&pmu_file)?;
serde_json::to_writer_pretty(f, &events)?;
println!(
"\nCustom PMU config generated at: {:?}. Use this with 'aperf record --pmu-file {:?}'.",
pmu_file, pmu_file
);
Ok(())
}
pub fn get_config(choice: &str, cpmu: &CustomPMU) -> Result<Vec<NamedCtr>> {
if choice == "User provided" {
if let Some(f) = &cpmu.pmu_file {
let file = std::fs::File::open(PathBuf::from(f))?;
return Ok(serde_json::from_reader(&file)?);
} else {
println!("No custom config file provided.");
return Err(PDError::PMUCustomFileNotFound.into());
}
};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
match choice {
"Intel" => to_events(x86_perf_list::INTEL_EVENTS),
"Intel Sapphire Rapids" => {
form_events_map(x86_perf_list::INTEL_EVENTS, x86_perf_list::SPR_CTRS)
}
"Intel Icelake" => form_events_map(x86_perf_list::INTEL_EVENTS, x86_perf_list::ICX_CTRS),
"AMD" => to_events(x86_perf_list::AMD_EVENTS),
"AMD Genoa" => form_events_map(x86_perf_list::AMD_EVENTS, x86_perf_list::GENOA_CTRS),
"AMD Milan" => form_events_map(x86_perf_list::AMD_EVENTS, x86_perf_list::MILAN_CTRS),
_ => Ok(Vec::new()),
}
#[cfg(target_arch = "aarch64")]
match choice {
"Graviton" => to_events(arm64_perf_list::GRV_EVENTS),
_ => Ok(Vec::new()),
}
}
pub fn modify_existing_config(cpmu: &CustomPMU) -> Result<()> {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let config = Select::new(
"Select a config:",
vec![
"User provided",
"Intel",
"Intel Sapphire Rapids",
"Intel Icelake",
"AMD",
"AMD Genoa",
"AMD Milan",
],
)
.prompt()?;
#[cfg(target_arch = "aarch64")]
let config = Select::new("Select a config:", vec!["User provided", "Graviton"]).prompt()?;
let mut perf_list = get_config(config, cpmu)?;
loop {
let option = Select::new("Select action:", vec!["Add", "Delete", "Done"]).prompt()?;
if option == "Add" {
if perf_list.is_empty() {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
let example: Vec<NamedCtr> = to_events(x86_perf_list::INTEL_EVENTS)?;
#[cfg(target_arch = "aarch64")]
let example: Vec<NamedCtr> = to_events(arm64_perf_list::GRV_EVENTS)?;
println!(
"\nAperf PMU Event structure (Example)\n{}",
serde_json::to_string_pretty(&example[0])?
);
} else {
println!(
"\nAperf PMU Event structure (Ex: {})\n{}",
config,
serde_json::to_string_pretty(&perf_list[0])?
);
}
perf_list.append(&mut add_events(perf_list.clone())?);
} else if option == "Delete" {
perf_list = delete_events(perf_list.clone())?;
} else if option == "Done" {
let f = std::fs::File::create("aperf_existing_modified.json")?;
serde_json::to_writer_pretty(f, &perf_list)?;
println!(
"\nCustom PMU config generated at: aperf_existing_modified.json. Use this with 'aperf record --pmu-file aperf_existing_modified.json'.",
);
break;
}
}
Ok(())
}
pub fn custom_pmu(cpmu: &CustomPMU) -> Result<()> {
if cpmu.verify {
if let Some(custom_file) = &cpmu.pmu_file {
let f = std::fs::File::open(custom_file)?;
let check_format: Result<Vec<NamedCtr>, serde_json::Error> =
serde_json::from_reader(&f);
if check_format.is_ok() {
println!("Custom PMU file is valid.");
return Ok(());
} else {
return Err(PDError::PMUFileInvalid.into());
}
}
return Err(PDError::PMUCustomFileNotFound.into());
}
let choice = Select::new(
"Aperf Custom PMU config:",
vec!["Create from scratch", "Modify existing config"],
)
.prompt()?;
if choice == "Create from scratch" {
create_pmu_config(cpmu)
} else {
modify_existing_config(cpmu)
}
}