Skip to main content

darwin_kperf/sampler/
error.rs

1//! Error types for the sampler API.
2
3use alloc::string::String;
4use core::{error, ffi::c_int, fmt};
5
6use darwin_kperf_sys::load::LoadError;
7
8use crate::{FrameworkError, event::Event};
9
10/// An error from the [`Sampler`](super::Sampler) API.
11#[derive(Debug)]
12pub enum SamplerError {
13    /// Failed to load a framework via `dlopen`/`dlsym`.
14    Load(LoadError),
15    /// A `kperfdata.framework` (KPEP) operation failed.
16    Framework(FrameworkError),
17    /// The PMC database name is not valid UTF-8.
18    InvalidCpuName,
19    /// The detected CPU is not recognized by this build.
20    UnknownCpu(String),
21    /// An event is not available on the detected CPU.
22    EventUnavailable(Event),
23    /// The framework's `kpep_event` struct size does not match our definition.
24    EventLayoutMismatch { expected: usize, actual: usize },
25    /// A framework function returned a null pointer where non-null was expected.
26    UnexpectedNullPointer,
27    /// Insufficient privileges to access performance counters.
28    ///
29    /// Run with `sudo` or sign the binary with the
30    /// `com.apple.private.kernel.kpc` entitlement.
31    MissingPrivileges,
32    /// Failed to force-acquire all hardware counters.
33    FailedToForceAllCounters(c_int),
34    /// Failed to set KPC register configuration.
35    FailedToSetKpcConfig(c_int),
36    /// Failed to enable counting.
37    UnableToStartCounting(c_int),
38    /// Failed to disable counting.
39    UnableToStopCounting(c_int),
40    /// Failed to enable per-thread counting.
41    UnableToStartThreadCounting(c_int),
42    /// Failed to disable per-thread counting.
43    UnableToStopThreadCounting(c_int),
44    /// Failed to read thread counters.
45    UnableToReadCounters(c_int),
46    /// Failed to release force-acquired counters during teardown.
47    UnableToResetControl(c_int),
48    /// Attempted to sample while counting is not enabled.
49    SamplerNotRunning,
50}
51
52impl fmt::Display for SamplerError {
53    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            Self::Load(error) => write!(fmt, "framework load failed: {error}"),
56            Self::Framework(error) => fmt::Display::fmt(error, fmt),
57            Self::InvalidCpuName => fmt.write_str("PMC database name is not valid UTF-8"),
58            Self::UnknownCpu(name) => write!(fmt, "unrecognized CPU: {name}"),
59            #[expect(clippy::use_debug)]
60            Self::EventUnavailable(event) => {
61                write!(fmt, "event {event:?} is unavailable on this CPU")
62            }
63            Self::EventLayoutMismatch { expected, actual } => {
64                write!(
65                    fmt,
66                    "kpep_event layout mismatch: expected stride {expected}, got {actual}"
67                )
68            }
69            Self::UnexpectedNullPointer => {
70                fmt.write_str("framework returned unexpected null pointer")
71            }
72            Self::MissingPrivileges => {
73                fmt.write_str("insufficient privileges to access performance counters")
74            }
75            Self::FailedToForceAllCounters(code) => {
76                write!(fmt, "failed to force-acquire counters (code {code})")
77            }
78            Self::FailedToSetKpcConfig(code) => {
79                write!(fmt, "failed to set KPC config (code {code})")
80            }
81            Self::UnableToStartCounting(code) => {
82                write!(fmt, "failed to start counting (code {code})")
83            }
84            Self::UnableToStopCounting(code) => {
85                write!(fmt, "failed to stop counting (code {code})")
86            }
87            Self::UnableToStartThreadCounting(code) => {
88                write!(fmt, "failed to start thread counting (code {code})")
89            }
90            Self::UnableToStopThreadCounting(code) => {
91                write!(fmt, "failed to stop thread counting (code {code})")
92            }
93            Self::UnableToReadCounters(code) => {
94                write!(fmt, "failed to read thread counters (code {code})")
95            }
96            Self::UnableToResetControl(code) => {
97                write!(fmt, "failed to release counters (code {code})")
98            }
99            Self::SamplerNotRunning => {
100                fmt.write_str("attempted to sample while counting is not enabled")
101            }
102        }
103    }
104}
105
106impl error::Error for SamplerError {
107    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
108        match self {
109            Self::Load(err) => Some(err),
110            Self::Framework(err) => Some(err),
111            Self::InvalidCpuName
112            | Self::UnknownCpu(_)
113            | Self::EventUnavailable(_)
114            | Self::EventLayoutMismatch { .. }
115            | Self::UnexpectedNullPointer
116            | Self::MissingPrivileges
117            | Self::FailedToForceAllCounters(_)
118            | Self::FailedToSetKpcConfig(_)
119            | Self::UnableToStartCounting(_)
120            | Self::UnableToStopCounting(_)
121            | Self::UnableToStartThreadCounting(_)
122            | Self::UnableToStopThreadCounting(_)
123            | Self::UnableToReadCounters(_)
124            | Self::UnableToResetControl(_)
125            | Self::SamplerNotRunning => None,
126        }
127    }
128}
129
130impl From<LoadError> for SamplerError {
131    fn from(value: LoadError) -> Self {
132        Self::Load(value)
133    }
134}
135
136impl From<FrameworkError> for SamplerError {
137    fn from(value: FrameworkError) -> Self {
138        Self::Framework(value)
139    }
140}
141
142/// Converts a KPEP return code into a [`Result`], mapping non-zero to
143/// [`FrameworkError`].
144pub(crate) const fn try_kpep(code: c_int) -> Result<(), FrameworkError> {
145    if code == 0 {
146        Ok(())
147    } else {
148        Err(FrameworkError::from_code(code))
149    }
150}
151
152/// Converts a KPC return code into a [`Result`], using `error` to construct
153/// the [`SamplerError`] variant for non-zero codes.
154pub(crate) fn try_kpc(code: c_int, error: fn(c_int) -> SamplerError) -> Result<(), SamplerError> {
155    if code == 0 { Ok(()) } else { Err(error(code)) }
156}