Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

slabtop: Add command #42

Merged
merged 27 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f127056
slabtop: Add command
Krysztal112233 Mar 29, 2024
4e1bed0
chores: add `slabtop` into `Cargo.toml`
Krysztal112233 Mar 31, 2024
0fa8774
slabtop: Add command
Krysztal112233 Mar 29, 2024
2aac53d
slabtop: Implemented parsing `/proc/slabinfo`
Krysztal112233 Mar 29, 2024
2d49743
slabtop: Implement `sort` fot parse module
Krysztal112233 Mar 31, 2024
a3f18ef
slabtop: implemented sort by cache utilization
Krysztal112233 Apr 3, 2024
6488cc9
slabtop: disable unimplemented arg
Krysztal112233 Apr 12, 2024
af2c730
chores: introduce ratatui for implement TUI
Krysztal112233 Apr 12, 2024
3997f4d
slabtop: implement header.
Krysztal112233 Apr 12, 2024
aef9dd7
slabtop: implemented simple output without TUI
Krysztal112233 Apr 12, 2024
fe69dca
Cargo.toml: remove utmpx
cakebaker Apr 1, 2024
b07604d
w: Format time and get cmdline (#41)
fortifiedhill Apr 1, 2024
0961902
fix typo
sylvestre Apr 2, 2024
b09afaa
chores: update Cargo.lock
Krysztal112233 Apr 12, 2024
01793f4
chores: clean `Cargo.lock`
Krysztal112233 Apr 12, 2024
c97ceb2
w: fix build failed.
Krysztal112233 Apr 12, 2024
21d9092
slabtop: add #[allow(unused)] to field for further version checking(m…
Krysztal112233 Apr 12, 2024
5407aaa
tests: add `test_slabtop.rs`
Krysztal112233 Apr 12, 2024
152224f
slabtop: move unit testing into `tests/by_utils`
Krysztal112233 Apr 12, 2024
b3006e7
slabinfo: remove version field of `SlabInfo`
Krysztal112233 Apr 13, 2024
dfb9fa0
slabinfo: change `SlabInfo` from pub to pub(crate)
Krysztal112233 Apr 13, 2024
6fb3ed2
Merge remote-tracking branch 'upstream/main'
Krysztal112233 Apr 14, 2024
d1c2380
w: fix conflict
Krysztal112233 Apr 14, 2024
fd4d04c
w: apply fmt
Krysztal112233 Apr 14, 2024
1c3231c
Merge upstream changes
Krysztal112233 Apr 16, 2024
b142ea3
slabtop: fix incorrect data.
Krysztal112233 Apr 16, 2024
c4eaca7
docs: move `slabtop` to `Ongoing` section
Krysztal112233 Apr 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.37", default-features = false, features = [
"clock",
] }
ratatui = "0.26.1"
Krysztal112233 marked this conversation as resolved.
Show resolved Hide resolved
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
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);
301 changes: 301 additions & 0 deletions src/uu/slabtop/src/parse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
// 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 struct SlabInfo {
#[allow(unused)] // for slabinfo checking
pub(crate) version: String,
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(crate) fn parse(content: String) -> Option<SlabInfo> {
let mut lines: Vec<&str> = content.lines().collect();

let version = 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 {
version,
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 {
sylvestre marked this conversation as resolved.
Show resolved Hide resolved
// <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(crate) fn object_minimum(&self) -> u64 {
Krysztal112233 marked this conversation as resolved.
Show resolved Hide resolved
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(crate) 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(crate) 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(crate) fn total_active_objs(&self) -> u64 {
self.total("active_objs")
}

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

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

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

pub(crate) 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(crate) 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(crate) 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(crate) 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
Loading