1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4
5use libc::pid_t;
6use std::os::raw::{c_int, c_ulong};
7
8pub unsafe fn perf_event_open(
27 attrs: *mut bindings::perf_event_attr,
28 pid: pid_t,
29 cpu: c_int,
30 group_fd: c_int,
31 flags: c_ulong,
32) -> c_int {
33 unsafe {
34 libc::syscall(
35 bindings::__NR_perf_event_open as libc::c_long,
36 attrs as *const bindings::perf_event_attr,
37 pid,
38 cpu,
39 group_fd,
40 flags,
41 ) as c_int
42 }
43}
44
45pub mod bindings {
46 include!(concat!(env!("OUT_DIR"), "/perf_bindings.rs"));
47}
48
49pub mod ioctls {
50 use crate::perf;
51 use std::os::raw::{c_int, c_uint};
52
53 #[allow(clippy::missing_safety_doc)]
54 pub unsafe fn enable(fd: c_int, arg: c_uint) -> c_int {
55 unsafe { libc::ioctl(fd, perf::bindings::ENABLE as libc::Ioctl, arg) }
56 }
57
58 #[allow(clippy::missing_safety_doc)]
59 pub unsafe fn reset(fd: c_int, arg: c_uint) -> c_int {
60 unsafe { libc::ioctl(fd, perf::bindings::RESET as libc::Ioctl, arg) }
61 }
62}
63
64use anyhow::Context as _;
65use anyhow::Result;
66use libbpf_rs::MapCore;
67use libbpf_rs::MapFlags;
68
69pub const PERF_MAP_STRIDE: u32 = 4096;
71
72#[derive(Clone, Debug)]
76pub struct PerfEventSpec {
77 pub event_id: u64,
79 pub type_: u32,
81 pub config: u64,
83 pub display_name: String,
85}
86
87fn parse_hardware_event(s: &str) -> Option<u64> {
88 match s {
89 "cpu-cycles" | "cycles" => Some(0),
90 "instructions" => Some(1),
91 "cache-references" => Some(2),
92 "cache-misses" => Some(3),
93 "branch-instructions" | "branches" => Some(4),
94 "branch-misses" => Some(5),
95 "bus-cycles" => Some(6),
96 "stalled-cycles-frontend" | "idle-cycles-frontend" => Some(7),
97 "stalled-cycles-backend" | "idle-cycles-backend" => Some(8),
98 "ref-cycles" => Some(9),
99 _ => None,
100 }
101}
102
103fn parse_software_event(s: &str) -> Option<u64> {
104 match s {
105 "cpu-clock" => Some(0),
106 "task-clock" => Some(1),
107 "page-faults" | "faults" => Some(2),
108 "context-switches" | "cs" => Some(3),
109 "cpu-migrations" | "migrations" => Some(4),
110 "minor-faults" => Some(5),
111 "major-faults" => Some(6),
112 "alignment-faults" => Some(7),
113 "emulation-faults" => Some(8),
114 "dummy" => Some(9),
115 "bpf-output" => Some(10),
116 _ => None,
117 }
118}
119
120fn parse_hw_cache_event(s: &str) -> Option<u64> {
121 let (cache_id, prefix_len) = if s.starts_with("L1-dcache-") {
122 (0, 10)
123 } else if s.starts_with("L1-icache-") {
124 (1, 10)
125 } else if s.starts_with("LLC-") {
126 (2, 4)
127 } else if s.starts_with("dTLB-") {
128 (3, 5)
129 } else if s.starts_with("iTLB-") {
130 (4, 5)
131 } else if s.starts_with("branch-") {
132 (5, 7)
133 } else if s.starts_with("node-") {
134 (6, 5)
135 } else {
136 return None;
137 };
138
139 let suffix = &s[prefix_len..];
140 let (op_id, result_id) = match suffix {
141 "loads" => (0, 0),
142 "load-misses" => (0, 1),
143 "stores" => (1, 0),
144 "store-misses" => (1, 1),
145 "prefetches" => (2, 0),
146 "prefetch-misses" => (2, 1),
147 _ => return None,
148 };
149
150 Some((result_id << 16) | (op_id << 8) | cache_id)
151}
152
153pub fn parse_perf_event(s: &str) -> Result<PerfEventSpec, String> {
156 let s = s.trim();
157 if s.is_empty() || s == "0" || s.eq_ignore_ascii_case("0x0") {
158 return Ok(PerfEventSpec {
159 event_id: 0,
160 type_: bindings::PERF_TYPE_RAW,
161 config: 0,
162 display_name: s.to_string(),
163 });
164 }
165
166 if let Some(hex_str) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
167 if let Ok(config) = u64::from_str_radix(hex_str, 16) {
168 return Ok(PerfEventSpec {
169 event_id: config,
170 type_: bindings::PERF_TYPE_RAW,
171 config,
172 display_name: s.to_string(),
173 });
174 }
175 }
176
177 if let Some(config) = parse_hardware_event(s) {
178 let event_id = (bindings::PERF_TYPE_HARDWARE as u64) << 32 | config;
179 return Ok(PerfEventSpec {
180 event_id,
181 type_: bindings::PERF_TYPE_HARDWARE,
182 config,
183 display_name: s.to_string(),
184 });
185 }
186
187 if let Some(config) = parse_software_event(s) {
188 let event_id = (bindings::PERF_TYPE_SOFTWARE as u64) << 32 | config;
189 return Ok(PerfEventSpec {
190 event_id,
191 type_: bindings::PERF_TYPE_SOFTWARE,
192 config,
193 display_name: s.to_string(),
194 });
195 }
196
197 if let Some(config) = parse_hw_cache_event(s) {
198 let event_id = (bindings::PERF_TYPE_HW_CACHE as u64) << 32 | config;
199 return Ok(PerfEventSpec {
200 event_id,
201 type_: bindings::PERF_TYPE_HW_CACHE,
202 config,
203 display_name: s.to_string(),
204 });
205 }
206
207 Err(format!(
208 "Invalid perf event '{}': use hex (0xN) or a symbolic name (e.g. cache-misses, LLC-load-misses, page-faults)",
209 s
210 ))
211}
212
213pub fn setup_perf_events(
218 map: &impl MapCore,
219 cpu: i32,
220 spec: &PerfEventSpec,
221 counter_idx: u32,
222) -> Result<()> {
223 if spec.event_id == 0 {
224 return Ok(());
225 }
226
227 let mut attrs = bindings::perf_event_attr {
229 type_: spec.type_,
230 config: spec.config,
231 size: std::mem::size_of::<bindings::perf_event_attr>() as u32,
232 ..Default::default()
233 };
234
235 let fd = unsafe { perf_event_open(&mut attrs, -1, cpu, -1, 0) };
236
237 if fd < 0 {
238 let err = std::io::Error::last_os_error();
239 return Err(anyhow::anyhow!(
240 "Failed to open perf event '{}' on CPU {}: {}",
241 spec.display_name,
242 cpu,
243 err
244 ));
245 }
246
247 let key = cpu as u32 + counter_idx * PERF_MAP_STRIDE;
248
249 map.update(&key.to_ne_bytes(), &fd.to_ne_bytes(), MapFlags::ANY)
250 .with_context(|| "Failed to update perf_events map")?;
251
252 Ok(())
253}