From a7f436ca8fd88bdbe4431201689b8afba242e39a Mon Sep 17 00:00:00 2001 From: Seiya Nuta Date: Sat, 18 Dec 2021 14:43:43 +0900 Subject: [PATCH] Implement /proc/metrics (#126) --- kernel/fs/mod.rs | 1 + kernel/fs/procfs/metrics.rs | 91 ++++++++++++++++++++++++++++ kernel/fs/procfs/mod.rs | 43 +++++++++++++ kernel/main.rs | 9 +++ kernel/net/tcp_socket.rs | 29 ++++++++- kernel/process/mod.rs | 2 +- kernel/process/process.rs | 17 +++++- kernel/user_buffer.rs | 8 +++ libs/kerla_utils/bitmap_allocator.rs | 4 ++ runtime/page_allocator.rs | 22 +++++++ 10 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 kernel/fs/procfs/metrics.rs create mode 100644 kernel/fs/procfs/mod.rs diff --git a/kernel/fs/mod.rs b/kernel/fs/mod.rs index b78136af..df7a0632 100644 --- a/kernel/fs/mod.rs +++ b/kernel/fs/mod.rs @@ -5,5 +5,6 @@ pub mod inode; pub mod mount; pub mod opened_file; pub mod path; +pub mod procfs; pub mod stat; pub mod tmpfs; diff --git a/kernel/fs/procfs/metrics.rs b/kernel/fs/procfs/metrics.rs new file mode 100644 index 00000000..93d82e5a --- /dev/null +++ b/kernel/fs/procfs/metrics.rs @@ -0,0 +1,91 @@ +use core::fmt; + +use kerla_runtime::page_allocator::read_allocator_stats; + +use crate::{ + fs::{ + inode::{FileLike, INodeNo}, + opened_file::OpenOptions, + stat::{FileMode, Stat, S_IFCHR}, + }, + net::read_tcp_stats, + process::read_process_stats, + result::Result, + user_buffer::UserBufferMut, + user_buffer::{UserBufWriter, UserBuffer}, +}; + +/// The `/proc/metrics` file. It returns the metrics of the kernel in Prometheus format. +pub(super) struct MetricsFile {} + +impl MetricsFile { + pub fn new() -> MetricsFile { + MetricsFile {} + } +} + +impl fmt::Debug for MetricsFile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Metrics").finish() + } +} + +impl FileLike for MetricsFile { + fn stat(&self) -> Result { + Ok(Stat { + inode_no: INodeNo::new(2), + mode: FileMode::new(S_IFCHR | 0o666), + ..Stat::zeroed() + }) + } + + fn read(&self, offset: usize, buf: UserBufferMut<'_>, _options: &OpenOptions) -> Result { + use core::fmt::Write; + + if offset > 0 { + // EOF. I guess there's a better way to do this. + return Ok(0); + } + + let process_metrics = read_process_stats(); + let allocator_metrics = read_allocator_stats(); + let tcp_metrics = read_tcp_stats(); + + let mut writer = UserBufWriter::from(buf); + let _ = write!( + writer, + concat!( + "# HELP: process_fork_total The total # of process forks.\n", + "# TYPE: process_fork_total counter\n", + "process_fork_total {fork_total}\n", + "# HELP: memory_pages_total The total # of pages can be allocated.\n", + "# TYPE: memory_pages_total gauge\n", + "memory_pages_total {num_free_pages}\n", + "# HELP: memory_pages_free The total # of pages can be allocated.\n", + "# TYPE: memory_pages_free gauge\n", + "memory_pages_free {num_total_pages}\n", + "# HELP: passive_opens_total The total # of established passive TCP opens.\n", + "# TYPE: passive_opens_total counter\n", + "passive_opens_total {passive_opens_total}\n", + "# HELP: tcp_read_bytes_total The total bytes read from TCP socket buffers.\n", + "# TYPE: tcp_read_bytes_total counter\n", + "tcp_read_bytes_total {tcp_read_bytes_total}\n", + "# HELP: tcp_written_bytes_total The total bytes written into TCP socket buffers.\n", + "# TYPE: tcp_written_bytes_total counter\n", + "tcp_written_bytes_total {tcp_written_bytes_total}\n", + ), + fork_total = process_metrics.fork_total, + num_free_pages = allocator_metrics.num_free_pages, + num_total_pages = allocator_metrics.num_total_pages, + passive_opens_total = tcp_metrics.passive_opens_total, + tcp_read_bytes_total = tcp_metrics.read_bytes_total, + tcp_written_bytes_total = tcp_metrics.written_bytes_total, + ); + + Ok(writer.written_len()) + } + + fn write(&self, _offset: usize, buf: UserBuffer<'_>, _options: &OpenOptions) -> Result { + Ok(buf.len()) + } +} diff --git a/kernel/fs/procfs/mod.rs b/kernel/fs/procfs/mod.rs new file mode 100644 index 00000000..51d1bb12 --- /dev/null +++ b/kernel/fs/procfs/mod.rs @@ -0,0 +1,43 @@ +use crate::{ + fs::{ + file_system::FileSystem, + inode::{Directory, FileLike}, + }, + result::Result, +}; +use alloc::sync::Arc; +use kerla_utils::once::Once; + +use self::metrics::MetricsFile; + +use super::tmpfs::TmpFs; + +mod metrics; + +pub static PROC_FS: Once> = Once::new(); +static METRICS_FILE: Once> = Once::new(); + +pub struct ProcFs(TmpFs); + +impl ProcFs { + pub fn new() -> ProcFs { + let tmpfs = TmpFs::new(); + let root_dir = tmpfs.root_tmpfs_dir(); + + METRICS_FILE.init(|| Arc::new(MetricsFile::new()) as Arc); + + root_dir.add_file("metrics", METRICS_FILE.clone()); + + ProcFs(tmpfs) + } +} + +impl FileSystem for ProcFs { + fn root_dir(&self) -> Result> { + self.0.root_dir() + } +} + +pub fn init() { + PROC_FS.init(|| Arc::new(ProcFs::new())); +} diff --git a/kernel/main.rs b/kernel/main.rs index a7d4a1b4..6a416365 100644 --- a/kernel/main.rs +++ b/kernel/main.rs @@ -53,6 +53,7 @@ use crate::{ initramfs::{self, INITRAM_FS}, mount::RootFs, path::Path, + procfs::{self, PROC_FS}, }, process::{switch, Process}, syscalls::SyscallHandler, @@ -167,6 +168,8 @@ pub fn boot_kernel(#[cfg_attr(debug_assertions, allow(unused))] bootinfo: &BootI profiler.lap_time("pipe init"); poll::init(); profiler.lap_time("poll init"); + procfs::init(); + profiler.lap_time("procfs init"); devfs::init(); profiler.lap_time("devfs init"); tmpfs::init(); @@ -195,12 +198,18 @@ pub fn boot_kernel(#[cfg_attr(debug_assertions, allow(unused))] bootinfo: &BootI // Prepare the root file system. let mut root_fs = RootFs::new(INITRAM_FS.clone()).unwrap(); + let proc_dir = root_fs + .lookup_dir(Path::new("/proc")) + .expect("failed to locate /dev"); let dev_dir = root_fs .lookup_dir(Path::new("/dev")) .expect("failed to locate /dev"); let tmp_dir = root_fs .lookup_dir(Path::new("/tmp")) .expect("failed to locate /tmp"); + root_fs + .mount(proc_dir, PROC_FS.clone()) + .expect("failed to mount procfs"); root_fs .mount(dev_dir, DEV_FS.clone()) .expect("failed to mount devfs"); diff --git a/kernel/net/tcp_socket.rs b/kernel/net/tcp_socket.rs index 53a32cc7..e0677f91 100644 --- a/kernel/net/tcp_socket.rs +++ b/kernel/net/tcp_socket.rs @@ -9,7 +9,12 @@ use crate::{ user_buffer::{UserBufReader, UserBufWriter, UserBufferMut}, }; use alloc::{collections::BTreeSet, sync::Arc, vec::Vec}; -use core::{cmp::min, convert::TryInto, fmt}; +use core::{ + cmp::min, + convert::TryInto, + fmt, + sync::atomic::{AtomicUsize, Ordering}, +}; use crossbeam::atomic::AtomicCell; use kerla_runtime::spinlock::{SpinLock, SpinLockGuard}; use smoltcp::socket::{SocketRef, TcpSocketBuffer}; @@ -19,6 +24,24 @@ use super::{process_packets, SOCKETS, SOCKET_WAIT_QUEUE}; const BACKLOG_MAX: usize = 8; static INUSE_ENDPOINTS: SpinLock> = SpinLock::new(BTreeSet::new()); +static PASSIVE_OPENS_TOTAL: AtomicUsize = AtomicUsize::new(0); +static WRITTEN_BYTES_TOTAL: AtomicUsize = AtomicUsize::new(0); +static READ_BYTES_TOTAL: AtomicUsize = AtomicUsize::new(0); + +#[derive(Debug)] +pub struct Stats { + pub passive_opens_total: usize, + pub written_bytes_total: usize, + pub read_bytes_total: usize, +} + +pub fn read_tcp_stats() -> Stats { + Stats { + passive_opens_total: PASSIVE_OPENS_TOTAL.load(Ordering::SeqCst), + written_bytes_total: WRITTEN_BYTES_TOTAL.load(Ordering::SeqCst), + read_bytes_total: READ_BYTES_TOTAL.load(Ordering::SeqCst), + } +} /// Looks for an accept'able socket in the backlog. fn get_ready_backlog_index( @@ -101,6 +124,8 @@ impl FileLike for TcpSocket { let smol_socket: SocketRef<'_, smoltcp::socket::TcpSocket> = sockets_lock.get(socket.handle); + PASSIVE_OPENS_TOTAL.fetch_add(1, Ordering::SeqCst); + Ok(Some(( socket as Arc, smol_socket.remote_endpoint().into(), @@ -220,6 +245,7 @@ impl FileLike for TcpSocket { process_packets(); match copied_len { Ok(0) => { + WRITTEN_BYTES_TOTAL.fetch_add(total_len, Ordering::SeqCst); return Ok(total_len); } Ok(copied_len) => { @@ -253,6 +279,7 @@ impl FileLike for TcpSocket { } Ok(copied_len) => { // Continue reading. + READ_BYTES_TOTAL.fetch_add(copied_len, Ordering::SeqCst); Ok(Some(copied_len)) } // TODO: Handle FIN diff --git a/kernel/process/mod.rs b/kernel/process/mod.rs index f90b791b..13b83e1f 100644 --- a/kernel/process/mod.rs +++ b/kernel/process/mod.rs @@ -16,7 +16,7 @@ pub mod signal; mod switch; mod wait_queue; -pub use process::{gc_exited_processes, PId, Process, ProcessState}; +pub use process::{gc_exited_processes, read_process_stats, PId, Process, ProcessState}; pub use switch::switch; pub use wait_queue::WaitQueue; diff --git a/kernel/process/process.rs b/kernel/process/process.rs index 5f7577dc..a8c0ad85 100644 --- a/kernel/process/process.rs +++ b/kernel/process/process.rs @@ -26,9 +26,9 @@ use alloc::collections::BTreeMap; use alloc::sync::{Arc, Weak}; use alloc::vec::Vec; use atomic_refcell::{AtomicRef, AtomicRefCell}; -use core::cmp::max; use core::mem::size_of; use core::sync::atomic::{AtomicI32, Ordering}; +use core::{cmp::max, sync::atomic::AtomicUsize}; use crossbeam::atomic::AtomicCell; use goblin::elf64::program_header::PT_LOAD; use kerla_runtime::{ @@ -44,6 +44,19 @@ type ProcessTable = BTreeMap>; pub(super) static PROCESSES: SpinLock = SpinLock::new(BTreeMap::new()); pub(super) static EXITED_PROCESSES: SpinLock>> = SpinLock::new(Vec::new()); +static FORK_TOTAL: AtomicUsize = AtomicUsize::new(0); + +#[derive(Debug)] +pub struct Stats { + pub fork_total: usize, +} + +pub fn read_process_stats() -> Stats { + Stats { + fork_total: FORK_TOTAL.load(Ordering::SeqCst), + } +} + /// Returns an unused PID. Note that this function does not reserve the PID: /// keep the process table locked until you insert the process into the table! pub(super) fn alloc_pid(table: &mut ProcessTable) -> Result { @@ -512,6 +525,8 @@ impl Process { parent.children().push(child.clone()); process_table.insert(pid, child.clone()); SCHEDULER.lock().enqueue(pid); + + FORK_TOTAL.fetch_add(1, Ordering::Relaxed); Ok(child) } } diff --git a/kernel/user_buffer.rs b/kernel/user_buffer.rs index 33b0622a..97c6f05a 100644 --- a/kernel/user_buffer.rs +++ b/kernel/user_buffer.rs @@ -296,6 +296,14 @@ impl<'a> From> for UserBufWriter<'a> { } } +impl<'a> core::fmt::Write for UserBufWriter<'a> { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.write_bytes(s.as_bytes()) + .map_err(|_| core::fmt::Error)?; + Ok(()) + } +} + /// A user-provided NULL-terminated string. /// /// It's a copy of the string (not a reference) since the user can modify the diff --git a/libs/kerla_utils/bitmap_allocator.rs b/libs/kerla_utils/bitmap_allocator.rs index 81423430..78d4feac 100644 --- a/libs/kerla_utils/bitmap_allocator.rs +++ b/libs/kerla_utils/bitmap_allocator.rs @@ -33,6 +33,10 @@ impl BitMapAllocator { } } + pub fn num_total_pages(&self) -> usize { + (self.end - self.base) / PAGE_SIZE + } + pub fn includes(&mut self, ptr: usize) -> bool { self.base <= ptr && ptr < self.end } diff --git a/runtime/page_allocator.rs b/runtime/page_allocator.rs index 1f95dfaf..bae0d07a 100644 --- a/runtime/page_allocator.rs +++ b/runtime/page_allocator.rs @@ -1,4 +1,5 @@ use core::ops::Deref; +use core::sync::atomic::{AtomicUsize, Ordering}; use crate::{address::PAddr, arch::PAGE_SIZE, bootinfo::RamArea, spinlock::SpinLock}; use arrayvec::ArrayVec; @@ -15,6 +16,8 @@ use kerla_utils::bitmap_allocator::BitMapAllocator as Allocator; // use kerla_utils::bump_allocator::BumpAllocator as Allocator; static ZONES: SpinLock> = SpinLock::new(ArrayVec::new_const()); +static NUM_FREE_PAGES: AtomicUsize = AtomicUsize::new(0); +static NUM_TOTAL_PAGES: AtomicUsize = AtomicUsize::new(0); fn num_pages_to_order(num_pages: usize) -> usize { // TODO: Use log2 instead @@ -29,6 +32,19 @@ fn num_pages_to_order(num_pages: usize) -> usize { unreachable!(); } +#[derive(Debug)] +pub struct Stats { + pub num_free_pages: usize, + pub num_total_pages: usize, +} + +pub fn read_allocator_stats() -> Stats { + Stats { + num_free_pages: NUM_FREE_PAGES.load(Ordering::SeqCst), + num_total_pages: NUM_TOTAL_PAGES.load(Ordering::SeqCst), + } +} + bitflags! { pub struct AllocPageFlags: u32 { // TODO: Currently both of them are unused in the allocator. @@ -70,6 +86,7 @@ impl Drop for OwnedPages { } } +// TODO: Use alloc_page pub fn alloc_pages(num_pages: usize, flags: AllocPageFlags) -> Result { let order = num_pages_to_order(num_pages); let mut zones = ZONES.lock(); @@ -83,6 +100,7 @@ pub fn alloc_pages(num_pages: usize, flags: AllocPageFlags) -> Result