Skip to content
144 changes: 126 additions & 18 deletions src/run/runner/valgrind/helpers/ignored_objects_path.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,131 @@
use crate::prelude::*;
use std::{path::PathBuf, process::Command};

fn get_python_objects() -> Vec<String> {
let output = Command::new("python")
.arg("-c")
.arg("import sysconfig; print('/'.join(sysconfig.get_config_vars('LIBDIR', 'INSTSONAME')))")
.output();
fn find_venv_python_paths() -> anyhow::Result<Vec<String>> {
let output = Command::new("uv")
.args(["python", "find"])
.current_dir(std::env::current_dir()?)
.output()?;
if !output.status.success() {
bail!(
"Failed to get venv python path: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let python_path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
if !python_path.exists() {
return Ok(vec![]);
}
debug!(
"Resolved venv python path: {}",
python_path.to_string_lossy()
);

if output.is_err() {
let err = output.err().unwrap().to_string();
debug!("Failed to get python shared objects: {err}");
return vec![];
Ok(vec![python_path.to_string_lossy().to_string()])
}

fn find_uv_python_paths() -> anyhow::Result<Vec<String>> {
let output = Command::new("uv")
.args([
"python",
"list",
"--only-installed",
"--output-format",
"json",
])
// IMPORTANT: Set to the cwd, so that we also find python
// installations in virtual environments.
.current_dir(std::env::current_dir()?)
.output()?;
if !output.status.success() {
bail!(
"Failed to get uv python paths: {}",
String::from_utf8_lossy(&output.stderr)
);
}
let output = output.unwrap();

let json_output = String::from_utf8_lossy(&output.stdout);
let json: serde_json::Value = serde_json::from_str(&json_output).unwrap_or_default();
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using unwrap_or_default() on JSON parsing will silently return an empty JSON value for invalid JSON, which could mask parsing errors. Consider using proper error handling with context() to provide meaningful error messages when JSON parsing fails.

Suggested change
let json: serde_json::Value = serde_json::from_str(&json_output).unwrap_or_default();
let json: serde_json::Value = serde_json::from_str(&json_output)
.context("Failed to parse JSON output from 'uv python list'")?;

Copilot uses AI. Check for mistakes.
let arr = json
.as_array()
.context("Failed to parse uv python paths: not an array")?;
let paths: Vec<String> = arr
.iter()
.filter_map(|obj| obj.get("path"))
.filter_map(|p| p.as_str())
.map(|s| s.to_string())
.collect();
Ok(paths)
}

fn find_system_python_paths() -> anyhow::Result<Vec<String>> {
let output = Command::new("which").args(["-a", "python"]).output()?;
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using which command is not portable across all systems (not available on Windows). Consider using a more portable approach or add platform-specific handling for cross-platform compatibility.

Copilot uses AI. Check for mistakes.
if !output.status.success() {
debug!(
"Failed to get python shared objects: {}",
bail!(
"Failed to get system python path: {}",
String::from_utf8_lossy(&output.stderr)
);
return vec![];
}

let so_output = String::from_utf8_lossy(&output.stdout).trim().to_string();
vec![so_output]
let paths = String::from_utf8_lossy(&output.stdout)
.lines()
.map(|line| line.trim().to_string())
.collect();
Ok(paths)
}

fn find_python_paths() -> anyhow::Result<Vec<String>> {
let uv_paths = find_uv_python_paths().unwrap_or_default();
debug!("uv python paths: {uv_paths:?}");
let system_paths = find_system_python_paths().unwrap_or_default();
debug!("system python paths: {system_paths:?}");
let venv_paths = find_venv_python_paths().unwrap_or_default();
debug!("venv python paths: {venv_paths:?}");

// For each python path, look at the folder to possibly identify more python versions

let mut paths = uv_paths;
paths.extend(system_paths);
paths.extend(venv_paths);
paths.sort();
paths.dedup();
Ok(paths)
}

fn get_python_objects() -> Vec<String> {
let mut python_objects = Vec::new();
for path in find_python_paths().unwrap_or_default() {
// Get the parent directory of the python binary, then join with lib
let python_path = PathBuf::from(&path);
let Some(parent_dir) = python_path.parent() else {
continue;
};
let Some(install_dir) = parent_dir.parent() else {
continue;
};

let lib_dir = install_dir.join("lib");
let Ok(entries) = std::fs::read_dir(&lib_dir) else {
continue;
};

for entry in entries.flatten() {
let file_name = entry.file_name();
let file_name_str = file_name.to_string_lossy();

if !file_name_str.starts_with("libpython") {
continue;
}

let entry_path = entry.path();
let Some(full_path) = entry_path.to_str() else {
continue;
};
python_objects.push(full_path.to_string());
}
}

python_objects
}

fn get_node_objects() -> Vec<String> {
Expand Down Expand Up @@ -60,10 +163,15 @@ fn normalize_object_paths(objects_path_to_ignore: &mut [String]) {

pub fn get_objects_path_to_ignore() -> Vec<String> {
let mut objects_path_to_ignore = vec![];
objects_path_to_ignore.extend(get_python_objects());
objects_path_to_ignore.extend(get_node_objects());
debug!("objects_path_to_ignore before normalization: {objects_path_to_ignore:?}");
objects_path_to_ignore.extend(get_python_objects());
objects_path_to_ignore.extend(find_python_paths().unwrap_or_default());

debug!("objects_path_to_ignore before normalization: {objects_path_to_ignore:#?}");
normalize_object_paths(&mut objects_path_to_ignore);
debug!("objects_path_to_ignore after normalization: {objects_path_to_ignore:?}");
objects_path_to_ignore.sort();
objects_path_to_ignore.dedup();
debug!("objects_path_to_ignore after normalization: {objects_path_to_ignore:#?}");

objects_path_to_ignore
}
2 changes: 1 addition & 1 deletion src/run/runner/valgrind/helpers/venv_compat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function add_symlink() {
echo "Python installation (system): $system_path"
echo "Python installation (venv): $venv_path"

libpython_name="$(ldd "$venv_python" 2>/dev/null | grep -o -m1 'libpython[^[:space:]]*' || true)"
libpython_name="$(ldd "$venv_python" 2>/dev/null | grep -o 'libpython[^[:space:]]*' | head -1 | tr -d '\n' || true)"
if [ -z "$libpython_name" ]; then
echo "Error: exact libpython name not found in $(ldd $venv_python)" >&2
return 0
Expand Down
24 changes: 0 additions & 24 deletions src/run/runner/wall_time/perf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,30 +445,6 @@ impl BenchmarkData {
}
}

// When python is statically linked, we'll not find it in the ignored modules. Add it manually:
let python_modules = self.symbols_by_pid.values().filter_map(|proc| {
proc.loaded_modules().find(|path| {
path.file_name()
.map(|name| name.to_string_lossy().starts_with("python"))
.unwrap_or(false)
})
});
for path in python_modules {
if let Some(mapping) = self
.symbols_by_pid
.values()
.find_map(|proc| proc.module_mapping(path))
{
let (Some((base_addr, _)), Some((_, end_addr))) = (
mapping.iter().min_by_key(|(base_addr, _)| base_addr),
mapping.iter().max_by_key(|(_, end_addr)| end_addr),
) else {
continue;
};
to_ignore.push((path.to_string_lossy().into(), *base_addr, *end_addr));
}
}

to_ignore
},
};
Expand Down
4 changes: 0 additions & 4 deletions src/run/runner/wall_time/perf/perf_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,6 @@ impl ProcessSymbols {
.push((start_addr, end_addr));
}

pub fn loaded_modules(&self) -> impl Iterator<Item = &PathBuf> {
self.modules.keys()
}

pub fn module_mapping<P: AsRef<std::path::Path>>(
&self,
module_path: P,
Expand Down
Loading