darwin_kperf/database.rs
1//! Safe read-only view of the PMC event database.
2//!
3//! Each Apple Silicon CPU has a corresponding plist file in `/usr/share/kpep/`
4//! that catalogues every hardware event the chip supports: event names, hardware
5//! selectors, fixed-counter assignments, and human-readable aliases like
6//! `"Instructions"` or `"Cycles"`. The `kperfdata.framework` parses these plists
7//! into an in-memory `kpep_db`, and this module wraps that allocation with a
8//! safe, borrowing API.
9//!
10//! You get a [`Database`] by calling [`Sampler::database`](crate::Sampler::database).
11//! It borrows the `Sampler`'s lifetime, so the underlying database memory is
12//! freed when the `Sampler` is dropped. From there you can inspect the full
13//! event list, query fixed-counter events, or read database metadata like the
14//! CPU identifier and marketing name.
15//!
16//! ```rust,ignore
17//! let sampler = Sampler::new()?;
18//! let db = sampler.database();
19//!
20//! for event in db.events() {
21//! if let Some(alias) = event.alias() {
22//! println!("{}: {}", event.name(), alias);
23//! }
24//! }
25//! ```
26
27use core::{
28 ffi::{CStr, c_char},
29 marker::PhantomData,
30 ptr::NonNull,
31};
32
33use darwin_kperf_sys::kperfdata::{
34 KPEP_ARCH_ARM, KPEP_ARCH_ARM64, KPEP_ARCH_I386, KPEP_ARCH_X86_64, kpep_event,
35};
36
37use crate::event::Cpu;
38
39/// Reads a non-null `*const c_char` as a `&str`.
40///
41/// # Safety
42///
43/// `field` must be non-null, point to a valid NUL-terminated C string whose
44/// bytes are valid UTF-8, and the backing memory must remain alive and
45/// unmodified for at least `'db`.
46const unsafe fn as_str<'db>(field: *const c_char) -> &'db str {
47 assert!(!field.is_null());
48
49 // SAFETY: caller guarantees `field` is non-null and NUL-terminated.
50 let str = unsafe { CStr::from_ptr(field).to_str() };
51
52 match str {
53 Ok(str) => str,
54 Err(_) => panic!("string is not utf-8"),
55 }
56}
57
58/// Reads a possibly-null `*const c_char` as an `Option<&str>`.
59///
60/// # Safety
61///
62/// If `field` is non-null, it must point to a valid NUL-terminated C string
63/// whose bytes are valid UTF-8, and the backing memory must remain alive and
64/// unmodified for at least `'db`.
65const unsafe fn as_opt_str<'db>(field: *const c_char) -> Option<&'db str> {
66 if field.is_null() {
67 return None;
68 }
69
70 // SAFETY: we just checked for null; caller guarantees validity otherwise.
71 let str = unsafe { CStr::from_ptr(field).to_str() };
72
73 match str {
74 Ok(str) => Some(str),
75 Err(_) => panic!("string is not utf-8"),
76 }
77}
78
79/// CPU architecture reported by the PMC database.
80///
81/// Mirrors the `KPEP_ARCH_*` constants from `kperfdata.framework`. Includes
82/// non-Apple-Silicon values because the database format predates the ARM
83/// transition.
84#[derive(Debug, Copy, Clone, PartialEq, Eq)]
85pub enum Architecture {
86 /// 32-bit x86 (`IA-32`).
87 I386,
88 /// 64-bit x86 (`x86_64` / `AMD64`).
89 X86_64,
90 /// 32-bit ARM.
91 Arm,
92 /// 64-bit ARM (`AArch64`).
93 Arm64,
94}
95
96impl Architecture {
97 const fn from_sys(value: u32) -> Self {
98 match value {
99 KPEP_ARCH_I386 => Self::I386,
100 KPEP_ARCH_X86_64 => Self::X86_64,
101 KPEP_ARCH_ARM => Self::Arm,
102 KPEP_ARCH_ARM64 => Self::Arm64,
103 _ => unreachable!(), // unknown architecture
104 }
105 }
106}
107
108/// Safe, read-only view of a `kpep_db` opened by the framework.
109///
110/// Each database describes every PMC event that a specific Apple Silicon CPU
111/// supports. The `'db` lifetime is tied to the framework-allocated database;
112/// all string and event pointers inside the database remain valid for this
113/// lifetime.
114pub struct Database<'db> {
115 inner: &'db darwin_kperf_sys::kperfdata::kpep_db,
116}
117
118impl<'db> Database<'db> {
119 /// Wraps a framework-allocated `kpep_db`.
120 ///
121 /// # Safety
122 ///
123 /// The `kpep_db` must have been created by `kpep_db_create` and must
124 /// remain alive and unmodified for `'db`.
125 pub(crate) const unsafe fn from_raw(db: &'db darwin_kperf_sys::kperfdata::kpep_db) -> Self {
126 Self { inner: db }
127 }
128
129 /// Returns a non-null pointer to the underlying `kpep_db`.
130 ///
131 /// This is useful if you need to call `kperfdata.framework` functions
132 /// directly through the [`VTable`](darwin_kperf_sys::kperfdata::VTable).
133 ///
134 /// # Safety
135 ///
136 /// The returned pointer borrows the database. The caller must not mutate
137 /// it, free it, or use it after the [`Database`]'s lifetime has expired.
138 #[must_use]
139 pub const unsafe fn as_raw(&self) -> NonNull<darwin_kperf_sys::kperfdata::kpep_db> {
140 NonNull::from_ref(self.inner)
141 }
142
143 /// Database name, e.g. `"a14"`, `"as4"`.
144 #[must_use]
145 pub const fn name(&self) -> &'db str {
146 // SAFETY: the framework guarantees `kpep_db.name` is a non-null,
147 // NUL-terminated C string that lives as long as the database.
148 unsafe { as_str(self.inner.name) }
149 }
150
151 /// Plist CPU identifier, e.g. `"cpu_7_8_10b282dc"`.
152 #[must_use]
153 pub const fn cpu_id(&self) -> &'db str {
154 // SAFETY: same as `name`, framework-owned, NUL-terminated, valid for 'db.
155 unsafe { as_str(self.inner.cpu_id) }
156 }
157
158 /// The `Cpu` corresponding to this database.
159 #[must_use]
160 pub const fn cpu(&self) -> Option<Cpu> {
161 Cpu::from_db_name(self.name())
162 }
163
164 /// Marketing name, e.g. `"Apple A14/M1"`.
165 #[must_use]
166 pub const fn marketing_name(&self) -> &'db str {
167 // SAFETY: same as `name`, framework-owned, NUL-terminated, valid for 'db.
168 unsafe { as_str(self.inner.marketing_name) }
169 }
170
171 /// All events in the database.
172 #[must_use]
173 pub const fn events(&self) -> &'db [DatabaseEvent<'db>] {
174 if self.inner.event_arr.is_null() {
175 return &[];
176 }
177
178 // SAFETY:
179 // - `event_arr` is a contiguous buffer of `event_count` `kpep_event` structs.
180 // - `DatabaseEvent` is `#[repr(transparent)]` over `kpep_event`, so the slice
181 // reinterpretation is layout-compatible.
182 // - The framework guarantees the buffer is properly aligned and lives for `'db`.
183 // - The buffer is not mutated for `'db`.
184 unsafe { core::slice::from_raw_parts(self.inner.event_arr.cast(), self.inner.event_count) }
185 }
186
187 /// Events assigned to fixed counter registers.
188 ///
189 /// Each element is a reference into the [`events`](Self::events) array.
190 #[must_use]
191 pub const fn fixed_events(&self) -> &'db [&'db DatabaseEvent<'db>] {
192 if self.inner.fixed_event_arr.is_null() {
193 return &[];
194 }
195
196 // SAFETY:
197 // - `fixed_event_arr` points to `fixed_counter_count` contiguous `*mut kpep_event` values,
198 // each pointing into `event_arr`.
199 // - `DatabaseEvent` is `#[repr(transparent)]` over `kpep_event`, so `&DatabaseEvent` has
200 // the same representation as `&kpep_event`, which has the same representation as `*const
201 // kpep_event`.
202 // - The framework guarantees all pointers are non-null, properly aligned, and point to
203 // valid events that live for `'db`.
204 // - The pointed-to events are not mutated for `'db`, satisfying Rust's shared-reference
205 // aliasing rules.
206 unsafe {
207 core::slice::from_raw_parts(
208 self.inner.fixed_event_arr.cast(),
209 self.inner.fixed_counter_count,
210 )
211 }
212 }
213
214 /// The CPU architecture this database targets.
215 #[must_use]
216 pub const fn architecture(&self) -> Architecture {
217 Architecture::from_sys(self.inner.archtecture)
218 }
219}
220
221/// A single hardware performance counter event from the PMC database.
222///
223/// Each event describes one thing the CPU can count: retired instructions,
224/// cache misses, branch mispredictions, micro-ops, and so on. The [`name`](Self::name)
225/// is the hardware-specific identifier (e.g. `"INST_ALL"`), while
226/// [`alias`](Self::alias) provides a human-readable label when one exists
227/// (e.g. `"Instructions"`).
228#[derive(Debug, Copy, Clone)]
229#[repr(transparent)]
230pub struct DatabaseEvent<'db> {
231 event: kpep_event,
232 _marker: PhantomData<&'db ()>,
233}
234
235impl<'db> DatabaseEvent<'db> {
236 /// Unique event name, e.g. `"INST_ALL"`.
237 #[must_use]
238 pub const fn name(&self) -> &'db str {
239 // SAFETY: framework-owned, NUL-terminated, valid for 'db.
240 unsafe { as_str(self.event.name) }
241 }
242
243 /// Human-readable description, if available.
244 #[must_use]
245 pub const fn description(&self) -> Option<&'db str> {
246 // SAFETY: null indicates absence; otherwise same validity as `name`.
247 unsafe { as_opt_str(self.event.description) }
248 }
249
250 /// Errata notes, if any.
251 #[must_use]
252 pub const fn errata(&self) -> Option<&'db str> {
253 // SAFETY: null indicates absence; otherwise same validity as `name`.
254 unsafe { as_opt_str(self.event.errata) }
255 }
256
257 /// Human-readable alias, e.g. `"Instructions"`, `"Cycles"`.
258 #[must_use]
259 pub const fn alias(&self) -> Option<&'db str> {
260 // SAFETY: null indicates absence; otherwise same validity as `name`.
261 unsafe { as_opt_str(self.event.alias) }
262 }
263
264 /// Fallback event name for fixed counters.
265 #[must_use]
266 pub const fn fallback(&self) -> Option<&'db str> {
267 // SAFETY: null indicates absence; otherwise same validity as `name`.
268 unsafe { as_opt_str(self.event.fallback) }
269 }
270
271 /// Whether this event is bound to a fixed counter register.
272 ///
273 /// The framework sets bit 0 of the event's `flags` field during
274 /// `_event_init` when the plist contains a `"fixed_counter"` key.
275 #[must_use]
276 pub const fn is_fixed(&self) -> bool {
277 self.event.flags & 1 != 0
278 }
279}