#!/usr/bin/env bash # run-resources.sh — Infrastructure resource snapshot. # # Collects disk usage, RAM usage, Anthropic API call counts and budget burn, # and Woodpecker CI queue depth. Writes evidence/resources/YYYY-MM-DD.json. # # Exit codes: # 0 snapshot written successfully # 2 infrastructure error # # Environment: # DISK_PATH filesystem to measure (default: /) # ANTHROPIC_BUDGET_USD_LIMIT budget ceiling in USD (default: 50) # WOODPECKER_API_URL Woodpecker CI base URL (default: http://localhost:8090; empty to skip) set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" DATE="$(date -u +%Y-%m-%d)" OUT_DIR="$REPO_ROOT/evidence/resources" OUT_FILE="$OUT_DIR/$DATE.json" CALL_LOG="$REPO_ROOT/tmp/anthropic-call-log.jsonl" DISK_PATH="${DISK_PATH:-/}" ANTHROPIC_BUDGET_USD_LIMIT="${ANTHROPIC_BUDGET_USD_LIMIT:-50}" WOODPECKER_API_URL="${WOODPECKER_API_URL:-http://localhost:8090}" die() { echo "ERROR: $*" >&2; exit 2; } mkdir -p "$OUT_DIR" # ── collect-disk ────────────────────────────────────────────────────────────── disk_used_bytes=0 disk_total_bytes=0 disk_used_pct=0 if command -v df >/dev/null 2>&1; then # -B1 gives bytes; tail -1 skips header; awk grabs used, total, pct read -r disk_total_bytes disk_used_bytes disk_used_pct < <( df -B1 "$DISK_PATH" | tail -1 | awk '{gsub(/%/,"",$5); print $2, $3, $5}' ) else echo "WARN: df not available, disk metrics will be 0" >&2 fi # ── collect-ram ─────────────────────────────────────────────────────────────── ram_used_bytes=0 ram_total_bytes=0 ram_used_pct=0 if command -v free >/dev/null 2>&1; then # free -b: second line (Mem:) has total, used read -r ram_total_bytes ram_used_bytes < <( free -b | awk '/^Mem:/ {print $2, $3}' ) if [[ "$ram_total_bytes" -gt 0 ]]; then ram_used_pct=$(awk "BEGIN {printf \"%.1f\", $ram_used_bytes / $ram_total_bytes * 100}") fi elif command -v vm_stat >/dev/null 2>&1; then # macOS fallback page_size=$(vm_stat | head -1 | grep -o '[0-9]*') pages_active=$(vm_stat | awk '/Pages active/ {gsub(/\./,"",$3); print $3}') pages_wired=$(vm_stat | awk '/Pages wired/ {gsub(/\./,"",$4); print $4}') pages_free=$(vm_stat | awk '/Pages free/ {gsub(/\./,"",$3); print $3}') pages_inactive=$(vm_stat | awk '/Pages inactive/ {gsub(/\./,"",$3); print $3}') ram_used_bytes=$(( (pages_active + pages_wired) * page_size )) ram_total_bytes=$(( (pages_active + pages_wired + pages_free + pages_inactive) * page_size )) if [[ "$ram_total_bytes" -gt 0 ]]; then ram_used_pct=$(awk "BEGIN {printf \"%.1f\", $ram_used_bytes / $ram_total_bytes * 100}") fi else echo "WARN: neither free nor vm_stat available, RAM metrics will be 0" >&2 fi # ── collect-api ─────────────────────────────────────────────────────────────── anthropic_calls_24h=0 anthropic_budget_usd_used=0 anthropic_budget_pct=0 if [[ -f "$CALL_LOG" ]]; then cutoff=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%S 2>/dev/null \ || date -u -v-24H +%Y-%m-%dT%H:%M:%S 2>/dev/null \ || echo "") today=$(date -u +%Y-%m-%d) if [[ -n "$cutoff" ]]; then anthropic_calls_24h=$(awk -F'"ts"' -v cutoff="$cutoff" ' NF>1 { split($2,a,"\""); if (a[2] >= cutoff) count++ } END { print count+0 } ' "$CALL_LOG") fi anthropic_budget_usd_used=$(awk -F'"' -v today="$today" ' /"ts"/ && $0 ~ today { match($0, /"cost_usd"[[:space:]]*:[[:space:]]*([0-9.]+)/, m) if (m[1] != "") sum += m[1] } END { printf "%.2f", sum+0 } ' "$CALL_LOG") fi if [[ "$ANTHROPIC_BUDGET_USD_LIMIT" != "0" ]]; then anthropic_budget_pct=$(awk "BEGIN {printf \"%.1f\", $anthropic_budget_usd_used / $ANTHROPIC_BUDGET_USD_LIMIT * 100}") fi # ── collect-ci ──────────────────────────────────────────────────────────────── woodpecker_queue_depth="null" woodpecker_running="null" if [[ -n "$WOODPECKER_API_URL" ]]; then if ci_json=$(curl -sf --max-time 5 "$WOODPECKER_API_URL/api/queue/info" 2>/dev/null); then woodpecker_queue_depth=$(echo "$ci_json" | jq '.pending // .waiting // 0') woodpecker_running=$(echo "$ci_json" | jq '.running // 0') else echo "WARN: Woodpecker CI unreachable at $WOODPECKER_API_URL, CI metrics will be null" >&2 fi fi # ── verdict ─────────────────────────────────────────────────────────────────── verdict="ok" for pct in "$disk_used_pct" "$ram_used_pct" "$anthropic_budget_pct"; do # Strip trailing % if present pct_num="${pct//%/}" if awk "BEGIN {exit !($pct_num >= 95)}"; then verdict="critical" break fi if awk "BEGIN {exit !($pct_num >= 80)}"; then verdict="warn" fi done # ── write JSON ──────────────────────────────────────────────────────────────── cat > "$OUT_FILE" <