I am working on writing some CPU-intensive code and trying to better understand how to profile a language like rust on Windows 11. So far, I have used tools such as Superluminal and WPR/WPA (windows performance).
Here are a few questions I have had so far:
How do I make sense of some of these? I am getting a bit lost diving through the call graphs, and my goal is to try and understand what is causing performance degradation.
Is there other tools available? My research into this showed a lot of the tools are for linux-based systems.
Why do so many paths lead to dead ends where no symbol was found? I have debug = true flag enabled for my build profile. Maybe this is common, I am new to systems-level language profiling.
Are there are good resources out there for this sort of thing to help me learn?
Attached image of the sort of UI I have been seeing so far. I generally just dive through these threads' call graphs until I see routines that take up abnormally large portions of the execution time, but can't tell if there is a better way to find/visualize these.
I was able to get useful profiles with Windows' CPU profiling. I opened them in Visual Studio and VS was able to find the source rs files and show perf info just fine.
I also wrote some code to automate collecting the profiles which may or may not be particularly useful to you. I haven't tested it again now but it worked for what I needed a couple months ago ¯\_(ツ)_/¯
Automate collecting perf info
#[derive(Debug)]
enum DiagnosticsError {
Io(std::io::Error),
Exit(ExitStatusError),
}
impl From<std::io::Error> for DiagnosticsError {
fn from(from: std::io::Error) -> Self {
Self::Io(from)
}
}
impl From<ExitStatusError> for DiagnosticsError {
fn from(from: ExitStatusError) -> Self {
Self::Exit(from)
}
}
struct Diagnostics {
session: u8,
collector_path: Option<PathBuf>,
}
impl Diagnostics {
fn start(
collector_path: impl AsRef<Path>,
session: u8,
config: impl AsRef<Path>,
) -> Result<Self, DiagnosticsError> {
let collector_path = collector_path.as_ref().to_owned();
{
let session = session.to_string();
let config = format!(
"/loadConfig:{}",
collector_path.join(&config).as_os_str().to_str().unwrap()
);
let attach = format!("/attach:{}", std::process::id());
Command::new(collector_path.join("VSDiagnostics.exe"))
.args(["start", &session, &config, &attach])
.spawn()?
.wait()?
.exit_ok()?
}
Ok(Self {
session,
collector_path: Some(collector_path),
})
}
fn stop(mut self, dest: impl AsRef<Path>) -> Result<(), DiagnosticsError> {
self.stop_inner(dest)
}
fn stop_inner(&mut self, dest: impl AsRef<Path>) -> Result<(), DiagnosticsError> {
{
let session = self.session.to_string();
let output = format!("/output:{}", dest.as_ref().as_os_str().to_str().unwrap());
let attach = format!("/attach:{}", std::process::id());
Command::new(
self.collector_path
.take()
.unwrap()
.join("VSDiagnostics.exe"),
)
.args(["stop", &session, &output, &attach])
.spawn()?
.wait()?
.exit_ok()?;
Ok(())
}
}
}
impl Drop for Diagnostics {
fn drop(&mut self) {
if self.collector_path.is_none() {
return;
}
let dir = std::env::current_dir()
.unwrap()
.join(format!("{}.diagsession", self.session));
eprintln!(
"WARNING: Stop was not called, stopping session with session name {}",
dir.as_os_str().to_string_lossy()
);
self.stop_inner(dir).unwrap();
}
}
pub fn run_test() {
let diagnostics = Diagnostics::start(r"C:\Program Files\Microsoft Visual Studio\2022\Community\Team Tools\DiagnosticsHub\Collector", 1, r"AgentConfigs\CpuUsageHigh.json").unwrap();
println!("Starting test");
// Run profiled code here
let mut dir = std::env::current_dir().unwrap().join(name);
dir.set_extension("diagsession");
println!("{:?}", dir);
println!("{:#?}", rx.recv().unwrap());
diagnostics.stop(dir).unwrap();
}
Thanks for the suggestion to use Visual Studio! I have been using primarily vscode so far while writing (mostly because I enjoyed the language support offered), and didn't think to go into Visual Studio. I was able to get more readable results with better visualizations for where my program spent most of its time through its Performance Profiler.
And I appreciate you passing the script along. I will try it out if I hit another wall here.
There's another situation where Visual Studio shines, albeit a pretty niche one: It works pretty good with Visual Studio's remote debugging facilities. This comes in handy if you, for instance, need to debug a Rust service during early boot.
Visual Studio is a handy tool for Rust-on-Windows developers to have in their toolbox, imho.