#!/bin/sh
# run_case.sh — 轻重载"申请耗时"标准用例 harness (iOS / macOS / Linux 通用)
#
# 用法:
#   ./run_case.sh -s <N M> -m <touch|memset> -r <runs> [-b <ballast_mib>] [-w settle_s] [-t hold_s]
# 示例:
#   ./run_case.sh -s 200M -m touch  -r 5              # 轻载: 200M 纯申请 x 5 轮
#   ./run_case.sh -s 200M -m memset -r 5              # 轻载: 200M 申请+写入 x 5 轮
#   ./run_case.sh -s 200M -m touch  -r 5 -b 3600      # 重载: 自动建 3600MiB ballast, 测完自动释放
#
# 输出: 约束格式记录行 CASE/PHASE/REC/SUMMARY (key=value 令牌, 见 protocol.html)
#   REC = 耗时 (mmap_us/fill_ms/unmap_ms) + 缺页 (faults/majflt) + rss
#       + 申请前 (pre_*) 与申请驻留时 (post_*) 的内存统计
#   post_*: 每轮 fill 后 mem_bench 保持驻留 HOLD_FILL 秒, 驻留期采样
#
# 依赖: 同目录 mem_bench / mem_stress; 内存源 (Darwin: vm_stat+sysctl / Linux: /proc/meminfo)
# 约束: 纯 POSIX sh (dash/ash/bash/zsh), 不依赖 awk/bc/python — iOS 精简用户态可跑

SIZE=200M; MODE=touch; RUNS=5; BALLAST=0; SETTLE=20; HOLD=900
HOLD_FILL=3   # 每轮 fill 后驻留保持秒数 (post_* 采样窗口)
DIR=$(dirname "$0")
BENCH="$DIR/mem_bench"; STRESS="$DIR/mem_stress"
BPID=""; BCPID=""
TMPB=/tmp/.run_case.bench.$$

usage() { sed -n '3,15p' "$0"; exit 2; }
while getopts "s:m:r:b:w:t:h" opt 2>/dev/null; do
    case $opt in
        s) SIZE=$OPTARG ;; m) MODE=$OPTARG ;; r) RUNS=$OPTARG ;;
        b) BALLAST=$OPTARG ;; w) SETTLE=$OPTARG ;; t) HOLD=$OPTARG ;;
        h) usage ;; *) usage ;;
    esac
done

# ---- 参数校验 (size 仅支持整 MiB) ----
case $SIZE in ''|*[!0-9M]*) echo "ERROR code=bad_size size=$SIZE (仅整 MiB, 如 200M)"; exit 2 ;; esac
SIZE_MIB=${SIZE%M}
case $SIZE_MIB in ''|*[!0-9]*) echo "ERROR code=bad_size size=$SIZE"; exit 2 ;; esac
case $MODE in touch|memset) ;; *) echo "ERROR code=bad_mode mode=$MODE (touch|memset)"; exit 2 ;; esac
case $RUNS in ''|*[!0-9]*|0) echo "ERROR code=bad_runs runs=$RUNS"; exit 2 ;; esac
case $BALLAST in ''|*[!0-9]*) echo "ERROR code=bad_ballast ballast=$BALLAST"; exit 2 ;; esac
[ -x "$BENCH" ] || { echo "ERROR code=no_bench path=$BENCH"; exit 2; }
if [ "$BALLAST" -gt 0 ]; then [ -x "$STRESS" ] || { echo "ERROR code=no_stress path=$STRESS"; exit 2; }; fi

ts() { date +%Y-%m-%dT%H:%M:%S%z; }

# ---- 平台检测 ----
KERNEL=$(uname -s)
case $KERNEL in Darwin|Linux) ;; *) echo "ERROR code=unsupported_kernel kernel=$KERNEL"; exit 2 ;; esac
ARCH=$(uname -m)
PAGE_BYTES=0; TOTAL_MIB=-1

if [ "$KERNEL" = "Darwin" ]; then
    # vm_stat 首行固定为 "Mach Virtual Memory Statistics: (page size of NNNN bytes)"
    _hdr=$(vm_stat 2>/dev/null | { IFS= read -r _l; echo "$_l"; })
    PAGE_BYTES=0
    for _t in $_hdr; do
        case $_t in
            [0-9]*) PAGE_BYTES=$_t ;;
        esac
    done
    [ "$PAGE_BYTES" -gt 0 ] 2>/dev/null || PAGE_BYTES=16384
    _ms=$(sysctl -n hw.memsize 2>/dev/null) && TOTAL_MIB=$((_ms / 1048576))
fi

kib_pages_to_mib() { echo $(( $1 * PAGE_BYTES / 1048576 )); }

