Skip to main content

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}