goosedump 0.12.43

Browse, search, compact, and learn from coding-agent sessions
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) Jarkko Sakkinen 2026

//! Optional process metrics for local-inference profiling.

#[derive(Clone, Copy, Debug)]
pub(super) struct ProcessSnapshot {
    minor_faults: u64,
    major_faults: u64,
    read_bytes: u64,
    rss_kib: u64,
    peak_rss_kib: u64,
}

#[derive(Clone, Copy, Debug)]
pub(super) struct ProcessDelta {
    pub(super) minor_faults: u64,
    pub(super) major_faults: u64,
    pub(super) read_bytes: u64,
    pub(super) rss_kib_start: u64,
    pub(super) rss_kib_end: u64,
    pub(super) peak_rss_kib: u64,
}

impl ProcessSnapshot {
    #[cfg(target_os = "linux")]
    pub(super) fn capture() -> Option<Self> {
        let stat = std::fs::read_to_string("/proc/self/stat").ok()?;
        let io = std::fs::read_to_string("/proc/self/io").ok()?;
        let status = std::fs::read_to_string("/proc/self/status").ok()?;
        let (minor_faults, major_faults) = parse_stat(&stat)?;
        Some(Self {
            minor_faults,
            major_faults,
            read_bytes: parse_named_value(&io, "read_bytes:")?,
            rss_kib: parse_named_kib(&status, "VmRSS:")?,
            peak_rss_kib: parse_named_kib(&status, "VmHWM:")?,
        })
    }

    #[cfg(not(target_os = "linux"))]
    pub(super) const fn capture() -> Option<Self> {
        None
    }

    pub(super) fn delta(start: Option<Self>, end: Option<Self>) -> Option<ProcessDelta> {
        let (start, end) = (start?, end?);
        Some(ProcessDelta {
            minor_faults: end.minor_faults.saturating_sub(start.minor_faults),
            major_faults: end.major_faults.saturating_sub(start.major_faults),
            read_bytes: end.read_bytes.saturating_sub(start.read_bytes),
            rss_kib_start: start.rss_kib,
            rss_kib_end: end.rss_kib,
            peak_rss_kib: end.peak_rss_kib,
        })
    }
}

#[cfg(target_os = "linux")]
fn parse_stat(input: &str) -> Option<(u64, u64)> {
    let (_, fields) = input.rsplit_once(')')?;
    let mut fields = fields.split_whitespace();
    let minor_faults = fields.nth(7)?.parse().ok()?;
    let major_faults = fields.nth(1)?.parse().ok()?;
    Some((minor_faults, major_faults))
}

#[cfg(target_os = "linux")]
fn parse_named_value(input: &str, name: &str) -> Option<u64> {
    input.lines().find_map(|line| {
        line.strip_prefix(name)?
            .split_whitespace()
            .next()?
            .parse()
            .ok()
    })
}

#[cfg(target_os = "linux")]
fn parse_named_kib(input: &str, name: &str) -> Option<u64> {
    let line = input.lines().find_map(|line| line.strip_prefix(name))?;
    let (value, unit) = line.trim().split_once(char::is_whitespace)?;
    if unit.trim() != "kB" {
        return None;
    }
    value.parse().ok()
}