#!/usr/bin/env bash
# Samples this box's own kernel facts and publishes one complete JSON summary that the
# controller reads. The summary carries sampledAtEpochMs; a reader with no other signal
# that this timer stopped uses it to reject a frozen file.
set -euo pipefail

STATE_DIR="${BUILDBOX_TELEMETRY_STATE_DIR:-$HOME/.local/state/overdeck}"
STATE_FILE="$STATE_DIR/buildbox-telemetry.json"
LOCK_FILE="$STATE_DIR/buildbox-telemetry.lock"
PROC_ROOT="${BUILDBOX_TELEMETRY_PROC_ROOT:-/proc}"
SYS_ROOT="${BUILDBOX_TELEMETRY_SYS_ROOT:-/sys}"
CGROUP_ROOT="${BUILDBOX_TELEMETRY_CGROUP_ROOT:-$SYS_ROOT/fs/cgroup}"
SAMPLE_SLEEP_SECONDS="${BUILDBOX_TELEMETRY_SAMPLE_SLEEP_SECONDS:-1}"
SESSION_READER="${BUILDBOX_TELEMETRY_SESSION_READER:-$HOME/.claude/lib/agent-session-reader.mjs}"

mkdir -p "$STATE_DIR"
exec 9>"$LOCK_FILE"
flock -n 9 || exit 0

session_count=null
if command -v node >/dev/null 2>&1 && command -v timeout >/dev/null 2>&1 && [ -r "$SESSION_READER" ]; then
  session_count=$(SESSION_READER="$SESSION_READER" timeout 2 node -e '
    const LIVE = new Set(["ALIVE-WORKING", "ALIVE-IDLE", "DETACHED-ALIVE"]);
    import(process.env.SESSION_READER).then(async (reader) => {
      const { existsSync } = await import("node:fs");
      const { join } = await import("node:path");
      if (!existsSync(join(reader.ledgerDir(), "sessions"))) process.exit(1);
      const sessions = await reader.classify(reader.readEntries(), { sampleMs: 0, includeDirty: false });
      process.stdout.write(String(sessions.filter((entry) => LIVE.has(entry.state)).length));
    }).catch(() => process.exit(1));
  ' 2>/dev/null) || session_count=null
  case $session_count in ''|*[!0-9]*) session_count=null ;; esac
fi

# Remote work is placed by three independent paths that no single job table sees. The
# kernel's process list does see all three, so each unit is counted by the directory that
# identifies it, deduplicated across the wrapper processes one unit fans out into.
remote_work=null
ci_jobs_running=null
if [ -n "${BUILDBOX_TELEMETRY_PROCESS_LIST:-}" ] || command -v ps >/dev/null 2>&1; then
  counted=$({ if [ -n "${BUILDBOX_TELEMETRY_PROCESS_LIST:-}" ]; then
      cat "$BUILDBOX_TELEMETRY_PROCESS_LIST"
    else
      ps -eo args --no-headers 2>/dev/null
    fi; } | awk '
    { here_seat = ""; here_job = ""
      if (match($0, /\.harness-seat\/[A-Za-z0-9._-]+/)) { here_seat = substr($0, RSTART, RLENGTH); seat[here_seat] = 1 }
      if (match($0, /\.rb\/jobs\/[A-Za-z0-9._-]+/)) { here_job = substr($0, RSTART, RLENGTH); job[here_job] = 1 }
      if (match($0, /cdx-offload\/[A-Za-z0-9._-]+/)) shell[substr($0, RSTART, RLENGTH)] = 1
      if ($0 ~ /(^|[[:space:]\/])Runner\.Worker([[:space:]]|$)/) ci++
      # A seat is dispatched through the remote-build transport, so its supervisor argv carries
      # both markers; that job is the seat, not a second unit standing beside it.
      if (here_seat != "" && here_job != "") seat_job[here_job] = 1 }
    END { if (NR == 0) exit 1
          seats = 0; for (k in seat) seats++
          jobs = 0; for (k in job) if (!(k in seat_job)) jobs++
          shells = 0; for (k in shell) shells++
          printf "{\"agentSeats\":%d,\"remoteBuildJobs\":%d,\"offloadShells\":%d,\"ciJobsRunning\":%d}", seats, jobs, shells, ci }
  ') || counted=""
  case $counted in '{"agentSeats":'*'}')
    remote_work=$(printf '%s' "$counted" | sed 's/,"ciJobsRunning":[0-9][0-9]*}$/}/')
    ci_jobs_running=$(printf '%s' "$counted" | sed -n 's/.*"ciJobsRunning":\([0-9][0-9]*\)}$/\1/p')
    ;;
  esac
fi

