diff --git a/src/run/runner/valgrind/helpers/ignored_objects_path.rs b/src/run/runner/valgrind/helpers/ignored_objects_path.rs index df2d4471..f18e4a40 100644 --- a/src/run/runner/valgrind/helpers/ignored_objects_path.rs +++ b/src/run/runner/valgrind/helpers/ignored_objects_path.rs @@ -1,28 +1,131 @@ use crate::prelude::*; use std::{path::PathBuf, process::Command}; -fn get_python_objects() -> Vec { - 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> { + 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> { + 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(); + let arr = json + .as_array() + .context("Failed to parse uv python paths: not an array")?; + let paths: Vec = 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> { + let output = Command::new("which").args(["-a", "python"]).output()?; 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> { + 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 { + 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 { @@ -60,10 +163,15 @@ fn normalize_object_paths(objects_path_to_ignore: &mut [String]) { pub fn get_objects_path_to_ignore() -> Vec { 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 } diff --git a/src/run/runner/valgrind/helpers/venv_compat.sh b/src/run/runner/valgrind/helpers/venv_compat.sh index fb5ac21e..533a17e7 100755 --- a/src/run/runner/valgrind/helpers/venv_compat.sh +++ b/src/run/runner/valgrind/helpers/venv_compat.sh @@ -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 diff --git a/src/run/runner/wall_time/perf/mod.rs b/src/run/runner/wall_time/perf/mod.rs index e0db17e1..d04109c4 100644 --- a/src/run/runner/wall_time/perf/mod.rs +++ b/src/run/runner/wall_time/perf/mod.rs @@ -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 }, }; diff --git a/src/run/runner/wall_time/perf/perf_map.rs b/src/run/runner/wall_time/perf/perf_map.rs index 9dcc0cec..34bf0785 100644 --- a/src/run/runner/wall_time/perf/perf_map.rs +++ b/src/run/runner/wall_time/perf/perf_map.rs @@ -213,10 +213,6 @@ impl ProcessSymbols { .push((start_addr, end_addr)); } - pub fn loaded_modules(&self) -> impl Iterator { - self.modules.keys() - } - pub fn module_mapping>( &self, module_path: P,