diff --git a/Cargo.lock b/Cargo.lock index a181b77..e914cfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,6 +140,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.2.1" @@ -824,6 +830,15 @@ version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + [[package]] name = "log" version = "0.4.14" @@ -1004,6 +1019,20 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989d43012e2ca1c4a02507c67282691a0a3207f9dc67cec596b43fe925b3d325" +[[package]] +name = "plist" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38d026d73eeaf2ade76309d0c65db5a35ecf649e3cec428db316243ea9d6711" +dependencies = [ + "base64", + "chrono", + "indexmap", + "line-wrap", + "serde", + "xml-rs", +] + [[package]] name = "plotters" version = "0.3.1" @@ -1083,7 +1112,9 @@ dependencies = [ "futures", "futures-timer", "heim", + "plist", "plotters", + "serde", ] [[package]] @@ -1168,6 +1199,12 @@ dependencies = [ "semver", ] +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "same-file" version = "1.0.6" @@ -1195,6 +1232,26 @@ dependencies = [ "pest", ] +[[package]] +name = "serde" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "servo-fontconfig" version = "0.5.1" @@ -1521,3 +1578,9 @@ checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ "winapi", ] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" diff --git a/Cargo.toml b/Cargo.toml index a857b8f..e5b8f25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,16 @@ futures = "0.3.15" futures-timer = "3.0.2" plotters = "0.3" clap = "3.0.0-beta.2" +plist = "1.2.1" [dependencies.heim] version = "0.1.0-rc.1" features = ["process"] +[dependencies.serde] +version = "1.0.127" +features = ["serde_derive"] + [patch.crates-io.heim] version = "0.1.0-rc.1" git = "https://github.com/heim-rs/heim" diff --git a/src/main.rs b/src/main.rs index 9e60cb8..b27acbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,14 @@ use futures::stream::StreamExt; use heim::process::{CpuUsage, Pid, Process, Status}; use heim::units::ratio; use plotters::prelude::*; +use power_metrics::{PowerMetrics, PowerMetricsResult}; use std::io::BufReader; use std::io::{BufRead, Write}; use std::process; use std::time::{Duration, Instant}; +mod power_metrics; + fn main() { let opts: Opts = Opts::parse(); @@ -21,6 +24,7 @@ fn main() { } let mut powershell = None; + let power_metrics = PowerMetrics::new(); #[cfg(target_os = "windows")] if opts.category.contains(&"gpu".to_owned()) { @@ -40,6 +44,8 @@ fn main() { last_record_time = now; } + let power_metrics_result = power_metrics.poll(); + 'p: for process in processes.iter_mut() { let mut message = format!("{}({})", &process.name, process.process.pid(),); @@ -64,7 +70,8 @@ fn main() { } } "gpu" => { - if let Some(gpu_percent) = process.poll_gpu_percent(powershell.as_mut()) + if let Some(gpu_percent) = + process.poll_gpu_percent(powershell.as_mut(), &power_metrics_result) { process.value_percents[idx].push(gpu_percent); @@ -244,13 +251,30 @@ impl ProcessInfo { } } - fn poll_gpu_percent(&mut self, powershell: Option<&mut Powershell>) -> Option { - let powershell = powershell?; - let r = powershell.poll_gpu_percent(self.process.pid()); - if r.is_none() { - self.valid = false; + #[allow(unused_variables)] + fn poll_gpu_percent( + &mut self, + powershell: Option<&mut Powershell>, + power_metrics_result: &PowerMetricsResult, + ) -> Option { + #[cfg(target_os = "windows")] + { + let powershell = powershell?; + let r = powershell.poll_gpu_percent(self.process.pid()); + if r.is_none() { + self.valid = false; + } + r + } + + #[cfg(not(target_os = "windows"))] + { + let r = power_metrics_result.gpu_percent(self.process.pid()); + if r.is_none() { + self.valid = false; + } + r } - r } fn avg_percent(&self, idx: usize) -> f32 { @@ -283,6 +307,7 @@ impl Powershell { } } + #[allow(dead_code)] fn poll_gpu_percent(&mut self, pid: Pid) -> Option { let mut gpu_percent = 0.0; let mut r = String::new(); diff --git a/src/power_metrics.rs b/src/power_metrics.rs new file mode 100644 index 0000000..a3ccb2a --- /dev/null +++ b/src/power_metrics.rs @@ -0,0 +1,52 @@ +use heim::process::Pid; +use serde::Deserialize; +use std::process::Command; + +#[derive(Debug, Deserialize)] +pub struct PowerMetricsResult { + tasks: Vec, +} + +impl PowerMetricsResult { + pub fn gpu_percent(&self, pid: Pid) -> Option { + self.tasks + .iter() + .find(|task| task.pid == pid) + .map(|task| task.gputime_ms_per_s / 10.0) + } +} + +#[derive(Debug, Deserialize)] +pub struct Task { + pid: Pid, + #[serde(default)] + gputime_ms_per_s: f32, +} + +pub struct PowerMetrics {} + +impl PowerMetrics { + pub fn new() -> Self { + Self {} + } + + pub fn poll(&self) -> PowerMetricsResult { + let o = Command::new("powermetrics") + .args([ + "--samplers", + "tasks", + "--show-process-gpu", + "-n1", + "-i1000", + "-f", + "plist", + ]) + .output() + .unwrap(); + + assert_eq!(o.status.code(), Some(0)); + + let r: PowerMetricsResult = plist::from_bytes(o.stdout.as_slice()).unwrap(); + r + } +}