#!/usr/bin/env bash
# gpu-forensic-logger.sh —— 崩溃前取证记录器（GPU 机 zywpc / 192.168.31.31）
#
# 目的：本机 2026-09-15 18:38 CST 硬挂时，journald 里没有任何 Xid/NVRM/温度报错，
#       最后一分钟的数据也随页面缓存丢了。本脚本每 20 秒把 GPU 遥测 + 新增内核报错 +
#       ComfyUI 队列/采样进度 追加落盘并 fsync，因此**日志最后一行 = 挂死时刻**，
#       下一轮 GPU/内核异常就能留下第一手证据。
#
# 用法：systemd 单元 gpu-forensic-logger.service（Restart=always）
# 日志：/home/zyw/gpu-forensic.log（超过 200MB 自动轮转为 .1）
# 手动前台跑：bash ~/gpu-forensic-logger.sh

set -u

LOG="${FORENSIC_LOG:-/home/zyw/gpu-forensic.log}"
STATE="${FORENSIC_STATE:-/run/gpu-forensic.state}"
INTERVAL="${FORENSIC_INTERVAL:-20}"
MAX_BYTES=$((200 * 1024 * 1024))
PREV_EPOCH=0

mkdir -p "$(dirname "$LOG")"

# 轮转
if [ -f "$LOG" ]; then
  size=$(stat -c%s "$LOG" 2>/dev/null || echo 0)
  if [ "$size" -gt "$MAX_BYTES" ]; then
    mv -f "$LOG" "$LOG.1"
  fi
fi

append() { printf '%s\n' "$*" >>"$LOG"; }

append "### forensic logger started pid=$$ at $(date -u +%Y-%m-%dT%H:%M:%SZ)"

while :; do
  NOW_EPOCH=$(date +%s)
  TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)

  {
    printf '### %s (epoch=%s)\n' "$TS" "$NOW_EPOCH"

    # 1) GPU 遥测（nvidia-smi 自己挂住时不能拖死记录器）
    gpu=$(timeout 15 nvidia-smi \
      --query-gpu=utilization.gpu,memory.used,memory.total,temperature.gpu,power.draw,clocks.sm,clocks_throttle_reasons.active \
      --format=csv,noheader 2>&1)
    printf '  gpu: %s\n' "${gpu:-<nvidia-smi 无输出>}"

    # 2) 整机遥测
    printf '  load: %s\n' "$(cut -d' ' -f1-3 /proc/loadavg 2>/dev/null)"
    printf '  mem: %s\n' "$(free -m 2>/dev/null | awk '/^Mem:/{printf "used=%sMB avail=%sMB", $3, $7}')"
    if command -v sensors >/dev/null 2>&1; then
      s=$(timeout 10 sensors 2>/dev/null | grep -E "Package id 0|Core 0|Tctl|fan1|Composite" | tr '\n' ';')
      [ -n "$s" ] && printf '  temp: %s\n' "$s"
    fi

    # 3) 磁盘/文件系统卡死迹象（有进程进 D 状态基本就是 I/O 或驱动挂住）
    dstate=$(ps -eo pid,stat,comm --no-headers 2>/dev/null | awk '$2 ~ /^D/{printf "%s(%s) ", $1, $3}')
    [ -n "$dstate" ] && printf '  D-state: %s\n' "$dstate"

    # 4) 本轮新增内核报错
    if [ "$PREV_EPOCH" -gt 0 ]; then
      kern=$(timeout 20 journalctl -k --since "@$PREV_EPOCH" --until "@$NOW_EPOCH" --no-pager -q 2>/dev/null \
        | grep -iE "xid|nvrm|nvidia|gsp|thermal|throttl|mce|edac|hardware error|hung task|blocked for more|Oops|BUG:|panic|watchdog|pcie.*(error|down)" \
        | tail -20)
      [ -n "$kern" ] && printf '%s\n' "$kern" | sed 's/^/  kern: /'
    fi

    # 5) ComfyUI 状态：队列深度 + 采样进度最后一行（进度条不推进=卡住）
    if command -v curl >/dev/null 2>&1; then
      q=$(timeout 8 curl -s --noproxy '*' http://127.0.0.1:8189/prompt 2>/dev/null)
      printf '  queue: %s\n' "${q:-<API 无响应>}"
    fi
    if [ -f /home/zyw/comfyui_8189_opt.log ]; then
      last=$(tail -c 400 /home/zyw/comfyui_8189_opt.log 2>/dev/null | tr '\r' '\n' | grep -aE "it/s|s/it|Prompt executed|got prompt" | tail -1)
      printf '  comfy: %s\n' "${last:-<无进度行>}"
    fi
  } >>"$LOG" 2>&1

  # 6) 落盘：尽量把这一轮真正写进磁盘（挂死时页面缓存会丢）
  printf 'epoch=%s ts=%s\n' "$NOW_EPOCH" "$TS" >"$STATE" 2>/dev/null || true
  sync -d "$LOG" 2>/dev/null || sync 2>/dev/null || true

  PREV_EPOCH=$NOW_EPOCH
  sleep "$INTERVAL"
done
