Compare commits
1 Commits
prod
...
c75d4febb3
| Author | SHA1 | Date | |
|---|---|---|---|
| c75d4febb3 |
Generated
+85
@@ -171,6 +171,12 @@ version = "3.20.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytecount"
|
||||||
|
version = "0.6.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@@ -356,6 +362,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
"tabled",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
@@ -421,6 +428,12 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -1106,6 +1119,17 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "papergrid"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6978128c8b51d8f4080631ceb2302ab51e32cc6e8615f735ee2f83fd269ae3f1"
|
||||||
|
dependencies = [
|
||||||
|
"bytecount",
|
||||||
|
"fnv",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
@@ -1170,6 +1194,28 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr2",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
@@ -1509,6 +1555,30 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tabled"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e39a2ee1fbcd360805a771e1b300f78cc88fec7b8d3e2f71cd37bbf23e725c7d"
|
||||||
|
dependencies = [
|
||||||
|
"papergrid",
|
||||||
|
"tabled_derive",
|
||||||
|
"testing_table",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tabled_derive"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ea5d1b13ca6cff1f9231ffd62f15eefd72543dab5e468735f1a456728a02846"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error2",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.27.0"
|
version = "3.27.0"
|
||||||
@@ -1522,6 +1592,15 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "testing_table"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.18"
|
version = "2.0.18"
|
||||||
@@ -1678,6 +1757,12 @@ version = "1.0.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ rust-argon2 = "3.0.0"
|
|||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
serde_yaml = "0.9.34"
|
serde_yaml = "0.9.34"
|
||||||
|
tabled = "0.20.0"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tokio = { version = "1.45.1", features = ["fs", "io-std", "macros", "process", "rt"] }
|
tokio = { version = "1.45.1", features = ["fs", "io-std", "macros", "process", "rt"] }
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,16 @@ enum Command {
|
|||||||
#[arg(long, default_value = "5s")]
|
#[arg(long, default_value = "5s")]
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Cg {
|
||||||
|
#[command(subcommand)]
|
||||||
|
cmd: CgCmd,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum CgCmd {
|
||||||
|
Ls,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
@@ -157,6 +167,10 @@ async fn main() -> Result<()> {
|
|||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
.map(|_| ())?),
|
.map(|_| ())?),
|
||||||
|
|
||||||
|
C::Cg { cmd } => match cmd {
|
||||||
|
CgCmd::Ls => Ok(dkl::cgroup::ls().await?),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
use log::warn;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::io::Result;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use crate::human::Human;
|
||||||
|
|
||||||
|
const CGROUP_ROOT: &str = "/sys/fs/cgroup/";
|
||||||
|
|
||||||
|
pub async fn ls() -> Result<()> {
|
||||||
|
let mut cgs = walk(CGROUP_ROOT.to_string()).await;
|
||||||
|
|
||||||
|
cgs.sort_by(|a, b| a.path.cmp(&b.path));
|
||||||
|
|
||||||
|
let mut table = tabled::builder::Builder::new();
|
||||||
|
table.push_record(["cgroup", "workg set", "anon", "max"]);
|
||||||
|
|
||||||
|
for cg in cgs {
|
||||||
|
let name = cg.path.strip_prefix(CGROUP_ROOT).unwrap();
|
||||||
|
table.push_record([
|
||||||
|
name,
|
||||||
|
&cg.memory.working_set().human(),
|
||||||
|
&cg.memory.stat.anon.human(),
|
||||||
|
&cg.memory.max.human(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
use tabled::settings::{
|
||||||
|
object::{Column, Row},
|
||||||
|
Alignment, Modify,
|
||||||
|
};
|
||||||
|
let mut table = table.build();
|
||||||
|
table.with(tabled::settings::Style::psql());
|
||||||
|
table.with(Alignment::right());
|
||||||
|
table.with(Modify::list(Column::from(0), Alignment::left()));
|
||||||
|
table.with(Modify::list(Row::from(0), Alignment::left()));
|
||||||
|
|
||||||
|
println!("{}", table);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn walk(root: String) -> Vec<Cgroup> {
|
||||||
|
let mut todo = vec![root];
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
|
||||||
|
while let Some(path) = todo.pop() {
|
||||||
|
match read(&path, |d| todo.push(d)).await {
|
||||||
|
Ok(cg) => results.push(cg),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("reading dir {path} failed: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cgroup {
|
||||||
|
path: String,
|
||||||
|
memory: Memory,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Memory {
|
||||||
|
current: Option<u64>,
|
||||||
|
low: Option<u64>,
|
||||||
|
high: Option<Max>,
|
||||||
|
min: Option<u64>,
|
||||||
|
max: Option<Max>,
|
||||||
|
stat: MemoryStat,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Memory {
|
||||||
|
/// working set as defined by cAdvisor
|
||||||
|
/// (https://github.com/google/cadvisor/blob/master/container/libcontainer/handler.go#L853-L862)
|
||||||
|
fn working_set(&self) -> Option<u64> {
|
||||||
|
let cur = self.current?;
|
||||||
|
let inactive = self.stat.inactive_file?;
|
||||||
|
(inactive <= cur).then(|| cur - inactive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MemoryStat {
|
||||||
|
anon: Option<u64>,
|
||||||
|
file: Option<u64>,
|
||||||
|
kernel: Option<u64>,
|
||||||
|
kernel_stack: Option<u64>,
|
||||||
|
pagetables: Option<u64>,
|
||||||
|
shmem: Option<u64>,
|
||||||
|
inactive_file: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Max {
|
||||||
|
Num(u64),
|
||||||
|
Max,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Max {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Num(n) => write!(f, "{n}"),
|
||||||
|
Self::Max => f.write_str("max"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Human for Max {
|
||||||
|
fn human(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Num(n) => n.human(),
|
||||||
|
Self::Max => "+∞".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Max {
|
||||||
|
type Err = std::num::ParseIntError;
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, <Self as FromStr>::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"max" => Self::Max,
|
||||||
|
s => Self::Num(s.parse()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read(dir: &str, mut f: impl FnMut(String)) -> Result<Cgroup> {
|
||||||
|
let mut rd = fs::read_dir(dir).await?;
|
||||||
|
|
||||||
|
let mut cg = Cgroup {
|
||||||
|
path: dir.to_string(),
|
||||||
|
memory: Memory::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Some(entry) = rd.next_entry().await? {
|
||||||
|
let path = entry.path();
|
||||||
|
let Some(path) = path.to_str() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if entry.file_type().await?.is_dir() {
|
||||||
|
f(path.to_string());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((_, name)) = path.rsplit_once('/') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some((controller, param)) = name.split_once('.') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
match controller {
|
||||||
|
"memory" => match param {
|
||||||
|
"current" => cg.memory.current = read_parse(path).await?,
|
||||||
|
"low" => cg.memory.low = read_parse(path).await?,
|
||||||
|
"high" => cg.memory.high = read_parse(path).await?,
|
||||||
|
"min" => cg.memory.min = read_parse(path).await?,
|
||||||
|
"max" => cg.memory.max = read_parse(path).await?,
|
||||||
|
"stat" => cg.memory.stat.read_from(path).await?,
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cg)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_parse<T: FromStr>(path: &str) -> Result<Option<T>>
|
||||||
|
where
|
||||||
|
T::Err: Display,
|
||||||
|
{
|
||||||
|
(fs::read_to_string(path).await?)
|
||||||
|
.trim_ascii()
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| std::io::Error::other(format!("{path}: parse failed: {e}")))
|
||||||
|
.map(|v| Some(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryStat {
|
||||||
|
async fn read_from(&mut self, path: &str) -> Result<()> {
|
||||||
|
for line in (fs::read_to_string(path).await?).lines() {
|
||||||
|
let Some((key, value)) = line.split_once(' ') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let value = value.parse::<u64>().ok();
|
||||||
|
match key {
|
||||||
|
"anon" => self.anon = value,
|
||||||
|
"file" => self.file = value,
|
||||||
|
"kernel" => self.kernel = value,
|
||||||
|
"kernel_stack" => self.kernel_stack = value,
|
||||||
|
"pagetables" => self.pagetables = value,
|
||||||
|
"shmem" => self.shmem = value,
|
||||||
|
"inactive_file" => self.inactive_file = value,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use human_units::FormatSize;
|
||||||
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
|
pub trait Human {
|
||||||
|
fn human(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Quantity(u64);
|
||||||
|
|
||||||
|
impl Display for Quantity {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
|
self.0.format_size().fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Human for Quantity {
|
||||||
|
fn human(&self) -> String {
|
||||||
|
self.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Human for u64 {
|
||||||
|
fn human(&self) -> String {
|
||||||
|
self.format_size().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Human> Human for Option<T> {
|
||||||
|
fn human(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Some(h) => h.human(),
|
||||||
|
None => "◌".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
pub mod apply;
|
pub mod apply;
|
||||||
|
pub mod rc;
|
||||||
|
pub mod cgroup;
|
||||||
|
pub mod human;
|
||||||
pub mod bootstrap;
|
pub mod bootstrap;
|
||||||
pub mod dls;
|
pub mod dls;
|
||||||
pub mod dynlay;
|
pub mod dynlay;
|
||||||
|
|||||||
Reference in New Issue
Block a user