use std::alloc::{GlobalAlloc, Layout};
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
use rusty_mp3::{Mp3Decoder, Mp3Encoder, Mp3EncoderConfig};
static N_ALLOC: AtomicUsize = AtomicUsize::new(0);
static N_ZEROED: AtomicUsize = AtomicUsize::new(0);
static N_REALLOC: AtomicUsize = AtomicUsize::new(0);
static N_FREE: AtomicUsize = AtomicUsize::new(0);
static BYTES: AtomicUsize = AtomicUsize::new(0);
static MOVED: AtomicUsize = AtomicUsize::new(0);
static LIVE: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);
fn grow_live(by: usize) {
let now = LIVE.fetch_add(by, Relaxed) + by;
PEAK.fetch_max(now, Relaxed);
}
const SLOTS: usize = 512;
static SIZE_KEY: [AtomicUsize; SLOTS] = [const { AtomicUsize::new(0) }; SLOTS];
static SIZE_N: [AtomicUsize; SLOTS] = [const { AtomicUsize::new(0) }; SLOTS];
static SIZES_ON: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
static TRACE_SIZE: AtomicUsize = AtomicUsize::new(usize::MAX);
static TRACE_LATCH: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
fn tally_size(size: usize) {
if !SIZES_ON.load(Relaxed) {
return;
}
if size == TRACE_SIZE.load(Relaxed) && !TRACE_LATCH.swap(true, Relaxed) {
eprintln!(
"allocation of {size} B:\n{}",
std::backtrace::Backtrace::force_capture()
);
}
let key = size + 1; let mut i = (size.wrapping_mul(0x9E37_79B9)) % SLOTS;
for _ in 0..SLOTS {
match SIZE_KEY[i].compare_exchange(0, key, Relaxed, Relaxed) {
Ok(_) => {
SIZE_N[i].fetch_add(1, Relaxed);
return;
}
Err(k) if k == key => {
SIZE_N[i].fetch_add(1, Relaxed);
return;
}
Err(_) => i = (i + 1) % SLOTS,
}
}
}
struct Counting;
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
N_ALLOC.fetch_add(1, Relaxed);
BYTES.fetch_add(l.size(), Relaxed);
grow_live(l.size());
tally_size(l.size());
unsafe { rusty_alloc_api::RustyAlloc.alloc(l) }
}
unsafe fn alloc_zeroed(&self, l: Layout) -> *mut u8 {
N_ZEROED.fetch_add(1, Relaxed);
BYTES.fetch_add(l.size(), Relaxed);
grow_live(l.size());
tally_size(l.size());
unsafe { rusty_alloc_api::RustyAlloc.alloc_zeroed(l) }
}
unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 {
N_REALLOC.fetch_add(1, Relaxed);
BYTES.fetch_add(new.saturating_sub(l.size()), Relaxed);
MOVED.fetch_add(l.size(), Relaxed);
if new >= l.size() {
grow_live(new - l.size());
} else {
LIVE.fetch_sub(l.size() - new, Relaxed);
}
unsafe { rusty_alloc_api::RustyAlloc.realloc(p, l, new) }
}
unsafe fn dealloc(&self, p: *mut u8, l: Layout) {
N_FREE.fetch_add(1, Relaxed);
LIVE.fetch_sub(l.size(), Relaxed);
unsafe { rusty_alloc_api::RustyAlloc.dealloc(p, l) }
}
}
#[global_allocator]
static GLOBAL_ALLOC: Counting = Counting;
#[derive(Clone, Copy)]
struct Snap {
alloc: usize,
zeroed: usize,
realloc: usize,
free: usize,
bytes: usize,
}
fn snap() -> Snap {
Snap {
alloc: N_ALLOC.load(Relaxed),
zeroed: N_ZEROED.load(Relaxed),
realloc: N_REALLOC.load(Relaxed),
free: N_FREE.load(Relaxed),
bytes: BYTES.load(Relaxed),
}
}
impl Snap {
fn since(self, base: Self) -> Self {
Self {
alloc: self.alloc - base.alloc,
zeroed: self.zeroed - base.zeroed,
realloc: self.realloc - base.realloc,
free: self.free - base.free,
bytes: self.bytes - base.bytes,
}
}
fn total(self) -> usize {
self.alloc + self.zeroed + self.realloc
}
}
const SR: u32 = 44_100;
const CH: u16 = 2;
const SPF: usize = 1152;
fn make_pcm(frames: usize) -> Vec<f32> {
let n = frames * SPF;
let mut out = Vec::with_capacity(n * CH as usize);
let mut lcg: u32 = 0x1234_5678;
for i in 0..n {
lcg = lcg.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
let noise = (lcg >> 9) as f32 / (1 << 23) as f32 - 0.5;
let t = i as f32 / SR as f32;
let base = 0.34 * (2.0 * std::f32::consts::PI * 220.0 * t).sin()
+ 0.22 * (2.0 * std::f32::consts::PI * 440.0 * t).sin()
+ 0.11 * (2.0 * std::f32::consts::PI * 1760.0 * t).sin();
out.push((base + 0.04 * noise).clamp(-1.0, 1.0));
out.push((base * 0.92 + 0.05 * noise).clamp(-1.0, 1.0));
}
out
}
fn fnv1a(bytes: &[u8]) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for &b in bytes {
h ^= u64::from(b);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
h
}
fn main() {
let args: Vec<String> = std::env::args().skip(1).collect();
let frames: usize = args.first().and_then(|s| s.parse().ok()).unwrap_or(200);
let kbps: u32 = args.get(1).and_then(|s| s.parse().ok()).unwrap_or(192);
let pcm = make_pcm(frames);
let mut enc = Mp3Encoder::new(Mp3EncoderConfig {
bitrate_kbps: kbps,
vbr_quality: None,
});
let mut mp3: Vec<u8> = Vec::with_capacity(frames * 1024);
let sizes = std::env::var_os("ALLOCAUDIT_SIZES").is_some();
if let Some(n) = std::env::var("ALLOCAUDIT_TRACE")
.ok()
.and_then(|v| v.parse().ok())
{
TRACE_SIZE.store(n, Relaxed);
}
SIZES_ON.store(sizes || TRACE_SIZE.load(Relaxed) != usize::MAX, Relaxed);
let t0 = snap();
for f in 0..frames {
let s = f * SPF * CH as usize;
let e = s + SPF * CH as usize;
enc.push_pcm_f32(&pcm[s..e], CH, SR).unwrap();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
}
enc.finish();
while let Ok(p) = enc.next_packet() {
mp3.extend_from_slice(&p);
}
let enc_stats = snap().since(t0);
SIZES_ON.store(false, Relaxed);
let mut dec = Mp3Decoder::new();
let mut pcm_out = 0usize;
let t1 = snap();
dec.push(&mp3);
dec.flush();
while let Ok(frame) = dec.next_frame() {
pcm_out += frame.samples.len() / frame.channels.max(1) as usize;
}
let dec_stats = snap().since(t1);
let s16: Vec<i16> = pcm.iter().map(|&x| (x * 32767.0) as i16).collect();
let mut enc2 = Mp3Encoder::new(Mp3EncoderConfig {
bitrate_kbps: kbps,
vbr_quality: None,
});
let mut mp3_whole: Vec<u8> = Vec::with_capacity(frames * 1024);
let (moved0, live0) = (MOVED.load(Relaxed), LIVE.load(Relaxed));
PEAK.store(live0, Relaxed);
let t2 = snap();
enc2.push_pcm_s16(&s16, CH, SR).unwrap();
enc2.finish();
while let Ok(p) = enc2.next_packet() {
mp3_whole.extend_from_slice(&p);
}
let whole_stats = snap().since(t2);
let whole_moved = MOVED.load(Relaxed) - moved0;
let whole_peak = PEAK.load(Relaxed) - live0;
let whole_hash = fnv1a(&mp3_whole);
let (ef, df) = (frames.max(1), frames.max(1));
let mp3_len = mp3.len();
let mp3_hash = fnv1a(&mp3);
println!("rusty_mp3 allocation audit — under rusty_alloc (counting shim delegates to it)");
println!(
" workload: {frames} frames, {CH} ch @ {SR} Hz, CBR {kbps}k -> {mp3_len} bytes, \
{pcm_out} decoded samples/ch"
);
println!(" bitstream fnv1a: {mp3_hash:#018x} <- byte-identity gate\n");
println!(
" {:<8} {:>9} {:>9} {:>9} {:>9} {:>11} {:>12}",
"phase", "alloc", "zeroed", "realloc", "free", "total", "per-frame"
);
for (name, s, per) in [("encode", enc_stats, ef), ("decode", dec_stats, df)] {
println!(
" {:<8} {:>9} {:>9} {:>9} {:>9} {:>11} {:>12.2}",
name,
s.alloc,
s.zeroed,
s.realloc,
s.free,
s.total(),
s.total() as f64 / per as f64
);
}
println!(
"\n bytes requested: encode {} KiB, decode {} KiB",
enc_stats.bytes / 1024,
dec_stats.bytes / 1024
);
println!(
" zeroed share of encode allocations: {:.1}% \
(the vec![0f32; n] pattern — zero-fill paid, then overwritten)",
100.0 * enc_stats.zeroed as f64 / enc_stats.total().max(1) as f64
);
println!(
"\n encode, whole input in ONE s16 push (the CLI/WAV shape):\n \
{} allocs ({:.2}/frame), {} KiB requested, {} KiB carried by realloc, \
peak live +{} KiB\n bitstream fnv1a: {whole_hash:#018x}",
whole_stats.total(),
whole_stats.total() as f64 / ef as f64,
whole_stats.bytes / 1024,
whole_moved / 1024,
whole_peak / 1024,
);
if sizes {
let mut rows: Vec<(usize, usize)> = (0..SLOTS)
.filter_map(|i| {
let k = SIZE_KEY[i].load(Relaxed);
(k != 0).then(|| (k - 1, SIZE_N[i].load(Relaxed)))
})
.collect();
rows.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
println!(
"
streamed-encode allocation sizes (bytes x count, per frame):"
);
for (size, n) in rows.iter().take(16) {
println!(
" {size:>8} B x {n:>7} {:.2}/frame",
*n as f64 / ef as f64
);
}
}
}