read -r load _ _ < "$PROC_ROOT/loadavg"
cores=$(nproc --all)
mem_total=$(awk '/^MemTotal:/ {print $2*1024}' "$PROC_ROOT/meminfo")
mem_avail=$(awk '/^MemAvailable:/ {print $2*1024}' "$PROC_ROOT/meminfo")
mem_used=$((mem_total - mem_avail))
swap_total=$(awk '/^SwapTotal:/ {print $2*1024}' "$PROC_ROOT/meminfo")
swap_free=$(awk '/^SwapFree:/ {print $2*1024}' "$PROC_ROOT/meminfo")
swap_used=$((swap_total - swap_free))
cpu1=$(awk '/^cpu[0-9]+ / {idle=$5+$6; total=0; for (i=2; i<=NF; i++) total+=$i; print idle, total}' "$PROC_ROOT/stat")
net1=$(awk 'NR>2 && $1 !~ /^lo:/ {gsub(/:/,"",$1); rx+=$2; tx+=$10} END {print rx+0, tx+0}' "$PROC_ROOT/net/dev")
sleep "$SAMPLE_SLEEP_SECONDS"
cpu2=$(awk '/^cpu[0-9]+ / {idle=$5+$6; total=0; for (i=2; i<=NF; i++) total+=$i; print idle, total}' "$PROC_ROOT/stat")
net2=$(awk 'NR>2 && $1 !~ /^lo:/ {gsub(/:/,"",$1); rx+=$2; tx+=$10} END {print rx+0, tx+0}' "$PROC_ROOT/net/dev")
core_loads=$(paste <(printf '%s\n' "$cpu1") <(printf '%s\n' "$cpu2") | awk '{
  d_idle=$3-$1; d_total=$4-$2;
  if (d_total <= 0) pct=0; else pct=(d_total-d_idle)*100/d_total;
  if (NR>1) printf ","; printf "%.2f", pct
}')
read -r net_rx1 net_tx1 <<< "$net1"
read -r net_rx2 net_tx2 <<< "$net2"
net_rx=$((net_rx2 - net_rx1)); net_tx=$((net_tx2 - net_tx1))
[ "$net_rx" -ge 0 ] || net_rx=0
[ "$net_tx" -ge 0 ] || net_tx=0
disks=$(df -B1 --output=avail,size,target -x tmpfs -x devtmpfs -x squashfs 2>/dev/null | awk 'NR>1 {t=$3; for (i=4; i<=NF; i++) t=t" "$i; if (substr(t,1,11)=="/run/media/" || substr(t,1,7)=="/media/" || t=="/media") next; printf "%s{\"mountpoint\":\"%s\",\"freeBytes\":%s,\"sizeBytes\":%s}", (n++?",":""), t, $1, $2}')
pkg=0; max=0; crit=95
for tz in "$SYS_ROOT"/class/thermal/thermal_zone*/temp; do
  [ -r "$tz" ] || continue
  t=$(cat "$tz" 2>/dev/null || echo 0); t=$((t / 1000)); [ "$t" -gt "$max" ] && max=$t
  type_file=${tz%/temp}/type
  [ -r "$type_file" ] && grep -q pkg "$type_file" 2>/dev/null && pkg=$t
done
for trip in "$SYS_ROOT"/class/thermal/thermal_zone*/trip_point_*_temp; do
  [ -r "$trip" ] || continue
  trip_type=${trip%_temp}_type
  [ -r "$trip_type" ] && [ "$(cat "$trip_type" 2>/dev/null || true)" = critical ] || continue
  t=$(cat "$trip" 2>/dev/null || echo 0); t=$((t / 1000))
  [ "$t" -gt 0 ] && [ "$t" -lt "$crit" ] && crit=$t
done
[ "$pkg" -eq 0 ] && pkg=$max
stall_some60=0; stall_full60=0; stall_full300=0
if [ -r "$PROC_ROOT/pressure/memory" ]; then
  stall_some60=$(awk '/^some /{split($3,a,"="); print a[2]}' "$PROC_ROOT/pressure/memory")
  stall_full60=$(awk '/^full /{split($3,a,"="); print a[2]}' "$PROC_ROOT/pressure/memory")
  stall_full300=$(awk '/^full /{split($4,a,"="); print a[2]}' "$PROC_ROOT/pressure/memory")
fi
tmp_used=$(df -B1 --output=used /tmp 2>/dev/null | awk 'NR==2 {print $1+0; exit}' || echo 0)
tmp_size=$(df -B1 --output=size /tmp 2>/dev/null | awk 'NR==2 {print $1+0; exit}' || echo 0)
[ -n "$tmp_used" ] || tmp_used=0; [ -n "$tmp_size" ] || tmp_size=0
uid=$(id -u); cgbase="$CGROUP_ROOT/user.slice/user-$uid.slice/user@$uid.service"
work_slices=""; slice_sep=""
for slice in agent.slice build.slice agent-seat.slice; do
  dir=$cgbase/$slice; [ -d "$dir" ] || continue
  slice_mem=$(cat "$dir/memory.current" 2>/dev/null || echo 0)
  slice_oom=$(awk '/^oom_kill /{print $2}' "$dir/memory.events" 2>/dev/null || echo 0)
  slice_pids=$(cat "$dir/pids.current" 2>/dev/null || echo 0)
  [ -n "$slice_oom" ] || slice_oom=0
  work_slices="$work_slices$slice_sep{\"slice\":\"$slice\",\"memoryBytes\":$slice_mem,\"oomKillTotal\":$slice_oom,\"pidsCurrent\":$slice_pids}"; slice_sep=","
done
sampled_at=$(($(date +%s) * 1000))
printf '{"sampledAtEpochMs":%s,"sessions":%s,"remoteWork":%s,"ciJobsRunning":%s,"load":%s,"cores":%s,"coreLoadPercent":[%s],"memUsedBytes":%s,"memTotalBytes":%s,"swapUsedBytes":%s,"swapTotalBytes":%s,"netRxBytesPerSecond":%s,"netTxBytesPerSecond":%s,"disks":[%s],"temperatures":{"pkg":%s,"max":%s,"crit":%s},"guard":{"memoryStallSome60":%s,"memoryStallFull60":%s,"memoryStallFull300":%s,"tmpUsedBytes":%s,"tmpSizeBytes":%s,"workSlices":[%s]}}\n' \
  "$sampled_at" "$session_count" "$remote_work" "$ci_jobs_running" "$load" "$cores" "$core_loads" "$mem_used" "$mem_total" "$swap_used" "$swap_total" "$net_rx" "$net_tx" "$disks" "$pkg" "$max" "$crit" "$stall_some60" "$stall_full60" "$stall_full300" "$tmp_used" "$tmp_size" "$work_slices" >"$STATE_FILE.tmp"
mv "$STATE_FILE.tmp" "$STATE_FILE"
