Files
karuo-ai/01_卡资(金)/金仓_存储备份/脚本/karuo_perf_guard.sh
2026-07-19 00:21:46 +08:00

160 lines
5.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# 卡若AI · 轻量防卡顿守护(金仓)
# 仅阈值触发;不删 TM 快照、不碰 state.vscdb、不删用户文件。
# 用法:
# bash karuo_perf_guard.sh --once # 执行一轮
# bash karuo_perf_guard.sh --dry-run # 只报告不动作
# bash karuo_perf_guard.sh --status # 只看指标
set -euo pipefail
MODE="${1:---once}"
DRY=0
LOG="${KARUO_PERF_GUARD_LOG:-$HOME/Library/Logs/karuo_perf_guard.log}"
MAX_LOG_LINES=2000
# 阈值(可用环境变量覆盖)
ORPHAN_NODE_MIN="${KARUO_ORPHAN_NODE_MIN:-15}"
REPORTCRASH_PCPU_MIN="${KARUO_REPORTCRASH_PCPU_MIN:-25}"
REPORTCRASH_ETIME_MIN="${KARUO_REPORTCRASH_ETIME_MIN:-3600}"
MEM_FREE_MB_MIN="${KARUO_MEM_FREE_MB_MIN:-800}"
LOAD_MIN_FOR_PURGE="${KARUO_LOAD_MIN_FOR_PURGE:-15}"
GIT_STUCK_ETIME_MIN="${KARUO_GIT_STUCK_ETIME_MIN:-600}"
GIT_STUCK_PCPU_MIN="${KARUO_GIT_STUCK_PCPU_MIN:-40}"
DISK_WARN_PCT="${KARUO_DISK_WARN_PCT:-95}"
mkdir -p "$(dirname "$LOG")"
rotate_log() {
[[ -f "$LOG" ]] || return 0
local n
n=$(wc -l <"$LOG" | tr -d ' ')
if [[ "$n" -gt "$MAX_LOG_LINES" ]]; then
tail -n 500 "$LOG" >"${LOG}.tmp" && mv "${LOG}.tmp" "$LOG"
fi
}
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$LOG"
}
mem_free_mb() {
local free_pages page_size
free_pages=$(vm_stat 2>/dev/null | awk '/Pages free/ {gsub(/\./, "", $3); print $3}' || echo 0)
page_size=$(sysctl -n hw.pagesize 2>/dev/null || echo 16384)
echo $((free_pages * page_size / 1024 / 1024))
}
load_avg() {
sysctl -n vm.loadavg 2>/dev/null | awk '{print int($2)}'
}
disk_used_pct() {
df /System/Volumes/Data 2>/dev/null | awk 'NR==2 {gsub(/%/, "", $5); print $5}'
}
status_report() {
local free_mb load disk node_n
free_mb=$(mem_free_mb)
load=$(load_avg)
disk=$(disk_used_pct)
node_n=$(pgrep -c node 2>/dev/null || echo 0)
log "status load=$load free_mb≈${free_mb} disk=${disk}% node=${node_n}"
if [[ "$disk" -gt "$DISK_WARN_PCT" ]]; then
log "WARN 磁盘>${DISK_WARN_PCT}% — 建议清 Downloads/缓存(守护不会自动删用户文件)"
fi
}
orphan_codegraph_cleanup() {
local count
count=$(pgrep -fc '@colbymchenry/codegraph-darwin-arm64/node -e' 2>/dev/null || echo 0)
if [[ "$count" -gt "$ORPHAN_NODE_MIN" ]]; then
log "CodeGraph orphan watchdog=${count} (>${ORPHAN_NODE_MIN}) → cleanup"
if [[ "$DRY" -eq 0 ]]; then
pkill -f '@colbymchenry/codegraph-darwin-arm64/node -e' 2>/dev/null || true
fi
fi
}
reportcrash_cleanup() {
local pid pcpu etime comm
pid=$(pgrep -f '/ReportCrash agent' 2>/dev/null | head -1 || true)
[[ -z "$pid" ]] && return 0
pcpu=$(ps -p "$pid" -o %cpu= 2>/dev/null | tr -d ' ' || echo 0)
etime=$(ps -p "$pid" -o etimes= 2>/dev/null | tr -d ' ' || echo 0)
comm=$(ps -p "$pid" -o command= 2>/dev/null || true)
[[ "$comm" != *"ReportCrash agent"* ]] && return 0
if awk "BEGIN{exit !($pcpu > $REPORTCRASH_PCPU_MIN)}" && [[ "${etime:-0}" -gt "$REPORTCRASH_ETIME_MIN" ]]; then
log "ReportCrash 卡死 pid=$pid pcpu=${pcpu}% etime=${etime}s → kill"
if [[ "$DRY" -eq 0 ]]; then
kill -9 "$pid" 2>/dev/null || true
fi
fi
}
feishu_api_dedup() {
local -a pids=()
local keep pid
while IFS= read -r pid; do
[[ -n "$pid" ]] && pids+=("$pid")
done < <(pgrep -f 'feishu_api\.py' 2>/dev/null | sort -n || true)
((${#pids[@]} <= 1)) && return 0
keep="${pids[0]}"
for pid in "${pids[@]:1}"; do
log "重复 feishu_api pid=$pid(保留 $keep)→ kill"
if [[ "$DRY" -eq 0 ]]; then
kill "$pid" 2>/dev/null || true
fi
done
}
stuck_git_scm_cleanup() {
local pid pcpu etime cmd
while read -r pid pcpu cmd; do
[[ -z "$pid" ]] && continue
[[ "$cmd" != *git* ]] && continue
[[ "$cmd" != *"ls-files"* && "$cmd" != *"diff"* && "$cmd" != *"status"* ]] && continue
etime=$(ps -p "$pid" -o etimes= 2>/dev/null | tr -d ' ' || echo 0)
if awk "BEGIN{exit !($pcpu > $GIT_STUCK_PCPU_MIN)}" && [[ "${etime:-0}" -gt "$GIT_STUCK_ETIME_MIN" ]]; then
log "git SCM 长时间高CPU pid=$pid pcpu=${pcpu}% etime=${etime}s → kill"
if [[ "$DRY" -eq 0 ]]; then
kill "$pid" 2>/dev/null || true
fi
fi
done < <(ps aux 2>/dev/null | awk '!/awk/ && /\/git / {print $2, $3, substr($0, index($0,$11))}' || true)
}
memory_purge_if_needed() {
local free_mb load
free_mb=$(mem_free_mb)
load=$(load_avg)
if [[ "$free_mb" -lt "$MEM_FREE_MB_MIN" ]] && [[ "$load" -gt "$LOAD_MIN_FOR_PURGE" ]]; then
log "内存偏低 free≈${free_mb}MB load=$load → purge"
if [[ "$DRY" -eq 0 ]]; then
purge 2>/dev/null || true
fi
fi
}
run_once() {
rotate_log
status_report
[[ "$MODE" == "--status" ]] && return 0
orphan_codegraph_cleanup
reportcrash_cleanup
feishu_api_dedup
stuck_git_scm_cleanup
memory_purge_if_needed
}
case "$MODE" in
--once | --dry-run | --status)
[[ "$MODE" == "--dry-run" ]] && DRY=1
run_once
;;
*)
echo "用法: $0 [--once|--dry-run|--status]"
exit 1
;;
esac