# ---- 统一内存快照 -> R_* (整 MiB, -1 = 平台无此指标) ----
snap_mem() {
    R_TOTAL=$TOTAL_MIB; R_AVAIL=-1; R_FREE_SPEC=-1; R_COMP_OCC=-1
    if [ "$KERNEL" = "Linux" ]; then
        R_FREE=-1; R_FILE=-1; R_ANON=-1; R_SWAP_COMP=-1
        [ -r /proc/meminfo ] || return 0
        _t=0 _f=0 _a=0 _c=0 _an=0 _st=0 _sf=0
        while IFS=' :' read -r k v rest; do
            case $k in
                MemTotal) _t=$v ;; MemFree) _f=$v ;; MemAvailable) _a=$v ;;
                Cached) _c=$v ;; AnonPages) _an=$v ;;
                SwapTotal) _st=$v ;; SwapFree) _sf=$v ;;
            esac
        done < /proc/meminfo
        R_TOTAL=$((_t / 1024)); R_FREE=$((_f / 1024)); R_AVAIL=$((_a / 1024))
        R_FILE=$((_c / 1024)); R_ANON=$((_an / 1024))
        R_SWAP_COMP=$(( (_st - _sf) / 1024 ))
    else
        vm_stat > /tmp/.run_case.$$ 2>/dev/null || { R_FREE=-1; R_FILE=-1; R_ANON=-1; R_SWAP_COMP=-1; return 0; }
        _pf=0 _psp=0 _pfb=0 _pan=0 _psc=0 _poc=0
        while IFS= read -r line; do
            case $line in
                "Pages free:"*)                   set -- $line; _pf=${3%.}  ;;
                "Pages speculative:"*)            set -- $line; _psp=${3%.} ;;
                "File-backed pages:"*)            set -- $line; _pfb=${3%.} ;;
                "Anonymous pages:"*)              set -- $line; _pan=${3%.} ;;
                "Pages stored in compressor:"*)   set -- $line; _psc=${5%.} ;;
                "Pages occupied by compressor:"*) set -- $line; _poc=${5%.} ;;
            esac
        done < /tmp/.run_case.$$
        rm -f /tmp/.run_case.$$
        R_FREE=$(kib_pages_to_mib $_pf)
        R_FREE_SPEC=$(kib_pages_to_mib $((_pf + _psp)))
        R_FILE=$(kib_pages_to_mib $_pfb)
        R_ANON=$(kib_pages_to_mib $_pan)
        R_SWAP_COMP=$(kib_pages_to_mib $_psc)
        R_COMP_OCC=$(kib_pages_to_mib $_poc)
    fi
}

mem_tokens() { # $1=前缀(pre/post)
    printf '%s_total=%s %s_free=%s %s_avail=%s %s_free_spec=%s %s_file=%s %s_anon=%s %s_swap_comp=%s %s_comp_occ=%s' \
        "$1" "$R_TOTAL" "$1" "$R_FREE" "$1" "$R_AVAIL" "$1" "$R_FREE_SPEC" \
        "$1" "$R_FILE" "$1" "$R_ANON" "$1" "$R_SWAP_COMP" "$1" "$R_COMP_OCC"
}

cleanup() {
    rc=$?
    trap - EXIT INT TERM
    if [ -n "$BCPID" ]; then
        kill "$BCPID" 2>/dev/null; wait "$BCPID" 2>/dev/null; BCPID=""
    fi
    if [ -n "$BPID" ]; then
        kill "$BPID" 2>/dev/null; wait "$BPID" 2>/dev/null; BPID=""
        snap_mem
        printf 'PHASE name=ballast_released ts=%s ' "$(ts)"; mem_tokens post; echo
    fi
    rm -f /tmp/.run_case.$$ "$TMPB"
    exit "$rc"
}
trap cleanup EXIT INT TERM

