#!/bin/bash
# xcache_natural.sh — How much cg-256 returns to buddy from NATURAL drain
# (no shrink, no memory pressure). Tests the lower bound: what min_partial
# overflow alone gives us.

set +e
DBG=/sys/kernel/debug/slab_observe

snapshot_all() {
    local cache=$1 out=$2
    echo "$cache" > "$DBG/target"
    sed -nE 's/^pfn=([0-9a-f]+) addr=([0-9a-f]+).*/\2/p' "$DBG/dump_all" | sort -u > "$out"
}

shrink() {
    echo fuzz | sudo -S sh -c "echo 1 > /sys/kernel/slab/$1/shrink" 2>/dev/null
}

cleanup() {
    [ -n "$CG_PID" ] && kill -9 $CG_PID 2>/dev/null
    ipcs -q | awk '/^0x/{print $2}' | xargs -I{} ipcrm -q {} 2>/dev/null
}
trap cleanup EXIT

echo "=== Natural-drain test (no shrink, no pressure) ==="
ipcs -q | awk '/^0x/{print $2}' | xargs -I{} ipcrm -q {} 2>/dev/null

# Pre-shrink baseline.
shrink kmalloc-cg-256
sleep 1
snapshot_all "kmalloc-cg-256" /tmp/cg_baseline.pages
echo "[T0] cg-256 pages: $(wc -l </tmp/cg_baseline.pages)"

# Spawn holder.
nohup python3 /tmp/cg256_holder.py 2048 > /tmp/cg.out 2>&1 &
disown
sleep 5
CG_PID=$(awk '/pid=/{gsub(/pid=/,"",$2); print $2}' /tmp/cg.out)
snapshot_all "kmalloc-cg-256" /tmp/cg_filled.pages
echo "[T1, holder up] cg-256 pages: $(wc -l </tmp/cg_filled.pages)"

# Drain.
kill -USR1 $CG_PID
sleep 2

# Sample cg-256 at increasing intervals — see if natural discard happens.
for delay in 1 5 10 30 60; do
    sleep $((delay - $(echo 0)))
    snapshot_all "kmalloc-cg-256" /tmp/cg_after_t${delay}.pages
    echo "[T2 + ${delay}s] cg-256 pages: $(wc -l </tmp/cg_after_t${delay}.pages)"
done

# Compare to shrink.
shrink kmalloc-cg-256
sleep 1
snapshot_all "kmalloc-cg-256" /tmp/cg_after_shrink.pages
echo "[T3, after shrink] cg-256 pages: $(wc -l </tmp/cg_after_shrink.pages)"

# Show what was on partial when natural drain stalled.
echo
echo "[Pre-shrink partial-list state]:"
echo "kmalloc-cg-256" > "$DBG/target"
cat "$DBG/dump_slabs" 2>/dev/null | head -30
