Implementation of load average calculation and corresponding /proc entry

Moved everything into the kernel side. Add a SchedulerStats trait

Use 'fixed' to represent fixed-point numbers

Make the loadavg calculation lazy
This commit is contained in:
Champii1
2024-10-18 18:46:58 +02:00
committed by Tate, Hongliang Tian
parent efd49a96e3
commit 4701eaf0cb
12 changed files with 256 additions and 3 deletions

41
Cargo.lock generated
View File

@ -173,6 +173,7 @@ dependencies = [
"controlled", "controlled",
"core2", "core2",
"cpio-decoder", "cpio-decoder",
"fixed",
"getset", "getset",
"hashbrown", "hashbrown",
"id-alloc", "id-alloc",
@ -291,6 +292,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "az"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]] [[package]]
name = "bit_field" name = "bit_field"
version = "0.10.2" version = "0.10.2"
@ -444,6 +451,12 @@ version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242" checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.1.25" version = "0.1.25"
@ -606,6 +619,18 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" 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]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -670,6 +695,16 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 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]] [[package]]
name = "hash32" name = "hash32"
version = "0.3.1" version = "0.3.1"
@ -1576,6 +1611,12 @@ dependencies = [
name = "typeflags-util" name = "typeflags-util"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]] [[package]]
name = "uart_16550" name = "uart_16550"
version = "0.3.0" version = "0.3.0"

View File

@ -58,6 +58,9 @@ inherit-methods-macro = { git = "https://github.com/asterinas/inherit-methods-ma
getset = "0.1.2" getset = "0.1.2"
takeable = "0.2.2" takeable = "0.2.2"
cfg-if = "1.0" 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] [target.riscv64gc-unknown-none-elf.dependencies]
riscv = { version = "0.11.1", features = ["s-mode"] } riscv = { version = "0.11.1", features = ["s-mode"] }

View File

@ -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: <https://www.man7.org/linux/man-pages/man5/proc_loadavg.5.html>
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<dyn Inode>) -> Arc<dyn Inode> {
ProcFileBuilder::new(Self).parent(parent).build().unwrap()
}
}
impl FileOps for LoadAvgFileOps {
fn data(&self) -> Result<Vec<u8>> {
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())
}
}

View File

