#!/bin/bash
# xcache_v8.sh — drain+spray in one Python coprocess to minimize delay
# between cg-256 free and rnd-XX-256 alloc. Buddy LIFO is hot but volatile;
# any delay lets system allocators steal the just-freed pages.

set +e
DBG=/sys/kernel/debug/slab_observe
PRESSURE_MB=${1:-0}

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"
}

snapshot_all_rnd() {
    local out=$1
    > "$out"
    for i in $(seq -f "%02g" 0 15); do
        snapshot_all "kmalloc-rnd-${i}-256" /tmp/rnd_${i}.tmp
        cat /tmp/rnd_${i}.tmp >> "$out"
    done
    sort -u "$out" -o "$out"
}

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

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

echo "=== xcache_v8 (coprocess, pressure=${PRESSURE_MB}MB) ==="

ipcs -q | awk '/^0x/{print $2}' | xargs -I{} ipcrm -q {} 2>/dev/null
keyctl clear @p 2>/dev/null

# Phase 0: clean baseline.
shrink kmalloc-cg-256
for i in $(seq -f "%02g" 0 15); do shrink "kmalloc-rnd-${i}-256"; done
sleep 1
snapshot_all "kmalloc-cg-256" /tmp/cg_baseline.pages
snapshot_all_rnd /tmp/rnd_baseline.pages
echo "[Baseline] cg=$(wc -l </tmp/cg_baseline.pages) rnd=$(wc -l </tmp/rnd_baseline.pages)"

# Optional pressure.
if [ "$PRESSURE_MB" -gt 0 ]; then
    nohup python3 -c "
import mmap, time, os
N = ${PRESSURE_MB} * 1024 * 1024
m = mmap.mmap(-1, N)
for i in range(0, N, 4096): m[i] = 1
print(os.getpid(), flush=True)
time.sleep(120)
" > /tmp/press.out 2>&1 &
    disown
    sleep 4
    PRESS_PID=$(head -1 /tmp/press.out)
    echo "[Pressure] ${PRESSURE_MB} MB allocated (pid=$PRESS_PID)"
fi

# Spawn Python coprocess; use bash's coproc keyword for proper bidirectional FDs.
coproc COX { python3 /tmp/xcache_unified.py 2048 4096 2>&1; }
COPROC_PID=$COX_PID

read_cox_until() {
    local marker=$1
    while IFS= read -r -u "${COX[0]}" line; do
        echo "  [cox] $line"
        [[ "$line" == "$marker"* ]] && return 0
    done
    return 1
}

# Phase 1: wait for FILLED.
read_cox_until "FILLED"
read_cox_until "WAIT_SNAPSHOT_FILLED"

snapshot_all "kmalloc-cg-256" /tmp/cg_filled.pages
n_filled=$(wc -l </tmp/cg_filled.pages)
echo "[Phase 1] cg-256 filled: $n_filled pages"

# Tell coprocess to drain+spray.
echo "GO" >&"${COX[1]}"

# Wait for SPRAYED.
read_cox_until "DRAINED"
read_cox_until "SPRAYED"
read_cox_until "WAIT_SNAPSHOT_AFTER"

# Snapshot AS FAST AS POSSIBLE.
snapshot_all "kmalloc-cg-256" /tmp/cg_after.pages
n_cg_after=$(wc -l </tmp/cg_after.pages)
snapshot_all_rnd /tmp/rnd_after.pages
n_rnd_after=$(wc -l </tmp/rnd_after.pages)
echo "[Phase 2] cg-256 after: $n_cg_after  rnd-XX-256 after: $n_rnd_after"

# Tell coprocess to clean up.
echo "GO" >&"${COX[1]}"
read_cox_until "DONE"
wait $COPROC_PID 2>/dev/null

# Compute sets.
comm -23 /tmp/cg_filled.pages /tmp/cg_after.pages > /tmp/cg_freed.pages
comm -23 /tmp/rnd_after.pages /tmp/rnd_baseline.pages > /tmp/rnd_added.pages
comm -12 /tmp/cg_freed.pages /tmp/rnd_added.pages > /tmp/xcache_hit.pages

n_freed=$(wc -l </tmp/cg_freed.pages)
n_added=$(wc -l </tmp/rnd_added.pages)
hits=$(wc -l </tmp/xcache_hit.pages)
rate="n/a"
[ "$n_freed" -gt 0 ] && rate=$(awk -v h=$hits -v c=$n_freed 'BEGIN{printf "%.1f%%", h/c*100}')

echo
printf "RESULT  pressure=%dMB  filled=%d  freed=%d  rnd_added=%d  hits=%d  rate=%s\n" \
    "$PRESSURE_MB" "$n_filled" "$n_freed" "$n_added" "$hits" "$rate"
