use crate::{chunk::Chunk, options::Options};
use smallvec::SmallVec;
use smol_str::{format_smolstr, SmolStr, SmolStrBuilder};
use std::path::Path;
use tokio::fs;
pub async fn show(opts: &Options) -> Option<Chunk<SmolStr>> {
if !opts.huge_pages {
return None;
}
let mut builder = SmolStrBuilder::new();
let huge_pages = get_hugepages_status().await;
if let Some(huge_pages) = huge_pages {
for hp in huge_pages {
builder.push_str(
&hp.pages
.iter()
.map(|page| format_smolstr!("{}x{}", page.count, format_kb(page.size_kb)))
.collect::<SmallVec<[_; 4]>>()
.join(","),
);
}
Some(Chunk::new(opts.select_str("⎘", ""), builder.finish()))
} else {
None
}
}
#[derive(Debug)]
pub struct HugePageInfo {
pub size_kb: u64,
pub count: u64,
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct HugePage {
pub node: Option<u32>,
pub pages: SmallVec<[HugePageInfo; 4]>,
}
async fn process_hugepage_dir(path: &Path, node: Option<u32>) -> Option<HugePage> {
let mut pages_info = SmallVec::<[_; 4]>::new();
if let Ok(mut entries) = fs::read_dir(path).await {
let mut tmp = entries.next_entry().await.ok()?.into_iter();
for entry in &mut tmp {
let file_name = entry.file_name();
let file_name_str = match file_name.to_str() {
Some(s) => s,
None => continue, };
if file_name_str.starts_with("hugepages-") {
if let Some(size_str) = file_name_str
.strip_prefix("hugepages-")
.and_then(|s| s.strip_suffix("kB"))
{
if let Ok(size_kb) = size_str.parse::<u64>() {
let nr_hugepages_path = entry.path().join("nr_hugepages");
if let Ok(content) = fs::read_to_string(nr_hugepages_path).await {
if let Ok(count) = content.trim().parse::<u64>() {
if count > 0 {
pages_info.push(HugePageInfo { size_kb, count });
}
}
}
}
}
}
}
}
if !pages_info.is_empty() {
Some(HugePage {
node,
pages: pages_info,
})
} else {
None
}
}
pub async fn get_hugepages_status() -> Option<SmallVec<[HugePage; 4]>> {
const NUMA_NODE_BASE_DIR: &str = "/sys/devices/system/node";
const NO_NUMA_HUGE_DIR: &str = "/sys/kernel/mm/hugepages";
let mut hugepages = SmallVec::<[_; 4]>::new();
if Path::new(NUMA_NODE_BASE_DIR).exists() {
if let Ok(mut entries) = fs::read_dir(NUMA_NODE_BASE_DIR).await {
while let Some(entry) = entries.next_entry().await.ok()? {
let file_name = entry.file_name();
let file_name_str = match file_name.to_str() {
Some(s) => s,
None => continue,
};
if let Some(node_id) = file_name_str.strip_prefix("node") {
if let Ok(node_id) = node_id.parse::<u32>() {
let hugepages_path = entry.path().join("hugepages");
if let Some(hp) = process_hugepage_dir(&hugepages_path, Some(node_id)).await
{
hugepages.push(hp);
}
}
}
}
}
} else {
let path = Path::new(NO_NUMA_HUGE_DIR);
if let Some(hp) = process_hugepage_dir(path, None).await {
hugepages.push(hp);
}
}
if hugepages.is_empty() {
None
} else {
Some(hugepages)
}
}
fn format_kb(kb: u64) -> String {
if kb < 1024 {
format!("{}KB", kb)
} else if kb < 1024 * 1024 {
format!("{}MB", kb / 1024)
} else {
format!("{}GB", kb / (1024 * 1024))
}
}