# 浮点(<=3位小数) -> 毫单位整数: "35.096"->35096, "12.0"->12000
to_milli() {
    _i=${1%%.*}; _f=${1#*.}
    [ "$_f" = "$1" ] && _f=""
    case $_f in
        "") _f=000 ;; ?) _f=${_f}00 ;; ??) _f=${_f}0 ;; ???) ;; *) _f=${_f%${_f#???}} ;;
    esac
    printf '%s%s' "$_i" "$_f"
}
fmt3() { printf '%d.%03d' $(($1 / 1000)) $(($1 % 1000)); }

# ---- 开始 ----
printf 'CASE ts=%s platform=%s kernel=%s arch=%s size_mib=%s mode=%s runs=%s ballast_mib=%s tool=mem_bench\n' \
    "$(ts)" "$KERNEL" "$KERNEL-$(uname -r)" "$ARCH" "$SIZE_MIB" "$MODE" "$RUNS" "$BALLAST"
snap_mem
printf 'PHASE name=case_start ts=%s ' "$(ts)"; mem_tokens pre; echo

# ---- 重载: 建 ballast (-P 50 半随机 ≈ 真实数据压缩比, 防 watchdog panic) ----
if [ "$BALLAST" -gt 0 ]; then
    printf 'PHASE name=ballast_start ts=%s ballast_mib=%s hold_s=%s ' "$(ts)" "$BALLAST" "$HOLD"; mem_tokens pre; echo
    nohup "$STRESS" "${BALLAST}M" -P 50 -q -t "$HOLD" >/dev/null 2>&1 &
    BPID=$!
    _waited=0
    while [ "$_waited" -lt "$SETTLE" ]; do
        sleep 2; _waited=$((_waited + 2))
        snap_mem
        if [ "$R_FREE" -lt 16 ]; then
            echo "PHASE name=ballast_abort ts=$(ts) reason=free_below_16MiB free_mib=$R_FREE"; exit 3
        fi
        kill -0 "$BPID" 2>/dev/null || { echo "PHASE name=ballast_abort ts=$(ts) reason=ballast_died"; exit 3; }
    done
    printf 'PHASE name=ballast_settled ts=%s waited_s=%s pid=%s ' "$(ts)" "$_waited" "$BPID"; mem_tokens pre; echo
fi

# ---- 测量循环: 每轮 pre 快照 -> 单轮 bench(fill 后驻留 HOLD_FILL 秒) -> 驻留期 post 快照 -> 解析配对 ----
_sum=0; _min=999999999; _max=0; _n=0
i=1
while [ "$i" -le "$RUNS" ]; do
    snap_mem
    pre_tok=$(mem_tokens pre)

    # bench 后台跑: fill(数十~数百 ms) 后保持驻留 3s; shell 在 ~1s 处采 post 快照
    "$BENCH" "$SIZE" -r 1 -m "$MODE" -H "$HOLD_FILL" >"$TMPB" 2>&1 &
    BCPID=$!
    sleep 1
    snap_mem
    post_tok=$(mem_tokens post)
    wait "$BCPID"; brc=$?
    BCPID=""
    if [ "$brc" -ne 0 ]; then
        echo "ERROR code=bench_failed run=$i rc=$brc"
        cat "$TMPB"
        exit 4
    fi
    out=$(cat "$TMPB")

    mmap_v=""; fill_v=""; unit_v=""; unit_l=""; unmap_v=""; rss_v=""; faults_v=""; majflt_v=""; found=0
    while IFS= read -r line; do
        case $line in
        "run 1/1:"*)
            found=1; _st=""
            set -- $line
            for t in "$@"; do
                case $_st in
                    mmap)  mmap_v=$t ;;
                    fill)  fill_v=$t ;;
                    unit)  unit_v=$t; _st=unit_l; continue ;;
                    unit_l) unit_l=${t%)}; _st="" ;;
                    unmap) unmap_v=$t ;;
                    rss)   rss_v=$t ;;
                esac
                case $t in
                    mmap=) _st=mmap ;;
                    ')=')  _st=fill ;;
                    'fill(memset)=') _st=fill ;;
                    '(')   _st=unit ;;
                    unmap=) _st=unmap ;;
                    rss=)  _st=rss ;;
                    faults=*)  faults_v=${t#faults=} ;;
                    majflt=*)  majflt_v=${t#majflt=} ;;
                    *) case $_st in mmap|fill|unmap|rss) _st="" ;; esac ;;
                esac
            done
            ;;
        esac
    done <<EOF
$out
EOF
    [ "$found" = 1 ] || { echo "ERROR code=parse_failed run=$i"; printf '%s\n' "$out"; exit 4; }
    [ -n "$faults_v" ] || faults_v=-1
    [ -n "$majflt_v" ] || majflt_v=-1

    printf 'REC run=%s size_mib=%s mode=%s ballast_mib=%s mmap_us=%s fill_ms=%s unit=%s unit_label=%s unmap_ms=%s rss_mib=%s faults=%s majflt=%s %s %s\n' \
        "$i" "$SIZE_MIB" "$MODE" "$BALLAST" "$mmap_v" "$fill_v" "$unit_v" "$unit_l" "$unmap_v" "$rss_v" "$faults_v" "$majflt_v" "$pre_tok" "$post_tok"

    _m=$(to_milli "$fill_v")
    _sum=$((_sum + _m)); _n=$((_n + 1))
    [ "$_m" -lt "$_min" ] && _min=$_m
    [ "$_m" -gt "$_max" ] && _max=$_m
    i=$((i + 1))
    [ "$i" -le "$RUNS" ] && sleep 1
done

if [ "$_n" -gt 0 ]; then
    printf 'SUMMARY runs=%s fill_ms_avg=%s fill_ms_min=%s fill_ms_max=%s\n' \
        "$_n" "$(fmt3 $((_sum / _n)))" "$(fmt3 $_min)" "$(fmt3 $_max)"
fi
