use std::collections::HashMap;
use std::path::Path;
use std::time::Duration;
use std::time::Instant;
use addr2line::Loader;
use anyhow::anyhow;
use itm::TracePacket;
use probe_rs::config::Registry;
use probe_rs::{
architecture::arm::{
SwoConfig,
component::{Dwt, TraceSink, enable_tracing, find_component},
dp::DpAddress,
memory::PeripheralType,
},
probe::list::Lister,
};
use crate::util::flash::{build_loader, run_flash_download};
use tracing::info;
#[derive(clap::Parser)]
pub struct ProfileCmd {
#[clap(flatten)]
run: super::run::Cmd,
#[clap(long)]
flash: bool,
#[clap(long)]
line_info: bool,
#[clap(long)]
duration: u64, #[clap(long, default_value_t = 0)]
core: usize,
#[clap(long, default_value_t = 25)]
limit: usize,
#[clap(subcommand)]
method: ProfileMethod,
}
#[derive(clap::Subcommand, Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ProfileMethod {
#[clap(name = "naive")]
Naive,
#[clap(name = "itm")]
Itm {
clk: u32,
baud: u32,
},
#[clap(name = "pcsr")]
Pcsr,
}
impl std::fmt::Display for ProfileMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let s = format!("{self:?}");
write!(f, "{}", s.to_lowercase())
}
}
impl ProfileCmd {
pub fn run(self, registry: &mut Registry, lister: &Lister) -> anyhow::Result<()> {
let (mut session, probe_options) = self
.run
.shared_options
.probe_options
.simple_attach(registry, lister)?;
let loader = build_loader(
&mut session,
&self.run.shared_options.path,
self.run.shared_options.format_options,
None,
)?;
let file_location = self.run.shared_options.path.as_path();
let symbols = Symbols::try_from(file_location).map_err(|e| {
anyhow!(
"Failed to read symbol data from {}: {}",
file_location.display(),
e
)
})?;
if self.flash {
run_flash_download(
&mut session,
file_location,
&self.run.shared_options.download_options,
&probe_options,
loader,
)?;
}
let start = Instant::now();
let mut reads = 0;
let mut samples: HashMap<u32, u64> = HashMap::with_capacity(256 * (self.duration as usize));
let duration = Duration::from_secs(self.duration);
info!("Profiling...");
match self.method {
ProfileMethod::Naive => {
let mut core = session.core(self.core)?;
info!("Attached to Core {}", self.core);
core.reset()?;
let pc_reg = core.program_counter();
loop {
core.halt(Duration::from_millis(10))?;
let pc: u32 = core.read_core_reg(pc_reg)?;
*samples.entry(pc).or_insert(1) += 1;
reads += 1;
core.run()?;
if start.elapsed() > duration {
break;
}
}
}
ProfileMethod::Pcsr => {
enable_tracing(&mut session.core(self.core)?)?;
let components = session.get_arm_components(DpAddress::Default)?;
let component = find_component(&components, PeripheralType::Dwt)?;
let interface = session.get_arm_interface()?;
let mut dwt = Dwt::new(interface, component);
dwt.enable()?;
while start.elapsed() <= duration {
let pc = dwt.read_pcsr()?;
*samples.entry(pc).or_insert(1) += 1;
reads += 1;
}
}
ProfileMethod::Itm { clk, baud } => {
let sink = TraceSink::Swo(SwoConfig::new(clk).set_baud(baud));
session.setup_tracing(self.core, sink)?;
let components = session.get_arm_components(DpAddress::Default)?;
let component = find_component(&components, PeripheralType::Dwt)?;
let interface = session.get_arm_interface()?;
let mut dwt = Dwt::new(interface, component);
dwt.enable_pc_sampling()?;
let decoder = itm::Decoder::new(
session.swo_reader()?,
itm::DecoderOptions { ignore_eof: true },
);
let iter = decoder.singles();
for packet in iter {
if let TracePacket::PCSample { pc: Some(pc) } = packet? {
*samples.entry(pc).or_insert(1) += 1;
reads += 1;
}
if start.elapsed() > duration {
break;
}
}
}
}
let mut v = Vec::from_iter(samples);
v.sort_by(|&(_, a), &(_, b)| b.cmp(&a));
println!("Samples {reads}");
for (address, count) in v.into_iter().take(self.limit) {
let name = symbols
.get_name(address as u64)
.unwrap_or(format!("UNKNOWN - {address:08X}"));
if self.line_info {
let (file, num) = symbols
.get_location(address as u64)
.unwrap_or(("UNKNOWN", 0));
println!("{file}:{num}");
}
println!(
"{:>50} - {:.01}%",
name,
(count as f64 / reads as f64) * 100.0
);
}
Ok(())
}
}
pub(crate) struct Symbols {
loader: Loader,
}
impl Symbols {
pub fn try_from(path: &Path) -> Result<Self, Box<dyn std::error::Error>> {
let loader = Loader::new(path)?;
Ok(Self { loader })
}
pub fn get_name(&self, addr: u64) -> Option<String> {
let mut frames = self.loader.find_frames(addr).ok()?;
frames
.next()
.ok()
.flatten()
.and_then(|frame| {
frame
.function
.and_then(|name| name.demangle().map(|s| s.into_owned()).ok())
})
.or_else(|| self.loader.find_symbol(addr).map(|sym| sym.to_string()))
}
pub fn get_location(&self, addr: u64) -> Option<(&str, u32)> {
self.loader.find_location(addr).ok()?.and_then(|location| {
let file = location.file?;
let line = location.line?;
Some((file, line))
})
}
}