summaryrefslogtreecommitdiff
path: root/energy-monitor/src/process.rs
blob: b771c9e2e373c947f594918628d8ec8a699808a5 (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
use std::{collections::HashMap, os::unix::net::UnixStream, time::Instant};
use std::{fs, io};

use procfs::process::Process;

use crate::energy::{connect_to_service, request_all_processes, Pid, ProcessInfo};

use crate::SOCKET_PATH;

pub struct ProcessData {
    pub processes: Vec<Pid>,
    pub total_energy_history: Vec<(f64, f64)>,
    pub started_at: Instant,
    pub socket: UnixStream,
    pub process_info: HashMap<Pid, ProcessInfo>,
    pub rapl_offset: f64,
}

impl ProcessData {
    pub fn new() -> Self {
        Self {
            processes: Vec::new(),
            total_energy_history: Vec::new(),
            started_at: Instant::now(),
            process_info: HashMap::new(),
            socket: connect_to_service(SOCKET_PATH).expect("Failed to connect to socket"),
            rapl_offset: read_package_energy().unwrap(),
        }
    }

    pub fn update(&mut self) {
        self.fetch_processes();
    }

    pub fn fetch_processes(&mut self) {
        let energy_data = match request_all_processes(&mut self.socket) {
            Ok(data) => data,
            Err(_) => return, // Can't get energy data
        };

        // Use process data with procfs to get process names
        let mut updated_processes = Vec::new();
        let mut total_energy = 0.0;

        let elapsed = self.started_at.elapsed().as_secs_f64();
        for (pid, (energy, tree_energy)) in energy_data {
            let process_info = self.process_info.entry(pid).or_insert_with(|| {
                let name = get_process_name(pid).unwrap_or_else(|| format!("Process {}", pid));

                ProcessInfo::new(pid, name, energy, tree_energy)
            });

            let process_delta = energy - process_info.energy;
            let delta = tree_energy - process_info.tree_energy;
            let delta = (process_info.history.last().map(|x| x.1).unwrap_or(delta) + delta) * 0.5;
            process_info.energy = energy;
            process_info.tree_energy = tree_energy;
            process_info.add_history_point(elapsed, delta);

            updated_processes.push(pid);
            total_energy += process_delta;
        }

        // Sort by energy consumption (descending)
        updated_processes.sort_by(|a, b| {
            self.process_info[b]
                .energy
                .partial_cmp(&self.process_info[a].energy)
                .unwrap()
        });
        let mut sorted_processes = updated_processes.clone();

        // Update our local process list
        self.processes = updated_processes;
        sorted_processes.sort_unstable();

        self.process_info
            .retain(|pid, _| sorted_processes.binary_search(pid).is_ok());

        // Add total energy to history
        let elapsed = self.started_at.elapsed().as_secs_f64();

        let rapl_energy = read_package_energy().unwrap();
        let total_energy = rapl_energy - self.rapl_offset;
        self.rapl_offset = rapl_energy;

        self.add_total_energy_point(elapsed, total_energy);
    }

    fn add_total_energy_point(&mut self, timestamp: f64, energy: f64) {
        self.total_energy_history.push((timestamp, energy));

        // Keep history bounded
        if self.total_energy_history.len() > 60 {
            self.total_energy_history.remove(0);
        }
    }
}

// Helper function to get process name using procfs
fn get_process_name(pid: Pid) -> Option<String> {
    match Process::new(pid) {
        Ok(process) => match process.stat() {
            Ok(stat) => Some(stat.comm),
            Err(_) => None,
        },
        Err(_) => None,
    }
}

/// Read current package energy counter value in joules
pub fn read_package_energy() -> io::Result<f64> {
    let energy_path = "/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj";
    let energy_str = fs::read_to_string(energy_path)?;
    let energy_uj = energy_str
        .trim()
        .parse::<u64>()
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;

    // Convert from microjoules to joules
    Ok(energy_uj as f64 / 1_000_000.0)
}