diff --git a/Cargo.lock b/Cargo.lock index dd987e29..a695848a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,6 +173,7 @@ dependencies = [ "controlled", "core2", "cpio-decoder", + "fixed", "getset", "hashbrown", "id-alloc", @@ -291,6 +292,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "bit_field" version = "0.10.2" @@ -444,6 +451,12 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "ctor" version = "0.1.25" @@ -606,6 +619,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" +[[package]] +name = "fixed" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c6e0b89bf864acd20590dbdbad56f69aeb898abfc9443008fd7bd48b2cc85a" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + [[package]] name = "fnv" version = "1.0.7" @@ -670,6 +695,16 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hash32" version = "0.3.1" @@ -1576,6 +1611,12 @@ dependencies = [ name = "typeflags-util" version = "0.1.0" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "uart_16550" version = "0.3.0" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index e0d07269..da1b37c2 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -58,6 +58,9 @@ inherit-methods-macro = { git = "https://github.com/asterinas/inherit-methods-ma getset = "0.1.2" takeable = "0.2.2" cfg-if = "1.0" +# Fixed point numbers +# TODO: fork this crate to rewrite all the (unnecessary) unsafe usage +fixed = "1.28.0" [target.riscv64gc-unknown-none-elf.dependencies] riscv = { version = "0.11.1", features = ["s-mode"] } diff --git a/kernel/src/fs/procfs/loadavg.rs b/kernel/src/fs/procfs/loadavg.rs new file mode 100644 index 00000000..517ef622 --- /dev/null +++ b/kernel/src/fs/procfs/loadavg.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! This module offers `/proc/loadavg` file support, which tells the user space +//! about the cpu load average for the last 1, 5, and 15 minutes. +//! +//! Reference: + +use alloc::format; + +use crate::{ + fs::{ + procfs::template::{FileOps, ProcFileBuilder}, + utils::Inode, + }, + prelude::*, + process::posix_thread, + sched::{self, loadavg::get_loadavg}, +}; + +/// Represents the inode at `/proc/loadavg`. +pub struct LoadAvgFileOps; + +impl LoadAvgFileOps { + pub fn new_inode(parent: Weak) -> Arc { + ProcFileBuilder::new(Self).parent(parent).build().unwrap() + } +} + +impl FileOps for LoadAvgFileOps { + fn data(&self) -> Result> { + let avg = get_loadavg(); + let (nr_queued, nr_running) = sched::nr_queued_and_running(); + + let output = format!( + "{:.2} {:.2} {:.2} {}/{} {}\n", + avg[0], + avg[1], + avg[2], + nr_running, + nr_queued, + posix_thread::last_tid(), + ); + + Ok(output.into_bytes()) + } +} diff --git a/kernel/src/fs/procfs/mod.rs b/kernel/src/fs/procfs/mod.rs index 62fc5f55..2abb1cf4 100644 --- a/kernel/src/fs/procfs/mod.rs +++ b/kernel/src/fs/procfs/mod.rs @@ -2,6 +2,7 @@ use core::sync::atomic::{AtomicU64, Ordering}; +use loadavg::LoadAvgFileOps; use sys::SysDirOps; use self::{ @@ -21,6 +22,7 @@ use crate::{ }; mod filesystems; +mod loadavg; mod meminfo; mod pid; mod self_; @@ -106,6 +108,8 @@ impl DirOps for RootDirOps { FileSystemsFileOps::new_inode(this_ptr.clone()) } else if name == "meminfo" { MemInfoFileOps::new_inode(this_ptr.clone()) + } else if name == "loadavg" { + LoadAvgFileOps::new_inode(this_ptr.clone()) } else if let Ok(pid) = name.parse::() { let process_ref = process_table::get_process(pid).ok_or_else(|| Error::new(Errno::ENOENT))?; @@ -129,6 +133,8 @@ impl DirOps for RootDirOps { }); cached_children .put_entry_if_not_found("meminfo", || MemInfoFileOps::new_inode(this_ptr.clone())); + cached_children + .put_entry_if_not_found("loadavg", || LoadAvgFileOps::new_inode(this_ptr.clone())); for process in process_table::process_table_mut().iter() { let pid = process.pid().to_string(); diff --git a/kernel/src/fs/utils/page_cache.rs b/kernel/src/fs/utils/page_cache.rs index ebc5d8a7..8c721c7d 100644 --- a/kernel/src/fs/utils/page_cache.rs +++ b/kernel/src/fs/utils/page_cache.rs @@ -374,7 +374,7 @@ impl PageCacheManager { for (_, page) in pages .iter_mut() - .filter(|(idx, _)| page_idx_range.contains(idx)) + .filter(|(idx, _)| page_idx_range.contains(*idx)) { page.set_state(PageState::UpToDate); } diff --git a/kernel/src/process/posix_thread/mod.rs b/kernel/src/process/posix_thread/mod.rs index ac10c5a3..cc5096e5 100644 --- a/kernel/src/process/posix_thread/mod.rs +++ b/kernel/src/process/posix_thread/mod.rs @@ -305,3 +305,8 @@ static POSIX_TID_ALLOCATOR: AtomicU32 = AtomicU32::new(1); pub fn allocate_posix_tid() -> Tid { POSIX_TID_ALLOCATOR.fetch_add(1, Ordering::SeqCst) } + +/// Returns the last allocated tid +pub fn last_tid() -> Tid { + POSIX_TID_ALLOCATOR.load(Ordering::SeqCst) - 1 +} diff --git a/kernel/src/sched/mod.rs b/kernel/src/sched/mod.rs index e232d2f8..8621493f 100644 --- a/kernel/src/sched/mod.rs +++ b/kernel/src/sched/mod.rs @@ -2,6 +2,10 @@ pub mod priority; mod priority_scheduler; +mod stats; + +// Export the stats getter functions. +pub use stats::{loadavg, nr_queued_and_running}; // There may be multiple scheduling policies in the system, // and subsequent schedulers can be placed under this module. diff --git a/kernel/src/sched/priority_scheduler.rs b/kernel/src/sched/priority_scheduler.rs index 982650bd..cb73664c 100644 --- a/kernel/src/sched/priority_scheduler.rs +++ b/kernel/src/sched/priority_scheduler.rs @@ -5,6 +5,7 @@ use core::sync::atomic::Ordering; use ostd::{ cpu::{num_cpus, CpuId, CpuSet, PinCurrentCpu}, task::{ + disable_preempt, scheduler::{ info::CommonSchedInfo, inject_scheduler, EnqueueFlags, LocalRunQueue, Scheduler, UpdateFlags, @@ -14,13 +15,23 @@ use ostd::{ trap::disable_local, }; -use super::priority::{Priority, PriorityRange}; +use super::{ + priority::{Priority, PriorityRange}, + stats::{set_stats_from_scheduler, SchedulerStats}, +}; use crate::{prelude::*, thread::Thread}; pub fn init() { let preempt_scheduler = Box::new(PreemptScheduler::default()); let scheduler = Box::>::leak(preempt_scheduler); + + // Inject the scheduler into the ostd for actual scheduling work. inject_scheduler(scheduler); + + // Set the scheduler into the system for statistics. + // We set this after injecting the scheduler into ostd, + // so that the loadavg statistics are updated after the scheduler is used. + set_stats_from_scheduler(scheduler); } /// The preempt scheduler. @@ -118,6 +129,29 @@ impl, U: Sync + Send + CommonSch } } +impl, U: Sync + Send + CommonSchedInfo> + SchedulerStats for PreemptScheduler +{ + fn nr_queued_and_running(&self) -> (u32, u32) { + let _preempt_guard = disable_preempt(); + let mut nr_queued = 0; + let mut nr_running = 0; + + for rq in self.rq.iter() { + let rq = rq.lock(); + + nr_queued += + rq.real_time_entities.len() + rq.normal_entities.len() + rq.lowest_entities.len(); + + if rq.current.is_some() { + nr_running += 1; + } + } + + (nr_queued as u32, nr_running) + } +} + impl Default for PreemptScheduler { fn default() -> Self { Self::new(num_cpus()) diff --git a/kernel/src/sched/stats/loadavg.rs b/kernel/src/sched/stats/loadavg.rs new file mode 100644 index 00000000..41f380ac --- /dev/null +++ b/kernel/src/sched/stats/loadavg.rs @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! This module implements the CPU load average calculation. +//! +//! Reference: + +use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; + +use ostd::{arch::timer::TIMER_FREQ, sync::RwLock, timer}; + +/// Fixed-point representation of the load average. +/// +/// This is an equivalent of an u32 with 21 bits for the integer part and 11 bits for the fractional part. +pub type FixedPoint = fixed::types::U21F11; + +/// 5 sec intervals +const LOAD_FREQ: u64 = 5 * TIMER_FREQ + 1; +/// 1/exp(5sec/1min) as fixed-point +const EXP_1: FixedPoint = FixedPoint::from_bits(1884); +/// 1/exp(5sec/5min) +const EXP_5: FixedPoint = FixedPoint::from_bits(2014); +/// 1/exp(5sec/15min) +const EXP_15: FixedPoint = FixedPoint::from_bits(2037); + +/// Load average of all CPU cores. +/// +/// The load average is calculated as an exponential moving average of the load +/// over the last 1, 5, and 15 minutes. +static LOAD_AVG: RwLock<[FixedPoint; 3]> = RwLock::new([FixedPoint::ZERO; 3]); + +/// Next time the load average will be updated (in jiffies). +static LOAD_AVG_NEXT_UPDATE: AtomicU64 = AtomicU64::new(0); + +/// Returns the calculated load average of the system. +pub fn get_loadavg() -> [FixedPoint; 3] { + *LOAD_AVG.read() +} + +/// Updates the load average of the system. +/// +/// This function should be called periodically to update the load average. +/// The `get_load` function should return the load (the number of queued tasks) of the system. +/// See `sched::stats::scheduler_stats::set_stats_from_scheduler()` for an example. +pub fn update_loadavg(get_load: F) +where + F: Fn() -> u32, +{ + let jiffies = timer::Jiffies::elapsed().as_u64(); + + // Return if the load average was updated less than 5 seconds ago. + if jiffies < LOAD_AVG_NEXT_UPDATE.load(Relaxed) { + return; + } + + // Update the next time the load average will be updated (now + 5sec) + LOAD_AVG_NEXT_UPDATE.store(jiffies + LOAD_FREQ, Relaxed); + + // Get the fixed-point representation of the load + let new_load = FixedPoint::from_num(get_load()); + + let mut load = LOAD_AVG.write(); + + // Calculate the new load average + load[0] = calc_loadavg(load[0], EXP_1, new_load); + load[1] = calc_loadavg(load[1], EXP_5, new_load); + load[2] = calc_loadavg(load[2], EXP_15, new_load); +} + +fn calc_loadavg(old_load: FixedPoint, exp: FixedPoint, new_load: FixedPoint) -> FixedPoint { + old_load * exp + new_load * (FixedPoint::ONE - exp) +} diff --git a/kernel/src/sched/stats/mod.rs b/kernel/src/sched/stats/mod.rs new file mode 100644 index 00000000..83be8d11 --- /dev/null +++ b/kernel/src/sched/stats/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MPL-2.0 + +pub mod loadavg; +mod scheduler_stats; + +pub use scheduler_stats::{nr_queued_and_running, set_stats_from_scheduler, SchedulerStats}; diff --git a/kernel/src/sched/stats/scheduler_stats.rs b/kernel/src/sched/stats/scheduler_stats.rs new file mode 100644 index 00000000..42899ee2 --- /dev/null +++ b/kernel/src/sched/stats/scheduler_stats.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MPL-2.0 + +use ostd::timer; +use spin::Once; + +use super::loadavg; + +/// The global scheduler statistic singleton +static SCHEDULER_STATS: Once<&'static dyn SchedulerStats> = Once::new(); + +/// Set the global scheduler statistics singleton. +/// +/// This function should be called once to set the scheduler statistics system. +/// It is used to get running stats from the scheduler and to periodically +/// calculate the system load average. +pub fn set_stats_from_scheduler(scheduler: &'static dyn SchedulerStats) { + SCHEDULER_STATS.call_once(|| scheduler); + + // Register a callback to update the load average periodically + timer::register_callback(|| { + loadavg::update_loadavg(|| nr_queued_and_running().0); + }); +} + +/// The trait for the scheduler statistics. +pub trait SchedulerStats: Sync + Send { + /// Returns a tuple with the number of tasks in the runqueues and the number of running tasks. + /// + /// We decided to return a tuple instead of having two separate functions to + /// avoid the overhead of disabling the preemption twice to inspect the scheduler. + fn nr_queued_and_running(&self) -> (u32, u32); +} + +/// Get the amount of tasks in the runqueues and the amount of running tasks. +pub fn nr_queued_and_running() -> (u32, u32) { + SCHEDULER_STATS.get().unwrap().nr_queued_and_running() +} diff --git a/kernel/src/syscall/fcntl.rs b/kernel/src/syscall/fcntl.rs index f49367d3..4a7b4962 100644 --- a/kernel/src/syscall/fcntl.rs +++ b/kernel/src/syscall/fcntl.rs @@ -46,7 +46,7 @@ fn handle_getfd(fd: FileDesc, ctx: &Context) -> Result { } fn handle_setfd(fd: FileDesc, arg: u64, ctx: &Context) -> Result { - let flags = if arg > u8::MAX.into() { + let flags = if arg > u64::from(u8::MAX) { return_errno_with_message!(Errno::EINVAL, "invalid fd flags"); } else { FdFlags::from_bits(arg as u8).ok_or(Error::with_message(Errno::EINVAL, "invalid flags"))?