use alloc::{format, string::String};
use ax_memory_addr::{PAGE_SIZE_4K, VirtAddr};
use ax_runtime::hal::paging::MappingFlags;
use super::AddrSpace;
const STACK_VMA_NAME: &str = "[stack]";
const HEAP_VMA_NAME: &str = "[heap]";
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ProcessMemStats {
pub vss_pages: u64,
pub text_pages: u64,
pub data_pages: u64,
pub stack_pages: u64,
pub exe_pages: u64,
pub shared_vss_pages: u64,
pub resident_pages: u64,
pub start_code: u64,
pub end_code: u64,
pub start_stack: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum VmaClass {
Stack,
Text,
Data,
Other,
}
fn user_stack_range() -> (usize, usize) {
let top = crate::config::USER_STACK_TOP;
let size = crate::config::USER_STACK_SIZE;
(top.saturating_sub(size), top)
}
fn is_stack_vma(path: &str, start: VirtAddr) -> bool {
if path == STACK_VMA_NAME {
return true;
}
let (stack_start, stack_end) = user_stack_range();
let start = start.as_usize();
start >= stack_start && start < stack_end
}
fn is_named_anon(path: &str) -> bool {
path == STACK_VMA_NAME || path == HEAP_VMA_NAME
}
fn classify_vma(path: &str, flags: MappingFlags, start: VirtAddr) -> VmaClass {
if is_stack_vma(path, start) {
return VmaClass::Stack;
}
if flags.contains(MappingFlags::EXECUTE) {
return VmaClass::Text;
}
if flags.contains(MappingFlags::WRITE) {
return VmaClass::Data;
}
VmaClass::Other
}
fn accumulate_vma(
stats: &mut ProcessMemStats,
pages: u64,
path: &str,
flags: MappingFlags,
start: VirtAddr,
end: VirtAddr,
shared: bool,
) {
stats.vss_pages += pages;
if shared {
stats.shared_vss_pages += pages;
}
let class = classify_vma(path, flags, start);
match class {
VmaClass::Stack => stats.stack_pages += pages,
VmaClass::Text => {
stats.text_pages += pages;
if !path.is_empty() && !is_named_anon(path) {
stats.exe_pages += pages;
}
let start = start.as_usize() as u64;
let end = end.as_usize() as u64;
if stats.start_code == 0 || start < stats.start_code {
stats.start_code = start;
}
if end > stats.end_code {
stats.end_code = end;
}
}
VmaClass::Data => stats.data_pages += pages,
VmaClass::Other => {}
}
if class == VmaClass::Stack && stats.start_stack == 0 {
stats.start_stack = start.as_usize() as u64;
}
}
impl ProcessMemStats {
pub fn collect(aspace: &AddrSpace) -> Self {
let mut stats = Self::default();
for area in aspace.areas() {
let pages = (area.size() / PAGE_SIZE_4K) as u64;
let flags = area.flags();
let file_info = area
.backend()
.file_info()
.unwrap_or(super::BackendFileInfo {
path: String::new(),
offset: None,
inode: None,
dev: None,
shared: false,
});
accumulate_vma(
&mut stats,
pages,
&file_info.path,
flags,
area.start(),
area.end(),
file_info.shared,
);
}
stats.resident_pages = stats.vss_pages;
stats
}
pub const fn vsize_bytes(&self) -> u64 {
self.vss_pages * PAGE_SIZE_4K as u64
}
pub const fn rss_pages(&self) -> i64 {
self.resident_pages as i64
}
pub fn format_statm(&self) -> String {
format!(
"{} {} {} {} 0 {} 0\n",
self.vss_pages,
self.resident_pages,
self.shared_vss_pages,
self.text_pages,
self.data_pages,
)
}
pub fn format_status_vm_lines(&self) -> String {
let page_kb = PAGE_SIZE_4K as u64 / 1024;
let vss_kb = self.vss_pages * page_kb;
let resident_kb = self.resident_pages * page_kb;
let data_kb = self.data_pages * page_kb;
let stack_kb = self.stack_pages * page_kb;
let exe_kb = self.exe_pages * page_kb;
format!(
"VmPeak:\t{vss_kb} kB\nVmSize:\t{vss_kb} kB\nVmLck:\t0 kB\nVmPin:\t0 \
kB\nVmHWM:\t{resident_kb} kB\nVmRSS:\t{resident_kb} kB\nRssAnon:\t0 kB\nRssFile:\t0 \
kB\nRssShmem:\t0 kB\nVmData:\t{data_kb} kB\nVmStk:\t{stack_kb} kB\nVmExe:\t{exe_kb} \
kB\nVmLib:\t0 kB\nVmPTE:\t0 kB\nVmSwap:\t0 kB\n"
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_stack_by_name() {
assert_eq!(
classify_vma(
STACK_VMA_NAME,
MappingFlags::READ | MappingFlags::WRITE,
VirtAddr::from(0x1000),
),
VmaClass::Stack,
);
}
#[test]
fn classify_stack_by_address_range() {
let (stack_start, _) = user_stack_range();
assert_eq!(
classify_vma(
"",
MappingFlags::READ | MappingFlags::WRITE,
VirtAddr::from(stack_start + PAGE_SIZE_4K),
),
VmaClass::Stack,
);
}
#[test]
fn classify_text_and_data() {
assert_eq!(
classify_vma(
"",
MappingFlags::READ | MappingFlags::EXECUTE,
VirtAddr::from(0)
),
VmaClass::Text,
);
assert_eq!(
classify_vma(
"",
MappingFlags::READ | MappingFlags::WRITE,
VirtAddr::from(0)
),
VmaClass::Data,
);
}
#[test]
fn accumulate_mixed_vmas() {
let mut stats = ProcessMemStats::default();
accumulate_vma(
&mut stats,
4,
STACK_VMA_NAME,
MappingFlags::READ | MappingFlags::WRITE,
VirtAddr::from(crate::config::USER_STACK_TOP - crate::config::USER_STACK_SIZE),
VirtAddr::from(crate::config::USER_STACK_TOP),
false,
);
accumulate_vma(
&mut stats,
2,
"/bin/app",
MappingFlags::READ | MappingFlags::EXECUTE,
VirtAddr::from(0x1000),
VirtAddr::from(0x3000),
false,
);
accumulate_vma(
&mut stats,
3,
HEAP_VMA_NAME,
MappingFlags::READ | MappingFlags::WRITE,
VirtAddr::from(crate::config::USER_HEAP_BASE),
VirtAddr::from(crate::config::USER_HEAP_BASE + 3 * PAGE_SIZE_4K),
false,
);
assert_eq!(stats.vss_pages, 9);
assert_eq!(stats.stack_pages, 4);
assert_eq!(stats.text_pages, 2);
assert_eq!(stats.exe_pages, 2);
assert_eq!(stats.data_pages, 3);
assert_eq!(stats.start_code, 0x1000);
assert_eq!(stats.end_code, 0x3000);
}
#[test]
fn format_statm_matches_linux_field_order() {
let stats = ProcessMemStats {
vss_pages: 100,
text_pages: 10,
data_pages: 40,
stack_pages: 20,
exe_pages: 8,
shared_vss_pages: 5,
resident_pages: 100,
..Default::default()
};
assert_eq!(stats.format_statm(), "100 100 5 10 0 40 0\n");
}
#[test]
fn format_status_vm_lines_use_kilobytes() {
let stats = ProcessMemStats {
vss_pages: 256,
data_pages: 64,
stack_pages: 32,
exe_pages: 16,
resident_pages: 256,
..Default::default()
};
let lines = stats.format_status_vm_lines();
assert!(lines.contains("VmSize:\t1024 kB\n"));
assert!(lines.contains("VmRSS:\t1024 kB\n"));
assert!(lines.contains("VmData:\t256 kB\n"));
assert!(lines.contains("VmStk:\t128 kB\n"));
assert!(lines.contains("VmExe:\t64 kB\n"));
}
#[test]
fn resident_never_exceeds_vss() {
let stats = ProcessMemStats {
vss_pages: 42,
resident_pages: 30,
..Default::default()
};
assert!(stats.resident_pages <= stats.vss_pages);
assert_eq!(stats.rss_pages(), 30);
assert_eq!(stats.vsize_bytes(), 42 * PAGE_SIZE_4K as u64);
}
}