From b00879e19297284cf56fb790bbd8c59af06a1d9d Mon Sep 17 00:00:00 2001 From: J H Date: Mon, 21 Oct 2024 16:48:19 -0600 Subject: [PATCH 1/2] Feat: Add the memory for the stats. --- core/startos/src/context/rpc.rs | 4 +-- core/startos/src/lxc/dev.rs | 48 ++++++++++++++++++++++++++++++- core/startos/src/lxc/mod.rs | 28 ++++++++++++++++-- core/startos/src/service/mod.rs | 50 +++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 5 deletions(-) diff --git a/core/startos/src/context/rpc.rs b/core/startos/src/context/rpc.rs index cb5ce83c5..0e0bbb986 100644 --- a/core/startos/src/context/rpc.rs +++ b/core/startos/src/context/rpc.rs @@ -70,7 +70,7 @@ pub struct RpcContextSeed { pub hardware: Hardware, pub start_time: Instant, pub crons: SyncMutex>>, - #[cfg(feature = "dev")] + // #[cfg(feature = "dev")] pub dev: Dev, } @@ -278,7 +278,7 @@ impl RpcContext { hardware: Hardware { devices, ram }, start_time: Instant::now(), crons, - #[cfg(feature = "dev")] + // #[cfg(feature = "dev")] dev: Dev { lxc: Mutex::new(BTreeMap::new()), }, diff --git a/core/startos/src/lxc/dev.rs b/core/startos/src/lxc/dev.rs index 506cb2e9b..e2630cfda 100644 --- a/core/startos/src/lxc/dev.rs +++ b/core/startos/src/lxc/dev.rs @@ -8,10 +8,13 @@ use rpc_toolkit::{ use serde::{Deserialize, Serialize}; use ts_rs::TS; -use crate::context::{CliContext, RpcContext}; use crate::lxc::{ContainerId, LxcConfig}; use crate::prelude::*; use crate::rpc_continuations::Guid; +use crate::{ + context::{CliContext, RpcContext}, + service::ServiceStats, +}; pub fn lxc() -> ParentHandler { ParentHandler::new() @@ -36,6 +39,33 @@ pub fn lxc() -> ParentHandler { .with_about("List lxc containers") .with_call_remote::(), ) + .subcommand( + "stats", + from_fn_async(stats) + .with_custom_display_fn(|_, res| { + use prettytable::*; + let mut table = + table!(["Container ID", "Name", "Memory Usage", "Memory Limit"]); + for ServiceStats { + container_id, + package_id, + memory_usage, + memory_limit, + } in res + { + table.add_row(row![ + &*container_id, + &*package_id, + memory_usage, + memory_limit + ]); + } + table.printstd(); + Ok(()) + }) + .with_about("List information related to the lxc containers i.e. CPU, Memory, Disk") + .with_call_remote::(), + ) .subcommand( "remove", from_fn_async(remove) @@ -63,6 +93,22 @@ pub async fn list(ctx: RpcContext) -> Result, Error> { Ok(ctx.dev.lxc.lock().await.keys().cloned().collect()) } +pub async fn stats(ctx: RpcContext) -> Result, Error> { + let ids = ctx.db.peek().await.as_public().as_package_data().keys()?; + let guids: Vec<_> = ctx.dev.lxc.lock().await.keys().cloned().collect(); + + let mut stats = Vec::with_capacity(guids.len()); + for id in ids { + let service: tokio::sync::OwnedRwLockReadGuard> = + ctx.services.get(&id).await; + + let service_ref = service.as_ref().or_not_found(&id)?; + + stats.push(service_ref.stats().await?); + } + Ok(stats) +} + #[derive(Deserialize, Serialize, Parser, TS)] pub struct RemoveParams { #[ts(type = "string")] diff --git a/core/startos/src/lxc/mod.rs b/core/startos/src/lxc/mod.rs index 3979263f0..c0fb6eaba 100644 --- a/core/startos/src/lxc/mod.rs +++ b/core/startos/src/lxc/mod.rs @@ -1,8 +1,8 @@ -use std::collections::BTreeSet; use std::net::Ipv4Addr; use std::path::Path; use std::sync::{Arc, Weak}; use std::time::Duration; +use std::{collections::BTreeSet, ffi::OsString}; use clap::builder::ValueParserFactory; use futures::{AsyncWriteExt, StreamExt}; @@ -32,7 +32,7 @@ use crate::util::io::open_file; use crate::util::rpc_client::UnixRpcClient; use crate::util::{new_guid, Invoke}; -#[cfg(feature = "dev")] +// #[cfg(feature = "dev")] pub mod dev; const LXC_CONTAINER_DIR: &str = "/var/lib/lxc"; @@ -287,6 +287,30 @@ impl LxcContainer { self.rpc_bind.path() } + pub async fn command(&self, commands: &[&str]) -> Result { + let mut cmd = Command::new("lxc-attach"); + cmd.kill_on_drop(true); + + let output = cmd + .arg(&**self.guid) + .arg("--") + .args(commands) + .output() + .await?; + + if !output.status.success() { + return Err(Error::new( + eyre!( + "Command failed with exit code: {:?} \n Message: {:?}", + output.status.code(), + String::from_utf8(output.stderr) + ), + ErrorKind::Docker, + )); + } + Ok(String::from_utf8(output.stdout)?) + } + #[instrument(skip_all)] pub async fn exit(mut self) -> Result<(), Error> { Command::new("lxc-stop") diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index cc31efe11..0d4332f69 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -83,6 +83,32 @@ pub enum LoadDisposition { struct RootCommand(pub String); +#[derive(Clone, Debug, Serialize, Deserialize, Default, TS)] +pub struct MiB(u64); + +impl MiB { + fn new(value: u64) -> Self { + Self(value / 1024 / 1024) + } + fn from_MiB(value: u64) -> Self { + Self(value) + } +} + +impl std::fmt::Display for MiB { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} MiB", self.0) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default, TS)] +pub struct ServiceStats { + pub container_id: Arc, + pub package_id: PackageId, + pub memory_usage: MiB, + pub memory_limit: MiB, +} + pub struct ServiceRef(Arc); impl ServiceRef { pub fn weak(&self) -> Weak { @@ -553,6 +579,30 @@ impl Service { .clone(); Ok(container_id) } + #[instrument(skip_all)] + pub async fn stats(&self) -> Result { + let container = &self.seed.persistent_container; + let lxc_container = container.lxc_container.get().or_not_found("container")?; + let (total, used) = lxc_container + .command(&["free", "-m"]) + .await? + .split("\n") + .map(|x| x.split_whitespace().collect::>()) + .skip(1) + .filter_map(|x| { + Some(( + x.get(1)?.parse::().ok()?, + x.get(2)?.parse::().ok()?, + )) + }) + .fold((0, 0), |acc, (total, used)| (acc.0 + total, acc.1 + used)); + Ok(ServiceStats { + container_id: lxc_container.guid.clone(), + package_id: self.seed.id.clone(), + memory_limit: MiB::from_MiB(total), + memory_usage: MiB::from_MiB(used), + }) + } } #[derive(Debug, Clone)] From d8e7a56dd4a8179d369f4a0e4594c56ec283530b Mon Sep 17 00:00:00 2001 From: J H Date: Tue, 22 Oct 2024 09:48:11 -0600 Subject: [PATCH 2/2] Chore: Add % --- core/startos/src/lxc/dev.rs | 15 ++++++++++++--- core/startos/src/service/mod.rs | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/startos/src/lxc/dev.rs b/core/startos/src/lxc/dev.rs index e2630cfda..248546d88 100644 --- a/core/startos/src/lxc/dev.rs +++ b/core/startos/src/lxc/dev.rs @@ -44,8 +44,13 @@ pub fn lxc() -> ParentHandler { from_fn_async(stats) .with_custom_display_fn(|_, res| { use prettytable::*; - let mut table = - table!(["Container ID", "Name", "Memory Usage", "Memory Limit"]); + let mut table = table!([ + "Container ID", + "Name", + "Memory Usage", + "Memory Limit", + "Memory %" + ]); for ServiceStats { container_id, package_id, @@ -57,7 +62,11 @@ pub fn lxc() -> ParentHandler { &*container_id, &*package_id, memory_usage, - memory_limit + memory_limit, + format!( + "{:.2}", + memory_usage.0 as f64 / memory_limit.0 as f64 * 100.0 + ) ]); } table.printstd(); diff --git a/core/startos/src/service/mod.rs b/core/startos/src/service/mod.rs index 0d4332f69..078631252 100644 --- a/core/startos/src/service/mod.rs +++ b/core/startos/src/service/mod.rs @@ -84,7 +84,7 @@ pub enum LoadDisposition { struct RootCommand(pub String); #[derive(Clone, Debug, Serialize, Deserialize, Default, TS)] -pub struct MiB(u64); +pub struct MiB(pub u64); impl MiB { fn new(value: u64) -> Self {