Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Support memory decommission of instance memory on macOS #9164

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion client/executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ sc-tracing = { version = "3.0.0", path = "../tracing" }
tracing = "0.1.25"
tracing-subscriber = "0.2.18"
paste = "1.0"
regex = "1"

[features]
default = [ "std" ]
Expand Down
6 changes: 6 additions & 0 deletions client/executor/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[target.'cfg(target_os = "macos")'.dependencies]
mach = "0.3"

[target.'cfg(target_os = "linux")'.dependencies]
regex = "1"

[dependencies]
derive_more = "0.99.2"
pwasm-utils = "0.18.0"
Expand Down
1 change: 1 addition & 0 deletions client/executor/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ pub mod error;
pub mod sandbox;
pub mod wasm_runtime;
pub mod runtime_blob;
pub mod test_utils;
32 changes: 32 additions & 0 deletions client/executor/common/src/test_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// This file is part of Substrate.

// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Helper functions that are useful during testing. Those are mostly operating system
//! dependend because they are used to inspect the current state of the system.

#[cfg(target_os = "linux")]
mod linux;

#[cfg(target_os = "linux")]
pub use linux::*;

#[cfg(target_os = "macos")]
mod macos;

#[cfg(target_os = "macos")]
pub use macos::*;
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! A tool for extracting information about the memory consumption of the current process from
//! the procfs.
//! Implementation of Linux specific tests and/or helper functions.

use std::ops::Range;
use std::collections::BTreeMap;
use crate::wasm_runtime::WasmInstance;
use std::{
ops::Range,
collections::BTreeMap,
};

/// Returns how much bytes of the instance's memory is currently resident (backed by phys mem)
pub fn instance_resident_bytes(instance: &dyn WasmInstance) -> usize {
let base_addr = instance.linear_memory_range().unwrap().start;
Smaps::new().get_rss(base_addr).expect("failed to get rss")
}

/// An interface to the /proc/self/smaps
///
Expand All @@ -30,6 +38,7 @@ use std::collections::BTreeMap;
pub struct Smaps(Vec<(Range<usize>, BTreeMap<String, usize>)>);

