#!/bin/bash
# xcache_v7_pressure.sh — measure cross-cache crossover rate as a function
# of unprivileged pressure level.
#
# Methodology:
#   1. Pre-shrink for clean baseline (root, one-time setup).
#   2. Allocate cg-256 holder (msg_msg × 2048).
#   3. Drain holder.
#   4. Apply $PRESSURE_MB of unprivileged anon mmap pressure.
#   5. Snapshot cg-256 — count pages returned vs natural-drain baseline.
#   6. Spray rnd-XX-256 via add_key × 4096 (also memory-pressure-ish).
#   7. Snapshot rnd — compute crossover rate.
#
# Vary $PRESSURE_MB across runs to map the curve.

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 "$CG_PID" ] && kill -9 $CG_PID 2>/dev/null
    [ -n "$RND_PID" ] && kill -9 $RND_PID 2>/dev/null
    [ -n "$PRESS_PID" ] && kill -9 $PRESS_PID 2>/dev/null
    ipcs -q | awk '/^0x/{print $2}' | xargs -I{} ipcrm -q {} 2>/dev/null
}
trap cleanup EXIT

echo "=== xcache_v7: unprivileged pressure = ${PRESSURE_MB} MB ==="

# Phase 0: clean baseline (one-time root op, just for clean state).
ipcs -q | awk '/^0x/{print $2}' | xargs -I{} ipcrm -q {} 2>/dev/null
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)"

# Phase 1: cg-256 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
n_filled=$(wc -l </tmp/cg_filled.pages)
echo "[Phase 1] cg-256 with holder: $n_filled pages"

# Phase 2: drain holder.
kill -USR1 $CG_PID
sleep 3

# Phase 3: apply unprivileged pressure.
if [ "$PRESSURE_MB" -gt 0 ]; then
    echo "[Phase 3] Applying ${PRESSURE_MB} MB anon mmap pressure (unprivileged)…"
    nohup python3 -c "
import mmap, time, sys, os
N = ${PRESSURE_MB} * 1024 * 1024
m = mmap.mmap(-1, N)
# touch every page to force population (otherwise no real pressure)
for i in range(0, N, 4096):
    m[i] = 1
print(f'pressure populated; pid={os.getpid()}', flush=True)
time.sleep(60)
" > /tmp/press.out 2>&1 &
    disown
    sleep 5
    cat /tmp/press.out
    PRESS_PID=$(awk '/pid=/{gsub(/pid=/,"",$2); print $2}' /tmp/press.out)
fi

snapshot_all "kmalloc-cg-256" /tmp/cg_after_press.pages
n_after_press=$(wc -l </tmp/cg_after_press.pages)
echo "[Phase 3] cg-256 after drain+pressure: $n_after_press pages"
freed_to_buddy=$((n_filled - n_after_press))
echo "         pages freed to buddy: $freed_to_buddy"

# Phase 4: cg-pages-freed = (filled \ after_press) ∩ (NOT-baseline)
comm -23 /tmp/cg_filled.pages /tmp/cg_after_press.pages > /tmp/cg_freed.pages
echo "[Phase 4] cg-256 freed-pages set: $(wc -l </tmp/cg_freed.pages)"

# Phase 5: spray rnd.
echo "[Phase 5] Spray rnd-XX-256 via add_key × 4096…"
nohup python3 /tmp/rnd256_sprayer.py 4096 > /tmp/rnd.out 2>&1 &
disown
sleep 5
RND_PID=$(awk '/pid=/{gsub(/pid=/,"",$2); print $2}' /tmp/rnd.out)

snapshot_all_rnd /tmp/rnd_after.pages
comm -23 /tmp/rnd_after.pages /tmp/rnd_baseline.pages > /tmp/rnd_added.pages
n_added=$(wc -l </tmp/rnd_added.pages)
echo "[Phase 5] rnd-XX-256 new pages: $n_added"

# Phase 6: intersection.
comm -12 /tmp/cg_freed.pages /tmp/rnd_added.pages > /tmp/xcache_hit.pages
hits=$(wc -l < /tmp/xcache_hit.pages)
echo
echo "==============================================="
printf "RESULT  pressure=%dMB  filled=%d  freed=%d  rnd_added=%d  hits=%d  rate=" \
    "$PRESSURE_MB" "$n_filled" "$freed_to_buddy" "$n_added" "$hits"
if [ "$freed_to_buddy" -gt 0 ]; then
    awk -v h=$hits -v c=$freed_to_buddy 'BEGIN{printf "%.1f%%\n", h/c*100}'
else
    echo "n/a"
fi
echo "==============================================="
