#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(dead_code)]
use std::fs;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::sync::atomic::{AtomicI32, Ordering};
use std::time::Instant;
use crate::ported::crt::CRT_fatalError;
use crate::ported::machine::Machine;
use crate::ported::xutils::String_startsWith;
pub type memory_t = u64;
pub const MEMORY_MAX: memory_t = u64::MAX;
pub const HTOP_HUGEPAGE_BASE_SHIFT: usize = 16;
pub const HTOP_HUGEPAGE_COUNT: usize = 24;
const PROCCPUINFOFILE: &str = "/proc/cpuinfo";
const PROCSTATFILE: &str = "/proc/stat";
const PROCMEMINFOFILE: &str = "/proc/meminfo";
const PROCARCSTATSFILE: &str = "/proc/spl/kstat/zfs/arcstats";
const PROC_LINE_LENGTH: usize = 4096;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ZfsArcStats {
pub enabled: i32,
pub isCompressed: i32,
pub min: memory_t,
pub max: memory_t,
pub size: memory_t,
pub MFU: memory_t,
pub MRU: memory_t,
pub anon: memory_t,
pub header: memory_t,
pub other: memory_t,
pub compressed: memory_t,
pub uncompressed: memory_t,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ZramStats {
pub totalZram: memory_t,
pub usedZramComp: memory_t,
pub usedZramOrig: memory_t,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ZswapStats {
pub usedZswapComp: memory_t,
pub usedZswapOrig: memory_t,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CPUData {
pub totalTime: u64,
pub userTime: u64,
pub systemTime: u64,
pub systemAllTime: u64,
pub idleAllTime: u64,
pub idleTime: u64,
pub niceTime: u64,
pub ioWaitTime: u64,
pub irqTime: u64,
pub softIrqTime: u64,
pub stealTime: u64,
pub guestTime: u64,
pub totalPeriod: u64,
pub userPeriod: u64,
pub systemPeriod: u64,
pub systemAllPeriod: u64,
pub idleAllPeriod: u64,
pub idlePeriod: u64,
pub nicePeriod: u64,
pub ioWaitPeriod: u64,
pub irqPeriod: u64,
pub softIrqPeriod: u64,
pub stealPeriod: u64,
pub guestPeriod: u64,
pub frequency: f64,
pub physicalID: i32,
pub coreID: i32,
pub ccdID: i32,
pub coreIndex: i32,
pub threadIndex: i32,
pub online: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct GPUEngineData {
pub prevTime: u64,
pub curTime: u64,
pub key: Option<String>,
pub next: Option<Box<GPUEngineData>>,
}
#[repr(C)]
#[derive(Debug, Clone, Default, PartialEq)]
pub struct LinuxMachine {
pub super_: Machine,
pub jiffies: i64,
pub pageSize: usize,
pub pageSizeKB: usize,
pub runningTasks: u32,
pub boottime: i64,
pub period: f64,
pub cachedMem: memory_t,
pub sharedMem: memory_t,
pub usedMem: memory_t,
pub buffersMem: memory_t,
pub availableMem: memory_t,
pub cpuData: Vec<CPUData>,
pub maxPhysicalID: i32,
pub maxCoreID: i32,
pub totalHugePageMem: memory_t,
pub usedHugePageMem: [memory_t; HTOP_HUGEPAGE_COUNT],
pub prevGpuTime: u64,
pub curGpuTime: u64,
pub gpuEngineData: Option<Box<GPUEngineData>>,
pub zfs: ZfsArcStats,
pub zram: ZramStats,
pub zswap: ZswapStats,
}
fn LinuxMachine_updateCPUcount(this: &mut LinuxMachine) {
let mut existing: u32 = 0;
let mut active: u32 = 0;
if this.cpuData.is_empty() {
this.cpuData = vec![CPUData::default(); 2];
this.cpuData[0].online = true;
this.cpuData[1].online = true;
this.super_.activeCPUs = 1;
this.super_.existingCPUs = 1;
}
let dir = match fs::read_dir("/sys/devices/system/cpu") {
Ok(d) => d,
Err(_) => return,
};
let mut currExisting: u32 = this.super_.existingCPUs;
for entry in dir.flatten() {
match entry.file_type() {
Ok(ft) if !ft.is_dir() => continue,
_ => {}
}
let name = entry.file_name();
let name = match name.to_str() {
Some(n) => n,
None => continue,
};
if !String_startsWith(name, "cpu") {
continue;
}
let suffix = &name[3..];
if suffix.is_empty() || !suffix.bytes().all(|b| b.is_ascii_digit()) {
continue;
}
let sysid: u64 = match suffix.parse() {
Ok(v) => v,
Err(_) => continue,
};
if sysid >= u32::MAX as u64 {
continue;
}
let cpuid: u32 = sysid as u32 + 1;
existing += 1;
let max = existing.max(cpuid);
if max > currExisting {
this.cpuData
.resize(max as usize + 1, CPUData::default());
this.cpuData[0].online = true;
currExisting = max;
}
let online_path = format!("/sys/devices/system/cpu/{}/online", name);
let online = match fs::read(&online_path) {
Ok(buf) if !buf.is_empty() => buf[0] != b'0',
_ => true,
};
if online {
active += 1;
this.cpuData[cpuid as usize].online = true;
} else {
this.cpuData[cpuid as usize].online = false;
}
}
if existing < 1 {
return;
}
this.super_.activeCPUs = active;
debug_assert_eq!(existing, currExisting);
this.super_.existingCPUs = currExisting;
}
fn LinuxMachine_scanMemoryInfo(this: &mut LinuxMachine) {
let mut availableMem: memory_t = 0;
let mut freeMem: memory_t = 0;
let mut totalMem: memory_t = 0;
let mut buffersMem: memory_t = 0;
let mut cachedMem: memory_t = 0;
let mut sharedMem: memory_t = 0;
let mut swapTotalMem: memory_t = 0;
let mut swapCacheMem: memory_t = 0;
let mut swapFreeMem: memory_t = 0;
let mut sreclaimableMem: memory_t = 0;
let mut zswapCompMem: memory_t = 0;
let mut zswapOrigMem: memory_t = 0;
let content = match fs::read_to_string(PROCMEMINFOFILE) {
Ok(c) => c,
Err(_) => CRT_fatalError("Cannot open /proc/meminfo"),
};
let try_read = |line: &str, label: &str| -> Option<memory_t> {
if String_startsWith(line, label) {
line[label.len()..]
.split_whitespace()
.next()
.and_then(|t| t.parse::<memory_t>().ok())
} else {
None
}
};
for line in content.lines() {
if let Some(v) = try_read(line, "MemAvailable:") {
availableMem = v;
} else if let Some(v) = try_read(line, "MemFree:") {
freeMem = v;
} else if let Some(v) = try_read(line, "MemTotal:") {
totalMem = v;
} else if let Some(v) = try_read(line, "Buffers:") {
buffersMem = v;
} else if let Some(v) = try_read(line, "Cached:") {
cachedMem = v;
} else if let Some(v) = try_read(line, "Shmem:") {
sharedMem = v;
} else if let Some(v) = try_read(line, "SwapTotal:") {
swapTotalMem = v;
} else if let Some(v) = try_read(line, "SwapCached:") {
swapCacheMem = v;
} else if let Some(v) = try_read(line, "SwapFree:") {
swapFreeMem = v;
} else if let Some(v) = try_read(line, "SReclaimable:") {
sreclaimableMem = v;
} else if let Some(v) = try_read(line, "Zswap:") {
zswapCompMem = v;
} else if let Some(v) = try_read(line, "Zswapped:") {
zswapOrigMem = v;
}
}
this.super_.totalMem = totalMem;
this.cachedMem = cachedMem
.wrapping_add(sreclaimableMem)
.wrapping_sub(sharedMem);
this.sharedMem = sharedMem;
let usedDiff: memory_t = freeMem + cachedMem + sreclaimableMem + buffersMem;
this.usedMem = if totalMem >= usedDiff {
totalMem - usedDiff
} else {
totalMem.wrapping_sub(freeMem)
};
this.buffersMem = buffersMem;
this.availableMem = if availableMem != 0 {
availableMem.min(totalMem)
} else {
freeMem
};
this.super_.totalSwap = swapTotalMem;
this.super_.usedSwap = swapTotalMem
.wrapping_sub(swapFreeMem)
.wrapping_sub(swapCacheMem);
this.super_.cachedSwap = swapCacheMem;
this.zswap.usedZswapComp = zswapCompMem;
this.zswap.usedZswapOrig = zswapOrigMem;
}
fn LinuxMachine_scanHugePages(this: &mut LinuxMachine) {
this.totalHugePageMem = 0;
for i in 0..HTOP_HUGEPAGE_COUNT {
this.usedHugePageMem[i] = MEMORY_MAX;
}
let dir = match fs::read_dir("/sys/kernel/mm/hugepages") {
Ok(d) => d,
Err(_) => return,
};
for entry in dir.flatten() {
match entry.file_type() {
Ok(ft) if !ft.is_dir() => continue,
_ => {}
}
let name = entry.file_name();
let name = match name.to_str() {
Some(n) => n,
None => continue,
};
if !String_startsWith(name, "hugepages-") {
continue;
}
let rest = &name["hugepages-".len()..];
let digits: String = rest
.bytes()
.take_while(|b| b.is_ascii_digit())
.map(|b| b as char)
.collect();
let hugePageSize: u64 = match digits.parse() {
Ok(v) => v,
Err(_) => continue,
};
if rest.as_bytes().get(digits.len()) != Some(&b'k') {
continue;
}
let nr_path = format!("/sys/kernel/mm/hugepages/{}/nr_hugepages", name);
let total: memory_t = match fs::read_to_string(&nr_path) {
Ok(s) if !s.trim().is_empty() => s.trim().parse().unwrap_or(0),
_ => continue,
};
if total == 0 {
continue;
}
let free_path = format!("/sys/kernel/mm/hugepages/{}/free_hugepages", name);
let free: memory_t = match fs::read_to_string(&free_path) {
Ok(s) if !s.trim().is_empty() => s.trim().parse().unwrap_or(0),
_ => continue,
};
let ffsl = hugePageSize.trailing_zeros() as i64 + 1;
let shift = ffsl - 1 - (HTOP_HUGEPAGE_BASE_SHIFT as i64 - 10);
debug_assert!(shift >= 0 && shift < HTOP_HUGEPAGE_COUNT as i64);
this.totalHugePageMem += total * hugePageSize;
this.usedHugePageMem[shift as usize] = (total - free) * hugePageSize;
}
}
fn LinuxMachine_scanZramInfo(this: &mut LinuxMachine) {
let mut totalZram: memory_t = 0;
let mut usedZramComp: memory_t = 0;
let mut usedZramOrig: memory_t = 0;
let is_zram_block_name = |name: &str| -> bool {
match name.strip_prefix("zram") {
Some(rest) => !rest.is_empty() && rest.bytes().all(|b| b.is_ascii_digit()),
None => false,
}
};
if let Ok(dir) = fs::read_dir("/sys/block") {
for entry in dir.flatten() {
let name = entry.file_name();
let name = match name.to_str() {
Some(n) => n,
None => continue,
};
if !is_zram_block_name(name) {
continue;
}
let disksize = fs::read_to_string(format!("/sys/block/{}/disksize", name));
let mm_stat = fs::read_to_string(format!("/sys/block/{}/mm_stat", name));
if let (Ok(disksize), Ok(mm_stat)) = (disksize, mm_stat) {
let size: Option<memory_t> = disksize
.split_whitespace()
.next()
.and_then(|t| t.parse().ok());
let mut mm = mm_stat.split_whitespace();
let orig_data_size: Option<memory_t> = mm.next().and_then(|t| t.parse().ok());
let compr_data_size: Option<memory_t> = mm.next().and_then(|t| t.parse().ok());
if let (Some(size), Some(orig), Some(compr)) =
(size, orig_data_size, compr_data_size)
{
totalZram += size;
usedZramComp += compr;
usedZramOrig += orig;
}
}
}
}
this.zram.totalZram = totalZram / 1024;
this.zram.usedZramComp = usedZramComp / 1024;
this.zram.usedZramOrig = usedZramOrig / 1024;
if this.zram.usedZramComp > this.zram.usedZramOrig {
this.zram.usedZramComp = this.zram.usedZramOrig;
}
}
fn LinuxMachine_scanZfsArcstats(this: &mut LinuxMachine) {
let mut dbufSize: memory_t = 0;
let mut dnodeSize: memory_t = 0;
let mut bonusSize: memory_t = 0;
let content = match fs::read_to_string(PROCARCSTATSFILE) {
Ok(c) => c,
Err(_) => {
this.zfs.enabled = 0;
return;
}
};
let try_read = |line: &str, label: &str| -> Option<memory_t> {
if String_startsWith(line, label) {
line[label.len()..]
.split_whitespace()
.nth(1)
.and_then(|t| t.parse::<memory_t>().ok())
} else {
None
}
};
for line in content.lines() {
if let Some(v) = try_read(line, "c_min") {
this.zfs.min = v;
} else if let Some(v) = try_read(line, "c_max") {
this.zfs.max = v;
} else if String_startsWith(line, "compressed_size") {
match try_read(line, "compressed_size") {
Some(v) => {
this.zfs.compressed = v;
this.zfs.isCompressed = 1;
}
None => this.zfs.isCompressed = 0,
}
} else if let Some(v) = try_read(line, "uncompressed_size") {
this.zfs.uncompressed = v;
} else if let Some(v) = try_read(line, "size") {
this.zfs.size = v;
} else if let Some(v) = try_read(line, "hdr_size") {
this.zfs.header = v;
} else if let Some(v) = try_read(line, "dbuf_size") {
dbufSize = v;
} else if let Some(v) = try_read(line, "dnode_size") {
dnodeSize = v;
} else if let Some(v) = try_read(line, "bonus_size") {
bonusSize = v;
} else if let Some(v) = try_read(line, "anon_size") {
this.zfs.anon = v;
} else if let Some(v) = try_read(line, "mfu_size") {
this.zfs.MFU = v;
} else if let Some(v) = try_read(line, "mru_size") {
this.zfs.MRU = v;
}
}
this.zfs.enabled = if this.zfs.size > 0 { 1 } else { 0 };
this.zfs.size /= 1024;
this.zfs.min /= 1024;
this.zfs.max /= 1024;
this.zfs.MFU /= 1024;
this.zfs.MRU /= 1024;
this.zfs.anon /= 1024;
this.zfs.header /= 1024;
this.zfs.other = (dbufSize + dnodeSize + bonusSize) / 1024;
if this.zfs.isCompressed != 0 {
this.zfs.compressed /= 1024;
this.zfs.uncompressed /= 1024;
}
}
fn LinuxMachine_scanCPUTime(this: &mut LinuxMachine) {
LinuxMachine_updateCPUcount(this);
let existingCPUs = this.super_.existingCPUs;
let activeCPUs = this.super_.activeCPUs;
let file = match File::open(PROCSTATFILE) {
Ok(f) => f,
Err(_) => CRT_fatalError("Cannot open /proc/stat"),
};
let mut reader = BufReader::new(file);
debug_assert!(existingCPUs < u32::MAX - 1);
let mut adjCpuIdProcessed = vec![false; existingCPUs as usize + 1];
let mut buffer = String::new();
for i in 0..=existingCPUs {
buffer.clear();
let n = reader.read_line(&mut buffer).unwrap_or(0);
if n == 0 {
break;
}
let line = buffer.trim_end_matches('\n');
if !String_startsWith(line, "cpu") {
break;
}
let after = &line[3..];
let adjCpuId: u32;
let mut nums = [0u64; 10];
if i == 0 {
for (slot, tok) in nums.iter_mut().zip(after.split_whitespace()) {
*slot = tok.parse().unwrap_or(0);
}
adjCpuId = 0;
} else {
let mut toks = after.split_whitespace();
let cpuid: u32 = match toks.next().and_then(|t| t.parse().ok()) {
Some(v) => v,
None => break,
};
if cpuid >= existingCPUs {
break;
}
for (slot, tok) in nums.iter_mut().zip(toks) {
*slot = tok.parse().unwrap_or(0);
}
adjCpuId = cpuid + 1;
}
if adjCpuId > existingCPUs {
break;
}
let mut usertime = nums[0];
let mut nicetime = nums[1];
let systemtime = nums[2];
let idletime = nums[3];
let ioWait = nums[4];
let irq = nums[5];
let softIrq = nums[6];
let steal = nums[7];
let guest = nums[8];
let guestnice = nums[9];
usertime = usertime.wrapping_sub(guest);
nicetime = nicetime.wrapping_sub(guestnice);
let idlealltime = idletime + ioWait;
let systemalltime = systemtime + irq + softIrq;
let virtalltime = guest + guestnice;
let totaltime = usertime + nicetime + systemalltime + idlealltime + steal + virtalltime;
let cpuData = &mut this.cpuData[adjCpuId as usize];
cpuData.userPeriod = usertime.saturating_sub(cpuData.userTime);
cpuData.nicePeriod = nicetime.saturating_sub(cpuData.niceTime);
cpuData.systemPeriod = systemtime.saturating_sub(cpuData.systemTime);
cpuData.systemAllPeriod = systemalltime.saturating_sub(cpuData.systemAllTime);
cpuData.idleAllPeriod = idlealltime.saturating_sub(cpuData.idleAllTime);
cpuData.idlePeriod = idletime.saturating_sub(cpuData.idleTime);
cpuData.ioWaitPeriod = ioWait.saturating_sub(cpuData.ioWaitTime);
cpuData.irqPeriod = irq.saturating_sub(cpuData.irqTime);
cpuData.softIrqPeriod = softIrq.saturating_sub(cpuData.softIrqTime);
cpuData.stealPeriod = steal.saturating_sub(cpuData.stealTime);
cpuData.guestPeriod = virtalltime.saturating_sub(cpuData.guestTime);
cpuData.totalPeriod = totaltime.saturating_sub(cpuData.totalTime);
cpuData.userTime = usertime;
cpuData.niceTime = nicetime;
cpuData.systemTime = systemtime;
cpuData.systemAllTime = systemalltime;
cpuData.idleAllTime = idlealltime;
cpuData.idleTime = idletime;
cpuData.ioWaitTime = ioWait;
cpuData.irqTime = irq;
cpuData.softIrqTime = softIrq;
cpuData.stealTime = steal;
cpuData.guestTime = virtalltime;
cpuData.totalTime = totaltime;
adjCpuIdProcessed[adjCpuId as usize] = true;
}
for i in 0..=existingCPUs as usize {
if !adjCpuIdProcessed[i] {
this.cpuData[i] = CPUData::default();
}
}
this.period = this.cpuData[0].totalPeriod as f64 / activeCPUs as f64;
loop {
buffer.clear();
let n = reader.read_line(&mut buffer).unwrap_or(0);
if n == 0 {
break;
}
if String_startsWith(&buffer, "procs_running") {
this.runningTasks = buffer["procs_running".len()..]
.split_whitespace()
.next()
.and_then(|t| t.parse().ok())
.unwrap_or(0);
break;
}
}
}
fn scanCPUFrequencyFromSysCPUFreq(this: &mut LinuxMachine) -> i32 {
static TIMEOUT: AtomicI32 = AtomicI32::new(0);
let existingCPUs = this.super_.existingCPUs;
let mut numCPUsWithFrequency = 0;
let mut totalFrequency: u64 = 0;
if TIMEOUT.load(Ordering::Relaxed) > 0 {
TIMEOUT.fetch_sub(1, Ordering::Relaxed);
return -1;
}
for i in 0..existingCPUs {
if !Machine_isCPUonline(this, i) {
continue;
}
let path = format!("/sys/devices/system/cpu/cpu{}/cpufreq/scaling_cur_freq", i);
let start = if i == 0 { Some(Instant::now()) } else { None };
let content = match fs::read_to_string(&path) {
Ok(c) => c,
Err(e) => return -(e.raw_os_error().unwrap_or(1)),
};
if let Some(frequency) = content
.split_whitespace()
.next()
.and_then(|t| t.parse::<u64>().ok())
{
let frequency = frequency / 1000;
this.cpuData[i as usize + 1].frequency = frequency as f64;
numCPUsWithFrequency += 1;
totalFrequency += frequency;
}
if let Some(start) = start {
let timeTakenUs = start.elapsed().as_micros();
if timeTakenUs > 500 {
TIMEOUT.store(30, Ordering::Relaxed);
return -1;
}
}
}
if numCPUsWithFrequency > 0 {
this.cpuData[0].frequency = totalFrequency as f64 / numCPUsWithFrequency as f64;
}
0
}
fn scanCPUFrequencyFromCPUinfo(this: &mut LinuxMachine) {
let existingCPUs = this.super_.existingCPUs;
let content = match fs::read_to_string(PROCCPUINFOFILE) {
Ok(c) => c,
Err(_) => return,
};
let mut numCPUsWithFrequency = 0;
let mut totalFrequency: f64 = 0.0;
let mut cpuid: i32 = -1;
for line in content.lines() {
let (label, value) = match line.split_once(':') {
Some((l, v)) => (l.trim(), v.trim()),
None => {
if line.is_empty() {
cpuid = -1;
}
continue;
}
};
if label == "processor" || label == "cpu number" {
if let Ok(v) = value.parse::<i32>() {
cpuid = v;
}
continue;
}
let frequency: Option<f64> = match label {
"cpu MHz" | "CPU MHz" | "cpu MHz dynamic" => value.parse().ok(),
"clock" => value.trim_end_matches("MHz").trim().parse().ok(),
_ => None,
};
if let Some(frequency) = frequency {
if cpuid < 0 || cpuid as u32 > existingCPUs - 1 {
continue;
}
let cpuData = &mut this.cpuData[cpuid as usize + 1];
if !(cpuData.frequency >= 0.0) {
cpuData.frequency = frequency;
}
numCPUsWithFrequency += 1;
totalFrequency += frequency;
} else if line.is_empty() {
cpuid = -1;
}
}
if numCPUsWithFrequency > 0 {
this.cpuData[0].frequency = totalFrequency / numCPUsWithFrequency as f64;
}
}
fn LinuxMachine_fetchCPUTopologyFromCPUinfo(this: &mut LinuxMachine) {
let existingCPUs = this.super_.existingCPUs;
let content = match fs::read_to_string(PROCCPUINFOFILE) {
Ok(c) => c,
Err(_) => return,
};
let mut cpuid: i32 = -1;
let mut coreid: i32 = -1;
let mut physicalid: i32 = -1;
let mut max_physicalid: i32 = -1;
let mut max_coreid: i32 = -1;
for line in content.lines() {
if line.is_empty() {
if cpuid >= 0 && (cpuid as u32) < existingCPUs {
let cpuData = &mut this.cpuData[cpuid as usize + 1];
cpuData.coreID = coreid;
cpuData.physicalID = physicalid;
if coreid > max_coreid {
max_coreid = coreid;
}
if physicalid > max_physicalid {
max_physicalid = physicalid;
}
cpuid = -1;
coreid = -1;
physicalid = -1;
}
} else if String_startsWith(line, "processor") {
if let Some(v) = line
.split_once(':')
.and_then(|(_, v)| v.trim().parse().ok())
{
cpuid = v;
}
} else if String_startsWith(line, "physical id") {
if let Some(v) = line
.split_once(':')
.and_then(|(_, v)| v.trim().parse().ok())
{
physicalid = v;
}
} else if String_startsWith(line, "core id") {
if let Some(v) = line
.split_once(':')
.and_then(|(_, v)| v.trim().parse().ok())
{
coreid = v;
}
}
}
this.maxPhysicalID = max_physicalid;
this.maxCoreID = max_coreid;
}
fn LinuxMachine_assignCCDs(this: &mut LinuxMachine, ccds: i32) {
let existingCPUs = this.super_.existingCPUs;
if ccds == 0 {
for i in 0..existingCPUs as usize + 1 {
this.cpuData[i].ccdID = -1;
}
return;
}
let coresPerCCD = existingCPUs as i32 / ccds;
let mut ccd = 0;
let mut nc = coresPerCCD;
for p in 0..=this.maxPhysicalID {
for c in 0..=this.maxCoreID {
for i in 1..=existingCPUs as usize {
if this.cpuData[i].physicalID != p || this.cpuData[i].coreID != c {
continue;
}
this.cpuData[i].ccdID = ccd;
nc -= 1;
if nc <= 0 {
nc = coresPerCCD;
ccd += 1;
}
}
}
}
}
fn LinuxMachine_computeThreadIndices(this: &mut LinuxMachine) {
let existingCPUs = this.super_.existingCPUs as usize;
for i in 1..=existingCPUs {
let mut threadIndex = 0;
for j in 1..i {
if this.cpuData[i].physicalID == this.cpuData[j].physicalID
&& this.cpuData[i].coreID == this.cpuData[j].coreID
{
threadIndex += 1;
}
}
this.cpuData[i].threadIndex = threadIndex;
}
let mut maxCoreIndex = 0;
for i in 1..=existingCPUs {
this.cpuData[i].coreIndex = maxCoreIndex;
maxCoreIndex += 1;
for j in (1..i).rev() {
if this.cpuData[i].physicalID == this.cpuData[j].physicalID
&& this.cpuData[i].coreID == this.cpuData[j].coreID
{
debug_assert!(this.cpuData[i].threadIndex != this.cpuData[j].threadIndex);
this.cpuData[i].coreIndex = this.cpuData[j].coreIndex;
maxCoreIndex -= 1;
break;
}
}
}
this.cpuData[0].coreIndex = 0;
this.cpuData[0].threadIndex = 0;
}
fn LinuxMachine_scanCPUFrequency(this: &mut LinuxMachine) {
let existingCPUs = this.super_.existingCPUs;
for i in 0..=existingCPUs as usize {
this.cpuData[i].frequency = f64::NAN;
}
if scanCPUFrequencyFromSysCPUFreq(this) == 0 {
return;
}
scanCPUFrequencyFromCPUinfo(this);
}
pub fn Machine_scan(this: &mut LinuxMachine) {
LinuxMachine_scanMemoryInfo(this);
LinuxMachine_scanHugePages(this);
LinuxMachine_scanZfsArcstats(this);
LinuxMachine_scanZramInfo(this);
LinuxMachine_scanCPUTime(this);
let show_cpu_frequency = this
.super_
.settings
.as_ref()
.expect("Machine_scan: super->settings (C dereferences it unconditionally)")
.showCPUFrequency;
if show_cpu_frequency {
LinuxMachine_scanCPUFrequency(this);
}
}
pub fn Machine_new() {
todo!("port of LinuxMachine.c:823 — blocked on Machine_init (machine.rs stub) + sysconf")
}
pub fn Machine_delete() {
todo!("deliberate non-port: free() teardown handled by Drop (LinuxMachine.c:877)")
}
pub fn Machine_isCPUonline(this: &LinuxMachine, id: u32) -> bool {
debug_assert!(id < this.super_.existingCPUs);
this.cpuData[id as usize + 1].online
}
pub fn Machine_getCPUPhysicalCoreID(this: &LinuxMachine, id: u32) -> i32 {
debug_assert!(id < this.super_.existingCPUs);
this.cpuData[id as usize + 1].coreIndex
}
pub fn Machine_getCPUThreadIndex(this: &LinuxMachine, id: u32) -> i32 {
debug_assert!(id < this.super_.existingCPUs);
this.cpuData[id as usize + 1].threadIndex
}
#[cfg(test)]
mod tests {
use super::*;
fn machine_with_cpus(n: u32) -> LinuxMachine {
let mut m = LinuxMachine {
cpuData: vec![CPUData::default(); n as usize + 1],
..Default::default()
};
m.super_.existingCPUs = n;
m.super_.activeCPUs = n;
for c in m.cpuData.iter_mut() {
c.online = true;
}
m
}
#[test]
fn isCPUonline_reads_offset_by_one() {
let mut m = machine_with_cpus(3);
m.cpuData[2].online = false; assert!(Machine_isCPUonline(&m, 0));
assert!(!Machine_isCPUonline(&m, 1));
assert!(Machine_isCPUonline(&m, 2));
}
#[test]
fn getCPU_indices_read_offset_by_one() {
let mut m = machine_with_cpus(2);
m.cpuData[1].coreIndex = 5;
m.cpuData[1].threadIndex = 1;
m.cpuData[2].coreIndex = 7;
m.cpuData[2].threadIndex = 0;
assert_eq!(Machine_getCPUPhysicalCoreID(&m, 0), 5);
assert_eq!(Machine_getCPUThreadIndex(&m, 0), 1);
assert_eq!(Machine_getCPUPhysicalCoreID(&m, 1), 7);
assert_eq!(Machine_getCPUThreadIndex(&m, 1), 0);
}
#[test]
fn assignCCDs_zero_clears_all_to_minus_one() {
let mut m = machine_with_cpus(4);
for c in m.cpuData.iter_mut() {
c.ccdID = 42;
}
LinuxMachine_assignCCDs(&mut m, 0);
for c in &m.cpuData {
assert_eq!(c.ccdID, -1);
}
}
#[test]
fn assignCCDs_splits_cores_into_two_ccds() {
let mut m = machine_with_cpus(4);
m.maxPhysicalID = 0;
m.maxCoreID = 3;
for (i, c) in m.cpuData.iter_mut().enumerate().skip(1) {
c.physicalID = 0;
c.coreID = i as i32 - 1;
}
LinuxMachine_assignCCDs(&mut m, 2);
assert_eq!(m.cpuData[1].ccdID, 0);
assert_eq!(m.cpuData[2].ccdID, 0);
assert_eq!(m.cpuData[3].ccdID, 1);
assert_eq!(m.cpuData[4].ccdID, 1);
}
#[test]
fn computeThreadIndices_pairs_smt_siblings() {
let mut m = machine_with_cpus(4);
for (i, c) in m.cpuData.iter_mut().enumerate().skip(1) {
c.physicalID = 0;
c.coreID = (i as i32 - 1) / 2; }
LinuxMachine_computeThreadIndices(&mut m);
assert_eq!(m.cpuData[1].threadIndex, 0);
assert_eq!(m.cpuData[2].threadIndex, 1);
assert_eq!(m.cpuData[3].threadIndex, 0);
assert_eq!(m.cpuData[4].threadIndex, 1);
assert_eq!(m.cpuData[1].coreIndex, m.cpuData[2].coreIndex);
assert_eq!(m.cpuData[3].coreIndex, m.cpuData[4].coreIndex);
assert_ne!(m.cpuData[1].coreIndex, m.cpuData[3].coreIndex);
assert_eq!(m.cpuData[0].coreIndex, 0);
assert_eq!(m.cpuData[0].threadIndex, 0);
}
}