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}