// 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.
///|
extern "c" fn get_pid_ffi() -> Int = "sysinfo_get_pid"
///|
extern "c" fn sleep_millis_ffi(millis : Int) = "sysinfo_sleep_millis"
///|
extern "c" fn is_supported_system_ffi() -> Int = "sysinfo_is_supported_system"
///|
extern "c" fn os_tag_ffi() -> Bytes = "sysinfo_get_os_tag"
///|
extern "c" fn host_name_ffi() -> Bytes = "sysinfo_host_name"
///|
extern "c" fn kernel_version_ffi() -> Bytes = "sysinfo_kernel_version"
///|
extern "c" fn cpu_arch_ffi() -> Bytes = "sysinfo_cpu_arch"
///|
extern "c" fn kernel_long_version_ffi() -> Bytes = "sysinfo_kernel_long_version"
///|
extern "c" fn clock_ticks_per_second_ffi() -> Int = "sysinfo_clock_ticks_per_second"
///|
extern "c" fn logical_cpu_count_ffi() -> Int = "sysinfo_logical_cpu_count"
///|
extern "c" fn physical_cpu_count_ffi() -> Int = "sysinfo_physical_cpu_count"
///|
extern "c" fn process_exists_ffi(pid : Int) -> Int = "sysinfo_process_exists"
///|
#borrow(path)
extern "c" fn readlink_path_ffi(path : Bytes) -> Bytes = "sysinfo_readlink_path"
///|
extern "c" fn boot_time_seconds_ffi() -> Int64 = "sysinfo_boot_time_seconds"
///|
extern "c" fn uptime_seconds_ffi() -> UInt64 = "sysinfo_uptime_seconds"
///|
extern "c" fn load_average_ffi() -> Bytes = "sysinfo_load_average"
///|
extern "c" fn macos_os_version_ffi() -> Bytes = "sysinfo_macos_os_version"
///|
extern "c" fn macos_battery_temperature_ffi() -> Int = "sysinfo_macos_battery_temperature"
///|
extern "c" fn macos_memory_stats_ffi() -> Bytes = "sysinfo_macos_memory_stats"
///|
extern "c" fn macos_cpu_info_ffi() -> Bytes = "sysinfo_macos_cpu_info"
///|
extern "c" fn macos_cpu_snapshots_ffi() -> Bytes = "sysinfo_macos_cpu_snapshots"
///|
extern "c" fn macos_vm_io_counters_ffi() -> Bytes = "sysinfo_macos_vm_io_counters"
///|
extern "c" fn macos_process_table_ffi() -> Bytes = "sysinfo_macos_process_table"
///|
extern "c" fn mount_table_ffi() -> Bytes = "sysinfo_mount_table"
///|
extern "c" fn network_table_ffi() -> Bytes = "sysinfo_network_table"
///|
extern "c" fn user_table_ffi() -> Bytes = "sysinfo_user_table"
///|
extern "c" fn group_table_ffi() -> Bytes = "sysinfo_group_table"
///|
extern "c" fn macos_disk_table_ffi() -> Bytes = "sysinfo_macos_disk_table"
///|
extern "c" fn kill_pid_ffi(pid : Int, signal : Int) -> Int = "sysinfo_kill"
///|
#borrow(status)
extern "c" fn wait_pid_ffi(
pid : Int,
options : Int,
status : @ref.Ref[Int],
) -> Int = "sysinfo_wait_pid"
///|
extern "c" fn get_open_files_limit_ffi() -> Int = "sysinfo_get_open_files_soft_limit"
///|
extern "c" fn set_open_files_limit_ffi(limit : Int) -> Int = "sysinfo_set_open_files_soft_limit"
///|
extern "c" fn proc_args_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_args_macos"
///|
extern "c" fn proc_environ_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_environ_macos"
///|
extern "c" fn proc_exe_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_exe_macos"
///|
extern "c" fn proc_cwd_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_cwd_macos"
///|
extern "c" fn proc_root_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_root_macos"
///|
extern "c" fn proc_disk_usage_macos_ffi(pid : Int) -> Bytes = "sysinfo_proc_disk_usage_macos"
///|
extern "c" fn proc_open_files_macos_ffi(pid : Int) -> Int = "sysinfo_proc_open_files_macos"
///|
fn sleep_millis_native(millis : Int) -> Unit {
if millis > 0 {
sleep_millis_ffi(millis)
}
}
///|
fn decode_bytes(bytes : Bytes) -> String {
@utf8.decode_lossy(bytes)
}
///|
fn decode_bytes_trimmed(bytes : Bytes) -> String {
decode_bytes(bytes).trim().to_string()
}
///|
fn readlink_path(path : String) -> String? {
let raw = decode_bytes_trimmed(readlink_path_ffi(@utf8.encode(path)))
if raw == "" {
None
} else {
Some(raw)
}
}
///|
fn read_text_file(path : String) -> String? {
Some(@fs.read_file_to_string(path)) catch {
_ => None
}
}
///|
fn read_text_file_or_empty(path : String) -> String {
match read_text_file(path) {
Some(s) => s
None => ""
}
}
///|
fn list_dir(path : String) -> Array[String] {
@fs.read_dir(path) catch {
_ => []
}
}
///|
fn file_exists(path : String) -> Bool {
@fs.path_exists(path)
}
///|
fn split_non_empty_lines(s : String) -> Array[String] {
let out : Array[String] = []
for line in s.split("\n") {
let trimmed = line.trim().to_string()
if trimmed != "" {
out.push(trimmed)
}
}
out
}
///|
fn split_whitespace(s : String) -> Array[String] {
let out : Array[String] = []
let mut cur = StringBuilder::new()
for ch in s {
if ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' {
if !cur.is_empty() {
out.push(cur.to_string())
cur = StringBuilder::new()
}
} else {
cur.write_char(ch)
}
}
if !cur.is_empty() {
out.push(cur.to_string())
}
out
}
///|
fn parse_int_or(value : String, fallback : Int) -> Int {
@string.parse_int(value) catch {
_ => fallback
}
}
///|
fn parse_uint64_or(value : String, fallback : UInt64) -> UInt64 {
@string.parse_uint64(value) catch {
_ => fallback
}
}
///|
fn parse_double_or(value : String, fallback : Double) -> Double {
@string.parse_double(value) catch {
_ => fallback
}
}
///|
fn map_signal_to_native(signal : Signal) -> Int {
match signal {
Signal::Abort => 6
Signal::Alarm => 14
Signal::Bus => 10
Signal::Child => 17
Signal::Continue => 18
Signal::FloatingPointException => 8
Signal::Hangup => 1
Signal::IO => 29
Signal::IOT => 6
Signal::Illegal => 4
Signal::Interrupt => 2
Signal::Kill => 9
Signal::Pipe => 13
Signal::Poll => 29
Signal::Power => 30
Signal::Profiling => 27
Signal::Quit => 3
Signal::Segv => 11
Signal::Stop => 19
Signal::Sys => 31
Signal::TSTP => 20
Signal::TTIN => 21
Signal::TTOU => 22
Signal::Term => 15
Signal::Trap => 5
Signal::Urgent => 23
Signal::User1 => 10
Signal::User2 => 12
Signal::VirtualAlarm => 26
Signal::Winch => 28
Signal::XCPU => 24
Signal::XFSZ => 25
}
}
///|
fn get_os_tag() -> String {
decode_bytes(os_tag_ffi()).trim().to_string()
}
///|
fn process_exists(pid : Pid) -> Bool {
process_exists_ffi(pid.to_int()) == 1
}
///|
pub fn get_current_pid() -> Result[Pid, String] {
let pid = get_pid_ffi()
if pid > 0 {
Ok(Pid::from(pid))
} else {
Err("failed to get current pid")
}
}
///|
fn read_macos_process_args(pid : Pid) -> Array[String] {
split_non_empty_lines(decode_bytes(proc_args_macos_ffi(pid.to_int())))
}
///|
fn read_macos_process_environ(pid : Pid) -> Array[String] {
split_non_empty_lines(decode_bytes(proc_environ_macos_ffi(pid.to_int())))
}
///|
fn read_macos_process_path(path_kind : String, pid : Pid) -> String? {
let from_proc = if path_kind == "exe" {
decode_bytes(proc_exe_macos_ffi(pid.to_int()))
} else if path_kind == "cwd" {
decode_bytes(proc_cwd_macos_ffi(pid.to_int()))
} else {
decode_bytes(proc_root_macos_ffi(pid.to_int()))
}
let trimmed = from_proc.trim().to_string()
if trimmed == "" {
None
} else {
Some(trimmed)
}
}
///|
fn read_macos_process_disk_usage(pid : Pid, previous : DiskUsage) -> DiskUsage {
let fields = split_whitespace(
decode_bytes(proc_disk_usage_macos_ffi(pid.to_int())),
)
if fields.length() < 2 {
return DiskUsage::zero()
}
let total_read = parse_uint64_or(fields[0], 0UL)
let total_written = parse_uint64_or(fields[1], 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 read_macos_process_open_files(pid : Pid) -> Int? {
let value = proc_open_files_macos_ffi(pid.to_int())
if value >= 0 {
Some(value)
} else {
None
}
}