#!/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