Skip to main content

darwin_kperf_sys/
kperf.rs

1//! Types, constants, and function pointer type aliases for `kperf.framework`.
2//!
3//! This module covers two subsystems that live inside the same framework:
4//!
5//! - **KPC** (Kernel Performance Counters): configuring which counter classes are active
6//!   ([`kpc_set_counting`]), programming hardware register values ([`kpc_set_config`]), and reading
7//!   back counter accumulations per-thread or per-CPU ([`kpc_get_thread_counters`],
8//!   [`kpc_get_cpu_counters`]).
9//!
10//! - **KPERF** (Kernel Performance): the sampling subsystem that fires actions on timer triggers,
11//!   enabling continuous profiling with configurable sample sources ([`kperf_action_samplers_set`],
12//!   [`kperf_timer_period_set`]).
13//!
14//! Every KPC and KPERF function is a thin wrapper around a `sysctl` call into
15//! the XNU kernel. The specific `sysctl` node is noted in each type alias's
16//! documentation.
17//!
18//! # `VTable`
19//!
20//! Because the framework is private, symbols are not available at link time.
21//! [`VTable::load`] resolves all function pointers eagerly from a [`LibraryHandle`] at runtime,
22//! failing immediately if any symbol is missing.
23
24#![expect(non_camel_case_types)]
25use core::{
26    ffi::{c_char, c_int},
27    fmt,
28};
29
30use crate::load::{LibraryHandle, LibrarySymbol, LoadError};
31
32// -----------------------------------------------------------------------------
33// Types
34// -----------------------------------------------------------------------------
35
36pub type kpc_config_t = u64;
37
38// -----------------------------------------------------------------------------
39// KPC class constants
40// -----------------------------------------------------------------------------
41
42pub const KPC_CLASS_FIXED: u32 = 0;
43pub const KPC_CLASS_CONFIGURABLE: u32 = 1;
44pub const KPC_CLASS_POWER: u32 = 2;
45pub const KPC_CLASS_RAWPMU: u32 = 3;
46
47pub const KPC_CLASS_FIXED_MASK: u32 = 1 << KPC_CLASS_FIXED;
48pub const KPC_CLASS_CONFIGURABLE_MASK: u32 = 1 << KPC_CLASS_CONFIGURABLE;
49pub const KPC_CLASS_POWER_MASK: u32 = 1 << KPC_CLASS_POWER;
50pub const KPC_CLASS_RAWPMU_MASK: u32 = 1 << KPC_CLASS_RAWPMU;
51
52// -----------------------------------------------------------------------------
53// PMU version constants
54// -----------------------------------------------------------------------------
55
56pub const KPC_PMU_ERROR: u32 = 0;
57pub const KPC_PMU_INTEL_V3: u32 = 1;
58pub const KPC_PMU_ARM_APPLE: u32 = 2;
59pub const KPC_PMU_INTEL_V2: u32 = 3;
60pub const KPC_PMU_ARM_V2: u32 = 4;
61
62pub const KPC_MAX_COUNTERS: usize = 32;
63
64// -----------------------------------------------------------------------------
65// kperf sampler constants
66// -----------------------------------------------------------------------------
67
68pub const KPERF_SAMPLER_TH_INFO: u32 = 1 << 0;
69pub const KPERF_SAMPLER_TH_SNAPSHOT: u32 = 1 << 1;
70pub const KPERF_SAMPLER_KSTACK: u32 = 1 << 2;
71pub const KPERF_SAMPLER_USTACK: u32 = 1 << 3;
72pub const KPERF_SAMPLER_PMC_THREAD: u32 = 1 << 4;
73pub const KPERF_SAMPLER_PMC_CPU: u32 = 1 << 5;
74pub const KPERF_SAMPLER_PMC_CONFIG: u32 = 1 << 6;
75pub const KPERF_SAMPLER_MEMINFO: u32 = 1 << 7;
76pub const KPERF_SAMPLER_TH_SCHEDULING: u32 = 1 << 8;
77pub const KPERF_SAMPLER_TH_DISPATCH: u32 = 1 << 9;
78pub const KPERF_SAMPLER_TK_SNAPSHOT: u32 = 1 << 10;
79pub const KPERF_SAMPLER_SYS_MEM: u32 = 1 << 11;
80pub const KPERF_SAMPLER_TH_INSCYC: u32 = 1 << 12;
81pub const KPERF_SAMPLER_TK_INFO: u32 = 1 << 13;
82
83pub const KPERF_ACTION_MAX: u32 = 32;
84pub const KPERF_TIMER_MAX: u32 = 8;
85
86// -----------------------------------------------------------------------------
87// Function pointer types
88// -----------------------------------------------------------------------------
89
90/// Prints the current CPU identification string to the buffer (same as `snprintf`),
91/// such as `"cpu_7_8_10b282dc_46"`. This string can be used to locate the PMC
92/// database in `/usr/share/kpep`.
93///
94/// Returns the string's length, or a negative value if an error occurs.
95///
96/// This method does not require root privileges.
97///
98/// Reads `hw.cputype`, `hw.cpusubtype`, `hw.cpufamily`, and `machdep.cpu.model`
99/// via sysctl.
100pub type kpc_cpu_string = unsafe extern "C" fn(buf: *mut c_char, buf_size: usize) -> c_int;
101
102/// Gets the version of KPC that's being run.
103///
104/// Returns one of the `KPC_PMU_*` version constants.
105///
106/// Reads `kpc.pmu_version` via sysctl.
107pub type kpc_pmu_version = unsafe extern "C" fn() -> u32;
108
109/// Gets running PMC classes.
110///
111/// Returns a combination of `KPC_CLASS_*_MASK` constants, or 0 if an error
112/// occurs or no class is set.
113///
114/// Reads `kpc.counting` via sysctl.
115pub type kpc_get_counting = unsafe extern "C" fn() -> u32;
116
117/// Sets PMC classes to enable counting.
118///
119/// `classes` is a combination of `KPC_CLASS_*_MASK` constants; pass 0 to
120/// shut down counting.
121///
122/// Returns 0 for success.
123///
124/// Writes `kpc.counting` via sysctl.
125pub type kpc_set_counting = unsafe extern "C" fn(classes: u32) -> c_int;
126
127/// Gets running PMC classes for the current thread.
128///
129/// Returns a combination of `KPC_CLASS_*_MASK` constants, or 0 if an error
130/// occurs or no class is set.
131///
132/// Reads `kpc.thread_counting` via sysctl.
133pub type kpc_get_thread_counting = unsafe extern "C" fn() -> u32;
134
135/// Sets PMC classes to enable counting for the current thread.
136///
137/// `classes` is a combination of `KPC_CLASS_*_MASK` constants; pass 0 to
138/// shut down counting.
139///
140/// Returns 0 for success.
141///
142/// Writes `kpc.thread_counting` via sysctl.
143pub type kpc_set_thread_counting = unsafe extern "C" fn(classes: u32) -> c_int;
144
145/// Gets how many config registers there are for a given mask.
146///
147/// For example, Intel may return 1 for [`KPC_CLASS_FIXED_MASK`] and 4 for
148/// [`KPC_CLASS_CONFIGURABLE_MASK`].
149///
150/// `classes` is a combination of `KPC_CLASS_*_MASK` constants.
151///
152/// Returns 0 if an error occurs or no class is set.
153///
154/// This method does not require root privileges.
155///
156/// Reads `kpc.config_count` via sysctl.
157pub type kpc_get_config_count = unsafe extern "C" fn(classes: u32) -> u32;
158
159/// Gets how many counters there are for a given mask.
160///
161/// For example, Intel may return 3 for [`KPC_CLASS_FIXED_MASK`] and 4 for
162/// [`KPC_CLASS_CONFIGURABLE_MASK`].
163///
164/// `classes` is a combination of `KPC_CLASS_*_MASK` constants.
165///
166/// This method does not require root privileges.
167///
168/// Reads `kpc.counter_count` via sysctl.
169pub type kpc_get_counter_count = unsafe extern "C" fn(classes: u32) -> u32;
170
171/// Gets config registers.
172///
173/// `config` is a buffer to receive values; it should be at least
174/// `kpc_get_config_count(classes) * size_of::<KpcConfig>()` bytes.
175///
176/// Returns 0 for success.
177///
178/// Reads `kpc.config_count` and `kpc.config` via sysctl.
179pub type kpc_get_config = unsafe extern "C" fn(classes: u32, config: *mut kpc_config_t) -> c_int;
180
181/// Sets config registers.
182///
183/// `config` is a buffer of values; it should be at least
184/// `kpc_get_config_count(classes) * size_of::<KpcConfig>()` bytes.
185///
186/// Returns 0 for success.
187///
188/// Reads `kpc.config_count` and writes `kpc.config` via sysctl.
189pub type kpc_set_config = unsafe extern "C" fn(classes: u32, config: *mut kpc_config_t) -> c_int;
190
191/// Gets counter accumulations.
192///
193/// If `all_cpus` is true, the buffer element count should be at least
194/// `cpu_count * counter_count`. Otherwise, it should be at least `counter_count`.
195///
196/// See [`kpc_get_counter_count`].
197///
198/// - `all_cpus`: `true` for all CPUs, `false` for the current CPU.
199/// - `classes`: a combination of `KPC_CLASS_*_MASK` constants.
200/// - `curcpu`: pointer to receive the current CPU id; may be null.
201/// - `buf`: buffer to receive counter values.
202///
203/// Returns 0 for success.
204///
205/// Reads `hw.ncpu`, `kpc.counter_count`, and `kpc.counters` via sysctl.
206pub type kpc_get_cpu_counters =
207    unsafe extern "C" fn(all_cpus: bool, classes: u32, curcpu: *mut c_int, buf: *mut u64) -> c_int;
208
209/// Gets counter accumulations for the current thread.
210///
211/// - `tid`: thread id, should be 0.
212/// - `buf_count`: number of elements in `buf` (not bytes); should be at least
213///   `kpc_get_counter_count()`.
214/// - `buf`: buffer to receive counter values.
215///
216/// Returns 0 for success.
217///
218/// Reads `kpc.thread_counters` via sysctl.
219pub type kpc_get_thread_counters =
220    unsafe extern "C" fn(tid: u32, buf_count: u32, buf: *mut u64) -> c_int;
221
222/// Acquires or releases the counters used by the Power Manager.
223///
224/// `val`: 1 to acquire, 0 to release.
225///
226/// Returns 0 for success.
227///
228/// Writes `kpc.force_all_ctrs` via sysctl.
229pub type kpc_force_all_ctrs_set = unsafe extern "C" fn(val: c_int) -> c_int;
230
231/// Gets the state of `force_all_ctrs`.
232///
233/// Returns 0 for success.
234///
235/// Reads `kpc.force_all_ctrs` via sysctl.
236pub type kpc_force_all_ctrs_get = unsafe extern "C" fn(val_out: *mut c_int) -> c_int;
237
238/// Sets the number of actions. Should be [`KPERF_ACTION_MAX`].
239///
240/// Writes `kperf.action.count` via sysctl.
241pub type kperf_action_count_set = unsafe extern "C" fn(count: u32) -> c_int;
242
243/// Gets the number of actions.
244///
245/// Reads `kperf.action.count` via sysctl.
246pub type kperf_action_count_get = unsafe extern "C" fn(count: *mut u32) -> c_int;
247
248/// Sets what to sample when a trigger fires an action, e.g.
249/// [`KPERF_SAMPLER_PMC_CPU`].
250///
251/// Writes `kperf.action.samplers` via sysctl.
252pub type kperf_action_samplers_set = unsafe extern "C" fn(actionid: u32, sample: u32) -> c_int;
253
254/// Gets what to sample when a trigger fires an action.
255///
256/// Reads `kperf.action.samplers` via sysctl.
257pub type kperf_action_samplers_get = unsafe extern "C" fn(actionid: u32, sample: *mut u32) -> c_int;
258
259/// Applies a task filter to the action. Pass -1 to disable the filter.
260///
261/// Writes `kperf.action.filter_by_task` via sysctl.
262pub type kperf_action_filter_set_by_task = unsafe extern "C" fn(actionid: u32, port: i32) -> c_int;
263
264/// Applies a pid filter to the action. Pass -1 to disable the filter.
265///
266/// Writes `kperf.action.filter_by_pid` via sysctl.
267pub type kperf_action_filter_set_by_pid = unsafe extern "C" fn(actionid: u32, pid: i32) -> c_int;
268
269/// Sets the number of time triggers. Should be [`KPERF_TIMER_MAX`].
270///
271/// Writes `kperf.timer.count` via sysctl.
272pub type kperf_timer_count_set = unsafe extern "C" fn(count: u32) -> c_int;
273
274/// Gets the number of time triggers.
275///
276/// Reads `kperf.timer.count` via sysctl.
277pub type kperf_timer_count_get = unsafe extern "C" fn(count: *mut u32) -> c_int;
278
279/// Sets timer number and period.
280///
281/// Writes `kperf.timer.period` via sysctl.
282pub type kperf_timer_period_set = unsafe extern "C" fn(actionid: u32, tick: u64) -> c_int;
283
284/// Gets timer number and period.
285///
286/// Reads `kperf.timer.period` via sysctl.
287pub type kperf_timer_period_get = unsafe extern "C" fn(actionid: u32, tick: *mut u64) -> c_int;
288
289/// Sets timer number and action id.
290///
291/// Writes `kperf.timer.action` via sysctl.
292pub type kperf_timer_action_set = unsafe extern "C" fn(actionid: u32, timerid: u32) -> c_int;
293
294/// Gets timer number and action id.
295///
296/// Reads `kperf.timer.action` via sysctl.
297pub type kperf_timer_action_get = unsafe extern "C" fn(actionid: u32, timerid: *mut u32) -> c_int;
298
299/// Sets which timer ID does PET (Profile Every Thread).
300///
301/// Writes `kperf.timer.pet_timer` via sysctl.
302pub type kperf_timer_pet_set = unsafe extern "C" fn(timerid: u32) -> c_int;
303
304/// Gets which timer ID does PET (Profile Every Thread).
305///
306/// Reads `kperf.timer.pet_timer` via sysctl.
307pub type kperf_timer_pet_get = unsafe extern "C" fn(timerid: *mut u32) -> c_int;
308
309/// Enables or disables sampling.
310///
311/// Writes `kperf.sampling` via sysctl.
312pub type kperf_sample_set = unsafe extern "C" fn(enabled: u32) -> c_int;
313
314/// Gets whether sampling is currently active.
315///
316/// Reads `kperf.sampling` via sysctl.
317pub type kperf_sample_get = unsafe extern "C" fn(enabled: *mut u32) -> c_int;
318
319/// Resets kperf: stops sampling, kdebug, timers, and actions.
320///
321/// Returns 0 for success.
322pub type kperf_reset = unsafe extern "C" fn() -> c_int;
323
324/// Converts nanoseconds to CPU ticks.
325pub type kperf_ns_to_ticks = unsafe extern "C" fn(ns: u64) -> u64;
326
327/// Converts CPU ticks to nanoseconds.
328pub type kperf_ticks_to_ns = unsafe extern "C" fn(ticks: u64) -> u64;
329
330/// Gets the CPU tick frequency (`mach_absolute_time`).
331pub type kperf_tick_frequency = unsafe extern "C" fn() -> u64;
332
333// -----------------------------------------------------------------------------
334// VTable
335// -----------------------------------------------------------------------------
336
337macro_rules! load_sym {
338    ($handle:expr, $name:ident, $cname:expr) => {{
339        // SAFETY: the symbol is a function pointer with the signature declared
340        // by the corresponding type alias.
341        unsafe { core::mem::transmute::<LibrarySymbol, $name>($handle.symbol($cname)?) }
342    }};
343}
344
345/// Eagerly-resolved function pointers for `kperf.framework`.
346///
347/// Each field is a C function pointer obtained via `dlsym` from Apple's private
348/// `kperf.framework`. The framework exposes two subsystems:
349///
350/// - **KPC** (Kernel Performance Counters): enabling counter classes ([`kpc_set_counting`]),
351///   programming hardware registers ([`kpc_set_config`]), reading per-thread or per-CPU
352///   accumulations ([`kpc_get_thread_counters`], [`kpc_get_cpu_counters`]), and force-acquiring
353///   counters from the Power Manager ([`kpc_force_all_ctrs_set`]).
354///
355/// - **KPERF** (Kernel Performance): the sampling subsystem that fires actions on timer triggers
356///   ([`kperf_action_samplers_set`], [`kperf_timer_period_set`]), with tick/nanosecond conversion
357///   helpers ([`kperf_ns_to_ticks`], [`kperf_ticks_to_ns`]).
358///
359/// All function pointers are resolved eagerly by [`load`](Self::load). If any
360/// symbol is missing from the framework, loading fails immediately rather than
361/// deferring to first use. The resolved pointers remain valid for as long as
362/// the originating [`LibraryHandle`] is open.
363///
364/// # Safety
365///
366/// Every field is an `unsafe extern "C" fn`. The caller is responsible for
367/// upholding the preconditions documented on each function pointer type alias:
368/// buffer sizes, pointer validity, and privilege requirements. Most KPC/KPERF
369/// calls require root privileges.
370pub struct VTable {
371    pub kpc_cpu_string: kpc_cpu_string,
372    pub kpc_pmu_version: kpc_pmu_version,
373    pub kpc_get_counting: kpc_get_counting,
374    pub kpc_set_counting: kpc_set_counting,
375    pub kpc_get_thread_counting: kpc_get_thread_counting,
376    pub kpc_set_thread_counting: kpc_set_thread_counting,
377    pub kpc_get_config_count: kpc_get_config_count,
378    pub kpc_get_counter_count: kpc_get_counter_count,
379    pub kpc_get_config: kpc_get_config,
380    pub kpc_set_config: kpc_set_config,
381    pub kpc_get_cpu_counters: kpc_get_cpu_counters,
382    pub kpc_get_thread_counters: kpc_get_thread_counters,
383    pub kpc_force_all_ctrs_set: kpc_force_all_ctrs_set,
384    pub kpc_force_all_ctrs_get: kpc_force_all_ctrs_get,
385    pub kperf_action_count_set: kperf_action_count_set,
386    pub kperf_action_count_get: kperf_action_count_get,
387    pub kperf_action_samplers_set: kperf_action_samplers_set,
388    pub kperf_action_samplers_get: kperf_action_samplers_get,
389    pub kperf_action_filter_set_by_task: kperf_action_filter_set_by_task,
390    pub kperf_action_filter_set_by_pid: kperf_action_filter_set_by_pid,
391    pub kperf_timer_count_set: kperf_timer_count_set,
392    pub kperf_timer_count_get: kperf_timer_count_get,
393    pub kperf_timer_period_set: kperf_timer_period_set,
394    pub kperf_timer_period_get: kperf_timer_period_get,
395    pub kperf_timer_action_set: kperf_timer_action_set,
396    pub kperf_timer_action_get: kperf_timer_action_get,
397    pub kperf_timer_pet_set: kperf_timer_pet_set,
398    pub kperf_timer_pet_get: kperf_timer_pet_get,
399    pub kperf_sample_set: kperf_sample_set,
400    pub kperf_sample_get: kperf_sample_get,
401    pub kperf_reset: kperf_reset,
402    pub kperf_ns_to_ticks: kperf_ns_to_ticks,
403    pub kperf_ticks_to_ns: kperf_ticks_to_ns,
404    pub kperf_tick_frequency: kperf_tick_frequency,
405}
406
407impl fmt::Debug for VTable {
408    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
409        fmt.debug_struct("VTable").finish_non_exhaustive()
410    }
411}
412
413impl VTable {
414    /// Resolves every `kperf.framework` symbol from `handle`.
415    ///
416    /// Resolution is all-or-nothing: if any of the 34 required symbols cannot
417    /// be found, the call returns an error.
418    ///
419    /// # Errors
420    ///
421    /// Returns [`LoadError`] if any symbol cannot be resolved from the framework.
422    #[expect(clippy::too_many_lines)]
423    pub fn load(handle: &LibraryHandle) -> Result<Self, LoadError> {
424        Ok(Self {
425            kpc_cpu_string: load_sym!(handle, kpc_cpu_string, c"kpc_cpu_string"),
426            kpc_pmu_version: load_sym!(handle, kpc_pmu_version, c"kpc_pmu_version"),
427            kpc_get_counting: load_sym!(handle, kpc_get_counting, c"kpc_get_counting"),
428            kpc_set_counting: load_sym!(handle, kpc_set_counting, c"kpc_set_counting"),
429            kpc_get_thread_counting: load_sym!(
430                handle,
431                kpc_get_thread_counting,
432                c"kpc_get_thread_counting"
433            ),
434            kpc_set_thread_counting: load_sym!(
435                handle,
436                kpc_set_thread_counting,
437                c"kpc_set_thread_counting"
438            ),
439            kpc_get_config_count: load_sym!(handle, kpc_get_config_count, c"kpc_get_config_count"),
440            kpc_get_counter_count: load_sym!(
441                handle,
442                kpc_get_counter_count,
443                c"kpc_get_counter_count"
444            ),
445            kpc_get_config: load_sym!(handle, kpc_get_config, c"kpc_get_config"),
446            kpc_set_config: load_sym!(handle, kpc_set_config, c"kpc_set_config"),
447            kpc_get_cpu_counters: load_sym!(handle, kpc_get_cpu_counters, c"kpc_get_cpu_counters"),
448            kpc_get_thread_counters: load_sym!(
449                handle,
450                kpc_get_thread_counters,
451                c"kpc_get_thread_counters"
452            ),
453            kpc_force_all_ctrs_set: load_sym!(
454                handle,
455                kpc_force_all_ctrs_set,
456                c"kpc_force_all_ctrs_set"
457            ),
458            kpc_force_all_ctrs_get: load_sym!(
459                handle,
460                kpc_force_all_ctrs_get,
461                c"kpc_force_all_ctrs_get"
462            ),
463            kperf_action_count_set: load_sym!(
464                handle,
465                kperf_action_count_set,
466                c"kperf_action_count_set"
467            ),
468            kperf_action_count_get: load_sym!(
469                handle,
470                kperf_action_count_get,
471                c"kperf_action_count_get"
472            ),
473            kperf_action_samplers_set: load_sym!(
474                handle,
475                kperf_action_samplers_set,
476                c"kperf_action_samplers_set"
477            ),
478            kperf_action_samplers_get: load_sym!(
479                handle,
480                kperf_action_samplers_get,
481                c"kperf_action_samplers_get"
482            ),
483            kperf_action_filter_set_by_task: load_sym!(
484                handle,
485                kperf_action_filter_set_by_task,
486                c"kperf_action_filter_set_by_task"
487            ),
488            kperf_action_filter_set_by_pid: load_sym!(
489                handle,
490                kperf_action_filter_set_by_pid,
491                c"kperf_action_filter_set_by_pid"
492            ),
493            kperf_timer_count_set: load_sym!(
494                handle,
495                kperf_timer_count_set,
496                c"kperf_timer_count_set"
497            ),
498            kperf_timer_count_get: load_sym!(
499                handle,
500                kperf_timer_count_get,
501                c"kperf_timer_count_get"
502            ),
503            kperf_timer_period_set: load_sym!(
504                handle,
505                kperf_timer_period_set,
506                c"kperf_timer_period_set"
507            ),
508            kperf_timer_period_get: load_sym!(
509                handle,
510                kperf_timer_period_get,
511                c"kperf_timer_period_get"
512            ),
513            kperf_timer_action_set: load_sym!(
514                handle,
515                kperf_timer_action_set,
516                c"kperf_timer_action_set"
517            ),
518            kperf_timer_action_get: load_sym!(
519                handle,
520                kperf_timer_action_get,
521                c"kperf_timer_action_get"
522            ),
523            kperf_timer_pet_set: load_sym!(handle, kperf_timer_pet_set, c"kperf_timer_pet_set"),
524            kperf_timer_pet_get: load_sym!(handle, kperf_timer_pet_get, c"kperf_timer_pet_get"),
525            kperf_sample_set: load_sym!(handle, kperf_sample_set, c"kperf_sample_set"),
526            kperf_sample_get: load_sym!(handle, kperf_sample_get, c"kperf_sample_get"),
527            kperf_reset: load_sym!(handle, kperf_reset, c"kperf_reset"),
528            kperf_ns_to_ticks: load_sym!(handle, kperf_ns_to_ticks, c"kperf_ns_to_ticks"),
529            kperf_ticks_to_ns: load_sym!(handle, kperf_ticks_to_ns, c"kperf_ticks_to_ns"),
530            kperf_tick_frequency: load_sym!(handle, kperf_tick_frequency, c"kperf_tick_frequency"),
531        })
532    }
533}