-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathperf_executable.rs
More file actions
142 lines (125 loc) · 5.48 KB
/
perf_executable.rs
File metadata and controls
142 lines (125 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use runner_shared::perf_event::PerfEvent;
use crate::prelude::*;
use std::path::Path;
use std::{ffi::OsString, process::Command};
const FIND_PERF_CMD: &str =
"find /usr/lib -executable -path \"/usr/lib/linux-tools-*/perf\" | sort | tail -n1";
/// Attempts to find the path to the `perf` executable that is installed and working.
/// Returns None if `perf` is not installed or not functioning correctly.
pub fn get_working_perf_executable() -> Option<OsString> {
let is_installed = Command::new("which")
.arg("perf")
.output()
.is_ok_and(|output| output.status.success());
if !is_installed {
debug!("perf is not installed");
return None;
}
debug!("perf is installed, checking if it is functioning correctly");
if Command::new("perf")
.arg("--version") // here we use --version to check if perf is working
.output()
.is_ok_and(|output| output.status.success())
{
return Some("perf".into());
} else {
// The following is a workaround for this outstanding Ubuntu issue: https://bugs.launchpad.net/ubuntu/+source/linux-hwe-6.14/+bug/2117159/
debug!(
"perf command is not functioning correctly, trying to find alternative path using \"{FIND_PERF_CMD}\""
);
if let Ok(perf_path) = Command::new("sh").arg("-c").arg(FIND_PERF_CMD).output() {
if perf_path.status.success() {
let path = String::from_utf8_lossy(&perf_path.stdout)
.trim()
.to_string();
if path.is_empty() {
debug!("No alternative perf path found");
return None;
}
debug!("Found perf path: {path}");
// Check if this perf is working by getting its version
if let Ok(version_output) = Command::new(&path).arg("--version").output() {
if !version_output.status.success() {
debug!(
"Failed to get perf version from alternative path. stderr: {}",
String::from_utf8_lossy(&version_output.stderr)
);
return None;
}
let version = String::from_utf8_lossy(&version_output.stdout)
.trim()
.to_string();
debug!("Found perf version from alternative path: {version}");
return Some(path.into());
}
}
}
}
debug!("perf is installed but not functioning correctly");
None
}
/// Detects if the required perf events are available on this system.
/// Returns the flags to pass to perf record command if they are available, otherwise returns None.
pub fn get_event_flags(perf_executable: &OsString) -> anyhow::Result<Option<String>> {
let perf_events = PerfEvent::all_events();
let output = Command::new(perf_executable)
.arg("list")
.output()
.context("Failed to run perf list")?;
let stdout = String::from_utf8_lossy(&output.stdout);
// Check if all required events are available
// Expected format in `perf list` output:
//
// List of pre-defined events (to be used in -e or -M):
//
// branch-instructions OR branches [Hardware event]
// branch-misses [Hardware event]
// bus-cycles [Hardware event]
// cache-misses [Hardware event]
// cache-references [Hardware event]
// cpu-cycles OR cycles [Hardware event]
// instructions [Hardware event]
// ref-cycles [Hardware event]
let missing_events: Vec<PerfEvent> = perf_events
.iter()
.filter(|&&event| {
!stdout.lines().any(|line| {
line.split_whitespace()
.any(|word| word == event.to_perf_string())
})
})
.copied()
.collect();
if !missing_events.is_empty() {
warn!(
"Not all required perf events available. Missing: [{}], using default events",
missing_events.into_iter().join(", ")
);
return Ok(None);
}
let events_string = perf_events.into_iter().join(",");
debug!("All required perf events available: {events_string}",);
Ok(Some(format!("-e {{{events_string}}}")))
}
pub fn get_compression_flags<S: AsRef<Path>>(perf_executable: S) -> Result<Option<String>> {
let output = Command::new(perf_executable.as_ref())
.arg("version")
.arg("--build-options")
.output()
.context("Failed to run perf version --build-options")?;
let stdout = String::from_utf8_lossy(&output.stdout);
debug!("Perf version build options:\n{stdout}");
// Look for zstd compression support in the build options
// Expected format: " zstd: [ on ] # HAVE_ZSTD_SUPPORT"
let has_zstd = stdout
.lines()
.any(|line| line.to_lowercase().contains("zstd: [ on"));
if has_zstd {
debug!("perf supports zstd compression");
// 3 is a widely adopted default level (AWS Athena, Python, ...)
Ok(Some("--compression-level=3".to_string()))
} else {
warn!("perf does not support zstd compression");
Ok(None)
}
}