impl Smaps {
/// Create a in-memory representation of the calling processe's memory map.
pub fn new() -> Self {
let regex_start = regex::RegexBuilder::new("^([0-9a-f]+)-([0-9a-f]+)")
.multi_line(true)
Expand Down Expand Up @@ -68,15 +77,18 @@ impl Smaps {
Self(output)
}

/// Returns how much memory is currently resident in the memory mapping that is
/// associated with the specified address.
pub fn get_rss(&self, addr: usize) -> Option<usize> {
self.get_map(addr).get("Rss").cloned()
}

/// Get the mapping at the specified address.
fn get_map(&self, addr: usize) -> &BTreeMap<String, usize> {
&self.0
.iter()
.find(|(range, _)| addr >= range.start && addr < range.end)
.unwrap()
.1
}

pub fn get_rss(&self, addr: usize) -> Option<usize> {
self.get_map(addr).get("Rss").cloned()
}
}
109 changes: 109 additions & 0 deletions client/executor/common/src/test_utils/macos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// This file is part of Substrate.

// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Implementation of macOS specific tests and/or helper functions.

use crate::wasm_runtime::WasmInstance;
use std::{convert::TryInto, mem::MaybeUninit, ops::Range, fmt};
use mach::{
kern_return::KERN_SUCCESS,
traps::mach_task_self,
vm::mach_vm_region,
vm_page_size::vm_page_shift,
vm_region::{vm_region_extended_info, vm_region_info_t, VM_REGION_EXTENDED_INFO},
};

/// Size and metadata of a memory mapped region.
pub struct Region {
/// The virtual memory range (addr..addr + size) of the region.
pub range: Range<u64>,
/// Metadata describing the memory mapping.
pub info: vm_region_extended_info,
}

impl fmt::Display for Region {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{:016x?}: {:#?}", self.range, self.info)
}
}

impl Region {
/// The length of the covered area in bytes.
pub fn len(&self) -> u64 {
self.range.end - self.range.start
}
}

/// Returns how much bytes of the instance's memory is currently resident (backed by phys mem)
pub fn instance_resident_bytes(instance: &dyn WasmInstance) -> usize {
let range = instance.linear_memory_range().unwrap();
let regions = get_regions((range.start as u64)..(range.end as u64)).unwrap();
assert_ne!(regions.len(), 0);
let resident_pages: u64 = regions.iter().map(|r| u64::from(r.info.pages_resident)).sum();
let resident_size = unsafe { resident_pages << vm_page_shift };
resident_size.try_into().unwrap()
}

/// Get all consecutive memory mappings that lie inside the specified range.
///
/// Returns an error if some parts of the range are unmapped.
pub fn get_regions(range: Range<u64>) -> Result<Vec<Region>, String> {
let mut regions = Vec::new();
let mut addr = range.start;

loop {
let mut size = MaybeUninit::<u64>::uninit();
let mut info = MaybeUninit::<vm_region_extended_info>::uninit();
let result = unsafe {
mach_vm_region(
mach_task_self(),
&mut addr,
size.as_mut_ptr(),
VM_REGION_EXTENDED_INFO,
(info.as_mut_ptr()) as vm_region_info_t,
&mut vm_region_extended_info::count(),
&mut 0,
)
};
assert_eq!(result, KERN_SUCCESS, "mach_vm_region returned an error");
if result != KERN_SUCCESS {
Err(format!("Failed to get region at address 0x{:016x} with error {}", addr, result))?;
}

let size = unsafe { size.assume_init() };
let info = unsafe { info.assume_init() };

// We only consider mappings that are fully enclosed by the supplied range
if addr < range.start || addr + size > range.end {
break;
}

regions.push(Region {
range: addr..(addr + size),
info,
});

// Check whether this is the last region.
addr += size;
if addr == range.end {
break;
}
}

Ok(regions)
}
11 changes: 7 additions & 4 deletions client/executor/common/src/wasm_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

use crate::error::Error;
use sp_wasm_interface::Value;
use std::ops::Range;

/// A method to be used to find the entrypoint when calling into the runtime
///
Expand Down Expand Up @@ -94,12 +95,14 @@ pub trait WasmInstance: Send {
/// This method is only suitable for getting immutable globals.
fn get_global_const(&self, name: &str) -> Result<Option<Value>, Error>;

/// **Testing Only**. This function returns the base address of the linear memory.
/// **Testing Only**. This function returns the memory range covered by linear memory.
///
/// This is meant to be the starting address of the memory mapped area for the linear memory.
/// The start of the range is the starting address of the memory mapped area for the
/// linear memory as host memory virtual address.
///
/// This function is intended only for a specific test that measures physical memory consumption.
fn linear_memory_base_ptr(&self) -> Option<*const u8> {
/// This function is intended only for a specific test that measures physical memory
/// consumption.
fn linear_memory_range(&self) -> Option<Range<usize>> {
None
}
}
3 changes: 1 addition & 2 deletions client/executor/src/integration_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#[cfg(target_os = "linux")]
mod linux;
mod resident_memory;
mod sandbox;

use std::sync::Arc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,22 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Tests that are only relevant for Linux.
//! Tests that that resident memory is handled correctly.

// Constrain this only to wasmtime for the time being. Without this rustc will complain on unused
// imports and items. The alternative is to plop `cfg(feature = wasmtime)` everywhere which seems
// borthersome.
// bothersome.
#![cfg(feature = "wasmtime")]

use crate::WasmExecutionMethod;
use super::mk_test_runtime;
use codec::Encode as _;

mod smaps;
// As of right now we don't have windows support for inspecting resident memory.
#![cfg(any(target_os = "linux", target_os = "macos"))]

use self::smaps::Smaps;
use crate::{
WasmExecutionMethod,
integration_tests::mk_test_runtime,
};
use codec::Encode as _;
use sc_executor_common::test_utils::instance_resident_bytes;

#[test]
fn memory_consumption_compiled() {
Expand All @@ -48,25 +50,20 @@ fn memory_consumption_compiled() {
.as_i32()
.expect("`__heap_base` is an `i32`");

fn probe_rss(instance: &dyn sc_executor_common::wasm_runtime::WasmInstance) -> usize {
let base_addr = instance.linear_memory_base_ptr().unwrap() as usize;
Smaps::new().get_rss(base_addr).expect("failed to get rss")
}

instance
.call_export(
"test_dirty_plenty_memory",
&(heap_base as u32, 1u32).encode(),
)
.unwrap();
let probe_1 = probe_rss(&*instance);
let probe_1 = instance_resident_bytes(&*instance);
instance
.call_export(
"test_dirty_plenty_memory",
&(heap_base as u32, 1024u32).encode(),
)
.unwrap();
let probe_2 = probe_rss(&*instance);
let probe_2 = instance_resident_bytes(&*instance);

assert_eq!(probe_1, 0);
assert_eq!(probe_2, 0);
Expand Down
4 changes: 4 additions & 0 deletions client/executor/wasmtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[target.'cfg(target_os = "macos")'.dependencies]
mach = "0.3"
bitflags = "1"

[dependencies]
libc = "0.2.90"
cfg-if = "1.0"
Expand Down
Loading