// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
struct CpuSnapshot {
total : UInt64
idle : UInt64
} derive(Show, Eq)
///|
pub(all) struct System {
mut processes_data : Map[Pid, Process]
mut cpus_data : Array[Cpu]
mut total_memory_data : UInt64
mut free_memory_data : UInt64
mut available_memory_data : UInt64
mut used_memory_data : UInt64
mut total_swap_data : UInt64
mut free_swap_data : UInt64
mut used_swap_data : UInt64
mut cgroup_limits_data : CGroupLimits?
mut last_cpu_refresh_ms : UInt64
mut prev_cpu_snapshots : Array[CpuSnapshot]
mut prev_global_cpu_snapshot : CpuSnapshot?
mut prev_proc_cpu_total : Map[Pid, UInt64]
mut prev_proc_system_total : UInt64
}
///|
pub fn set_open_files_limit(new_limit : Int) -> Bool {
if get_os_tag() == "linux" {
set_open_files_limit_ffi(new_limit) == 1
} else {
false
}
}
///|
pub fn System::new() -> System {
System::new_with_specifics(RefreshKind::nothing())
}
///|
pub fn System::new_all() -> System {
System::new_with_specifics(RefreshKind::everything())
}
///|
pub fn System::new_with_specifics(refreshes : RefreshKind) -> System {
let s : System = {
processes_data: {},
cpus_data: [],
total_memory_data: 0UL,
free_memory_data: 0UL,
available_memory_data: 0UL,
used_memory_data: 0UL,
total_swap_data: 0UL,
free_swap_data: 0UL,
used_swap_data: 0UL,
cgroup_limits_data: None,
last_cpu_refresh_ms: 0UL,
prev_cpu_snapshots: [],
prev_global_cpu_snapshot: None,
prev_proc_cpu_total: {},
prev_proc_system_total: 0UL,
}
s.refresh_specifics(refreshes)
s
}
///|
pub fn System::refresh_specifics(
self : System,
refreshes : RefreshKind,
) -> Unit {
match refreshes.memory() {
Some(kind) => self.refresh_memory_specifics(kind)
None => ()
}
match refreshes.cpu() {
Some(kind) => self.refresh_cpu_specifics(kind)
None => ()
}
match refreshes.processes() {
Some(kind) =>
ignore(
self.refresh_processes_specifics(ProcessesToUpdate::All, true, kind),
)
None => ()
}
}
///|
pub fn System::refresh_all(self : System) -> Unit {
self.refresh_memory()
self.refresh_cpu_all()
ignore(self.refresh_processes(ProcessesToUpdate::All, true))
}
///|
pub fn System::refresh_memory(self : System) -> Unit {
self.refresh_memory_specifics(MemoryRefreshKind::everything())
}
///|
pub fn System::refresh_memory_specifics(
self : System,
refresh_kind : MemoryRefreshKind,
) -> Unit {
if !refresh_kind.ram() && !refresh_kind.swap() {
return
}
if get_os_tag() == "linux" {
let mem = parse_linux_meminfo()
if refresh_kind.ram() {
let total = mem.get_or_default("MemTotal", 0UL) * 1024UL
let free = mem.get_or_default("MemFree", 0UL) * 1024UL
let available = mem.get_or_default("MemAvailable", free / 1024UL) * 1024UL
self.total_memory_data = total
self.free_memory_data = free
self.available_memory_data = available
self.used_memory_data = if total > available {
total - available
} else {
0UL
}
}
if refresh_kind.swap() {
let total_swap = mem.get_or_default("SwapTotal", 0UL) * 1024UL
let free_swap = mem.get_or_default("SwapFree", 0UL) * 1024UL
self.total_swap_data = total_swap
self.free_swap_data = free_swap
self.used_swap_data = if total_swap > free_swap {
total_swap - free_swap
} else {
0UL
}
}
self.cgroup_limits_data = read_cgroup_limits_linux(self.total_memory_data)
} else if get_os_tag() == "macos" {
let stats = split_whitespace(decode_bytes(macos_memory_stats_ffi()))
let total = if stats.length() >= 1 {
parse_uint64_or(stats[0], 0UL)
} else {
0UL
}
let free = if stats.length() >= 2 {
parse_uint64_or(stats[1], 0UL)
} else {
0UL
}
let available = if stats.length() >= 3 {
parse_uint64_or(stats[2], 0UL)
} else {
free
}
let total_swap = if stats.length() >= 4 {
parse_uint64_or(stats[3], 0UL)
} else {
0UL
}
let used_swap = if stats.length() >= 5 {
parse_uint64_or(stats[4], 0UL)
} else {
0UL
}
if refresh_kind.ram() {
self.total_memory_data = total
self.free_memory_data = free
self.available_memory_data = available
self.used_memory_data = if total > available {
total - available
} else {
0UL
}
}
if refresh_kind.swap() {
let free_swap = if total_swap > used_swap {
total_swap - used_swap
} else {
0UL
}
self.total_swap_data = total_swap
self.free_swap_data = free_swap
self.used_swap_data = used_swap
}
self.cgroup_limits_data = None
}
}
///|
pub fn System::refresh_cpu_usage(self : System) -> Unit {
self.refresh_cpu_specifics(CpuRefreshKind::nothing().with_cpu_usage())
}
///|
pub fn System::refresh_cpu_frequency(self : System) -> Unit {
self.refresh_cpu_specifics(CpuRefreshKind::nothing().with_frequency())
}
///|
pub fn System::refresh_cpu_list(
self : System,
refresh_kind : CpuRefreshKind,
) -> Unit {
self.refresh_cpu_specifics(refresh_kind)
}
///|
pub fn System::refresh_cpu_all(self : System) -> Unit {
self.refresh_cpu_specifics(CpuRefreshKind::everything())
}
///|
pub fn System::refresh_cpu_specifics(
self : System,
refresh_kind : CpuRefreshKind,
) -> Unit {
if !refresh_kind.cpu_usage() && !refresh_kind.frequency() {
return
}
if get_os_tag() == "linux" {
refresh_cpu_linux(self, refresh_kind)
} else if get_os_tag() == "macos" {
refresh_cpu_macos(self, refresh_kind)
}
}
///|
pub fn System::refresh_processes(
self : System,
processes_to_update : ProcessesToUpdate,
remove_dead_processes : Bool,
) -> Int {
self.refresh_processes_specifics(
processes_to_update,
remove_dead_processes,
ProcessRefreshKind::nothing()
.with_cpu()
.with_memory()
.with_disk_usage()
.with_user(UpdateKind::OnlyIfNotSet),
)
}
///|
fn merge_update_array_field(
kind : UpdateKind,
old_value : Array[String],
new_value : Array[String],
) -> Array[String] {
match kind {
UpdateKind::Always => new_value
UpdateKind::OnlyIfNotSet =>
if old_value.is_empty() {
new_value
} else {
old_value
}
UpdateKind::Never => old_value
}
}
///|
fn[T] merge_update_option_field(
kind : UpdateKind,
old_value : T?,
new_value : T?,
) -> T? {
match kind {
UpdateKind::Always => new_value
UpdateKind::OnlyIfNotSet =>
match old_value {
Some(_) => old_value
None => new_value
}
UpdateKind::Never => old_value
}
}
///|
fn merge_process_with_refresh_kind(
previous : Process?,
current : Process,
refresh_kind : ProcessRefreshKind,
) -> Process {
match previous {
None => current
Some(old) => {
let cmd = merge_update_array_field(
refresh_kind.cmd(),
old.cmd(),
current.cmd(),
)
let environ = merge_update_array_field(
refresh_kind.environ(),
old.environ(),
current.environ(),
)
let exe = merge_update_option_field(
refresh_kind.exe(),
old.exe(),
current.exe(),
)
let cwd = merge_update_option_field(
refresh_kind.cwd(),
old.cwd(),
current.cwd(),
)
let root = merge_update_option_field(
refresh_kind.root(),
old.root(),
current.root(),
)
let user_id = merge_update_option_field(
refresh_kind.user(),
old.user_id(),
current.user_id(),
)
let effective_user_id = merge_update_option_field(
refresh_kind.user(),
old.effective_user_id(),
current.effective_user_id(),
)
let group_id = merge_update_option_field(
refresh_kind.user(),
old.group_id(),
current.group_id(),
)
let effective_group_id = merge_update_option_field(
refresh_kind.user(),
old.effective_group_id(),
current.effective_group_id(),
)
{
pid: current.pid(),
parent: current.parent(),
name: current.name(),
cmd,
environ,
exe,
cwd,
root,
memory: if refresh_kind.memory() {
current.memory()
} else {
old.memory()
},
virtual_memory: if refresh_kind.memory() {
current.virtual_memory()
} else {
old.virtual_memory()
},
status: current.status(),
start_time: current.start_time(),
run_time: current.run_time(),
cpu_usage: if refresh_kind.cpu() {
current.cpu_usage()
} else {
old.cpu_usage()
},
accumulated_cpu_time: if refresh_kind.cpu() {
current.accumulated_cpu_time()
} else {
old.accumulated_cpu_time()
},
disk_usage: if refresh_kind.disk_usage() {
current.disk_usage()
} else {
old.disk_usage()
},
user_id,
effective_user_id,
group_id,
effective_group_id,
session_id: current.session_id(),
tasks: if refresh_kind.tasks() {
current.tasks()
} else {
old.tasks()
},
thread_kind: current.thread_kind(),
open_files: current.open_files(),
open_files_limit: current.open_files_limit(),
}
}
}
}
///|
pub fn System::refresh_processes_specifics(
self : System,
processes_to_update : ProcessesToUpdate,
remove_dead_processes : Bool,
refresh_kind : ProcessRefreshKind,
) -> Int {
let selected_pids = match processes_to_update {
ProcessesToUpdate::All => None
ProcessesToUpdate::Selected(pids) => Some(pids)
}
let all_processes = if get_os_tag() == "linux" {
collect_processes_linux(self, refresh_kind, selected_pids)
} else if get_os_tag() == "macos" {
collect_processes_macos(self, refresh_kind, selected_pids)
} else {
{}
}
let mut updated = 0
match processes_to_update {
ProcessesToUpdate::All => {
updated = all_processes.length()
if remove_dead_processes {
self.processes_data = all_processes
} else {
for pid, process in all_processes {
self.processes_data[pid] = merge_process_with_refresh_kind(
self.processes_data.get(pid),
process,
refresh_kind,
)
}
}
}
ProcessesToUpdate::Selected(pids) =>
for pid in pids {
match all_processes.get(pid) {
Some(process) => {
self.processes_data[pid] = merge_process_with_refresh_kind(
self.processes_data.get(pid),
process,
refresh_kind,
)
updated = updated + 1
}
None => if remove_dead_processes { self.processes_data.remove(pid) }
}
}
}
let next_cpu_totals : Map[Pid, UInt64] = {}
for pid, process in self.processes_data {
next_cpu_totals[pid] = process.accumulated_cpu_time()
}
self.prev_proc_cpu_total = next_cpu_totals
updated
}
///|
pub fn System::processes(self : System) -> Map[Pid, Process] {
self.processes_data
}
///|
pub fn System::process(self : System, pid : Pid) -> Process? {
self.processes_data.get(pid)
}
///|
pub fn System::processes_by_name(
self : System,
name : String,
) -> Array[(Pid, Process)] {
let out : Array[(Pid, Process)] = []
for pid, process in self.processes_data {
if process.name().to_lower().contains(name.to_lower()) {
out.push((pid, process))
}
}
out
}
///|
pub fn System::processes_by_exact_name(
self : System,
name : String,
) -> Array[(Pid, Process)] {
let out : Array[(Pid, Process)] = []
for pid, process in self.processes_data {
if process.name() == name {
out.push((pid, process))
}
}
out
}
///|
pub fn System::global_cpu_usage(self : System) -> Double {
if self.cpus_data.is_empty() {
0.0
} else {
let mut sum = 0.0
for cpu in self.cpus_data {
sum += cpu.cpu_usage()
}
sum / self.cpus_data.length().to_double()
}
}
///|
pub fn System::cpus(self : System) -> Array[Cpu] {
self.cpus_data
}
///|
pub fn System::total_memory(self : System) -> UInt64 {
self.total_memory_data
}
///|
pub fn System::free_memory(self : System) -> UInt64 {
self.free_memory_data
}
///|
pub fn System::available_memory(self : System) -> UInt64 {
self.available_memory_data
}
///|
pub fn System::used_memory(self : System) -> UInt64 {
self.used_memory_data
}
///|
pub fn System::total_swap(self : System) -> UInt64 {
self.total_swap_data
}
///|
pub fn System::free_swap(self : System) -> UInt64 {
self.free_swap_data
}
///|
pub fn System::used_swap(self : System) -> UInt64 {
self.used_swap_data
}
///|
pub fn System::cgroup_limits(self : System) -> CGroupLimits? {
self.cgroup_limits_data
}
///|
pub fn System::uptime() -> UInt64 {
if get_os_tag() == "unknown" {
0UL
} else {
uptime_seconds_ffi()
}
}
///|
pub fn System::boot_time() -> UInt64 {
let boot = boot_time_seconds_ffi()
if boot <= 0L {
0UL
} else {
boot.to_int().to_uint64()
}
}
///|
pub fn System::load_average() -> LoadAvg {
let fields = split_whitespace(decode_bytes(load_average_ffi()))
if fields.length() >= 3 {
{
one: parse_double_or(fields[0], 0.0),
five: parse_double_or(fields[1], 0.0),
fifteen: parse_double_or(fields[2], 0.0),
}
} else {
LoadAvg::zero()
}
}
///|
pub fn System::name() -> String? {
let os = get_os_tag()
if os == "linux" {
Some("Linux")
} else if os == "macos" {
Some("macOS")
} else {
None
}
}
///|
pub fn System::kernel_version() -> String? {
let value = decode_bytes_trimmed(kernel_version_ffi())
if value == "" {
None
} else {
Some(value)
}
}
///|
pub fn System::os_version() -> String? {
if get_os_tag() == "linux" {
let content = read_text_file_or_empty("/etc/os-release")
for line in content.split("\n") {
if line.has_prefix("VERSION_ID=") {
return Some(line[11:].trim(chars="\"").to_string())
}
}
None
} else if get_os_tag() == "macos" {
let v = decode_bytes_trimmed(macos_os_version_ffi())
if v == "" {
None
} else {
Some(v)
}
} else {
None
}
}
///|
pub fn System::long_os_version() -> String? {
if get_os_tag() == "linux" {
let content = read_text_file_or_empty("/etc/os-release")
for line in content.split("\n") {
if line.has_prefix("PRETTY_NAME=") {
return Some(line[12:].trim(chars="\"").to_string())
}
}
None
} else if get_os_tag() == "macos" {
let version = decode_bytes_trimmed(macos_os_version_ffi())
if version == "" {
None
} else {
Some("macOS \{version}")
}
} else {
None
}
}
///|
pub fn System::distribution_id() -> String {
if get_os_tag() == "linux" {
let content = read_text_file_or_empty("/etc/os-release")
for line in content.split("\n") {
if line.has_prefix("ID=") {
return line[3:].trim(chars="\"").to_string()
}
}
"linux"
} else if get_os_tag() == "macos" {
"macos"
} else {
"unknown"
}
}
///|
pub fn System::distribution_id_like() -> Array[String] {
if get_os_tag() == "linux" {
let content = read_text_file_or_empty("/etc/os-release")
for line in content.split("\n") {
if line.has_prefix("ID_LIKE=") {
return split_whitespace(
line[8:].trim(chars="\"").replace(old=" ", new=" ").to_string(),
)
}
}
[]
} else if get_os_tag() == "macos" {
["darwin"]
} else {
[]
}
}
///|
pub fn System::kernel_long_version() -> String {
decode_bytes_trimmed(kernel_long_version_ffi())
}
///|
pub fn System::host_name() -> String? {
let h = decode_bytes_trimmed(host_name_ffi())
if h == "" {
None
} else {
Some(h)
}
}
///|
pub fn System::cpu_arch() -> String {
let arch = decode_bytes_trimmed(cpu_arch_ffi())
if arch == "" {
"unknown"
} else {
arch
}
}
///|
pub fn System::physical_core_count() -> Int? {
if get_os_tag() == "unknown" {
None
} else {
let count = physical_cpu_count_ffi()
if count > 0 {
Some(count)
} else {
None
}
}
}
///|
pub fn System::open_files_limit() -> Int? {
if get_os_tag() == "unknown" {
return None
}
let value = get_open_files_limit_ffi()
if value > 0 {
Some(value)
} else {
None
}
}
///|
fn parse_linux_meminfo() -> Map[String, UInt64] {
let out : Map[String, UInt64] = {}
match read_text_file("/proc/meminfo") {
Some(content) =>
for line in content.split("\n") {
let trimmed = line.trim().to_string()
if trimmed == "" {
continue
}
let parts = split_whitespace(trimmed)
if parts.length() >= 2 {
let key = parts[0].trim(chars=":").to_string()
let value = parse_uint64_or(parts[1], 0UL)
out[key] = value
}
}
None => ()
}
out
}
///|
fn read_cgroup_value(path : String) -> UInt64? {
match read_text_file(path) {
Some(content) => {
let v = content.trim().to_string()
if v == "" || v == "max" {
None
} else {
Some(parse_uint64_or(v, 0UL))
}
}
None => None
}
}
///|
fn read_cgroup_limits_linux(total_memory : UInt64) -> CGroupLimits? {
let mem_max = read_cgroup_value("/sys/fs/cgroup/memory.max")
let mem_current = read_cgroup_value("/sys/fs/cgroup/memory.current")
match (mem_max, mem_current) {
(Some(max_v), Some(cur_v)) => {
let swap_max = read_cgroup_value("/sys/fs/cgroup/memory.swap.max")
let swap_current = read_cgroup_value("/sys/fs/cgroup/memory.swap.current")
let free_mem = if max_v > cur_v { max_v - cur_v } else { 0UL }
let free_swap = match (swap_max, swap_current) {
(Some(sm), Some(sc)) => if sm > sc { sm - sc } else { 0UL }
_ => 0UL
}
Some({
total_memory: if max_v == 0UL {
total_memory
} else {
max_v
},
free_memory: free_mem,
free_swap,
})
}
_ => None
}
}
///|
fn parse_cpu_line(line : String) -> (String, CpuSnapshot)? {
let fields = split_whitespace(line)
if fields.length() < 5 || !fields[0].has_prefix("cpu") {
return None
}
let mut total = 0UL
for i in 1..= 6 {
idle += parse_uint64_or(fields[5], 0UL)
}
Some((fields[0], { total, idle }))
}
///|
fn split_tab_fields(line : String) -> Array[String] {
line.split("\t").to_array().map(part => part.to_string())
}
///|
fn refresh_cpu_linux(system : System, refresh_kind : CpuRefreshKind) -> Unit {
match read_text_file("/proc/stat") {
Some(content) => {
let mut global : CpuSnapshot? = None
let per_cpu : Array[(String, CpuSnapshot)] = []
for line in content.split("\n") {
let trimmed = line.trim().to_string()
if !trimmed.has_prefix("cpu") {
continue
}
match parse_cpu_line(trimmed) {
Some((name, snapshot)) =>
if name == "cpu" {
global = Some(snapshot)
} else {
per_cpu.push((name, snapshot))
}
None => ()
}
}
if system.cpus_data.length() != per_cpu.length() {
system.cpus_data = []
system.prev_cpu_snapshots = []
let vendor = parse_linux_cpuinfo_value("vendor_id")
let brand = parse_linux_cpuinfo_value("model name")
let mut frequency = 0UL
if refresh_kind.frequency() {
let mhz = parse_double_or(parse_linux_cpuinfo_value("cpu MHz"), 0.0)
if mhz > 0.0 {
frequency = mhz.to_int().to_uint64()
}
}
for pair in per_cpu {
let (cpu_name, _) = pair
system.cpus_data.push({
name: cpu_name,
vendor_id: vendor,
brand,
frequency,
cpu_usage: 0.0,
})
system.prev_cpu_snapshots.push({ total: 0UL, idle: 0UL })
}
}
if refresh_kind.frequency() {
let mhz = parse_double_or(parse_linux_cpuinfo_value("cpu MHz"), 0.0)
let freq = if mhz > 0.0 { mhz.to_int().to_uint64() } else { 0UL }
for cpu in system.cpus_data {
cpu.frequency = freq
}
}
if refresh_kind.cpu_usage() {
for i in 0.. ()
}
}
///|
fn refresh_cpu_macos(system : System, refresh_kind : CpuRefreshKind) -> Unit {
let cpu_info = split_tab_fields(decode_bytes(macos_cpu_info_ffi()))
let logical_count = if cpu_info.length() >= 1 {
Int::max(parse_int_or(cpu_info[0], 1), 1)
} else {
Int::max(logical_cpu_count_ffi(), 1)
}
let vendor = if cpu_info.length() >= 2 && cpu_info[1] != "" {
cpu_info[1]
} else {
"Apple"
}
let brand = if cpu_info.length() >= 3 && cpu_info[2] != "" {
cpu_info[2]
} else {
"Apple Silicon"
}
let frequency_mhz = if cpu_info.length() >= 4 {
parse_uint64_or(cpu_info[3], 0UL) / 1000000UL
} else {
0UL
}
if system.cpus_data.length() != logical_count {
system.cpus_data = []
system.prev_cpu_snapshots = []
for i in 0.. Array[CpuSnapshot] {
let out : Array[CpuSnapshot] = []
for line in raw.split("\n") {
let fields = split_whitespace(line.trim().to_string())
if fields.length() < 2 {
continue
}
let total = parse_uint64_or(fields[0], 0UL)
let idle = parse_uint64_or(fields[1], 0UL)
out.push({ total, idle })
}
out
}
///|
fn compute_usage(previous : CpuSnapshot, current : CpuSnapshot) -> Double {
if previous.total == 0UL || current.total <= previous.total {
0.0
} else {
let diff_total = current.total - previous.total
let diff_idle = if current.idle > previous.idle {
current.idle - previous.idle
} else {
0UL
}
if diff_total == 0UL {
0.0
} else {
let active = if diff_idle >= diff_total {
0UL
} else {
diff_total - diff_idle
}
active.to_double() * 100.0 / diff_total.to_double()
}
}
}
///|
fn parse_linux_cpuinfo_value(key : String) -> String {
match read_text_file("/proc/cpuinfo") {
Some(content) => {
for line in content.split("\n") {
if line.has_prefix("\{key}") {
let parts = line.split(":").to_array()
if parts.length() >= 2 {
return parts[1].trim().to_string()
}
}
}
""
}
None => ""
}
}
///|
fn read_system_total_jiffies_linux() -> UInt64 {
match read_text_file("/proc/stat") {
Some(content) => {
for line in content.split("\n") {
let t = line.trim().to_string()
if t.has_prefix("cpu ") {
let fields = split_whitespace(t)
let mut total = 0UL
for i in 1.. 0UL
}
}
///|
fn parse_linux_proc_key_values(path : String) -> Map[String, String] {
let out : Map[String, String] = {}
match read_text_file(path) {
Some(content) =>
for line in content.split("\n") {
match line.find(":") {
Some(idx) => {
let key = line[:idx].trim().to_string()
let value = line[idx + 1:].trim().to_string()
out[key] = value
}
None => ()
}
}
None => ()
}
out
}
///|
fn parse_proc_kb_value(value : String) -> UInt64 {
let fields = split_whitespace(value)
if fields.is_empty() {
0UL
} else {
parse_uint64_or(fields[0], 0UL)
}
}
///|
fn parse_null_separated_file(path : String) -> Array[String] {
let bytes = @fs.read_file_to_bytes(path) catch { _ => b"" }
if bytes.length() == 0 {
return []
}
let out : Array[String] = []
let mut start = 0
for i in 0.. start {
out.push(@utf8.decode_lossy(bytes[start:i]))
}
start = i + 1
}
}
if start < bytes.length() {
out.push(@utf8.decode_lossy(bytes[start:]))
}
out
}
///|
fn parse_uid_pair(value : String) -> (Uid?, Uid?) {
let fields = split_whitespace(value)
if fields.length() >= 2 {
let uid0 = parse_int_or(fields[0], -1)
let uid1 = parse_int_or(fields[1], -1)
(
if uid0 >= 0 {
Some(Uid::from(uid0))
} else {
None
},
if uid1 >= 0 {
Some(Uid::from(uid1))
} else {
None
},
)
} else {
(None, None)
}
}
///|
fn parse_gid_pair(value : String) -> (Gid?, Gid?) {
let fields = split_whitespace(value)
if fields.length() >= 2 {
let gid0 = parse_int_or(fields[0], -1)
let gid1 = parse_int_or(fields[1], -1)
(
if gid0 >= 0 {
Some(Gid::from(gid0))
} else {
None
},
if gid1 >= 0 {
Some(Gid::from(gid1))
} else {
None
},
)
} else {
(None, None)
}
}
///|
fn parse_proc_disk_usage(path : String, previous : DiskUsage) -> DiskUsage {
let kv = parse_linux_proc_key_values(path)
let total_read = parse_uint64_or(kv.get_or_default("read_bytes", "0"), 0UL)
let total_written = parse_uint64_or(
kv.get_or_default("write_bytes", "0"),
0UL,
)
{
total_read_bytes: total_read,
read_bytes: if total_read >= previous.total_read_bytes {
total_read - previous.total_read_bytes
} else {
0UL
},
total_written_bytes: total_written,
written_bytes: if total_written >= previous.total_written_bytes {
total_written - previous.total_written_bytes
} else {
0UL
},
}
}
///|
fn parse_proc_stat_linux(
stat_text : String,
) -> (String, ProcessStatus, Pid?, Pid?, UInt64, UInt64)? {
let left = stat_text.find("(")
let right = stat_text.rev_find(")")
match (left, right) {
(Some(l), Some(r)) => {
if r <= l || r + 2 > stat_text.length() {
return None
}
let name = stat_text[l + 1:r].to_string()
let fields = split_whitespace(stat_text[r + 2:].to_string())
if fields.length() < 20 {
return None
}
let status = map_status_char(fields[0][:1].to_string())
let ppid_i = parse_int_or(fields[1], -1)
let parent = if ppid_i > 0 { Some(Pid::from(ppid_i)) } else { None }
let sid_i = parse_int_or(fields[3], -1)
let session = if sid_i > 0 { Some(Pid::from(sid_i)) } else { None }
let utime = parse_uint64_or(fields[11], 0UL)
let stime = parse_uint64_or(fields[12], 0UL)
let start_ticks = parse_uint64_or(fields[19], 0UL)
Some((name, status, parent, session, utime + stime, start_ticks))
}
_ => None
}
}
///|
fn map_status_char(ch : String) -> ProcessStatus {
if ch == "R" {
ProcessStatus::Run
} else if ch == "S" {
ProcessStatus::Sleep
} else if ch == "D" {
ProcessStatus::UninterruptibleDiskSleep
} else if ch == "K" {
ProcessStatus::Wakekill
} else if ch == "W" {
ProcessStatus::Waking
} else if ch == "P" {
ProcessStatus::Parked
} else if ch == "Z" {
ProcessStatus::Zombie
} else if ch == "T" {
ProcessStatus::Stop
} else if ch == "t" {
ProcessStatus::Tracing
} else if ch == "X" || ch == "x" {
ProcessStatus::Dead
} else if ch == "L" {
ProcessStatus::LockBlocked
} else {
ProcessStatus::Unknown(0)
}
}
///|
fn collect_processes_linux(
system : System,
refresh_kind : ProcessRefreshKind,
selected_pids : Array[Pid]?,
) -> Map[Pid, Process] {
let out : Map[Pid, Process] = {}
let entries = match selected_pids {
Some(pids) => pids.map(pid => pid.to_int().to_string())
None => list_dir("/proc")
}
let now_s = @env.now() / 1000UL
let boot_time = System::boot_time()
let ticks_per_second = Int::max(clock_ticks_per_second_ffi(), 100)
let system_total_jiffies = read_system_total_jiffies_linux()
let prev_system_total = system.prev_proc_system_total
let cpu_count = Int::max(system.cpus_data.length(), 1).to_double()
for entry in entries {
let pid_i = parse_int_or(entry, -1)
if pid_i <= 0 {
continue
}
let pid = Pid::from(pid_i)
let proc_dir = "/proc/\{pid_i}"
let stat_text = read_text_file_or_empty("\{proc_dir}/stat")
if stat_text == "" {
continue
}
match parse_proc_stat_linux(stat_text) {
Some((name, status, parent, session_id, proc_total, start_ticks)) => {
let status_map = parse_linux_proc_key_values("\{proc_dir}/status")
let cmd = if refresh_kind.cmd() == UpdateKind::Never {
[]
} else {
let parsed = parse_null_separated_file("\{proc_dir}/cmdline")
if parsed.is_empty() {
[name]
} else {
parsed
}
}
let environ = if refresh_kind.environ() == UpdateKind::Never {
[]
} else {
parse_null_separated_file("\{proc_dir}/environ")
}
let exe = if refresh_kind.exe() == UpdateKind::Never {
None
} else {
readlink_path("\{proc_dir}/exe")
}
let cwd = if refresh_kind.cwd() == UpdateKind::Never {
None
} else {
readlink_path("\{proc_dir}/cwd")
}
let root = if refresh_kind.root() == UpdateKind::Never {
None
} else {
readlink_path("\{proc_dir}/root")
}
let (memory, virtual_memory) = if refresh_kind.memory() {
(
parse_proc_kb_value(status_map.get_or_default("VmRSS", "")) * 1024UL,
parse_proc_kb_value(status_map.get_or_default("VmSize", "")) *
1024UL,
)
} else {
(0UL, 0UL)
}
let (user_id, effective_user_id) = if refresh_kind.user() ==
UpdateKind::Never {
(None, None)
} else {
parse_uid_pair(status_map.get_or_default("Uid", ""))
}
let (group_id, effective_group_id) = if refresh_kind.user() ==
UpdateKind::Never {
(None, None)
} else {
parse_gid_pair(status_map.get_or_default("Gid", ""))
}
let open_files = if file_exists("\{proc_dir}/fd") {
Some(list_dir("\{proc_dir}/fd").length())
} else {
None
}
let tasks = if refresh_kind.tasks() {
let tids : Array[Pid] = []
for tid_entry in list_dir("\{proc_dir}/task") {
let tid = parse_int_or(tid_entry, -1)
if tid > 0 {
tids.push(Pid::from(tid))
}
}
Some(tids)
} else {
None
}
let previous_total = system.prev_proc_cpu_total.get_or_default(
pid, proc_total,
)
let cpu_usage = if refresh_kind.cpu() {
let diff_proc = if proc_total > previous_total {
proc_total - previous_total
} else {
0UL
}
let diff_sys = if system_total_jiffies > prev_system_total {
system_total_jiffies - prev_system_total
} else {
0UL
}
if diff_sys == 0UL {
0.0
} else {
diff_proc.to_double() * 100.0 * cpu_count / diff_sys.to_double()
}
} else {
0.0
}
system.prev_proc_cpu_total[pid] = proc_total
let previous_disk_usage = match system.processes_data.get(pid) {
Some(old_proc) => old_proc.disk_usage()
None => DiskUsage::zero()
}
let disk_usage = if refresh_kind.disk_usage() {
parse_proc_disk_usage("\{proc_dir}/io", previous_disk_usage)
} else {
DiskUsage::zero()
}
let start_time = if ticks_per_second > 0 {
boot_time + start_ticks / ticks_per_second.to_uint64()
} else {
0UL
}
let run_time = if now_s > start_time { now_s - start_time } else { 0UL }
out[pid] = {
pid,
parent,
name,
cmd,
environ,
exe,
cwd,
root,
memory,
virtual_memory,
status,
start_time,
run_time,
cpu_usage,
accumulated_cpu_time: proc_total,
disk_usage,
user_id,
effective_user_id,
group_id,
effective_group_id,
session_id,
tasks,
thread_kind: None,
open_files,
open_files_limit: System::open_files_limit(),
}
match tasks {
Some(tids) =>
for tid in tids {
if tid == pid {
continue
}
let task_dir = "\{proc_dir}/task/\{tid.to_int()}"
let task_stat_text = read_text_file_or_empty("\{task_dir}/stat")
if task_stat_text == "" {
continue
}
match parse_proc_stat_linux(task_stat_text) {
Some(
(
task_name,
task_status,
_task_parent,
task_session_id,
task_total,
task_start_ticks,
)
) => {
let previous_task_total = system.prev_proc_cpu_total.get_or_default(
tid, task_total,
)
let task_cpu_usage = if refresh_kind.cpu() {
let diff_proc = if task_total > previous_task_total {
task_total - previous_task_total
} else {
0UL
}
let diff_sys = if system_total_jiffies > prev_system_total {
system_total_jiffies - prev_system_total
} else {
0UL
}
if diff_sys == 0UL {
0.0
} else {
diff_proc.to_double() *
100.0 *
cpu_count /
diff_sys.to_double()
}
} else {
0.0
}
system.prev_proc_cpu_total[tid] = task_total
let task_start_time = if ticks_per_second > 0 {
boot_time + task_start_ticks / ticks_per_second.to_uint64()
} else {
0UL
}
let task_run_time = if now_s > task_start_time {
now_s - task_start_time
} else {
0UL
}
out[tid] = {
pid: tid,
parent: Some(pid),
name: task_name,
cmd: [],
environ: [],
exe: None,
cwd: None,
root: None,
memory: 0UL,
virtual_memory: 0UL,
status: task_status,
start_time: task_start_time,
run_time: task_run_time,
cpu_usage: task_cpu_usage,
accumulated_cpu_time: task_total,
disk_usage: DiskUsage::zero(),
user_id,
effective_user_id,
group_id,
effective_group_id,
session_id: task_session_id,
tasks: None,
thread_kind: Some(ThreadKind::Userland),
open_files: None,
open_files_limit: None,
}
}
None => ()
}
}
None => ()
}
}
None => ()
}
}
system.prev_proc_system_total = system_total_jiffies
out
}
///|
fn collect_processes_macos(
system : System,
refresh_kind : ProcessRefreshKind,
selected_pids : Array[Pid]?,
) -> Map[Pid, Process] {
collect_processes_from_ps_macos(system, refresh_kind, selected_pids)
}
///|
fn collect_processes_from_ps_macos(
system : System,
refresh_kind : ProcessRefreshKind,
selected_pids : Array[Pid]?,
) -> Map[Pid, Process] {
let out : Map[Pid, Process] = {}
let now_s = @env.now() / 1000UL
let selected : Map[Int, Bool] = {}
let has_selected = selected_pids is Some(_)
let selected_empty = match selected_pids {
Some(pids) => pids.is_empty()
None => false
}
match selected_pids {
Some(pids) =>
for pid in pids {
selected[pid.to_int()] = true
}
None => ()
}
if selected_empty {
return out
}
let ticks_per_second = Int::max(clock_ticks_per_second_ffi(), 100)
let lines = split_non_empty_lines(decode_bytes(macos_process_table_ffi()))
for line in lines {
let fields = split_tab_fields(line)
if fields.length() < 13 {
continue
}
let pid_i = parse_int_or(fields[0], -1)
if has_selected && !selected.contains(pid_i) {
continue
}
let pid = Pid::from(pid_i)
if pid.to_int() <= 0 {
continue
}
let ppid_val = parse_int_or(fields[1], -1)
let ppid = if ppid_val > 0 { Some(Pid::from(ppid_val)) } else { None }
let effective_uid_i = parse_int_or(fields[2], -1)
let real_uid_i = parse_int_or(fields[3], -1)
let effective_gid_i = parse_int_or(fields[4], -1)
let real_gid_i = parse_int_or(fields[5], -1)
let cpu = parse_double_or(fields[6], 0.0)
let rss_kb = parse_uint64_or(fields[7], 0UL)
let vsz_kb = parse_uint64_or(fields[8], 0UL)
let status_char = fields[9]
let status = if status_char == "" {
ProcessStatus::Unknown(0)
} else {
map_status_char(status_char[:1].to_string())
}
let run_time = parse_uint64_or(fields[10], 0UL)
let session_i = parse_int_or(fields[11], -1)
let session_id = if session_i >= 0 {
Some(Pid::from(session_i))
} else {
None
}
let name = fields[12]
if name == "" || status == ProcessStatus::Zombie {
continue
}
let cmd = if refresh_kind.cmd() == UpdateKind::Never {
[]
} else {
let parsed = read_macos_process_args(pid)
if parsed.is_empty() {
[name]
} else {
parsed
}
}
let environ = if refresh_kind.environ() == UpdateKind::Never {
[]
} else {
read_macos_process_environ(pid)
}
let (user_id, effective_user_id, group_id, effective_group_id) = if refresh_kind.user() ==
UpdateKind::Never {
(None, None, None, None)
} else {
(
if real_uid_i >= 0 {
Some(Uid::from(real_uid_i))
} else {
None
},
if effective_uid_i >= 0 {
Some(Uid::from(effective_uid_i))
} else {
None
},
if real_gid_i >= 0 {
Some(Gid::from(real_gid_i))
} else {
None
},
if effective_gid_i >= 0 {
Some(Gid::from(effective_gid_i))
} else {
None
},
)
}
let open_files = read_macos_process_open_files(pid)
let prev_cpu_total = system.prev_proc_cpu_total.get_or_default(pid, 0UL)
let cpu_total = if refresh_kind.cpu() && cpu > 0.0 {
(cpu * run_time.to_double() * ticks_per_second.to_double() / 100.0)
.to_int()
.to_uint64()
} else {
prev_cpu_total
}
system.prev_proc_cpu_total[pid] = cpu_total
let previous_disk_usage = match system.processes_data.get(pid) {
Some(old_proc) => old_proc.disk_usage()
None => DiskUsage::zero()
}
let disk_usage = if refresh_kind.disk_usage() {
read_macos_process_disk_usage(pid, previous_disk_usage)
} else {
DiskUsage::zero()
}
out[pid] = {
pid,
parent: ppid,
name,
cmd,
environ,
exe: if refresh_kind.exe() == UpdateKind::Never {
None
} else {
read_macos_process_path("exe", pid)
},
cwd: if refresh_kind.cwd() == UpdateKind::Never {
None
} else {
read_macos_process_path("cwd", pid)
},
root: if refresh_kind.root() == UpdateKind::Never {
None
} else {
read_macos_process_path("root", pid)
},
memory: if refresh_kind.memory() {
rss_kb * 1024UL
} else {
0UL
},
virtual_memory: if refresh_kind.memory() {
vsz_kb * 1024UL
} else {
0UL
},
status,
start_time: if now_s > run_time {
now_s - run_time
} else {
0UL
},
run_time,
cpu_usage: if refresh_kind.cpu() {
cpu
} else {
0.0
},
accumulated_cpu_time: cpu_total,
disk_usage,
user_id,
effective_user_id,
group_id,
effective_group_id,
session_id,
tasks: if refresh_kind.tasks() {
None
} else {
None
},
thread_kind: None,
open_files,
open_files_limit: System::open_files_limit(),
}
}
out
}
///|
pub(all) struct Disks {
mut disks_data : Array[Disk]
} derive(Show)
///|
pub fn Disks::new() -> Disks {
{ disks_data: [] }
}
///|
pub fn Disks::new_with_refreshed_list() -> Disks {
Disks::new_with_refreshed_list_specifics(DiskRefreshKind::everything())
}
///|
pub fn Disks::new_with_refreshed_list_specifics(
refreshes : DiskRefreshKind,
) -> Disks {
let disks = Disks::new()
disks.refresh_specifics(false, refreshes)
disks
}
///|
pub fn Disks::list(self : Disks) -> Array[Disk] {
self.disks_data
}
///|
pub fn Disks::list_mut(self : Disks) -> Array[Disk] {
self.disks_data
}
///|
pub fn Disks::refresh(self : Disks, remove_not_listed_disks : Bool) -> Unit {
self.refresh_specifics(remove_not_listed_disks, DiskRefreshKind::everything())
}
///|
pub fn Disks::refresh_specifics(
self : Disks,
remove_not_listed_disks : Bool,
refreshes : DiskRefreshKind,
) -> Unit {
let refreshed = collect_disks(self.disks_data, refreshes)
if remove_not_listed_disks {
self.disks_data = refreshed
} else {
self.disks_data = refreshed
}
}
///|
pub fn Disk::refresh(self : Disk) -> Bool {
self.refresh_specifics(DiskRefreshKind::everything())
}
///|
pub fn Disk::refresh_specifics(
self : Disk,
refreshes : DiskRefreshKind,
) -> Bool {
let disks = collect_disks([self], refreshes)
for disk in disks {
if disk.mount_point() == self.mount_point() {
self.kind = disk.kind
self.total_space = disk.total_space
self.available_space = disk.available_space
self.usage = disk.usage
return true
}
}
false
}
///|
struct DiskIoSnapshot {
total_read_bytes : UInt64
total_written_bytes : UInt64
} derive(Show, Eq)
///|
fn DiskIoSnapshot::zero() -> DiskIoSnapshot {
{ total_read_bytes: 0UL, total_written_bytes: 0UL }
}
///|
struct MacosDiskInfo {
io : DiskIoSnapshot
kind : DiskKind
} derive(Show, Eq)
///|
struct MountEntry {
file_system : String
mount_point : String
file_system_type : String
total_space : UInt64
available_space : UInt64
is_read_only : Bool
is_removable : Bool
} derive(Show, Eq)
///|
fn collect_disks(
previous : Array[Disk],
refreshes : DiskRefreshKind,
) -> Array[Disk] {
if get_os_tag() == "linux" {
collect_disks_linux(previous, refreshes)
} else if get_os_tag() == "macos" {
collect_disks_macos(previous, refreshes)
} else {
[]
}
}
///|
fn collect_disks_linux(
previous : Array[Disk],
refreshes : DiskRefreshKind,
) -> Array[Disk] {
let out : Array[Disk] = []
let stats = if refreshes.io_usage() { collect_linux_diskstats() } else { {} }
let mounts = parse_mount_table_entries()
for entry in mounts {
let previous_usage = find_previous_disk_usage(previous, entry.mount_point)
let io = if refreshes.io_usage() {
find_linux_disk_io(stats, entry.file_system)
} else {
DiskIoSnapshot::zero()
}
out.push(
build_disk_entry(
entry.file_system,
entry.mount_point,
entry.total_space,
entry.available_space,
refreshes,
previous_usage,
io,
infer_disk_kind(entry.file_system),
entry.file_system_type,
entry.is_read_only,
entry.is_removable,
),
)
}
out
}
///|
fn collect_disks_macos(
previous : Array[Disk],
refreshes : DiskRefreshKind,
) -> Array[Disk] {
let out : Array[Disk] = []
let global_io_fallback = if refreshes.io_usage() {
read_macos_global_disk_io_snapshot()
} else {
DiskIoSnapshot::zero()
}
let disk_info_table = if refreshes.kind() || refreshes.io_usage() {
parse_macos_disk_table_entries()
} else {
{}
}
let mounts = parse_mount_table_entries()
for entry in mounts {
let previous_usage = find_previous_disk_usage(previous, entry.mount_point)
let disk_key = normalize_macos_disk_device(entry.file_system)
let disk_info = disk_info_table.get_or_default(disk_key, {
io: DiskIoSnapshot::zero(),
kind: infer_disk_kind(entry.file_system),
})
let io = if refreshes.io_usage() {
if disk_info.io.total_read_bytes > 0UL ||
disk_info.io.total_written_bytes > 0UL {
disk_info.io
} else {
global_io_fallback
}
} else {
DiskIoSnapshot::zero()
}
out.push(
build_disk_entry(
entry.file_system,
entry.mount_point,
entry.total_space,
entry.available_space,
refreshes,
previous_usage,
io,
disk_info.kind,
entry.file_system_type,
entry.is_read_only,
entry.is_removable,
),
)
}
out
}
///|
fn read_macos_global_disk_io_snapshot() -> DiskIoSnapshot {
let fields = split_whitespace(decode_bytes(macos_vm_io_counters_ffi()))
if fields.length() < 2 {
return DiskIoSnapshot::zero()
}
{
total_read_bytes: parse_uint64_or(fields[0], 0UL),
total_written_bytes: parse_uint64_or(fields[1], 0UL),
}
}
///|
fn normalize_macos_disk_device(device : String) -> String {
let base_view = if device.has_prefix("/dev/") { device[5:] } else { device }
let base = base_view.to_string()
if !base.has_prefix("disk") {
return base
}
let mut end = 4
while end < base.length() {
let ch = base[end]
if ch >= '0' && ch <= '9' {
end = end + 1
} else {
break
}
}
if end > 4 {
base[:end].to_string()
} else {
base
}
}
///|
fn parse_macos_disk_table_entries() -> Map[String, MacosDiskInfo] {
let out : Map[String, MacosDiskInfo] = {}
let lines = split_non_empty_lines(decode_bytes(macos_disk_table_ffi()))
for line in lines {
let fields = split_tab_fields(line)
if fields.length() < 4 {
continue
}
let key = normalize_macos_disk_device(fields[0])
if key == "" {
continue
}
let read_total = parse_uint64_or(fields[1], 0UL)
let write_total = parse_uint64_or(fields[2], 0UL)
let is_ssd = parse_int_or(fields[3], 0) != 0
let current : MacosDiskInfo = {
io: { total_read_bytes: read_total, total_written_bytes: write_total },
kind: if is_ssd {
DiskKind::SSD
} else {
DiskKind::HDD
},
}
match out.get(key) {
Some(old) =>
out[key] = {
io: {
total_read_bytes: if old.io.total_read_bytes >=
current.io.total_read_bytes {
old.io.total_read_bytes
} else {
current.io.total_read_bytes
},
total_written_bytes: if old.io.total_written_bytes >=
current.io.total_written_bytes {
old.io.total_written_bytes
} else {
current.io.total_written_bytes
},
},
kind: match (old.kind, current.kind) {
(DiskKind::SSD, _) => DiskKind::SSD
(_, DiskKind::SSD) => DiskKind::SSD
_ => old.kind
},
}
None => out[key] = current
}
}
out
}
///|
fn parse_mount_table_entries() -> Array[MountEntry] {
let out : Array[MountEntry] = []
let lines = split_non_empty_lines(decode_bytes(mount_table_ffi()))
for line in lines {
let fields = split_tab_fields(line)
if fields.length() < 7 {
continue
}
out.push({
file_system: fields[0],
mount_point: fields[1],
file_system_type: fields[2],
total_space: parse_uint64_or(fields[3], 0UL),
available_space: parse_uint64_or(fields[4], 0UL),
is_read_only: parse_int_or(fields[5], 0) != 0,
is_removable: parse_int_or(fields[6], 0) != 0,
})
}
out
}
///|
fn build_disk_entry(
fs : String,
mount : String,
total : UInt64,
avail : UInt64,
refreshes : DiskRefreshKind,
previous_usage : DiskUsage,
io : DiskIoSnapshot,
inferred_kind : DiskKind,
file_system_type : String,
is_read_only : Bool,
is_removable : Bool,
) -> Disk {
{
kind: if refreshes.kind() {
inferred_kind
} else {
DiskKind::Unknown("")
},
name: fs,
file_system: file_system_type,
mount_point: mount,
total_space: if refreshes.storage() {
total
} else {
0UL
},
available_space: if refreshes.storage() {
avail
} else {
0UL
},
is_removable,
is_read_only,
usage: if refreshes.io_usage() {
{
total_read_bytes: io.total_read_bytes,
read_bytes: if io.total_read_bytes >= previous_usage.total_read_bytes {
io.total_read_bytes - previous_usage.total_read_bytes
} else {
0UL
},
total_written_bytes: io.total_written_bytes,
written_bytes: if io.total_written_bytes >=
previous_usage.total_written_bytes {
io.total_written_bytes - previous_usage.total_written_bytes
} else {
0UL
},
}
} else {
DiskUsage::zero()
},
}
}
///|
fn find_previous_disk_usage(
previous : Array[Disk],
mount : String,
) -> DiskUsage {
for disk in previous {
if disk.mount_point() == mount {
return disk.usage()
}
}
DiskUsage::zero()
}
///|
fn collect_linux_diskstats() -> Map[String, DiskIoSnapshot] {
let out : Map[String, DiskIoSnapshot] = {}
match read_text_file("/proc/diskstats") {
Some(content) =>
for line in content.split("\n") {
let fields = split_whitespace(line.trim().to_string())
if fields.length() < 10 {
continue
}
let name = fields[2]
let read_sectors = parse_uint64_or(fields[5], 0UL)
let written_sectors = parse_uint64_or(fields[9], 0UL)
out[name] = {
total_read_bytes: read_sectors * 512UL,
total_written_bytes: written_sectors * 512UL,
}
}
None => ()
}
out
}
///|
fn find_linux_disk_io(
stats : Map[String, DiskIoSnapshot],
file_system : String,
) -> DiskIoSnapshot {
let base = if file_system.has_prefix("/dev/") {
file_system[5:].to_string()
} else {
file_system
}
match stats.get(base) {
Some(io) => io
None => {
let parent = parent_linux_block_device(base)
stats.get_or_default(parent, DiskIoSnapshot::zero())
}
}
}
///|
fn parent_linux_block_device(device : String) -> String {
let mut end = device.length()
while end > 0 {
let ch = device[end - 1]
if ch >= '0' && ch <= '9' {
end = end - 1
} else {
break
}
}
let trimmed = device[:end].to_string()
if trimmed.has_suffix("p") {
trimmed[:trimmed.length() - 1].to_string()
} else {
trimmed
}
}
///|
fn infer_disk_kind(name : String) -> DiskKind {
let lower = name.to_lower()
if lower.contains("nvme") || lower.contains("ssd") {
DiskKind::SSD
} else if lower.has_prefix("/dev/sd") || lower.contains("disk") {
DiskKind::HDD
} else {
DiskKind::Unknown(name)
}
}
///|
pub(all) struct Networks {
mut networks_data : Map[String, NetworkData]
} derive(Show)
///|
pub fn Networks::new() -> Networks {
{ networks_data: {} }
}
///|
pub fn Networks::new_with_refreshed_list() -> Networks {
let networks = Networks::new()
networks.refresh(false)
networks
}
///|
pub fn Networks::list(self : Networks) -> Map[String, NetworkData] {
self.networks_data
}
///|
pub fn Networks::refresh(
self : Networks,
remove_not_listed_interfaces : Bool,
) -> Unit {
let now_data = collect_networks(self.networks_data)
if remove_not_listed_interfaces {
self.networks_data = now_data
} else {
for name, data in now_data {
self.networks_data[name] = data
}
}
}
///|
fn collect_networks(
previous : Map[String, NetworkData],
) -> Map[String, NetworkData] {
if get_os_tag() == "unknown" {
{}
} else {
collect_networks_from_native_table(previous)
}
}
///|
fn network_delta(current : UInt64, previous : UInt64) -> UInt64 {
if current >= previous {
current - previous
} else {
0UL
}
}
///|
fn parse_ip_networks_blob(blob : String) -> Array[IpNetwork] {
let out : Array[IpNetwork] = []
for token in blob.split(",") {
let value = token.trim().to_string()
if value != "" {
out.push({ raw: value })
}
}
out
}
///|
fn collect_networks_from_native_table(
previous : Map[String, NetworkData],
) -> Map[String, NetworkData] {
let out : Map[String, NetworkData] = {}
let lines = split_non_empty_lines(decode_bytes(network_table_ffi()))
for line in lines {
let fields = split_tab_fields(line)
if fields.length() < 9 {
continue
}
let name = fields[0]
let total_rx = parse_uint64_or(fields[1], 0UL)
let total_tx = parse_uint64_or(fields[2], 0UL)
let total_rx_packets = parse_uint64_or(fields[3], 0UL)
let total_tx_packets = parse_uint64_or(fields[4], 0UL)
let total_rx_errors = parse_uint64_or(fields[5], 0UL)
let total_tx_errors = parse_uint64_or(fields[6], 0UL)
let mtu = parse_uint64_or(fields[7], 0UL)
let mac : MacAddr = if fields[8].contains(":") {
{ raw: fields[8].to_lower() }
} else {
{ raw: "00:00:00:00:00:00" }
}
let ip_networks = if fields.length() >= 10 {
parse_ip_networks_blob(fields[9])
} else {
[]
}
let old = previous.get(name)
let prev_rx = old.map(d => d.total_received()).unwrap_or(0UL)
let prev_tx = old.map(d => d.total_transmitted()).unwrap_or(0UL)
let prev_rxp = old.map(d => d.total_packets_received()).unwrap_or(0UL)
let prev_txp = old.map(d => d.total_packets_transmitted()).unwrap_or(0UL)
let prev_rxe = old.map(d => d.total_errors_on_received()).unwrap_or(0UL)
let prev_txe = old.map(d => d.total_errors_on_transmitted()).unwrap_or(0UL)
out[name] = {
received: network_delta(total_rx, prev_rx),
total_received: total_rx,
transmitted: network_delta(total_tx, prev_tx),
total_transmitted: total_tx,
packets_received: network_delta(total_rx_packets, prev_rxp),
total_packets_received: total_rx_packets,
packets_transmitted: network_delta(total_tx_packets, prev_txp),
total_packets_transmitted: total_tx_packets,
errors_on_received: network_delta(total_rx_errors, prev_rxe),
total_errors_on_received: total_rx_errors,
errors_on_transmitted: network_delta(total_tx_errors, prev_txe),
total_errors_on_transmitted: total_tx_errors,
mac_address: mac,
ip_networks,
mtu,
}
}
out
}
///|
pub(all) struct Components {
mut components_data : Array[Component]
} derive(Show)
///|
pub fn Components::new() -> Components {
{ components_data: [] }
}
///|
pub fn Components::new_with_refreshed_list() -> Components {
let components = Components::new()
components.refresh(false)
components
}
///|
pub fn Components::list(self : Components) -> Array[Component] {
self.components_data
}
///|
pub fn Components::list_mut(self : Components) -> Array[Component] {
self.components_data
}
///|
pub fn Components::refresh(
self : Components,
remove_not_listed_components : Bool,
) -> Unit {
let now = collect_components()
if remove_not_listed_components {
self.components_data = now
} else {
self.components_data = now
}
}
///|
pub fn Component::refresh(self : Component) -> Unit {
let now = collect_components()
for c in now {
if c.label() == self.label() {
self.temperature = c.temperature
self.max = c.max
self.critical = c.critical
self.id = c.id
return
}
}
}
///|
fn collect_components() -> Array[Component] {
if get_os_tag() == "linux" {
collect_components_linux()
} else if get_os_tag() == "macos" {
collect_components_macos()
} else {
[]
}
}
///|
fn collect_components_linux() -> Array[Component] {
let out : Array[Component] = []
let base = "/sys/class/hwmon"
if !file_exists(base) {
return out
}
let hwmons = list_dir(base)
for hw in hwmons {
let hw_path = "\{base}/\{hw}"
let entries = list_dir(hw_path)
for entry in entries {
if entry.has_prefix("temp") && entry.has_suffix("_input") {
let temp_path = "\{hw_path}/\{entry}"
let raw = read_text_file_or_empty(temp_path).trim().to_string()
if raw == "" {
continue
}
let milli = parse_double_or(raw, 0.0)
let temp = if milli > 0.0 { Some(milli / 1000.0) } else { None }
let base_name = entry.replace(old="_input", new="")
let label_path = "\{hw_path}/\{base_name}_label"
let max_path = "\{hw_path}/\{base_name}_max"
let crit_path = "\{hw_path}/\{base_name}_crit"
let label = if file_exists(label_path) {
read_text_file_or_empty(label_path).trim().to_string()
} else {
"\{hw}:\{base_name}"
}
let max_raw = read_text_file_or_empty(max_path).trim().to_string()
let crit_raw = read_text_file_or_empty(crit_path).trim().to_string()
let max = if max_raw == "" {
None
} else {
Some(parse_double_or(max_raw, 0.0) / 1000.0)
}
let critical = if crit_raw == "" {
None
} else {
Some(parse_double_or(crit_raw, 0.0) / 1000.0)
}
out.push({
temperature: temp,
max,
critical,
label,
id: Some("\{hw}/\{base_name}"),
})
}
}
}
out
}
///|
fn collect_components_macos() -> Array[Component] {
let out : Array[Component] = []
let raw_temp = macos_battery_temperature_ffi()
if raw_temp > 0 {
out.push({
temperature: Some(raw_temp.to_double() / 10.0 - 273.15),
max: None,
critical: None,
label: "Battery",
id: Some("AppleSmartBattery"),
})
}
out
}
///|
pub(all) struct Users {
mut users_data : Array[User]
} derive(Show)
///|
pub fn Users::new() -> Users {
{ users_data: [] }
}
///|
pub fn Users::new_with_refreshed_list() -> Users {
let users = Users::new()
users.refresh()
users
}
///|
pub fn Users::list(self : Users) -> Array[User] {
self.users_data
}
///|
pub fn Users::list_mut(self : Users) -> Array[User] {
self.users_data
}
///|
pub fn Users::refresh(self : Users) -> Unit {
self.users_data = collect_users()
}
///|
pub fn Users::get_user_by_id(self : Users, user_id : Uid) -> User? {
for user in self.users_data {
if user.id() == user_id {
return Some(user)
}
}
None
}
///|
pub(all) struct Groups {
mut groups_data : Array[Group]
} derive(Show)
///|
pub fn Groups::new() -> Groups {
{ groups_data: [] }
}
///|
pub fn Groups::new_with_refreshed_list() -> Groups {
let groups = Groups::new()
groups.refresh()
groups
}
///|
pub fn Groups::list(self : Groups) -> Array[Group] {
self.groups_data
}
///|
pub fn Groups::list_mut(self : Groups) -> Array[Group] {
self.groups_data
}
///|
pub fn Groups::refresh(self : Groups) -> Unit {
self.groups_data = collect_group_table().values().to_array()
}
///|
fn collect_group_table() -> Map[Gid, Group] {
let out : Map[Gid, Group] = {}
let lines = split_non_empty_lines(decode_bytes(group_table_ffi()))
for line in lines {
let fields = split_tab_fields(line)
if fields.length() < 2 {
continue
}
let gid_i = parse_int_or(fields[0], -1)
if gid_i < 0 {
continue
}
let gid = Gid::from(gid_i)
out[gid] = { id: gid, name: fields[1] }
}
out
}
///|
fn collect_group_membership_table() -> Map[String, Array[Group]] {
let groups = collect_group_table()
let out : Map[String, Array[Group]] = {}
let lines = split_non_empty_lines(decode_bytes(group_table_ffi()))
for line in lines {
let fields = split_tab_fields(line)
if fields.length() < 3 {
continue
}
let gid_i = parse_int_or(fields[0], -1)
if gid_i < 0 {
continue
}
let gid = Gid::from(gid_i)
match groups.get(gid) {
Some(group) => {
let members = fields[2].split(",").to_array()
for member_name in members {
let name = member_name.trim().to_string()
if name == "" {
continue
}
let list = out.get_or_default(name, [])
list.push(group)
out[name] = list
}
}
None => ()
}
}
out
}
///|
fn collect_users() -> Array[User] {
let groups = collect_group_table()
let membership = collect_group_membership_table()
let out : Array[User] = []
let lines = split_non_empty_lines(decode_bytes(user_table_ffi()))
for line in lines {
let fields = split_tab_fields(line)
if fields.length() < 3 {
continue
}
let uid_i = parse_int_or(fields[0], -1)
let gid_i = parse_int_or(fields[1], -1)
if uid_i < 0 || gid_i < 0 {
continue
}
let name = fields[2]
let gid = Gid::from(gid_i)
let user_groups : Array[Group] = []
let seen : Map[Gid, Bool] = {}
let primary_group = groups.get_or_default(gid, { id: gid, name: "" })
user_groups.push(primary_group)
seen[primary_group.id()] = true
match membership.get(name) {
Some(extra_groups) =>
for group in extra_groups {
if !seen.contains(group.id()) {
seen[group.id()] = true
user_groups.push(group)
}
}
None => ()
}
out.push({ id: Uid::from(uid_i), group_id: gid, name, groups: user_groups })
}
out
}