use std::sync::Mutex;
use sysinfo::System;
#[derive(Debug, Clone)]
pub struct SystemState {
pub cpu_load: f64,
pub available_ram_bytes: u64,
pub total_ram_bytes: u64,
pub cpu_temp_c: Option<f64>,
pub heavy_process_count: usize,
}
pub struct SystemMonitor {
sys: Mutex<System>,
}
impl SystemMonitor {
pub fn new() -> Self {
let sys = System::new_all();
Self {
sys: Mutex::new(sys),
}
}
pub fn snapshot(&self) -> SystemState {
let mut sys = self.sys.lock().unwrap();
sys.refresh_cpu_all();
sys.refresh_memory();
sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
let cpus = sys.cpus();
let cpu_load = if cpus.is_empty() {
0.0
} else {
cpus.iter().map(|c| c.cpu_usage() as f64).sum::<f64>() / cpus.len() as f64 / 100.0
};
let available_ram_bytes = sys.available_memory();
let total_ram_bytes = sys.total_memory();
let cpu_temp_c = {
let components = sysinfo::Components::new_with_refreshed_list();
components
.iter()
.filter(|c| {
let label = c.label().to_lowercase();
label.contains("cpu")
|| label.contains("core")
|| label.contains("package")
|| label.contains("tctl")
})
.filter_map(|c| c.temperature())
.map(|t| t as f64)
.reduce(f64::max)
};
let our_pid = sysinfo::get_current_pid().ok();
let heavy_process_count = sys
.processes()
.values()
.filter(|p| {
if let Some(our) = our_pid {
if p.pid() == our {
return false;
}
}
p.cpu_usage() > 10.0
})
.count();
SystemState {
cpu_load,
available_ram_bytes,
total_ram_bytes,
cpu_temp_c,
heavy_process_count,
}
}
}
impl Default for SystemMonitor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
#[non_exhaustive]
pub struct Testbed {
pub cpu_model: String,
pub arch: String,
pub os: String,
pub logical_cores: usize,
pub physical_cores: usize,
}
impl std::fmt::Display for Testbed {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} ({}/{} cores, {}/{})",
self.cpu_model, self.physical_cores, self.logical_cores, self.arch, self.os,
)
}
}
pub fn detect_testbed() -> Testbed {
let sys = System::new_all();
let cpu_model = sys
.cpus()
.first()
.map(|c| c.brand().trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
let logical_cores = sys.cpus().len().max(1);
let physical_cores = sysinfo::System::physical_core_count().unwrap_or(logical_cores);
Testbed {
cpu_model,
arch: std::env::consts::ARCH.to_string(),
os: std::env::consts::OS.to_string(),
logical_cores,
physical_cores,
}
}
pub fn timer_resolution_ns() -> u64 {
let mut min_delta = u64::MAX;
for _ in 0..1000 {
let a = std::time::Instant::now();
let b = std::time::Instant::now();
let delta = b.duration_since(a).as_nanos() as u64;
if delta > 0 && delta < min_delta {
min_delta = delta;
}
}
if min_delta == u64::MAX { 1 } else { min_delta }
}
pub fn detect_ci() -> Option<&'static str> {
if std::env::var("GITHUB_ACTIONS").is_ok() {
return Some("github-actions");
}
if std::env::var("GITLAB_CI").is_ok() {
return Some("gitlab-ci");
}
if std::env::var("CIRCLECI").is_ok() {
return Some("circleci");
}
if std::env::var("TRAVIS").is_ok() {
return Some("travis-ci");
}
if std::env::var("JENKINS_URL").is_ok() {
return Some("jenkins");
}
if std::env::var("BUILDKITE").is_ok() {
return Some("buildkite");
}
if std::env::var("AZURE_PIPELINES").is_ok() || std::env::var("TF_BUILD").is_ok() {
return Some("azure-pipelines");
}
if std::env::var("CI").is_ok() {
return Some("unknown-ci");
}
None
}
pub fn git_commit_hash() -> Option<String> {
std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.ok()
.and_then(|o| {
if o.status.success() {
String::from_utf8(o.stdout)
.ok()
.map(|s| s.trim().to_string())
} else {
None
}
})
}
pub fn git_short_hash() -> Option<String> {
std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|o| {
if o.status.success() {
String::from_utf8(o.stdout)
.ok()
.map(|s| s.trim().to_string())
} else {
None
}
})
}