| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- #!/usr/bin/env bash
- # mac-ops :: boot-perf.sh
- # Measure boot duration and identify slow startup components.
- # macOS records boot events in the unified log; we extract the markers.
- set -u
- DAYS=7
- SHOW_N=10
- while [[ $# -gt 0 ]]; do
- case "$1" in
- --days) DAYS="$2"; shift 2 ;;
- --show) SHOW_N="$2"; shift 2 ;;
- --help|-h)
- cat <<EOF
- Usage: $0 [options]
- --days N How many days of boot history to scan (default: 7)
- --show N How many recent boots to show (default: 10)
- --json, --redact, --quiet, --verbose
- Healthy:
- Apple Silicon Mac to login: 10-20s
- Intel Mac (SSD): 20-35s
- Intel Mac (HDD, vintage): 45-90s
- Failing storage: 60s+ with stalls
- EOF
- exit 0 ;;
- *) shift ;;
- esac
- done
- source "$(dirname "$0")/_lib/common.sh"
- parse_common_flags "$@"
- maybe_filter_self "$@"
- # ----------------------------------------------------------------------------
- section "1. RECENT BOOT TIMES"
- # ----------------------------------------------------------------------------
- # Approach: find each "kernel boot" marker, then compute time until loginwindow
- # completes its initial setup. The unified log has BOOT_TIME / "kernel boot"
- # markers as well as loginwindow setup messages.
- note " Scanning unified log for last ${DAYS}d of boot events..."
- # macOS marks the kernel boot start with "boot complete" + "boot session" + "first user event"
- # We grep for kernel-version + UUID lines that mark a fresh boot.
- boots_raw=$(log show --last "${DAYS}d" --style compact --predicate \
- 'process == "kernel" AND (eventMessage CONTAINS "Darwin Kernel Version" OR eventMessage CONTAINS "boot args")' \
- 2>/dev/null | head -100)
- if [[ -z "$boots_raw" ]]; then
- log_info "Boot events" "no boot markers found in window — try a wider --days N"
- else
- # Each fresh boot logs "Darwin Kernel Version" once; count them
- boot_count=$(echo "$boots_raw" | grep -c "Darwin Kernel Version" || echo 0)
- log_info "Boots in last ${DAYS}d" "${boot_count:-0}"
- note " Recent boot markers (most recent ${SHOW_N}):"
- echo "$boots_raw" | grep "Darwin Kernel Version" | tail -"$SHOW_N" | awk '{print $1, $2}' | sed 's/^/ /'
- fi
- # ----------------------------------------------------------------------------
- section "2. CURRENT BOOT DURATION ESTIMATE"
- # ----------------------------------------------------------------------------
- # Find the most recent boot marker (Darwin Kernel Version line)
- boot_start_line=$(log show --last "${DAYS}d" --style compact --predicate \
- 'process == "kernel" AND eventMessage CONTAINS "Darwin Kernel Version"' \
- 2>/dev/null | tail -1)
- boot_start_ts=$(echo "$boot_start_line" | awk '{print $1, $2}')
- if [[ -z "$boot_start_ts" ]]; then
- log_warn "Boot start timestamp" "could not extract"
- else
- note " Boot start: $boot_start_ts"
- # Find first WindowServer / loginwindow ready event AFTER boot
- if loginwindow_evt=$(log show --start "$boot_start_ts" --style compact 2>/dev/null \
- | grep -E "(loginwindow.*started|WindowServer.*started|opendirectoryd started)" \
- | head -3); then
- note " Earliest user-space events after boot:"
- echo "$loginwindow_evt" | sed 's/^/ /'
- fi
- # Attempt to compute seconds from boot to loginwindow
- first_user_event=$(echo "$loginwindow_evt" | head -1 | awk '{print $1, $2}')
- if [[ -n "$first_user_event" ]] && command -v gdate >/dev/null 2>&1; then
- b=$(gdate -d "$boot_start_ts" +%s 2>/dev/null)
- f=$(gdate -d "$first_user_event" +%s 2>/dev/null)
- if [[ -n "$b" ]] && [[ -n "$f" ]]; then
- diff=$((f - b))
- log_info "Boot duration (boot → user-space)" "${diff}s"
- fi
- else
- log_info "Boot duration calc" "install coreutils (brew install coreutils) for gdate-based timing"
- fi
- fi
- # ----------------------------------------------------------------------------
- section "3. SLOW LAUNCH AGENTS"
- # ----------------------------------------------------------------------------
- # Find agents that took long to start. Narrow to launchd messages specifically;
- # avoid matching Wi-Fi/airportd "throttled=0" noise.
- slow_events=$(log show --last "${DAYS}d" --style compact --predicate \
- 'process == "launchd" AND (eventMessage CONTAINS "took longer than" OR eventMessage CONTAINS "throttled by" OR eventMessage CONTAINS "exited with abnormal code")' \
- 2>/dev/null | head -20)
- if [[ -z "$slow_events" ]]; then
- log_pass "Slow launchd events" "none found"
- else
- n=$(echo "$slow_events" | wc -l | tr -d ' ')
- log_warn "Slow launchd events" "$n events — see below"
- echo "$slow_events" | head -10 | sed 's/^/ /'
- fi
- # ----------------------------------------------------------------------------
- section "4. LOGINWINDOW DELAYS"
- # ----------------------------------------------------------------------------
- # loginwindow logs assertions about slow login items
- loginwindow_delays=$(log show --last "${DAYS}d" --style compact \
- --predicate 'process == "loginwindow"' 2>/dev/null \
- | grep -iE "(delay|slow|timed out|waited)" \
- | head -10)
- if [[ -n "$loginwindow_delays" ]]; then
- log_warn "loginwindow delay messages" "see below"
- echo "$loginwindow_delays" | sed 's/^/ /'
- else
- log_pass "loginwindow delays" "none reported"
- fi
- # ----------------------------------------------------------------------------
- section "5. SAFE-BOOT / VERBOSE-BOOT INDICATORS"
- # ----------------------------------------------------------------------------
- # nvram for boot args
- boot_args=$(nvram boot-args 2>/dev/null | awk '{print $2}')
- if [[ "$boot_args" == *"-v"* ]] || [[ "$boot_args" == *"-x"* ]]; then
- log_warn "NVRAM boot-args" "$boot_args — non-default boot mode"
- else
- log_pass "NVRAM boot-args" "default (${boot_args:-empty})"
- fi
- # ----------------------------------------------------------------------------
- emit_summary
|