pointbreak 0.10.0

Durable terminal code review for changes humans and coding agents collaborate on together
Documentation
use thiserror::Error;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LongitudinalProcessCpuClockV1 {
    MachAbsoluteTime { numerator: u32, denominator: u32 },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LongitudinalProcessSnapshotV1 {
    pub pid: u32,
    pub user_cpu_nanos: u64,
    pub system_cpu_nanos: u64,
    pub resident_bytes: u64,
    pub cpu_clock: LongitudinalProcessCpuClockV1,
}

#[derive(Debug, Error, Eq, PartialEq)]
pub enum LongitudinalProcessCaptureError {
    #[error("longitudinal process capture is unsupported on this platform")]
    UnsupportedPlatform,
    #[error("mach_timebase_info failed with code {code}")]
    MachTimebaseInfo { code: i32 },
    #[error("invalid Mach timebase {numerator}/{denominator}")]
    InvalidTimebase { numerator: u32, denominator: u32 },
    #[error("process id {pid} cannot be represented by the macOS process API")]
    InvalidProcessId { pid: u32 },
    #[error("proc_pid_rusage failed for pid {pid} with code {code}")]
    ProcessRusage { pid: u32, code: i32 },
    #[error("converted process CPU time exceeds u64 nanoseconds")]
    CpuTimeOverflow,
}

pub fn longitudinal_mach_ticks_to_nanos_v1(
    ticks: u64,
    numerator: u32,
    denominator: u32,
) -> Result<u64, LongitudinalProcessCaptureError> {
    if numerator == 0 || denominator == 0 {
        return Err(LongitudinalProcessCaptureError::InvalidTimebase {
            numerator,
            denominator,
        });
    }
    let nanos = u128::from(ticks)
        .checked_mul(u128::from(numerator))
        .ok_or(LongitudinalProcessCaptureError::CpuTimeOverflow)?
        / u128::from(denominator);
    u64::try_from(nanos).map_err(|_| LongitudinalProcessCaptureError::CpuTimeOverflow)
}

#[cfg(target_os = "macos")]
#[allow(deprecated)]
fn mach_timebase_v1() -> Result<(u32, u32), LongitudinalProcessCaptureError> {
    let mut timebase = libc::mach_timebase_info { numer: 0, denom: 0 };
    let result = unsafe { libc::mach_timebase_info(&mut timebase) };
    if result != 0 {
        return Err(LongitudinalProcessCaptureError::MachTimebaseInfo { code: result });
    }
    if timebase.numer == 0 || timebase.denom == 0 {
        return Err(LongitudinalProcessCaptureError::InvalidTimebase {
            numerator: timebase.numer,
            denominator: timebase.denom,
        });
    }
    Ok((timebase.numer, timebase.denom))
}

#[cfg(target_os = "macos")]
fn process_rusage_v2(
    pid: i32,
    public_pid: u32,
) -> Result<libc::rusage_info_v2, LongitudinalProcessCaptureError> {
    use std::mem::MaybeUninit;

    let mut usage = MaybeUninit::<libc::rusage_info_v2>::uninit();
    let rusage_result =
        unsafe { libc::proc_pid_rusage(pid, libc::RUSAGE_INFO_V2, usage.as_mut_ptr().cast()) };
    if rusage_result != 0 {
        return Err(LongitudinalProcessCaptureError::ProcessRusage {
            pid: public_pid,
            code: rusage_result,
        });
    }
    Ok(unsafe { usage.assume_init() })
}

#[cfg(target_os = "macos")]
pub fn capture_longitudinal_process_snapshot_v1(
    pid: u32,
) -> Result<LongitudinalProcessSnapshotV1, LongitudinalProcessCaptureError> {
    let native_pid = i32::try_from(pid)
        .map_err(|_| LongitudinalProcessCaptureError::InvalidProcessId { pid })?;
    let (timebase_numerator, timebase_denominator) = mach_timebase_v1()?;
    let usage = process_rusage_v2(native_pid, pid)?;

    Ok(LongitudinalProcessSnapshotV1 {
        pid,
        user_cpu_nanos: longitudinal_mach_ticks_to_nanos_v1(
            usage.ri_user_time,
            timebase_numerator,
            timebase_denominator,
        )?,
        system_cpu_nanos: longitudinal_mach_ticks_to_nanos_v1(
            usage.ri_system_time,
            timebase_numerator,
            timebase_denominator,
        )?,
        resident_bytes: usage.ri_resident_size,
        cpu_clock: LongitudinalProcessCpuClockV1::MachAbsoluteTime {
            numerator: timebase_numerator,
            denominator: timebase_denominator,
        },
    })
}

#[cfg(not(target_os = "macos"))]
pub fn capture_longitudinal_process_snapshot_v1(
    _pid: u32,
) -> Result<LongitudinalProcessSnapshotV1, LongitudinalProcessCaptureError> {
    Err(LongitudinalProcessCaptureError::UnsupportedPlatform)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn longitudinal_process_cpu_converts_exact_mach_ticks() {
        assert_eq!(longitudinal_mach_ticks_to_nanos_v1(3, 125, 3).unwrap(), 125);
    }

    #[test]
    fn longitudinal_process_cpu_floors_fractional_nanoseconds() {
        assert_eq!(longitudinal_mach_ticks_to_nanos_v1(1, 2, 3).unwrap(), 0);
    }

    #[test]
    fn longitudinal_process_cpu_rejects_zero_denominator() {
        assert!(matches!(
            longitudinal_mach_ticks_to_nanos_v1(1, 1, 0),
            Err(LongitudinalProcessCaptureError::InvalidTimebase {
                numerator: 1,
                denominator: 0,
            })
        ));
    }

    #[test]
    fn longitudinal_process_cpu_rejects_zero_numerator() {
        assert!(matches!(
            longitudinal_mach_ticks_to_nanos_v1(1, 0, 1),
            Err(LongitudinalProcessCaptureError::InvalidTimebase {
                numerator: 0,
                denominator: 1,
            })
        ));
    }

    #[test]
    fn longitudinal_process_cpu_rejects_u64_overflow() {
        assert!(matches!(
            longitudinal_mach_ticks_to_nanos_v1(u64::MAX, u32::MAX, 1),
            Err(LongitudinalProcessCaptureError::CpuTimeOverflow)
        ));
    }

    #[cfg(target_os = "macos")]
    #[test]
    fn longitudinal_process_snapshot_matches_independent_cpu_clock() {
        fn getrusage_cpu_nanos() -> u64 {
            use std::mem::MaybeUninit;

            let mut usage = MaybeUninit::<libc::rusage>::uninit();
            assert_eq!(
                unsafe { libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) },
                0
            );
            let usage = unsafe { usage.assume_init() };
            let timeval_nanos = |value: libc::timeval| {
                u64::try_from(value.tv_sec).unwrap() * 1_000_000_000
                    + u64::try_from(value.tv_usec).unwrap() * 1_000
            };
            timeval_nanos(usage.ru_utime) + timeval_nanos(usage.ru_stime)
        }

        let pid = std::process::id();
        let reference_before = getrusage_cpu_nanos();
        let snapshot_before = capture_longitudinal_process_snapshot_v1(pid).unwrap();
        while getrusage_cpu_nanos() - reference_before < 25_000_000 {
            std::hint::black_box((0_u64..10_000).fold(0_u64, u64::wrapping_add));
        }
        let snapshot_after = capture_longitudinal_process_snapshot_v1(pid).unwrap();
        let reference_after = getrusage_cpu_nanos();

        assert_eq!(snapshot_after.pid, pid);
        assert!(snapshot_after.resident_bytes > 0);
        assert!(matches!(
            snapshot_after.cpu_clock,
            LongitudinalProcessCpuClockV1::MachAbsoluteTime {
                numerator,
                denominator,
            } if numerator > 0 && denominator > 0
        ));
        let reference_delta = reference_after - reference_before;
        let snapshot_delta = (snapshot_after.user_cpu_nanos + snapshot_after.system_cpu_nanos)
            - (snapshot_before.user_cpu_nanos + snapshot_before.system_cpu_nanos);
        let tolerance = reference_delta / 10 + 2_000_000;
        assert!(
            snapshot_delta.abs_diff(reference_delta) <= tolerance,
            "normalized snapshot delta {snapshot_delta}ns differs from independent \
             getrusage delta {reference_delta}ns by more than {tolerance}ns"
        );
    }

    #[cfg(target_os = "macos")]
    #[test]
    fn longitudinal_process_snapshot_rejects_unrepresentable_pid() {
        assert!(matches!(
            capture_longitudinal_process_snapshot_v1(u32::MAX),
            Err(LongitudinalProcessCaptureError::InvalidProcessId { pid: u32::MAX })
        ));
    }

    #[cfg(not(target_os = "macos"))]
    #[test]
    fn longitudinal_process_snapshot_rejects_unsupported_platforms() {
        assert!(matches!(
            capture_longitudinal_process_snapshot_v1(std::process::id()),
            Err(LongitudinalProcessCaptureError::UnsupportedPlatform)
        ));
    }
}