summaryrefslogtreecommitdiffstats
path: root/src/app/data_harvester/processes/macos.rs
blob: d5dffe85b1178c6f76e2b06a4a7014ec030d6910 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
//! Process data collection for macOS.  Uses sysinfo.

use super::{ProcessHarvest, UserTable};
use sysinfo::{ProcessExt, ProcessStatus, ProcessorExt, System, SystemExt};

fn get_macos_process_cpu_usage(
    pids: &[i32],
) -> std::io::Result<std::collections::HashMap<i32, f64>> {
    use itertools::Itertools;
    let output = std::process::Command::new("ps")
        .args(&["-o", "pid=,pcpu=", "-p"])
        .arg(
            // Has to look like this since otherwise, it you hit a `unstable_name_collisions` warning.
            Itertools::intersperse(pids.iter().map(i32::to_string), ",".to_string())
                .collect::<String>(),
        )
        .output()?;
    let mut result = std::collections::HashMap::new();
    String::from_utf8_lossy(&output.stdout)
        .split_whitespace()
        .chunks(2)
        .into_iter()
        .for_each(|chunk| {
            let chunk: Vec<&str> = chunk.collect();
            if chunk.len() != 2 {
                panic!("Unexpected `ps` output");
            }
            let pid = chunk[0].parse();
            let usage = chunk[1].parse();
            if let (Ok(pid), Ok(usage)) = (pid, usage) {
                result.insert(pid, usage);
            }
        });
    Ok(result)
}

pub fn get_process_data(
    sys: &System, use_current_cpu_total: bool, mem_total_kb: u64, user_table: &mut UserTable,
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
    let mut process_vector: Vec<ProcessHarvest> = Vec::new();
    let process_hashmap = sys.get_processes();
    let cpu_usage = sys.get_global_processor_info().get_cpu_usage() as f64 / 100.0;
    let num_processors = sys.get_processors().len() as f64;
    for process_val in process_hashmap.values() {
        let name = if process_val.name().is_empty() {
            let process_cmd = process_val.cmd();
            if process_cmd.len() > 1 {
                process_cmd[0].clone()
            } else {
                let process_exe = process_val.exe().file_stem();
                if let Some(exe) = process_exe {
                    let process_exe_opt = exe.to_str();
                    if let Some(exe_name) = process_exe_opt {
                        exe_name.to_string()
                    } else {
                        "".to_string()
                    }
                } else {
                    "".to_string()
                }
            }
        } else {
            process_val.name().to_string()
        };
        let command = {
            let command = process_val.cmd().join(" ");
            if command.is_empty() {
                name.to_string()
            } else {
                command
            }
        };

        let pcu = {
            let p = process_val.cpu_usage() as f64 / num_processors;
            if p.is_nan() {
                process_val.cpu_usage() as f64
            } else {
                p
            }
        };
        let process_cpu_usage = if use_current_cpu_total && cpu_usage > 0.0 {
            pcu / cpu_usage
        } else {
            pcu
        };

        let disk_usage = process_val.disk_usage();
        process_vector.push(ProcessHarvest {
            pid: process_val.pid(),
            parent_pid: process_val.parent(),
            children_pids: vec![],
            name,
            command,
            mem_usage_percent: if mem_total_kb > 0 {
                process_val.memory() as f64 * 100.0 / mem_total_kb as f64
            } else {
                0.0
            },
            mem_usage_bytes: process_val.memory() * 1024,
            cpu_usage_percent: process_cpu_usage,
            read_bytes_per_sec: disk_usage.read_bytes,
            write_bytes_per_sec: disk_usage.written_bytes,
            total_read_bytes: disk_usage.total_read_bytes,
            total_write_bytes: disk_usage.total_written_bytes,
            process_state: process_val.status().to_string(),
            process_state_char: convert_process_status_to_char(process_val.status()),
            uid: process_val.uid,
            user: user_table
                .get_uid_to_username_mapping(uid)
                .map(Into::into)
                .unwrap_or("N/A".into()),
        });
    }

    let unknown_state = ProcessStatus::Unknown(0).to_string();
    let cpu_usage_unknown_pids: Vec<i32> = process_vector
        .iter()
        .filter(|process| process.process_state == unknown_state)
        .map(|process| process.pid)
        .collect();
    let cpu_usages = get_macos_process_cpu_usage(&cpu_usage_unknown_pids)?;
    for process in &mut process_vector {
        if cpu_usages.contains_key(&process.pid) {
            process.cpu_usage_percent = if num_processors == 0.0 {
                *cpu_usages.get(&process.pid).unwrap()
            } else {
                *cpu_usages.get(&process.pid).unwrap() / num_processors
            };
        }
    }

    Ok(process_vector)
}

fn convert_process_status_to_char(status: ProcessStatus) -> char {
    match status {
        ProcessStatus::Run => 'R',
        ProcessStatus::Sleep => 'S',
        ProcessStatus::Idle => 'D',
        ProcessStatus::Zombie => 'Z',
        _ => '?',
    }
}