blob: bd558ba3f5f21282779ed9c27915b7b36127a39c [file]
// Copyright 2025 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
use crate::platform_rs::timer::DurationMono;
use crate::stall::StallAggregator;
use crate::vm::compression::VmCompression;
use crate::vm::discardable_vmo_tracker::{DiscardablePageCounts, DiscardableVmoTracker};
use crate::vm::evictor::{Evictor, EvictorStats};
use crate::vm::page_queues::{Counts, PageQueues, ReclaimCounts};
use crate::vm::page_state::VmPageState;
use crate::vm::{heap, page as vm_page, pmm};
use page::{SHIFT as PAGE_SHIFT, SIZE as PAGE_SIZE};
use zx_types::{
InfoMapsTypeUnionV1, InfoMapsTypeUnionV2, zx_info_kmem_stats_compression_t,
zx_info_kmem_stats_extended_t, zx_info_kmem_stats_t, zx_info_kmem_stats_v1_t,
zx_info_maps_mapping_v1_t, zx_info_maps_mapping_v2_t, zx_info_maps_t, zx_info_maps_v1_t,
zx_info_maps_v2_t, zx_info_memory_stall_t, zx_info_vmo_t, zx_info_vmo_v1_t, zx_info_vmo_v2_t,
zx_info_vmo_v3_t,
};
mod sealed {
pub trait Sealed {}
}
/// Trait for converting a [`zx_info_vmo_t`] to a versioned VMO info record.
pub trait VmoInfoVersion: sealed::Sealed {
/// Converts `vmo` to this versioned VMO info type.
fn from_vmo_info(vmo: &zx_info_vmo_t) -> Self;
}
impl sealed::Sealed for zx_info_vmo_t {}
impl VmoInfoVersion for zx_info_vmo_t {
#[inline]
fn from_vmo_info(vmo: &zx_info_vmo_t) -> Self {
*vmo
}
}
impl sealed::Sealed for zx_info_vmo_v1_t {}
impl VmoInfoVersion for zx_info_vmo_v1_t {
#[inline]
fn from_vmo_info(vmo: &zx_info_vmo_t) -> Self {
zx_info_vmo_v1_t {
koid: vmo.koid,
name: vmo.name,
size_bytes: vmo.size_bytes,
parent_koid: vmo.parent_koid,
num_children: vmo.num_children,
num_mappings: vmo.num_mappings,
share_count: vmo.share_count,
flags: vmo.flags,
committed_bytes: vmo.committed_bytes,
handle_rights: vmo.handle_rights,
cache_policy: vmo.cache_policy,
..Default::default()
}
}
}
impl sealed::Sealed for zx_info_vmo_v2_t {}
impl VmoInfoVersion for zx_info_vmo_v2_t {
#[inline]
fn from_vmo_info(vmo: &zx_info_vmo_t) -> Self {
zx_info_vmo_v2_t {
koid: vmo.koid,
name: vmo.name,
size_bytes: vmo.size_bytes,
parent_koid: vmo.parent_koid,
num_children: vmo.num_children,
num_mappings: vmo.num_mappings,
share_count: vmo.share_count,
flags: vmo.flags,
committed_bytes: vmo.committed_bytes,
handle_rights: vmo.handle_rights,
cache_policy: vmo.cache_policy,
metadata_bytes: vmo.metadata_bytes,
committed_change_events: vmo.committed_change_events,
..Default::default()
}
}
}
impl sealed::Sealed for zx_info_vmo_v3_t {}
impl VmoInfoVersion for zx_info_vmo_v3_t {
#[inline]
fn from_vmo_info(vmo: &zx_info_vmo_t) -> Self {
zx_info_vmo_v3_t {
koid: vmo.koid,
name: vmo.name,
size_bytes: vmo.size_bytes,
parent_koid: vmo.parent_koid,
num_children: vmo.num_children,
num_mappings: vmo.num_mappings,
share_count: vmo.share_count,
flags: vmo.flags,
committed_bytes: vmo.committed_bytes,
handle_rights: vmo.handle_rights,
cache_policy: vmo.cache_policy,
metadata_bytes: vmo.metadata_bytes,
committed_change_events: vmo.committed_change_events,
populated_bytes: vmo.populated_bytes,
..Default::default()
}
}
}
/// Converts a [`zx_info_vmo_t`] to the requested versioned VMO info record `T`.
#[inline]
pub fn vmo_info_to_version<T: VmoInfoVersion>(vmo: &zx_info_vmo_t) -> T {
T::from_vmo_info(vmo)
}
/// Trait for converting a [`zx_info_maps_t`] to a versioned maps info record.
pub trait MapsInfoVersion: sealed::Sealed {
/// Converts `maps` to this versioned maps info type.
fn from_maps_info(maps: &zx_info_maps_t) -> Self;
}
impl sealed::Sealed for zx_info_maps_t {}
impl MapsInfoVersion for zx_info_maps_t {
#[inline]
fn from_maps_info(maps: &zx_info_maps_t) -> Self {
*maps
}
}
impl sealed::Sealed for zx_info_maps_v1_t {}
impl MapsInfoVersion for zx_info_maps_v1_t {
#[inline]
fn from_maps_info(maps: &zx_info_maps_t) -> Self {
// SAFETY: InfoMapsTypeUnion has a single FromBytes variant (zx_info_maps_mapping_t) and is
// always initialized.
let mapping = unsafe { maps.u.mapping };
zx_info_maps_v1_t {
name: maps.name,
base: maps.base,
size: maps.size,
depth: maps.depth,
r#type: maps.r#type,
padding1: Default::default(),
u: InfoMapsTypeUnionV1 {
mapping: zx_info_maps_mapping_v1_t {
mmu_flags: mapping.mmu_flags,
padding1: Default::default(),
vmo_koid: mapping.vmo_koid,
vmo_offset: mapping.vmo_offset,
committed_pages: mapping.committed_bytes >> PAGE_SHIFT,
},
},
}
}
}
impl sealed::Sealed for zx_info_maps_v2_t {}
impl MapsInfoVersion for zx_info_maps_v2_t {
#[inline]
fn from_maps_info(maps: &zx_info_maps_t) -> Self {
// SAFETY: InfoMapsTypeUnion has a single FromBytes variant (zx_info_maps_mapping_t) and is
// always initialized.
let mapping = unsafe { maps.u.mapping };
zx_info_maps_v2_t {
name: maps.name,
base: maps.base,
size: maps.size,
depth: maps.depth,
r#type: maps.r#type,
padding1: Default::default(),
u: InfoMapsTypeUnionV2 {
mapping: zx_info_maps_mapping_v2_t {
mmu_flags: mapping.mmu_flags,
padding1: Default::default(),
vmo_koid: mapping.vmo_koid,
vmo_offset: mapping.vmo_offset,
committed_pages: mapping.committed_bytes >> PAGE_SHIFT,
populated_pages: mapping.populated_bytes >> PAGE_SHIFT,
},
},
}
}
}
/// Converts a [`zx_info_maps_t`] to the requested versioned maps info record `T`.
#[inline]
pub fn maps_info_to_version<T: MapsInfoVersion>(maps: &zx_info_maps_t) -> T {
T::from_maps_info(maps)
}
/// Trait for converting a [`zx_info_kmem_stats_t`] to a versioned kernel memory stats record.
pub trait KernelStatsInfoVersion: sealed::Sealed {
/// Converts `stats` to this versioned kernel memory stats type.
fn from_kmem_stats_info(stats: &zx_info_kmem_stats_t) -> Self;
}
impl sealed::Sealed for zx_info_kmem_stats_t {}
impl KernelStatsInfoVersion for zx_info_kmem_stats_t {
#[inline]
fn from_kmem_stats_info(stats: &zx_info_kmem_stats_t) -> Self {
*stats
}
}
impl sealed::Sealed for zx_info_kmem_stats_v1_t {}
impl KernelStatsInfoVersion for zx_info_kmem_stats_v1_t {
#[inline]
fn from_kmem_stats_info(stats: &zx_info_kmem_stats_t) -> Self {
zx_info_kmem_stats_v1_t {
total_bytes: stats.total_bytes,
free_bytes: stats.free_bytes + stats.free_loaned_bytes,
wired_bytes: stats.wired_bytes,
total_heap_bytes: stats.total_heap_bytes,
free_heap_bytes: stats.free_heap_bytes,
vmo_bytes: stats.vmo_bytes,
mmu_overhead_bytes: stats.mmu_overhead_bytes,
ipc_bytes: stats.ipc_bytes,
other_bytes: stats.other_bytes,
}
}
}
impl sealed::Sealed for zx_info_kmem_stats_extended_t {}
impl KernelStatsInfoVersion for zx_info_kmem_stats_extended_t {
#[inline]
fn from_kmem_stats_info(stats: &zx_info_kmem_stats_t) -> Self {
zx_info_kmem_stats_extended_t {
total_bytes: stats.total_bytes,
free_bytes: stats.free_bytes + stats.free_loaned_bytes,
wired_bytes: stats.wired_bytes,
total_heap_bytes: stats.total_heap_bytes,
free_heap_bytes: stats.free_heap_bytes,
vmo_bytes: stats.vmo_bytes,
mmu_overhead_bytes: stats.mmu_overhead_bytes,
ipc_bytes: stats.ipc_bytes,
other_bytes: stats.other_bytes,
vmo_pager_total_bytes: stats.vmo_reclaim_total_bytes,
vmo_pager_newest_bytes: stats.vmo_reclaim_newest_bytes,
vmo_pager_oldest_bytes: stats.vmo_reclaim_oldest_bytes,
vmo_discardable_locked_bytes: stats.vmo_discardable_locked_bytes,
vmo_discardable_unlocked_bytes: stats.vmo_discardable_unlocked_bytes,
vmo_reclaim_disable_bytes: stats.vmo_reclaim_disabled_bytes,
}
}
}
/// Converts a [`zx_info_kmem_stats_t`] to the requested versioned kernel memory stats record `T`.
#[inline]
pub fn kernel_stats_info_to_version<T: KernelStatsInfoVersion>(stats: &zx_info_kmem_stats_t) -> T {
T::from_kmem_stats_info(stats)
}
// TODO: figure out a better handle to hang this off to and push this copy code into
// that dispatcher.
/// Returns system-wide kernel memory statistics.
pub fn get_memory_stats() -> zx_info_kmem_stats_t {
// `get_count` returns an estimate so the sum of the counts may not equal the total.
let mut state_count = [0u64; VmPageState::COUNT];
for state in VmPageState::ALL {
state_count[state.index()] = vm_page::get_count(state);
}
let (_total_heap_bytes, free_heap_bytes) = heap::get_info();
let free_heap_bytes = free_heap_bytes as u64;
// Note that this intentionally uses u64 instead of
// usize in case we ever have a 32-bit userspace but more
// than 4GB physical memory.
let mut stats =
zx_info_kmem_stats_t { total_bytes: pmm::count_total_bytes(), ..Default::default() };
// Holds the sum of bytes in the broken out states. This sum could be less than the total
// because we aren't counting all possible states (e.g. VmPageState::ALLOC). This sum could
// be greater than the total because per-state counts are approximate.
let mut sum_bytes: u64 = 0;
let page_size = PAGE_SIZE as u64;
stats.free_bytes = state_count[VmPageState::FREE.index()] * page_size;
sum_bytes += stats.free_bytes;
stats.free_loaned_bytes = state_count[VmPageState::FREE_LOANED.index()] * page_size;
sum_bytes += stats.free_loaned_bytes;
stats.wired_bytes = state_count[VmPageState::WIRED.index()] * page_size;
sum_bytes += stats.wired_bytes;
stats.total_heap_bytes = state_count[VmPageState::HEAP.index()] * page_size;
sum_bytes += stats.total_heap_bytes;
stats.free_heap_bytes = free_heap_bytes;
stats.vmo_bytes = state_count[VmPageState::OBJECT.index()] * page_size;
sum_bytes += stats.vmo_bytes;
stats.mmu_overhead_bytes = (state_count[VmPageState::MMU.index()]
+ state_count[VmPageState::IOMMU.index()])
* page_size;
sum_bytes += stats.mmu_overhead_bytes;
stats.ipc_bytes = state_count[VmPageState::IPC.index()] * page_size;
sum_bytes += stats.ipc_bytes;
stats.cache_bytes = state_count[VmPageState::CACHE.index()] * page_size;
sum_bytes += state_count[VmPageState::CACHE.index()] * page_size;
stats.slab_bytes = state_count[VmPageState::SLAB.index()] * page_size;
sum_bytes += state_count[VmPageState::SLAB.index()] * page_size;
stats.zram_bytes = state_count[VmPageState::ZRAM.index()] * page_size;
sum_bytes += state_count[VmPageState::ZRAM.index()] * page_size;
// Is there unaccounted memory?
if stats.total_bytes > sum_bytes {
// Everything else gets counted as "other".
stats.other_bytes = stats.total_bytes - sum_bytes;
} else {
// One or more of our per-state counts may have been off. We'll ignore it.
stats.other_bytes = 0;
}
let reclaim_counts: ReclaimCounts = pmm::page_queues().get_reclaim_queue_counts();
let queue_counts: Counts = pmm::page_queues().queue_counts();
stats.vmo_reclaim_total_bytes = reclaim_counts.total as u64 * page_size;
stats.vmo_reclaim_newest_bytes = reclaim_counts.newest as u64 * page_size;
stats.vmo_reclaim_oldest_bytes = reclaim_counts.oldest as u64 * page_size;
stats.vmo_reclaim_disabled_bytes = queue_counts.high_priority as u64 * page_size;
let discardable_counts: DiscardablePageCounts =
DiscardableVmoTracker::debug_discardable_page_counts();
stats.vmo_discardable_locked_bytes = discardable_counts.locked * page_size;
stats.vmo_discardable_unlocked_bytes = discardable_counts.unlocked * page_size;
stats
}
/// Returns system-wide page compression statistics.
pub fn get_compression_stats() -> zx_info_kmem_stats_compression_t {
let Some(compression) = pmm::node().get_page_compression() else {
return zx_info_kmem_stats_compression_t::default();
};
let mut kstats = zx_info_kmem_stats_compression_t::default();
let stats = compression.get_stats();
kstats.uncompressed_storage_bytes = stats.memory_usage.uncompressed_content_bytes;
kstats.compressed_storage_bytes = stats.memory_usage.compressed_storage_bytes;
kstats.compressed_fragmentation_bytes = stats.memory_usage.compressed_storage_bytes
- stats.memory_usage.compressed_storage_used_bytes;
kstats.compression_time = stats.compression_time;
kstats.decompression_time = stats.decompression_time;
kstats.total_page_compression_attempts = stats.total_page_compression_attempts;
kstats.failed_page_compression_attempts = stats.failed_page_compression_attempts;
kstats.total_page_decompressions = stats.total_page_decompressions;
kstats.compressed_page_evictions = stats.compressed_page_evictions;
kstats.eager_page_compressions = PageQueues::get_lru_pages_compressed();
let evictor_stats: EvictorStats = Evictor::get_global_stats();
kstats.memory_pressure_page_compressions = evictor_stats.compression_other;
kstats.critical_memory_page_compressions = evictor_stats.compression_oom;
kstats.pages_decompressed_unit_ns = DurationMono::from_seconds(1).into_nanos() as u64;
zr::static_assert!(8 <= VmCompression::NUM_LOG_BUCKETS);
for i in 0..8 {
kstats.pages_decompressed_within_log_time[i] =
stats.pages_decompressed_within_log_seconds[i];
}
kstats
}
/// Returns system-wide memory stall statistics.
pub fn get_stall_stats() -> zx_info_memory_stall_t {
let stats = StallAggregator::get().read_stats();
zx_info_memory_stall_t {
stall_time_some: stats.stalled_time_some.into_nanos(),
stall_time_full: stats.stalled_time_full.into_nanos(),
}
}