Skip to main content

agentos_native_sidecar_core/
diagnostics.rs

1use agentos_kernel::process_table::{ProcessInfo, ProcessStatus};
2use agentos_sidecar_protocol::protocol::{ProcessSnapshotEntry, ProcessSnapshotStatus};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum SharedProcessSnapshotStatus {
6    Running,
7    Stopped,
8    Exited,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct SharedProcessSnapshotEntry {
13    pub process_id: String,
14    pub pid: u32,
15    pub ppid: u32,
16    pub pgid: u32,
17    pub sid: u32,
18    pub driver: String,
19    pub command: String,
20    pub args: Vec<String>,
21    pub cwd: String,
22    pub status: SharedProcessSnapshotStatus,
23    pub exit_code: Option<i32>,
24}
25
26pub fn process_status_from_kernel(status: ProcessStatus) -> SharedProcessSnapshotStatus {
27    match status {
28        ProcessStatus::Running => SharedProcessSnapshotStatus::Running,
29        ProcessStatus::Stopped => SharedProcessSnapshotStatus::Stopped,
30        ProcessStatus::Exited => SharedProcessSnapshotStatus::Exited,
31    }
32}
33
34pub fn process_snapshot_entry_from_kernel(
35    process_id: &str,
36    info: &ProcessInfo,
37    cwd: impl Into<String>,
38    exit_code: Option<i32>,
39) -> SharedProcessSnapshotEntry {
40    SharedProcessSnapshotEntry {
41        process_id: process_id.to_owned(),
42        pid: info.pid,
43        ppid: info.ppid,
44        pgid: info.pgid,
45        sid: info.sid,
46        driver: info.driver.clone(),
47        command: info.command.clone(),
48        args: Vec::new(),
49        cwd: cwd.into(),
50        status: if exit_code.is_some() {
51            SharedProcessSnapshotStatus::Exited
52        } else {
53            process_status_from_kernel(info.status)
54        },
55        exit_code: exit_code.or(info.exit_code),
56    }
57}
58
59pub fn protocol_process_snapshot_entry(entry: SharedProcessSnapshotEntry) -> ProcessSnapshotEntry {
60    ProcessSnapshotEntry {
61        process_id: entry.process_id,
62        pid: entry.pid,
63        ppid: entry.ppid,
64        pgid: entry.pgid,
65        sid: entry.sid,
66        driver: entry.driver,
67        command: entry.command,
68        args: entry.args,
69        cwd: entry.cwd,
70        status: match entry.status {
71            SharedProcessSnapshotStatus::Running => ProcessSnapshotStatus::Running,
72            SharedProcessSnapshotStatus::Stopped => ProcessSnapshotStatus::Stopped,
73            SharedProcessSnapshotStatus::Exited => ProcessSnapshotStatus::Exited,
74        },
75        exit_code: entry.exit_code,
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use agentos_kernel::user::ProcessIdentity;
83
84    fn process_info(status: ProcessStatus, exit_code: Option<i32>) -> ProcessInfo {
85        ProcessInfo {
86            pid: 42,
87            ppid: 1,
88            pgid: 42,
89            sid: 42,
90            driver: "javascript".to_owned(),
91            command: "node".to_owned(),
92            status,
93            exit_code,
94            identity: ProcessIdentity::default(),
95        }
96    }
97
98    #[test]
99    fn maps_kernel_process_statuses() {
100        assert_eq!(
101            process_status_from_kernel(ProcessStatus::Running),
102            SharedProcessSnapshotStatus::Running
103        );
104        assert_eq!(
105            process_status_from_kernel(ProcessStatus::Stopped),
106            SharedProcessSnapshotStatus::Stopped
107        );
108        assert_eq!(
109            process_status_from_kernel(ProcessStatus::Exited),
110            SharedProcessSnapshotStatus::Exited
111        );
112    }
113
114    #[test]
115    fn builds_process_snapshot_from_kernel_info() {
116        let entry = process_snapshot_entry_from_kernel(
117            "exec-1",
118            &process_info(ProcessStatus::Running, None),
119            "/workspace",
120            None,
121        );
122
123        assert_eq!(entry.process_id, "exec-1");
124        assert_eq!(entry.pid, 42);
125        assert_eq!(entry.ppid, 1);
126        assert_eq!(entry.pgid, 42);
127        assert_eq!(entry.sid, 42);
128        assert_eq!(entry.driver, "javascript");
129        assert_eq!(entry.command, "node");
130        assert_eq!(entry.args, Vec::<String>::new());
131        assert_eq!(entry.cwd, "/workspace");
132        assert_eq!(entry.status, SharedProcessSnapshotStatus::Running);
133        assert_eq!(entry.exit_code, None);
134    }
135
136    #[test]
137    fn explicit_exit_code_marks_snapshot_exited() {
138        let entry = process_snapshot_entry_from_kernel(
139            "exec-1",
140            &process_info(ProcessStatus::Running, Some(9)),
141            "/workspace",
142            Some(7),
143        );
144
145        assert_eq!(entry.status, SharedProcessSnapshotStatus::Exited);
146        assert_eq!(entry.exit_code, Some(7));
147    }
148
149    #[test]
150    fn maps_shared_process_snapshot_to_protocol_entry() {
151        let entry = protocol_process_snapshot_entry(SharedProcessSnapshotEntry {
152            process_id: String::from("proc-1"),
153            pid: 42,
154            ppid: 1,
155            pgid: 42,
156            sid: 42,
157            driver: String::from("javascript"),
158            command: String::from("node"),
159            args: vec![String::from("app.js")],
160            cwd: String::from("/workspace"),
161            status: SharedProcessSnapshotStatus::Stopped,
162            exit_code: None,
163        });
164
165        assert_eq!(entry.process_id, "proc-1");
166        assert_eq!(entry.pid, 42);
167        assert_eq!(entry.args, vec![String::from("app.js")]);
168        assert_eq!(entry.cwd, "/workspace");
169        assert_eq!(entry.status, ProcessSnapshotStatus::Stopped);
170        assert_eq!(entry.exit_code, None);
171    }
172}