Skip to content

Commit

Permalink
slabtop: Add command (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
Krysztal112233 authored Apr 16, 2024
1 parent 4b15d44 commit 9391cf1
Show file tree
Hide file tree
Showing 12 changed files with 1,097 additions and 34 deletions.
385 changes: 362 additions & 23 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ build = "build.rs"
default = ["feat_common_core"]
uudoc = []

feat_common_core = [
"pwdx",
"free",
"w",
"watch",
"pmap",
]
feat_common_core = ["pwdx", "free", "w", "watch", "pmap", "slabtop"]

[workspace.dependencies]
uucore = "0.0.25"
Expand All @@ -51,6 +45,8 @@ bytesize = "1.3.0"
chrono = { version = "0.4.38", default-features = false, features = [
"clock",
] }
ratatui = "0.26.1"
crossterm = "0.27.0"

[dependencies]
clap = { workspace = true }
Expand All @@ -60,14 +56,16 @@ uucore = { workspace = true }
phf = { workspace = true }
textwrap = { workspace = true }
sysinfo = { workspace = true }

ratatui = { workspace = true }
crossterm = { workspace = true }

#
pwdx = { optional = true, version = "0.0.1", package = "uu_pwdx", path = "src/uu/pwdx" }
free = { optional = true, version = "0.0.1", package = "uu_free", path = "src/uu/free" }
w = { optional = true, version = "0.0.1", package = "uu_w", path = "src/uu/w" }
watch = { optional = true, version = "0.0.1", package = "uu_watch", path = "src/uu/watch" }
pmap = { optional = true, version = "0.0.1", package = "uu_pmap", path = "src/uu/pmap" }
slabtop = { optional = true, version = "0.0.1", package = "uu_slabtop", path = "src/uu/slabtop" }

[dev-dependencies]
pretty_assertions = "1"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ Ongoing:
* `w`: Shows who is logged on and what they are doing.
* `watch`: Executes a program periodically, showing output fullscreen.
* `pmap`: Displays the memory map of a process.
* `slabtop`: Displays detailed kernel slab cache information in real time.

TODO:
* `ps`: Displays information about active processes.
* `pgrep`: Searches for processes based on name and other attributes.
* `pidwait`: Waits for a specific process to terminate.
* `skill`: Sends a signal to processes based on criteria like user, terminal, etc.
* `slabtop`: Displays detailed kernel slab cache information in real time.
* `tload`: Prints a graphical representation of system load average to the terminal.
* `top`: Displays real-time information about system processes.
* `vmstat`: Reports information about processes, memory, paging, block IO, traps, and CPU activity.
Expand Down
26 changes: 26 additions & 0 deletions src/uu/slabtop/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "uu_slabtop"
version = "0.0.1"
edition = "2021"
authors = ["uutils developers"]
license = "MIT"
description = "slabtop ~ (uutils) Display kernel slab cache information in real time"

homepage = "https://github.com/uutils/procps"
repository = "https://github.com/uutils/procps/tree/main/src/uu/slabtop"
keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]


[dependencies]
uucore = { workspace = true }
clap = { workspace = true }
ratatui = { workspace = true }
crossterm = { workspace = true }

[lib]
path = "src/slabtop.rs"

[[bin]]
name = "slabtop"
path = "src/main.rs"
7 changes: 7 additions & 0 deletions src/uu/slabtop/slabtop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# slabtop

```
slabtop [options]
```

Display kernel slab cache information in real time
1 change: 1 addition & 0 deletions src/uu/slabtop/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uucore::bin!(uu_slabtop);
295 changes: 295 additions & 0 deletions src/uu/slabtop/src/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
// This file is part of the uutils procps package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (words) symdir somefakedir

use std::{
cmp::Ordering,
fs,
io::{Error, ErrorKind},
};

#[derive(Debug, Default)]
pub(crate) struct SlabInfo {
pub(crate) meta: Vec<String>,
pub(crate) data: Vec<(String, Vec<u64>)>,
}

