use ic_platform::stable_structures::{Memory, VectorMemory};
use std::{cell::Cell, rc::Rc};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MemoryIoCounts {
pub reads: u64,
pub read_bytes: u64,
pub unsafe_reads: u64,
pub unsafe_read_bytes: u64,
pub writes: u64,
pub write_bytes: u64,
pub grows: u64,
pub grown_pages: u64,
pub overflowed: bool,
}
pub struct MeteredMemory<M: Memory = VectorMemory> {
state: Rc<MemoryState<M>>,
}
struct MemoryState<M> {
memory: M,
counts: Cell<MemoryIoCounts>,
}
impl<M: Memory> MeteredMemory<M> {
#[must_use]
pub fn new(memory: M) -> Self {
Self {
state: Rc::new(MemoryState {
memory,
counts: Cell::default(),
}),
}
}
#[must_use]
pub fn backing(&self) -> &M {
&self.state.memory
}
pub fn reset(&self) {
self.state.counts.set(MemoryIoCounts::default());
}
#[must_use]
pub fn counts(&self) -> MemoryIoCounts {
self.state.counts.get()
}
fn record_read(&self, bytes: usize, unsafe_read: bool) {
let mut counts = self.state.counts.get();
add_count(&mut counts.reads, 1, &mut counts.overflowed);
add_count(&mut counts.read_bytes, bytes as u64, &mut counts.overflowed);
if unsafe_read {
add_count(&mut counts.unsafe_reads, 1, &mut counts.overflowed);
add_count(
&mut counts.unsafe_read_bytes,
bytes as u64,
&mut counts.overflowed,
);
}
self.state.counts.set(counts);
}
}
impl<M: Memory> Clone for MeteredMemory<M> {
fn clone(&self) -> Self {
Self {
state: Rc::clone(&self.state),
}
}
}
impl<M: Memory + Default> Default for MeteredMemory<M> {
fn default() -> Self {
Self::new(M::default())
}
}
impl<M: Memory> Memory for MeteredMemory<M> {
fn size(&self) -> u64 {
self.state.memory.size()
}
fn grow(&self, pages: u64) -> i64 {
let result = self.state.memory.grow(pages);
let mut counts = self.state.counts.get();
add_count(&mut counts.grows, 1, &mut counts.overflowed);
if result >= 0 {
add_count(&mut counts.grown_pages, pages, &mut counts.overflowed);
}
self.state.counts.set(counts);
result
}
fn read(&self, offset: u64, dst: &mut [u8]) {
self.record_read(dst.len(), false);
self.state.memory.read(offset, dst);
}
#[expect(
unsafe_code,
reason = "forward the caller's Memory::read_unsafe contract without changing its buffer"
)]
unsafe fn read_unsafe(&self, offset: u64, dst: *mut u8, count: usize) {
self.record_read(count, true);
unsafe { self.state.memory.read_unsafe(offset, dst, count) }
}
fn write(&self, offset: u64, src: &[u8]) {
let mut counts = self.state.counts.get();
add_count(&mut counts.writes, 1, &mut counts.overflowed);
add_count(
&mut counts.write_bytes,
src.len() as u64,
&mut counts.overflowed,
);
self.state.counts.set(counts);
self.state.memory.write(offset, src);
}
}
const fn add_count(value: &mut u64, increment: u64, overflowed: &mut bool) {
let (sum, overflow) = value.overflowing_add(increment);
*value = if overflow { u64::MAX } else { sum };
*overflowed |= overflow;
}
#[cfg(test)]
mod tests;