Files
minimal/bin/dotfiles_doctor
T
2026-03-23 23:50:58 +00:00

128 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env zsh
# ===============================================================
# dotfiles_doctor — Zsh Config Health & Timeline Analyzer
# ===============================================================
# Be interactive-safe: never kill the session on errors here
unsetopt errexit 2>/dev/null || true
unsetopt nounset 2>/dev/null || true
unsetopt pipefail 2>/dev/null || true
setopt NO_NOMATCH
LOGFILE="$HOME/zshrc-log.json"
SHOW_TIMELINE=0
SLOW_THRESHOLD=${SLOW_THRESHOLD:-200}
WARN_THRESHOLD=${WARN_THRESHOLD:-500}
# --- Argument parsing (flags only; NDJSON log stays default unless overridden) ---
for arg in "$@"; do
case "$arg" in
--timeline) SHOW_TIMELINE=1 ;;
--logfile=*) LOGFILE="${arg#*=}" ;;
esac
done
# --- Pre-flight checks (non-fatal for interactive) ---
if ! command -v jq >/dev/null 2>&1; then
print -P "%F{red}❌ jq is required (brew install jq)%f"
return 1 2>/dev/null || exit 1
fi
if [[ ! -f "$LOGFILE" ]]; then
print -P "%F{red}❌ No log file found:%f $LOGFILE"
return 1 2>/dev/null || exit 1
fi
print
print "🩺 Dotfiles Doctor — Startup Diagnostic"
print "📄 Log: $LOGFILE"
print "⚙️ Slow threshold: ${SLOW_THRESHOLD}ms | Warn threshold: ${WARN_THRESHOLD}ms"
print "---------------------------------------------------------------"
# ---- Individual file timings (slurp NDJSON -> array; then iterate) ----
jq -sr '.[] | [.label, (.duration_ms // 0), (.exit_code // 0), .file] | @tsv' "$LOGFILE" |
while IFS=$'\t' read -r label duration exit_code file; do
color="" reset=$'\033[0m' emoji="✅"
if (( exit_code != 0 )); then color=$'\033[31m'; emoji="💥"
elif (( ${duration%.*} > WARN_THRESHOLD )); then color=$'\033[31m'; emoji="🔥"
elif (( ${duration%.*} > SLOW_THRESHOLD )); then color=$'\033[33m'; emoji="⚠️ "
else color=$'\033[32m'; fi
printf "%b%-22s %8.3fms (exit:%3d) %s%b\n" \
"$color$emoji " "$label" "$duration" "$exit_code" "$file" "$reset"
done
print "---------------------------------------------------------------"
# ---- Aggregate summary (right-justified, 3 decimals) ----
echo "📊 Aggregate timings:"
jq -sr '
group_by(.label)
| map({
label: .[0].label,
count: length,
total: (map(.duration_ms // 0) | add),
avg: ((map(.duration_ms // 0) | add) / length)
})
| sort_by(.label)
| .[]
| [.label, (.count|tostring), (.total|tostring), (.avg|tostring)]
| @tsv
' "$LOGFILE" |
awk -F '\t' '
BEGIN {
printf "%-26s %7s %12s %10s\n", "label", "count", "total_ms", "avg_ms"
printf "%-26s %7s %12s %10s\n", "-----", "-----", "--------", "------"
}
{
label=$1; count=$2+0; total=$3+0; avg=$4+0
printf "%-26s %7d %12.3f %10.3f\n", label, count, total, avg
}'
# ---- Totals ----
sum_total=$(jq -sr '[.[].duration_ms // 0] | add' "$LOGFILE")
# Session elapsed = (max end - min start) * 1000 (if present)
session_total_ms=$(jq -sr '
( [.[].start] | map(select(. != null)) ) as $s
| ( [.[].end] | map(select(. != null)) ) as $e
| if ($s|length)>0 and ($e|length)>0
then (( ($e|max) - ($s|min) ) * 1000)
else 0
end
' "$LOGFILE")
fails=$(jq -sr '[.[].exit_code // 0] | map(select(. != 0)) | length' "$LOGFILE")
slows=$(jq -sr "[.[].duration_ms // 0] | map(select(. > $SLOW_THRESHOLD)) | length" "$LOGFILE")
printf "\n🕒 Total (sum of durations): %.5fms\n" "$sum_total"
printf "🕒 Session elapsed (first→last): %.5fms\n" "$session_total_ms"
print "💀 Failed modules: $fails"
print "🐢 Slow modules: $slows"
print
if (( fails > 0 )); then
print "❗ One or more modules failed to load. Check 💥 entries."
elif (( slows > 0 )); then
print "⚠️ Some modules exceeded ${SLOW_THRESHOLD}ms."
else
print "✅ All modules healthy and fast."
fi
# ---- Timeline (optional; format times in shell, not jq) ----
if (( SHOW_TIMELINE )); then
print
print "🕰️ Load Timeline (start→end, μs precision)"
print "---------------------------------------------------------------"
jq -sr '.[] | select(.start != null and .end != null)
| [.label, (.start|tostring), (.end|tostring), (.duration_ms // 0), (.exit_code // 0), .file]
| @tsv' "$LOGFILE" |
while IFS=$'\t' read -r label start end duration exit_code file; do
# hh:mm:ss.fffff formatting in shell
s_sec="${start%.*}"; s_frac="${start#*.}"; s_frac="${s_frac%%[^0-9]*}"; s_frac="${s_frac:0:5}"
e_sec="${end%.*}" ; e_frac="${end#*.}" ; e_frac="${e_frac%%[^0-9]*}" ; e_frac="${e_frac:0:5}"
s_hms="$(date -r "$s_sec" +%H:%M:%S 2>/dev/null).$(printf "%05d" ${s_frac:-0})"
e_hms="$(date -r "$e_sec" +%H:%M:%S 2>/dev/null).$(printf "%05d" ${e_frac:-0})"
printf "%-22s %s → %s %8.3fms (exit:%3d) %s\n" \
"$label" "$s_hms" "$e_hms" "$duration" "$exit_code" "$file"
done
print "---------------------------------------------------------------"
fi