impl SlabInfo {
// parse slabinfo from /proc/slabinfo
// need root permission
pub fn new() -> Result<SlabInfo, Error> {
let content = fs::read_to_string("/proc/slabinfo")?;

Self::parse(content).ok_or(ErrorKind::Unsupported.into())
}

pub fn parse(content: String) -> Option<SlabInfo> {
let mut lines: Vec<&str> = content.lines().collect();

let _ = parse_version(lines.remove(0))?;
let meta = parse_meta(lines.remove(0));
let data: Vec<(String, Vec<u64>)> = lines.into_iter().filter_map(parse_data).collect();

Some(SlabInfo { meta, data })
}

pub fn fetch(&self, name: &str, meta: &str) -> Option<u64> {
// fetch meta's offset
let offset = self.offset(meta)?;

let (_, item) = self.data.iter().find(|(key, _)| key.eq(name))?;

item.get(offset).copied()
}

pub fn names(&self) -> Vec<&String> {
self.data.iter().map(|(k, _)| k).collect()
}

pub fn sort(mut self, by: char, ascending_order: bool) -> Self {
let mut sort = |by_meta: &str| {
if let Some(offset) = self.offset(by_meta) {
self.data.sort_by(|(_, data1), (_, data2)| {
match (data1.get(offset), data2.get(offset)) {
(Some(v1), Some(v2)) => {
if ascending_order {
v1.cmp(v2)
} else {
v2.cmp(v1)
}
}
_ => Ordering::Equal,
}
});
}
};

match by {
// <active_objs>
'a' => sort("active_objs"),
// <objperslab>
'b' => sort("objperslab"),
// <objsize> Maybe cache size I guess?
// TODO: Check is <objsize>
'c' => sort("objsize"),
// <num_slabs>
'l' => sort("num_slabs"),
// <active_slabs>
'v' => sort("active_slabs"),
// name, sort by lexicographical order
'n' => self.data.sort_by(|(name1, _), (name2, _)| {
if ascending_order {
name1.cmp(name2)
} else {
name2.cmp(name1)
}
}),
// <pagesperslab>
'p' => sort("pagesperslab"),
// <objsize>
's' => sort("objsize"),
// sort by cache utilization
'u' => {
let offset_active_objs = self.offset("active_objs");
let offset_num_objs = self.offset("num_objs");

if let (Some(offset_active_objs), Some(offset_num_objs)) =
(offset_active_objs, offset_num_objs)
{
self.data.sort_by(|(_, data1), (_, data2)| {
let cu = |active_jobs: Option<&u64>, num_jobs: Option<&u64>| match (
active_jobs,
num_jobs,
) {
(Some(active_jobs), Some(num_jobs)) => {
Some((*active_jobs as f64) / (*num_jobs as f64))
}
_ => None,
};
let cu1 = cu(data1.get(offset_active_objs), data1.get(offset_num_objs));
let cu2 = cu(data2.get(offset_active_objs), data2.get(offset_num_objs));

if let (Some(cu1), Some(cu2)) = (cu1, cu2) {
let result = if ascending_order {
cu1.partial_cmp(&cu2)
} else {
cu2.partial_cmp(&cu1)
};
match result {
Some(ord) => ord,
None => Ordering::Equal,
}
} else {
Ordering::Equal
}
})
}
}

// <num_objs>
// Default branch : `o`
_ => sort("num_objs"),
}

self
}

fn offset(&self, meta: &str) -> Option<usize> {
self.meta.iter().position(|it| it.eq(meta))
}

/////////////////////////////////// helpers ///////////////////////////////////

#[inline]
fn total(&self, meta: &str) -> u64 {
let Some(offset) = self.offset(meta) else {
return 0;
};

self.data
.iter()
.filter_map(|(_, data)| data.get(offset))
.sum::<u64>()
}

pub fn object_minimum(&self) -> u64 {
let Some(offset) = self.offset("objsize") else {
return 0;
};

match self
.data
.iter()
.filter_map(|(_, data)| data.get(offset))
.min()
{
Some(min) => *min,
None => 0,
}
}

pub fn object_maximum(&self) -> u64 {
let Some(offset) = self.offset("objsize") else {
return 0;
};

match self
.data
.iter()
.filter_map(|(_, data)| data.get(offset))
.max()
{
Some(max) => *max,
None => 0,
}
}

pub fn object_avg(&self) -> u64 {
let Some(offset) = self.offset("objsize") else {
return 0;
};

let iter = self.data.iter().filter_map(|(_, data)| data.get(offset));

let count = iter.clone().count();
let sum = iter.sum::<u64>();

if count == 0 {
0
} else {
(sum) / (count as u64)
}
}

pub fn total_active_objs(&self) -> u64 {
self.total("active_objs")
}

pub fn total_objs(&self) -> u64 {
self.total("num_objs")
}

pub fn total_active_slabs(&self) -> u64 {
self.total("active_slabs")
}

pub fn total_slabs(&self) -> u64 {
self.total("num_slabs")
}

pub fn total_active_size(&self) -> u64 {
self.names()
.iter()
.map(|name| {
self.fetch(name, "active_objs").unwrap_or_default()
* self.fetch(name, "objsize").unwrap_or_default()
})
.sum::<u64>()
}

pub fn total_size(&self) -> u64 {
self.names()
.iter()
.map(|name| {
self.fetch(name, "num_objs").unwrap_or_default()
* self.fetch(name, "objsize").unwrap_or_default()
})
.sum::<u64>()
}

pub fn total_active_cache(&self) -> u64 {
self.names()
.iter()
.map(|name| {
self.fetch(name, "objsize").unwrap_or_default()
* self.fetch(name, "active_objs").unwrap_or_default()
})
.sum::<u64>()
}

pub fn total_cache(&self) -> u64 {
self.names()
.iter()
.map(|name| {
self.fetch(name, "objsize").unwrap_or_default()
* self.fetch(name, "num_objs").unwrap_or_default()
})
.sum::<u64>()
}
}

pub(crate) fn parse_version(line: &str) -> Option<String> {
line.replace(':', " ")
.split_whitespace()
.last()
.map(String::from)
}

pub(crate) fn parse_meta(line: &str) -> Vec<String> {
line.replace(['#', ':'], " ")
.split_whitespace()
.filter(|it| it.starts_with('<') && it.ends_with('>'))
.map(|it| it.replace(['<', '>'], ""))
.collect()
}

pub(crate) fn parse_data(line: &str) -> Option<(String, Vec<u64>)> {
let split: Vec<String> = line
.replace(':', " ")
.split_whitespace()
.map(String::from)
.collect();

split.first().map(|name| {
(
name.to_string(),
split
.clone()
.into_iter()
.flat_map(|it| it.parse::<u64>())
.collect(),
)
})
}
Loading

0 comments on commit 9391cf1

Please sign in to comment.