Add support for `/proc/stat` and `/proc/uptime`

This commit is contained in:
Yang Zhichao 2025-09-19 21:55:13 +08:00 committed by Tate, Hongliang Tian
parent a13d2703fb
commit 66356e133d
5 changed files with 216 additions and 5 deletions

2
Cargo.lock generated
View File

@ -288,6 +288,7 @@ dependencies = [
name = "aster-softirq"
version = "0.1.0"
dependencies = [
"aster-util",
"component",
"intrusive-collections",
"ostd",
@ -325,6 +326,7 @@ dependencies = [
"aster-rights",
"aster-rights-proc",
"inherit-methods-macro",
"osdk-heap-allocator",
"ostd",
"typeflags-util",
]

12
kernel/src/fs/procfs/mod.rs Normal file → Executable file
View File

@ -12,11 +12,12 @@ use self::{
sys::SysDirOps,
template::{DirOps, ProcDir, ProcDirBuilder, ProcSymBuilder, SymOps},
thread_self::ThreadSelfSymOps,
uptime::UptimeFileOps,
};
use crate::{
events::Observer,
fs::{
procfs::filesystems::FileSystemsFileOps,
procfs::{filesystems::FileSystemsFileOps, stat::StatFileOps},
registry::{FsProperties, FsType},
utils::{mkmod, DirEntryVecExt, FileSystem, FsFlags, Inode, SuperBlock, NAME_MAX},
},
@ -34,9 +35,11 @@ mod loadavg;
mod meminfo;
mod pid;
mod self_;
mod stat;
mod sys;
mod template;
mod thread_self;
mod uptime;
pub(super) fn init() {
super::registry::register(&ProcFsType).unwrap();
@ -156,6 +159,10 @@ impl DirOps for RootDirOps {
LoadAvgFileOps::new_inode(this_ptr.clone())
} else if name == "cpuinfo" {
CpuInfoFileOps::new_inode(this_ptr.clone())
} else if name == "uptime" {
UptimeFileOps::new_inode(this_ptr.clone())
} else if name == "stat" {
StatFileOps::new_inode(this_ptr.clone())
} else if name == "cmdline" {
CmdLineFileOps::new_inode(this_ptr.clone())
} else if let Ok(pid) = name.parse::<Pid>() {
@ -188,6 +195,9 @@ impl DirOps for RootDirOps {
.put_entry_if_not_found("loadavg", || LoadAvgFileOps::new_inode(this_ptr.clone()));
cached_children
.put_entry_if_not_found("cpuinfo", || CpuInfoFileOps::new_inode(this_ptr.clone()));
cached_children
.put_entry_if_not_found("uptime", || UptimeFileOps::new_inode(this_ptr.clone()));
cached_children.put_entry_if_not_found("stat", || StatFileOps::new_inode(this_ptr.clone()));
cached_children
.put_entry_if_not_found("cmdline", || CmdLineFileOps::new_inode(this_ptr.clone()));
for process in process_table::process_table_mut().iter() {

View File

@ -0,0 +1,155 @@
// SPDX-License-Identifier: MPL-2.0
//! This module offers `/proc/stat` file support, which provides
//! information about kernel system statistics.
//!
//! Reference: <https://man7.org/linux/man-pages/man5/proc_stat.5.html>
use core::fmt::Write;
use aster_softirq::{
iter_irq_counts_across_all_cpus, iter_softirq_counts_across_all_cpus, softirq_id::*,
};
use crate::{
fs::{
procfs::template::{FileOps, ProcFileBuilder},
utils::{Inode, InodeMode},
},
prelude::*,
process::count_total_forks,
sched::nr_queued_and_running,
thread::collect_context_switch_count,
time::{cpu_time_stats::CpuTimeStatsManager, SystemTime, START_TIME},
};
/// Represents the inode at `/proc/stat`.
pub struct StatFileOps;
impl StatFileOps {
pub fn new_inode(parent: Weak<dyn Inode>) -> Arc<dyn Inode> {
ProcFileBuilder::new(Self, InodeMode::from_bits_truncate(0o444))
.parent(parent)
.build()
.unwrap()
}
fn collect_stats() -> String {
let mut stat_output = String::new();
let stats_manager = CpuTimeStatsManager::singleton();
// Global CPU statistics:
// cpu <user> <nice> <system> <idle> <iowait> <irq> <softirq> <steal> <guest> <guest_nice>
let global_stats = stats_manager.collect_stats_on_all_cpus();
writeln!(
stat_output,
"cpu {} {} {} {} {} {} {} {} {} {}",
global_stats.user.as_u64(),
global_stats.nice.as_u64(),
global_stats.system.as_u64(),
global_stats.idle.as_u64(),
global_stats.iowait.as_u64(),
global_stats.irq.as_u64(),
global_stats.softirq.as_u64(),
global_stats.steal.as_u64(),
global_stats.guest.as_u64(),
global_stats.guest_nice.as_u64()
)
.unwrap();
// Per-CPU lines
for cpu_id in ostd::cpu::all_cpus() {
let cpu_stats = stats_manager.collect_stats_on_cpu(cpu_id);
writeln!(
stat_output,
"cpu{} {} {} {} {} {} {} {} {} {} {}",
cpu_id.as_usize(),
cpu_stats.user.as_u64(),
cpu_stats.nice.as_u64(),
cpu_stats.system.as_u64(),
cpu_stats.idle.as_u64(),
cpu_stats.iowait.as_u64(),
cpu_stats.irq.as_u64(),
cpu_stats.softirq.as_u64(),
cpu_stats.steal.as_u64(),
cpu_stats.guest.as_u64(),
cpu_stats.guest_nice.as_u64()
)
.unwrap();
}
// IRQ statistics: the total count followed by per-IRQ counts
let irq_stats = iter_irq_counts_across_all_cpus();
let mut total_irqs = 0usize;
let mut irq_counts = Vec::new();
for count in irq_stats {
total_irqs += count;
irq_counts.push(count);
}
write!(stat_output, "intr {}", total_irqs).unwrap();
for count in irq_counts {
write!(stat_output, " {}", count).unwrap();
}
writeln!(stat_output).unwrap();
// Context switch count
let context_switches: usize = collect_context_switch_count();
writeln!(stat_output, "ctxt {}", context_switches).unwrap();
// Boot time (seconds since UNIX epoch)
if let Some(start_time) = START_TIME.get() {
let boot_time = start_time
.duration_since(&SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
writeln!(stat_output, "btime {}", boot_time).unwrap();
} else {
writeln!(stat_output, "btime {}", 0).unwrap();
}
// Process count (number of forks since boot)
writeln!(stat_output, "processes {}", count_total_forks()).unwrap();
// Running and blocked processes
let (_, running_count) = nr_queued_and_running();
writeln!(stat_output, "procs_running {}", running_count).unwrap();
// TODO: Blocked processes
writeln!(stat_output, "procs_blocked {}", 0).unwrap();
// Softirq statistics
let softirq_stats = iter_softirq_counts_across_all_cpus();
let softirq_stats: Vec<usize> = softirq_stats.collect();
let total_softirqs: usize = softirq_stats.iter().sum();
// We only have 5 defined softirq types; the rest are reserved.
// Fill in zeros for the reserved types to match the expected output format.
writeln!(
stat_output,
"softirq {} {} {} {} {} {} {} {} {} {} {}",
total_softirqs,
softirq_stats[TASKLESS_URGENT_SOFTIRQ_ID as usize], // TASKLESS_URGENT
softirq_stats[TIMER_SOFTIRQ_ID as usize], // TIMER
softirq_stats[TASKLESS_SOFTIRQ_ID as usize], // TASKLESS
softirq_stats[NETWORK_TX_SOFTIRQ_ID as usize], // NETWORK_TX
softirq_stats[NETWORK_RX_SOFTIRQ_ID as usize], // NETWORK_RX
0usize, // Reserved
0usize, // Reserved
0usize, // Reserved
0usize, // Reserved
0usize, // Reserved
)
.unwrap();
stat_output
}
}
impl FileOps for StatFileOps {
/// Retrieve the data for `/proc/stat`.
fn data(&self) -> Result<Vec<u8>> {
// Implementation to gather and format the statistics
let output = Self::collect_stats();
Ok(output.into_bytes())
}
}

View File

@ -0,0 +1,48 @@
// SPDX-License-Identifier: MPL-2.0
//! This module offers `/proc/uptime` file support, which provides
//! information about the system uptime and idle time.
//!
//! Reference: <https://man7.org/linux/man-pages/man5/proc_uptime.5.html>
use alloc::format;
use crate::{
fs::{
procfs::template::{FileOps, ProcFileBuilder},
utils::{Inode, InodeMode},
},
prelude::*,
time::cpu_time_stats::CpuTimeStatsManager,
};
/// Represents the inode at `/proc/uptime`.
pub struct UptimeFileOps;
impl UptimeFileOps {
pub fn new_inode(parent: Weak<dyn Inode>) -> Arc<dyn Inode> {
ProcFileBuilder::new(Self, InodeMode::from_bits_truncate(0o444))
.parent(parent)
.build()
.unwrap()
}
pub fn collect_uptime() -> String {
let uptime = aster_time::read_monotonic_time().as_secs_f32();
let cpustat = CpuTimeStatsManager::singleton();
let idle_time = cpustat
.collect_stats_on_all_cpus()
.idle
.as_duration()
.as_secs_f32();
format!("{:.2} {:.2}\n", uptime, idle_time)
}
}
impl FileOps for UptimeFileOps {
/// Retrieve the data for `/proc/uptime`.
fn data(&self) -> Result<Vec<u8>> {
let output = Self::collect_uptime();
Ok(output.into_bytes())
}
}

View File

@ -16,11 +16,7 @@ ProcSelfFdInfo.Flags
ProcCpuinfo.RequiredFieldsArePresent
ProcCpuinfo.DeniesWriteRoot
ProcCpuinfo.DeniesWriteNonRoot
ProcUptime.IsPresent
ProcMeminfo.ContainsBasicFields
ProcStat.ContainsBasicFields
ProcStat.EndsWithNewline
ProcStat.Fields
ProcSelfStat.PopulateWriteRSS
ProcSelfStat.PopulateNoneRSS
ProcPidStatusTest.HasBasicFields