use crate::ComponentExt;
use std::collections::HashMap;
use std::fs::{read_dir, File};
use std::io::Read;
use std::path::{Path, PathBuf};
#[doc = include_str!("../../md_doc/component.md")]
#[derive(Default)]
pub struct Component {
device_model: Option<String>,
name: String,
temperature: Option<f32>,
max: Option<f32>,
threshold_max: Option<f32>,
threshold_min: Option<f32>,
threshold_critical: Option<f32>,
sensor_type: Option<TermalSensorType>,
label: String,
input_file: Option<PathBuf>,
highest_file: Option<PathBuf>,
}
fn get_file_line(file: &Path, capacity: usize) -> Option<String> {
let mut reader = String::with_capacity(capacity);
let mut f = File::open(file).ok()?;
f.read_to_string(&mut reader).ok()?;
reader.truncate(reader.trim_end().len());
Some(reader)
}
fn read_number_from_file<N>(file: &Path) -> Option<N>
where
N: std::str::FromStr,
{
let mut reader = [0u8; 32];
let mut f = File::open(file).ok()?;
let n = f.read(&mut reader).ok()?;
let number = &reader[..n];
let number = std::str::from_utf8(number).ok()?;
let number = number.trim();
if cfg!(feature = "debug") {
assert!(!number.contains('\n') && !number.contains('\0'));
}
number.parse().ok()
}
#[inline]
fn get_temperature_from_file(file: &Path) -> Option<f32> {
let temp = read_number_from_file(file);
convert_temp_celsius(temp)
}
#[inline]
fn convert_temp_celsius(temp: Option<i32>) -> Option<f32> {
temp.map(|n| (n as f32) / 1000f32)
}
enum TermalSensorType {
CPUEmbeddedDiode,
Transistor3904,
ThermalDiode,
Thermistor,
AMDAMDSI,
IntelPECI,
Unknown(u8),
}
impl From<u8> for TermalSensorType {
fn from(input: u8) -> Self {
match input {
0 => Self::CPUEmbeddedDiode,
1 => Self::Transistor3904,
3 => Self::ThermalDiode,
4 => Self::Thermistor,
5 => Self::AMDAMDSI,
6 => Self::IntelPECI,
n => Self::Unknown(n),
}
}
}
fn fill_component(component: &mut Component, item: &str, folder: &Path, file: &str) {
let hwmon_file = folder.join(file);
match item {
"type" => {
component.sensor_type =
read_number_from_file::<u8>(&hwmon_file).map(TermalSensorType::from)
}
"input" => {
let temperature = get_temperature_from_file(&hwmon_file);
component.input_file = Some(hwmon_file);
component.temperature = temperature;
if component.max.is_none() {
component.max = temperature;
}
}
"label" => component.label = get_file_line(&hwmon_file, 10).unwrap_or_default(),
"highest" => {
component.max = get_temperature_from_file(&hwmon_file).or(component.temperature);
component.highest_file = Some(hwmon_file);
}
"max" => component.threshold_max = get_temperature_from_file(&hwmon_file),
"min" => component.threshold_min = get_temperature_from_file(&hwmon_file),
"crit" => component.threshold_critical = get_temperature_from_file(&hwmon_file),
_ => {
sysinfo_debug!(
"This hwmon-temp file is still not supported! Contributions are appreciated.;) {:?}",
hwmon_file,
);
}
}
}
impl Component {
fn from_hwmon(components: &mut Vec<Component>, folder: &Path) -> Option<()> {
let dir = read_dir(folder).ok()?;
let mut matchings: HashMap<u32, Component> = HashMap::with_capacity(10);
for entry in dir.flatten() {
let entry = entry.path();
let filename = entry.file_name().and_then(|x| x.to_str()).unwrap_or("");
if entry.is_dir() || !filename.starts_with("temp") {
continue;
}
let (id, item) = filename.split_once('_')?;
let id = id.get(4..)?.parse::<u32>().ok()?;
let component = matchings.entry(id).or_insert_with(Component::default);
let name = get_file_line(&folder.join("name"), 16);
component.name = name.unwrap_or_default();
let device_model = get_file_line(&folder.join("device/model"), 16);
component.device_model = device_model;
fill_component(component, item, folder, filename);
}
let compo = matchings
.into_iter()
.map(|(id, mut c)| {
c.label = c.format_label("temp", id);
c
})
.filter(|c| c.input_file.is_some());
components.extend(compo);
Some(())
}
fn format_label(&self, class: &str, id: u32) -> String {
let Component {
device_model,
name,
label,
..
} = self;
let has_label = !label.is_empty();
match (has_label, device_model) {
(true, Some(device_model)) => {
format!("{name} {label} {device_model} {class}{id}")
}
(true, None) => format!("{name} {label}"),
(false, Some(device_model)) => format!("{name} {device_model}"),
(false, None) => format!("{name} {class}{id}"),
}
}
}
impl ComponentExt for Component {
fn temperature(&self) -> f32 {
self.temperature.unwrap_or(f32::NAN)
}
fn max(&self) -> f32 {
self.max.unwrap_or(f32::NAN)
}
fn critical(&self) -> Option<f32> {
self.threshold_critical
}
fn label(&self) -> &str {
&self.label
}
fn refresh(&mut self) {
let current = self
.input_file
.as_ref()
.and_then(|file| get_temperature_from_file(file.as_path()));
let max = self
.highest_file
.as_ref()
.and_then(|file| get_temperature_from_file(file.as_path()))
.or_else(|| {
let last = self.temperature?;
let current = current?;
Some(last.max(current))
});
self.max = max;
self.temperature = current;
}
}
pub(crate) fn get_components() -> Vec<Component> {
let mut components = Vec::with_capacity(10);
if let Ok(dir) = read_dir(Path::new("/sys/class/hwmon/")) {
for entry in dir.flatten() {
let entry = entry.path();
if !entry.is_dir()
|| !entry
.file_name()
.and_then(|x| x.to_str())
.unwrap_or("")
.starts_with("hwmon")
{
continue;
}
Component::from_hwmon(&mut components, &entry);
}
components.sort_by(|c1, c2| c1.label.to_lowercase().cmp(&c2.label.to_lowercase()));
}
components
}