diff --git a/.gitignore b/.gitignore index c8f7d751..77746140 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target /config.toml +.gitlab-ci-local/ diff --git a/Cargo.toml b/Cargo.toml index ce375be3..f0426bbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ qemu_debug = [] serial_debug = [] system76_ec_debug = [] slab = ["slab_allocator"] +sys_stat = [] x86_kvm_pv = [] debugger = ["syscall_debug"] diff --git a/src/arch/aarch64/interrupt/irq.rs b/src/arch/aarch64/interrupt/irq.rs index f7a31a6e..6a52e755 100644 --- a/src/arch/aarch64/interrupt/irq.rs +++ b/src/arch/aarch64/interrupt/irq.rs @@ -1,6 +1,9 @@ use crate::{arch::device::ROOT_IC_IDX, dtb::irqchip::IRQ_CHIP}; use core::sync::atomic::Ordering; +#[cfg(feature = "sys_stat")] +use crate::percpu::PercpuBlock; + unsafe fn irq_ack() -> (u32, Option) { let ic = &mut IRQ_CHIP.irq_chip_list.chips[ROOT_IC_IDX.load(Ordering::Relaxed)].ic; let irq = ic.irq_ack(); @@ -31,6 +34,9 @@ exception_stack!(irq_at_el1, |_stack| { //TODO pub unsafe fn trigger(irq: u32) { + #[cfg(feature = "sys_stat")] + PercpuBlock::current().stats.add_irq(irq); + extern "C" { fn irq_trigger(irq: u32); } diff --git a/src/arch/x86/interrupt/irq.rs b/src/arch/x86/interrupt/irq.rs index 99907dd0..0345299e 100644 --- a/src/arch/x86/interrupt/irq.rs +++ b/src/arch/x86/interrupt/irq.rs @@ -18,6 +18,9 @@ use crate::{ time, }; +#[cfg(feature = "sys_stat")] +use crate::percpu::PercpuBlock; + #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum IrqMethod { @@ -100,6 +103,9 @@ pub unsafe fn acknowledge(irq: usize) { /// Sends an end-of-interrupt, so that the interrupt controller can go on to the next one. pub unsafe fn eoi(irq: u8) { + #[cfg(feature = "sys_stat")] + PercpuBlock::current().stats.add_irq(irq); + match irq_method() { IrqMethod::Pic => { if irq < 16 { diff --git a/src/arch/x86_64/interrupt/irq.rs b/src/arch/x86_64/interrupt/irq.rs index dc4036e0..0b10a327 100644 --- a/src/arch/x86_64/interrupt/irq.rs +++ b/src/arch/x86_64/interrupt/irq.rs @@ -2,9 +2,10 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use alloc::vec::Vec; +#[cfg(feature = "sys_stat")] +use crate::percpu::PercpuBlock; use crate::{ - context, - context::timeout, + context::{self, timeout}, device::{ ioapic, local_apic, pic, pit, serial::{COM1, COM2}, @@ -100,6 +101,9 @@ pub unsafe fn acknowledge(irq: usize) { /// Sends an end-of-interrupt, so that the interrupt controller can go on to the next one. pub unsafe fn eoi(irq: u8) { + #[cfg(feature = "sys_stat")] + PercpuBlock::current().stats.add_irq(irq); + match irq_method() { IrqMethod::Pic => { if irq < 16 { diff --git a/src/context/context.rs b/src/context/context.rs index 60b6a6c7..57de1ca4 100644 --- a/src/context/context.rs +++ b/src/context/context.rs @@ -7,6 +7,8 @@ use core::{ use spin::RwLock; use syscall::{RtSigInfo, SigProcControl, Sigcontrol}; +#[cfg(feature = "sys_stat")] +use crate::cpu_stats; use crate::{ arch::{interrupt::InterruptStack, paging::PAGE_SIZE}, common::aligned_box::AlignedBox, @@ -232,6 +234,8 @@ impl Context { #[cfg(feature = "syscall_debug")] syscall_debug_info: crate::syscall::debug::SyscallDebugInfo::default(), }; + #[cfg(feature = "sys_stat")] + cpu_stats::add_context(); Ok(this) } diff --git a/src/context/switch.rs b/src/context/switch.rs index b49d4a08..60f99e07 100644 --- a/src/context/switch.rs +++ b/src/context/switch.rs @@ -20,6 +20,9 @@ use crate::{ ptrace, time, }; +#[cfg(feature = "sys_stat")] +use crate::cpu_stats; + use super::ContextRef; enum UpdateResult { @@ -138,6 +141,13 @@ pub enum SwitchResult { /// to an idle context. pub fn switch() -> SwitchResult { let percpu = PercpuBlock::current(); + #[cfg(feature = "sys_stat")] + { + cpu_stats::add_context_switch(); + percpu + .stats + .add_time(percpu.switch_internals.pit_ticks.get()); + } //set PIT Interrupt counter to 0, giving each process same amount of PIT ticks percpu.switch_internals.pit_ticks.set(0); @@ -281,12 +291,25 @@ pub fn switch() -> SwitchResult { // need to use the `switch_finish_hook` to be able to release the locks. Newly created // contexts will return directly to the function pointer passed to context::spawn, and not // reach this code until the next context switch back. + #[cfg(feature = "sys_stat")] + { + if next_context.userspace { + percpu.stats.set_state(cpu_stats::CpuState::User); + } else { + percpu.stats.set_state(cpu_stats::CpuState::Kernel); + } + } SwitchResult::Switched } else { // No target was found, unset global lock and return arch::CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst); + #[cfg(feature = "sys_stat")] + { + percpu.stats.set_state(cpu_stats::CpuState::Idle); + } + SwitchResult::AllContextsIdle } } diff --git a/src/cpu_stats.rs b/src/cpu_stats.rs new file mode 100644 index 00000000..60806e9e --- /dev/null +++ b/src/cpu_stats.rs @@ -0,0 +1,146 @@ +use core::sync::atomic::{AtomicU64, AtomicU8, AtomicUsize, Ordering}; + +use alloc::{string::String, vec::Vec}; + +use crate::cpu_set::LogicalCpuId; + +/// The number of times (overall) where a CPU switched from one context to another. +static CONTEXT_SWITCH_COUNT: AtomicU64 = AtomicU64::new(0); +/// Number of times each Interrupt happened. +static IRQ_COUNT: [AtomicU64; 256] = [const { AtomicU64::new(0) }; 256]; +/// Number of contexts that were created. +static CONTEXTS_COUNT: AtomicU64 = AtomicU64::new(0); + +/// Current state of a CPU +#[repr(u8)] +#[derive(Copy, Clone, Debug, Default)] +pub enum CpuState { + /// Waiting for runnable context + #[default] + Idle = 0, + /// Runnnig a kernel context + Kernel = 1, + /// Running a context in the userspace + User = 2, +} + +/// Statistics for the CPUs. +#[derive(Debug, Default)] +pub struct CpuStats { + /// Number of ticks spent on userspace contexts + user: AtomicUsize, + /// Number of ticks spent on Niced userspace contexts + nice: AtomicUsize, + /// Number of ticks spent on kernel contexts + kernel: AtomicUsize, + /// Number of ticks spent idle + idle: AtomicUsize, + /// Number of times the CPU handled an interrupt + irq: AtomicUsize, + /// Current state of the CPU + state: AtomicU8, +} + +pub struct CpuStatsData { + /// Number of ticks spent on userspace contexts + pub user: usize, + /// Number of ticks spent on Niced userspace contexts + pub nice: usize, + /// Number of ticks spent on kernel contexts + pub kernel: usize, + /// Number of ticks spent idle + pub idle: usize, + /// Number of times the CPU handled an interrupt + pub irq: usize, +} + +impl CpuStats { + /// Set the CPU's current state + /// + /// # Parameters + /// * `new_state` - The state of the CPU for the following ticks. + pub fn set_state(&self, new_state: CpuState) { + self.state.store(new_state as u8, Ordering::Relaxed); + } + + /// Increments time statistics of a CPU + /// + /// Which statistic is incremented depends on the [`State`] of the CPU. + /// + /// # Parameters + /// * `ticks` - NUmber of ticks to add. + pub fn add_time(&self, ticks: usize) { + match self.state.load(Ordering::Relaxed) { + val if val == CpuState::Idle as u8 => self.idle.fetch_add(ticks, Ordering::Relaxed), + val if val == CpuState::User as u8 => self.user.fetch_add(ticks, Ordering::Relaxed), + val if val == CpuState::Kernel as u8 => self.kernel.fetch_add(ticks, Ordering::Relaxed), + _ => unreachable!("all possible values are covered"), + }; + } + + /// Add an IRQ event to both the global count and the CPU that handled it. + /// + /// This should be called in all [`crate::arch::interrupt:irq::eoi`], + /// for all architectures. + /// + /// # Parameters + /// * `irq` - The ID of the interrupt that happened. + pub fn add_irq(&self, irq: u8) { + IRQ_COUNT[irq as usize].fetch_add(1, Ordering::Relaxed); + self.irq.fetch_add(1, Ordering::Relaxed); + } +} + +impl CpuStatsData { + pub fn to_string(&self, cpu_id: LogicalCpuId) -> String { + format!( + "cpu{} {} {} {} {} {}", + cpu_id.get(), + self.user, + self.nice, + self.kernel, + self.idle, + self.irq, + ) + } +} + +impl Into for &CpuStats { + fn into(self) -> CpuStatsData { + CpuStatsData { + user: self.user.load(Ordering::Relaxed), + nice: self.nice.load(Ordering::Relaxed), + kernel: self.kernel.load(Ordering::Relaxed), + idle: self.idle.load(Ordering::Relaxed), + irq: self.irq.load(Ordering::Relaxed), + } + } +} + +/// Add a context switch to the count. +pub fn add_context_switch() { + CONTEXT_SWITCH_COUNT.fetch_add(1, Ordering::Relaxed); +} + +/// Get the number of context switches. +pub fn get_context_switch_count() -> u64 { + CONTEXT_SWITCH_COUNT.load(Ordering::Relaxed) +} + +/// Add a context creation to the count. +pub fn add_context() { + CONTEXTS_COUNT.fetch_add(1, Ordering::Relaxed); +} + +/// Get the number of contexts created. +pub fn get_contexts_count() -> u64 { + CONTEXTS_COUNT.load(Ordering::Relaxed) +} + +/// Get the count of each interrupt. +pub fn irq_counts() -> Vec { + IRQ_COUNT + .iter() + .map(|count| count.load(Ordering::Relaxed)) + .collect() +} diff --git a/src/main.rs b/src/main.rs index 3ee4cd8e..cdf58ed7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,6 +97,10 @@ mod dtb; /// Logical CPU ID and bitset types mod cpu_set; +/// Stats for the CPUs +#[cfg(feature = "sys_stat")] +mod cpu_stats; + /// Context management mod context; diff --git a/src/percpu.rs b/src/percpu.rs index 017f9a16..e89754ef 100644 --- a/src/percpu.rs +++ b/src/percpu.rs @@ -13,6 +13,12 @@ use crate::{ ptrace::Session, }; +#[cfg(feature = "sys_stat")] +use { + crate::cpu_stats::{CpuStats, CpuStatsData}, + alloc::vec::Vec, +}; + #[cfg(feature = "syscall_debug")] use crate::syscall::debug::SyscallDebugInfo; @@ -41,6 +47,9 @@ pub struct PercpuBlock { pub syscall_debug_info: Cell, pub misc_arch_info: crate::device::ArchPercpuMisc, + + #[cfg(feature = "sys_stat")] + pub stats: CpuStats, } const NULL: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); @@ -52,6 +61,20 @@ pub unsafe fn init_tlb_shootdown(id: LogicalCpuId, block: *mut PercpuBlock) { ALL_PERCPU_BLOCKS[id.get() as usize].store(block, Ordering::Release) } +#[cfg(feature = "sys_stat")] +pub fn get_all_stats() -> Vec<(LogicalCpuId, CpuStatsData)> { + let mut res = ALL_PERCPU_BLOCKS + .iter() + .filter_map(|block| unsafe { block.load(Ordering::Relaxed).as_ref() }) + .map(|block| { + let stats = &block.stats; + (block.cpu_id, stats.into()) + }) + .collect::>(); + res.sort_unstable_by_key(|(id, _stats)| id.get()); + res +} + // PercpuBlock::current() is implemented somewhere in the arch-specific modules #[cfg(not(feature = "multi_core"))] @@ -163,6 +186,9 @@ impl PercpuBlock { profiling: None, misc_arch_info: Default::default(), + + #[cfg(feature = "sys_stat")] + stats: CpuStats::default(), } } } diff --git a/src/scheme/sys/mod.rs b/src/scheme/sys/mod.rs index fd86c137..413e65ac 100644 --- a/src/scheme/sys/mod.rs +++ b/src/scheme/sys/mod.rs @@ -39,6 +39,9 @@ mod scheme_num; mod syscall; mod uname; +#[cfg(feature = "sys_stat")] +mod stat; + enum Handle { TopLevel, Resource { path: &'static str, data: Vec }, @@ -69,6 +72,8 @@ const FILES: &[(&'static str, SysFn)] = &[ ("env", || Ok(Vec::from(crate::init_env()))), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] ("spurious_irq", interrupt::irq::spurious_irq_resource), + #[cfg(feature = "sys_stat")] + ("stat", stat::resource), // Disabled because the debugger is inherently unsafe and probably will break the system. /* ("trigger_debugger", || unsafe { diff --git a/src/scheme/sys/stat.rs b/src/scheme/sys/stat.rs new file mode 100644 index 00000000..170d115d --- /dev/null +++ b/src/scheme/sys/stat.rs @@ -0,0 +1,89 @@ +use crate::{ + context::{contexts, ContextRef, Status}, + cpu_stats::{get_context_switch_count, get_contexts_count, irq_counts}, + percpu::get_all_stats, + syscall::error::Result, + time::START, +}; +use alloc::{string::String, vec::Vec}; + +/// Get the sys:stat data as displayed to the user. +pub fn resource() -> Result> { + let start_time_sec = *START.lock() / 1_000_000_000; + + let (contexts_running, contexts_blocked) = get_contexts_stats(); + let res = format!( + "{}{}\n\ + boot_time: {start_time_sec}\n\ + context_switches: {}\n\ + contexts_created: {}\n\ + contexts_running: {contexts_running}\n\ + contexts_blocked: {contexts_blocked}", + get_cpu_stats(), + get_irq_stats(), + get_context_switch_count(), + get_contexts_count(), + ); + + Ok(res.into_bytes()) +} + +/// Formats CPU stats. +fn get_cpu_stats() -> String { + let mut cpu_data = String::new(); + let stats = get_all_stats(); + + let mut total_user = 0; + let mut total_nice = 0; + let mut total_kernel = 0; + let mut total_idle = 0; + let mut total_irq = 0; + for (id, stat) in stats { + total_user += stat.user; + total_nice += stat.nice; + total_kernel += stat.kernel; + total_idle += stat.idle; + total_irq += stat.irq; + cpu_data += &format!("{}\n", stat.to_string(id)); + } + format!( + "cpu {total_user} {total_nice} {total_kernel} {total_idle} {total_irq}\n\ + {cpu_data}" + ) +} + +/// Formats IRQ stats. +fn get_irq_stats() -> String { + let irq = irq_counts(); + let mut irq_total = 0; + let per_irq = irq + .iter() + .map(|c| { + irq_total += *c; + format!("{c}") + }) + .collect::>() + .join(" "); + format!("IRQs {irq_total} {per_irq}") +} + +/// Format contexts stats. +fn get_contexts_stats() -> (u64, u64) { + let mut running = 0; + let mut blocked = 0; + + let statuses = contexts() + .iter() + .filter_map(ContextRef::upgrade) + .map(|context| context.read_arc().status.clone()) + .collect::>(); + + for status in statuses { + if matches!(status, Status::Runnable) { + running += 1; + } else if !matches!(status, Status::Dead) { + blocked += 1; + } + } + (running, blocked) +}