pub(crate) use super::cpu_id::PerfCpuId;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct PerfCpuRequest(i32);
impl PerfCpuRequest {
const fn new(value: i32) -> Self {
Self(value)
}
pub(crate) fn resolve_optional(
self,
cpu_count: usize,
) -> Result<Option<PerfCpuId>, PerfTargetError> {
match self.0 {
-1 => Ok(None),
value if value >= 0 && (value as usize) < cpu_count => {
Ok(Some(PerfCpuId::new(value as usize)))
}
_ => Err(PerfTargetError::InvalidTuple),
}
}
pub(crate) fn resolve_required(self, cpu_count: usize) -> Result<PerfCpuId, PerfTargetError> {
self.resolve_optional(cpu_count)?
.ok_or(PerfTargetError::InvalidTuple)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PerfTargetError {
InvalidTuple,
NoSuchProcess,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PerfTaskTarget {
Current,
Tid(u32),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PerfTargetKind {
Task,
Cpu,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PerfContextKey {
Task {
scheduler_id: ax_runtime::task::thread::ThreadId,
cpu: Option<PerfCpuId>,
},
Cpu(PerfCpuId),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PerfTarget {
Task {
task: PerfTaskTarget,
cpu: PerfCpuRequest,
},
Cpu(PerfCpuRequest),
}
impl PerfTarget {
pub(crate) fn parse(pid: i32, cpu: i32) -> Result<Self, PerfTargetError> {
if pid < -1 {
return Err(PerfTargetError::NoSuchProcess);
}
let cpu = PerfCpuRequest::new(cpu);
match pid {
-1 => Ok(Self::Cpu(cpu)),
0 => Ok(Self::Task {
task: PerfTaskTarget::Current,
cpu,
}),
value if value > 0 => Ok(Self::Task {
task: PerfTaskTarget::Tid(value as u32),
cpu,
}),
_ => unreachable!("negative task PIDs were rejected before CPU parsing"),
}
}
}
#[cfg(all(test, not(axtest)))]
mod tests {
use super::*;
#[test]
fn linux_perf_target_matrix_distinguishes_task_and_cpu_contexts() {
assert_ne!(PerfTargetKind::Task, PerfTargetKind::Cpu);
let PerfTarget::Task { task, cpu } = PerfTarget::parse(0, -1).unwrap() else {
panic!("pid 0 must select a task context");
};
assert_eq!(task, PerfTaskTarget::Current);
assert_eq!(cpu.resolve_optional(4).unwrap(), None);
let PerfTarget::Task { task, cpu } = PerfTarget::parse(42, 2).unwrap() else {
panic!("a positive pid must select a task context");
};
assert_eq!(task, PerfTaskTarget::Tid(42));
assert_eq!(cpu.resolve_optional(4).unwrap(), Some(PerfCpuId::new(2)));
let PerfTarget::Cpu(cpu) = PerfTarget::parse(-1, 3).unwrap() else {
panic!("pid -1 must select a CPU context");
};
assert_eq!(cpu.resolve_required(4).unwrap(), PerfCpuId::new(3));
}
#[test]
fn linux_perf_target_matrix_rejects_invalid_tuples() {
assert_eq!(
PerfTarget::parse(-2, 0),
Err(PerfTargetError::NoSuchProcess)
);
let PerfTarget::Cpu(cpu) = PerfTarget::parse(-1, -1).unwrap() else {
panic!("pid -1 must select a CPU context before CPU validation");
};
assert_eq!(cpu.resolve_required(4), Err(PerfTargetError::InvalidTuple));
for (pid, cpu) in [(0, -2), (1, 4)] {
let PerfTarget::Task { cpu, .. } = PerfTarget::parse(pid, cpu).unwrap() else {
panic!("pid={pid} must select a task before CPU validation");
};
assert_eq!(cpu.resolve_optional(4), Err(PerfTargetError::InvalidTuple));
}
}
}