darwin_kperf/lib.rs
1//! Read hardware performance counters on Apple Silicon.
2//!
3//! Every Apple Silicon chip (M1 through M5) has a Performance Monitoring Unit
4//! (PMU): a set of dedicated hardware registers that count low-level CPU events
5//! like retired instructions, elapsed cycles, branch mispredictions, and cache
6//! misses. These are the same counters that power Instruments and `xctrace`.
7//!
8//! This crate wraps Apple's private `kperf.framework` and `kperfdata.framework`
9//! to give you direct access to those counters from Rust. Both frameworks are
10//! loaded at runtime via `dlopen`, so there is no link-time dependency on
11//! private headers.
12//!
13//! # ⚠️ Experimental
14//!
15//! These frameworks are not part of any public SDK. Apple may change struct
16//! layouts, rename symbols, or drop them entirely in any macOS update. Testing
17//! requires root on physical hardware; the suite cannot run in CI, sandboxed
18//! environments, or under Miri.
19//!
20//! # Quick start
21//!
22//! ```rust,ignore
23//! use darwin_kperf::{Sampler, event::Event};
24//!
25//! let sampler = Sampler::new()?;
26//! let mut thread = sampler.thread([Event::FixedInstructions, Event::FixedCycles])?;
27//!
28//! thread.start()?;
29//! let before = thread.sample()?;
30//! // ... do work ...
31//! let after = thread.sample()?;
32//! thread.stop()?;
33//!
34//! let instructions = after[0] - before[0];
35//! let cycles = after[1] - before[1];
36//! ```
37//!
38//! # Raw values and deltas
39//!
40//! [`ThreadSampler::sample`] returns the raw hardware counter values at the
41//! moment you call it. These are running totals that the CPU maintains
42//! internally; they do not reset between calls. To measure a specific region
43//! of code, take two samples and subtract:
44//!
45//! ```rust,ignore
46//! let before = thread.sample()?;
47//! my_function();
48//! let after = thread.sample()?;
49//!
50//! let instructions_in_my_function = after[0] - before[0];
51//! ```
52//!
53//! The counters only track events on the calling thread, so other threads
54//! running concurrently will not pollute your measurements.
55//!
56//! # Choosing events
57//!
58//! Apple Silicon CPUs have two kinds of performance counters: fixed and
59//! configurable. Fixed counters are always available and always count the same
60//! thing (instructions and cycles). Configurable counters can be programmed to
61//! count any event the CPU supports, but there are only a handful of them
62//! (typically 6 or 8), and you can only use as many configurable events as
63//! there are configurable counter registers.
64//!
65//! For most use cases, [`Event::FixedInstructions`](event::Event::FixedInstructions)
66//! and [`Event::FixedCycles`](event::Event::FixedCycles) are what you want.
67//! Instruction counts are nearly deterministic for straight-line code, making
68//! them ideal for benchmarking. Cycle counts reflect actual execution time on
69//! the microarchitecture, but vary with frequency scaling and thermal state.
70//!
71//! If you need events like branch mispredictions or cache misses, use the
72//! configurable counter variants from the [`Event`](event::Event) enum. The
73//! [`Event::on`](event::Event::on) method resolves a chip-agnostic event name
74//! to the correct hardware-specific name for the detected CPU.
75//!
76//! # Platform
77//!
78//! macOS only. Requires root or the `com.apple.private.kernel.kpc` entitlement.
79//!
80//! ```sh
81//! sudo -E cargo test --package darwin-kperf -- --ignored --nocapture
82//! ```
83//!
84//! # Related crates
85//!
86//! [`darwin-kperf-sys`] provides the raw FFI bindings if you need direct
87//! access to the C function pointers and `repr(C)` structs.
88//!
89//! [`darwin-kperf-criterion`] plugs into Criterion.rs to benchmark with
90//! hardware counters instead of wall-clock time.
91//!
92//! # References
93//!
94//! The API surface and struct layouts are derived from [ibireme's
95//! `kpc_demo.c`][kpc-demo], a standalone C program that demonstrates the full
96//! KPC/KPEP workflow on Apple Silicon.
97//!
98//! [kpc-demo]: https://gist.github.com/ibireme/173517c208c7dc333ba962c1f0d67d12
99//! [`darwin-kperf-sys`]: https://docs.rs/darwin-kperf-sys
100//! [`darwin-kperf-criterion`]: https://docs.rs/darwin-kperf-criterion
101
102#![cfg(target_os = "macos")]
103#![cfg_attr(docsrs, feature(doc_cfg))]
104#![no_std]
105#![expect(unsafe_code)]
106
107extern crate alloc;
108
109mod framework;
110mod sampler;
111
112pub mod database;
113pub use darwin_kperf_events as event;
114pub(crate) mod utils;
115
116pub use darwin_kperf_sys::load::LoadError;
117
118pub use self::{
119 framework::{FrameworkError, FrameworkErrorKind, KPerf, KPerfData},
120 sampler::{Sampler, SamplerError, ThreadSampler},
121};