@ -2,6 +2,7 @@
use core::sync::atomic::{AtomicU64, Ordering}; use core::sync::atomic::{AtomicU64, Ordering};
use loadavg::LoadAvgFileOps;
use sys::SysDirOps; use sys::SysDirOps;
use self::{ use self::{
@ -21,6 +22,7 @@ use crate::{
}; };
mod filesystems; mod filesystems;
mod loadavg;
mod meminfo; mod meminfo;
mod pid; mod pid;
mod self_; mod self_;
@ -106,6 +108,8 @@ impl DirOps for RootDirOps {
FileSystemsFileOps::new_inode(this_ptr.clone()) FileSystemsFileOps::new_inode(this_ptr.clone())
} else if name == "meminfo" { } else if name == "meminfo" {
MemInfoFileOps::new_inode(this_ptr.clone()) MemInfoFileOps::new_inode(this_ptr.clone())
} else if name == "loadavg" {
LoadAvgFileOps::new_inode(this_ptr.clone())
} else if let Ok(pid) = name.parse::<Pid>() { } else if let Ok(pid) = name.parse::<Pid>() {
let process_ref = let process_ref =
process_table::get_process(pid).ok_or_else(|| Error::new(Errno::ENOENT))?; process_table::get_process(pid).ok_or_else(|| Error::new(Errno::ENOENT))?;
@ -129,6 +133,8 @@ impl DirOps for RootDirOps {
}); });
cached_children cached_children
.put_entry_if_not_found("meminfo", || MemInfoFileOps::new_inode(this_ptr.clone())); .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() { for process in process_table::process_table_mut().iter() {
let pid = process.pid().to_string(); let pid = process.pid().to_string();

View File

@ -374,7 +374,7 @@ impl PageCacheManager {
for (_, page) in pages for (_, page) in pages
.iter_mut() .iter_mut()
.filter(|(idx, _)| page_idx_range.contains(idx)) .filter(|(idx, _)| page_idx_range.contains(*idx))
{ {
page.set_state(PageState::UpToDate); page.set_state(PageState::UpToDate);
} }

View File

@ -305,3 +305,8 @@ static POSIX_TID_ALLOCATOR: AtomicU32 = AtomicU32::new(1);
pub fn allocate_posix_tid() -> Tid { pub fn allocate_posix_tid() -> Tid {
POSIX_TID_ALLOCATOR.fetch_add(1, Ordering::SeqCst) 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
}

View File

@ -2,6 +2,10 @@
pub mod priority; pub mod priority;
mod priority_scheduler; 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, // There may be multiple scheduling policies in the system,
// and subsequent schedulers can be placed under this module. // and subsequent schedulers can be placed under this module.

View File

@ -5,6 +5,7 @@ use core::sync::atomic::Ordering;
use ostd::{ use ostd::{
cpu::{num_cpus, CpuId, CpuSet, PinCurrentCpu}, cpu::{num_cpus, CpuId, CpuSet, PinCurrentCpu},
task::{ task::{
disable_preempt,
scheduler::{ scheduler::{
info::CommonSchedInfo, inject_scheduler, EnqueueFlags, LocalRunQueue, Scheduler, info::CommonSchedInfo, inject_scheduler, EnqueueFlags, LocalRunQueue, Scheduler,
UpdateFlags, UpdateFlags,
@ -14,13 +15,23 @@ use ostd::{
trap::disable_local, trap::disable_local,
}; };
use super::priority::{Priority, PriorityRange}; use super::{
priority::{Priority, PriorityRange},
stats::{set_stats_from_scheduler, SchedulerStats},
};
use crate::{prelude::*, thread::Thread}; use crate::{prelude::*, thread::Thread};
pub fn init() { pub fn init() {
let preempt_scheduler = Box::new(PreemptScheduler::default()); let preempt_scheduler = Box::new(PreemptScheduler::default());
let scheduler = Box::<PreemptScheduler<Thread, Task>>::leak(preempt_scheduler); let scheduler = Box::<PreemptScheduler<Thread, Task>>::leak(preempt_scheduler);
// Inject the scheduler into the ostd for actual scheduling work.
inject_scheduler(scheduler); 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. /// The preempt scheduler.
@ -118,6 +129,29 @@ impl<T: Sync + Send + PreemptSchedInfo + FromTask<U>, U: Sync + Send + CommonSch
} }
} }
impl<T: Sync + Send + PreemptSchedInfo + FromTask<U>, U: Sync + Send + CommonSchedInfo>
SchedulerStats for PreemptScheduler<T, U>
{
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<Thread, Task> { impl Default for PreemptScheduler<Thread, Task> {
fn default() -> Self { fn default() -> Self {
Self::new(num_cpus()) Self::new(num_cpus())

View File

@ -0,0 +1,71 @@
// SPDX-License-Identifier: MPL-2.0
//! This module implements the CPU load average calculation.
//!
//! Reference: <https://github.com/torvalds/linux/blob/46132e3/kernel/sched/loadavg.c>
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<F>(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)
}

View File

@ -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};

View File

@ -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()
}

View File

@ -46,7 +46,7 @@ fn handle_getfd(fd: FileDesc, ctx: &Context) -> Result<SyscallReturn> {
} }
fn handle_setfd(fd: FileDesc, arg: u64, ctx: &Context) -> Result<SyscallReturn> { fn handle_setfd(fd: FileDesc, arg: u64, ctx: &Context) -> Result<SyscallReturn> {
let flags = if arg > u8::MAX.into() { let flags = if arg > u64::from(u8::MAX) {
return_errno_with_message!(Errno::EINVAL, "invalid fd flags"); return_errno_with_message!(Errno::EINVAL, "invalid fd flags");
} else { } else {
FdFlags::from_bits(arg as u8).ok_or(Error::with_message(Errno::EINVAL, "invalid flags"))? FdFlags::from_bits(arg as u8).ok_or(Error::with_message(Errno::EINVAL, "invalid flags"))?