154 lines
5.5 KiB
Rust
154 lines
5.5 KiB
Rust
use std::{
|
|
alloc::{GlobalAlloc, Layout, System},
|
|
hint::black_box,
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
use crank_metrics::{CacheOutcome, record_cache_outcome};
|
|
use metrics_util::debugging::DebuggingRecorder;
|
|
|
|
const SAMPLES: usize = 40;
|
|
const WORK_UNITS: u64 = 500_000;
|
|
const MAX_PROFILE_ALLOCATION_BYTES: usize = 64 * 1024;
|
|
|
|
struct TrackingAllocator;
|
|
|
|
static CURRENT_HEAP_BYTES: AtomicUsize = AtomicUsize::new(0);
|
|
static PEAK_HEAP_BYTES: AtomicUsize = AtomicUsize::new(0);
|
|
static TOTAL_ALLOCATED_BYTES: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
#[global_allocator]
|
|
static ALLOCATOR: TrackingAllocator = TrackingAllocator;
|
|
|
|
unsafe impl GlobalAlloc for TrackingAllocator {
|
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
|
let pointer = unsafe { System.alloc(layout) };
|
|
if !pointer.is_null() {
|
|
record_allocation(layout.size());
|
|
}
|
|
pointer
|
|
}
|
|
|
|
unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
|
|
unsafe { System.dealloc(pointer, layout) };
|
|
CURRENT_HEAP_BYTES.fetch_sub(layout.size(), Ordering::Relaxed);
|
|
}
|
|
|
|
unsafe fn realloc(&self, pointer: *mut u8, old: Layout, new_size: usize) -> *mut u8 {
|
|
let resized = unsafe { System.realloc(pointer, old, new_size) };
|
|
if !resized.is_null() {
|
|
match new_size.cmp(&old.size()) {
|
|
std::cmp::Ordering::Greater => record_allocation(new_size - old.size()),
|
|
std::cmp::Ordering::Less => {
|
|
CURRENT_HEAP_BYTES.fetch_sub(old.size() - new_size, Ordering::Relaxed);
|
|
}
|
|
std::cmp::Ordering::Equal => {}
|
|
}
|
|
}
|
|
resized
|
|
}
|
|
|
|
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
|
|
let pointer = unsafe { System.alloc_zeroed(layout) };
|
|
if !pointer.is_null() {
|
|
record_allocation(layout.size());
|
|
}
|
|
pointer
|
|
}
|
|
}
|
|
|
|
fn record_allocation(bytes: usize) {
|
|
TOTAL_ALLOCATED_BYTES.fetch_add(bytes, Ordering::Relaxed);
|
|
let current = CURRENT_HEAP_BYTES.fetch_add(bytes, Ordering::Relaxed) + bytes;
|
|
PEAK_HEAP_BYTES.fetch_max(current, Ordering::Relaxed);
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct ProfileSample {
|
|
elapsed: Duration,
|
|
allocated_bytes: usize,
|
|
peak_heap_delta: usize,
|
|
}
|
|
|
|
#[test]
|
|
fn reproducible_foundation_profile_stays_inside_latency_and_cpu_proxy_budgets() {
|
|
let recorder = DebuggingRecorder::new();
|
|
let snapshotter = recorder.snapshotter();
|
|
let mut baseline = Vec::with_capacity(SAMPLES);
|
|
let mut enabled = Vec::with_capacity(SAMPLES);
|
|
|
|
metrics::with_local_recorder(&recorder, || {
|
|
record_cache_outcome(CacheOutcome::Hit);
|
|
for index in 0..SAMPLES {
|
|
if index % 2 == 0 {
|
|
baseline.push(sample(false));
|
|
enabled.push(sample(true));
|
|
} else {
|
|
enabled.push(sample(true));
|
|
baseline.push(sample(false));
|
|
}
|
|
}
|
|
});
|
|
|
|
baseline.sort_unstable_by_key(|sample| sample.elapsed);
|
|
enabled.sort_unstable_by_key(|sample| sample.elapsed);
|
|
let baseline_p95 = baseline[SAMPLES * 95 / 100].elapsed;
|
|
let enabled_p95 = enabled[SAMPLES * 95 / 100].elapsed;
|
|
let baseline_cpu: Duration = baseline.iter().map(|sample| sample.elapsed).sum();
|
|
let enabled_cpu: Duration = enabled.iter().map(|sample| sample.elapsed).sum();
|
|
let baseline_allocated: usize = baseline.iter().map(|sample| sample.allocated_bytes).sum();
|
|
let enabled_allocated: usize = enabled.iter().map(|sample| sample.allocated_bytes).sum();
|
|
let baseline_peak = baseline
|
|
.iter()
|
|
.map(|sample| sample.peak_heap_delta)
|
|
.max()
|
|
.unwrap_or_default();
|
|
let enabled_peak = enabled
|
|
.iter()
|
|
.map(|sample| sample.peak_heap_delta)
|
|
.max()
|
|
.unwrap_or_default();
|
|
let series = snapshotter.snapshot().into_vec().len();
|
|
|
|
println!(
|
|
"metrics_profile_v1 samples={SAMPLES} work_units={WORK_UNITS} baseline_p95_ns={} enabled_p95_ns={} baseline_cpu_ns={} enabled_cpu_ns={} baseline_allocated_bytes={baseline_allocated} enabled_allocated_bytes={enabled_allocated} baseline_peak_heap_delta={baseline_peak} enabled_peak_heap_delta={enabled_peak} logical_series={series}",
|
|
baseline_p95.as_nanos(),
|
|
enabled_p95.as_nanos(),
|
|
baseline_cpu.as_nanos(),
|
|
enabled_cpu.as_nanos()
|
|
);
|
|
assert!(enabled_p95.as_nanos() * 100 <= baseline_p95.as_nanos() * 105);
|
|
assert!(enabled_cpu.as_nanos() * 100 <= baseline_cpu.as_nanos() * 110);
|
|
assert!(enabled_allocated <= baseline_allocated + MAX_PROFILE_ALLOCATION_BYTES);
|
|
assert!(enabled_peak <= baseline_peak + MAX_PROFILE_ALLOCATION_BYTES);
|
|
assert_eq!(series, 1);
|
|
}
|
|
|
|
fn sample(with_metrics: bool) -> ProfileSample {
|
|
let heap_before = CURRENT_HEAP_BYTES.load(Ordering::Relaxed);
|
|
let allocated_before = TOTAL_ALLOCATED_BYTES.load(Ordering::Relaxed);
|
|
PEAK_HEAP_BYTES.store(heap_before, Ordering::Relaxed);
|
|
let started = Instant::now();
|
|
let mut value = 0x9e37_79b9_u64;
|
|
for index in 0..WORK_UNITS {
|
|
value = value.rotate_left(7) ^ index.wrapping_mul(0x100_0000_01b3);
|
|
}
|
|
black_box(value);
|
|
if with_metrics {
|
|
record_cache_outcome(CacheOutcome::Hit);
|
|
}
|
|
let elapsed = started.elapsed();
|
|
let allocated_bytes = TOTAL_ALLOCATED_BYTES
|
|
.load(Ordering::Relaxed)
|
|
.saturating_sub(allocated_before);
|
|
let peak_heap_delta = PEAK_HEAP_BYTES
|
|
.load(Ordering::Relaxed)
|
|
.saturating_sub(heap_before);
|
|
ProfileSample {
|
|
elapsed,
|
|
allocated_bytes,
|
|
peak_heap_delta,
|
|
}
|
|
}
|