diff --git a/profiles/mrsauravsahu/.zshrc b/profiles/mrsauravsahu/.zshrc index 75d8826..157cbe2 100755 --- a/profiles/mrsauravsahu/.zshrc +++ b/profiles/mrsauravsahu/.zshrc @@ -20,7 +20,10 @@ XARGS_OPTIONS=$(if [ "${currentOs}" = "linux" ]; then echo '--no-run-if-empty'; PATH_PREFIX="/opt/homebrew/bin" PATH_PREFIX="${PATH_PREFIX}:/home/linuxbrew/.linuxbrew/bin/" -export PATH="${PATH}:${PATH_PREFIX}" +# Keep PATH/path free of duplicates. PATH_PREFIX is prepended again at the end of +# this file, so exporting it here too is what put homebrew (and everything else) +# on PATH twice. +typeset -U path PATH # add friday scripts to the path if [ -d "${HOME}/GenAI/code/friday/scripts" ]; then @@ -61,11 +64,9 @@ alias ll='ls -l' alias l='ls' alias h=helm alias k=kubectl -alias colima_start='colima start --mount-type virtiofs --cpu 12 --memory 20 --disk 256 --vm-type vz --vz-rosetta' -PATH_PREFIX="${PATH_PREFIX}:/Users/Saurav_Sahu/.dotnet/tools" -PATH_PREFIX="${PATH_PREFIX}:/opt/homebrew/opt/ruby@3.2/bin" -PATH_PREFIX="${PATH_PREFIX}:/opt/homebrew/lib/ruby/gems/3.2.0/bin" +# macOS-only entries (colima, homebrew ruby@3.2, ~/.dotnet/tools) live in +# ~/.mrsauravsahu/dotfiles/darwin.zshrc - this file is the cross-platform base. PATH_PREFIX="${PATH_PREFIX}:${CLI_CONFIG_ROOT}/current/path" PATH_PREFIX="${CLI_CONFIG_ROOT}/current/asdf/shims:${PATH_PREFIX}" @@ -74,7 +75,9 @@ PATH_PREFIX="${CLI_CONFIG_ROOT}/current/asdf/shims:${PATH_PREFIX}" # fi if [ -d "${CLI_CONFIG_ROOT}/current/asdf" ]; then - PATH_PREFIX="${HOME}/${CLI_CONFIG_ROOT}/current/asdf/shims/bin:${PATH_PREFIX}" + # (dropped a "${HOME}/${CLI_CONFIG_ROOT}/..." entry here - CLI_CONFIG_ROOT is + # already absolute, so it expanded to /Users///Users//... and never + # existed. The correct shims dir is already prepended above.) . ${ASDF_DATA_DIR:-$HOME/.asdf}/plugins/golang/set-env.zsh . ${ASDF_DATA_DIR:-$HOME/.asdf}/plugins/dotnet/set-dotnet-env.zsh fi diff --git a/specs/003-startup-perf-audit/results.md b/specs/003-startup-perf-audit/results.md new file mode 100644 index 0000000..913eb3e --- /dev/null +++ b/specs/003-startup-perf-audit/results.md @@ -0,0 +1,110 @@ +# Shell Startup Perf Audit — Round 3 + +**Status:** Completed +**Baseline:** ~700ms wall (post-002) +**Result:** ~465ms wall — **231ms saved (33%)** + +Measured with 12 interleaved baseline/patched runs of `zsh -i -c exit` (interleaving +cancels machine drift; sequential A/B runs were too noisy to read). + +--- + +## Where the time actually went + +Coarse phase timing (`$EPOCHREALTIME` markers around each section) was the only +reliable instrument here. `zprof` misses subprocess cost, and `setopt xtrace` +emitted 66k lines and distorted the very thing being measured. + +| Phase | Cost | +|---|---| +| oh-my-posh `init zsh` | 103ms | +| compinit (full) | 95ms | +| thefuck `--alias` | 68ms | +| zimfw `init.zsh` | 54ms | +| everything else | ~3ms | +| **config subtotal** | **323ms** | +| global `/etc/zshrc` + `/etc/zprofile` | 90ms | + +--- + +## The "sometimes it's very slow" bug + +The 24h compinit cache guard from spec 002 was **permanently stuck in the slow path**: + +```zsh +if [[ -n ${ZDOTDIR:-$HOME}/.zcompdump(#qN.mh+24) ]]; then compinit; else compinit -C; fi +``` + +`compinit` only rewrites the dump file when completions actually changed. When they +haven't, the dump's mtime is never refreshed — so once it crossed 24 hours old it +matched `mh+24` forever, and **every** shell paid full `compinit` instead of `-C`. +The local `.zcompdump` was 18 days stale and still being re-checked on every start. + +Worse, when the dump is missing entirely, full `compinit` + `compdump` costs +**3.35s** (measured via zprof: 2992ms self time in `compinit`, 806 `compdef` calls, +129ms in `compdump`). That is the occasional multi-second hang. + +**Fix:** `touch` the dump after a full `compinit` so the guard resets and the next +start takes the fast path. + +--- + +## Fixes applied + +| Fix | Saving | File | +|---|---|---| +| `touch` zcompdump after full compinit — un-stick the 24h guard | ~80ms/start, avoids 3.3s cold stalls | `src/scripts/setup.programs-conf.zsh` | +| Lazy-load `thefuck` behind a self-replacing `fuck()` stub | ~68ms | `src/scripts/setup.programs-conf.zsh` | +| Cache `oh-my-posh init zsh` output, regenerate when binary/theme is newer | ~103ms | `src/installers/ohmyposh.configure.zsh` | +| `typeset -U path PATH` + drop the redundant early `export PATH` | PATH 85 → 41 entries, 0 dups | `profiles/mrsauravsahu/.zshrc` | +| Remove broken `${HOME}/${CLI_CONFIG_ROOT}/...` shims entry | — (dead path) | `profiles/mrsauravsahu/.zshrc` | +| Move macOS-only PATH/alias lines out of the cross-platform `.zshrc` | — (organization) | `profiles/mrsauravsahu/.zshrc` | + +### On caching oh-my-posh + +`oh-my-posh init zsh` output is deterministic **except** `POSH_SESSION_ID`, a fresh +UUID per call that keys oh-my-posh's own segment cache. Freezing it into the cached +file would make every shell share one session ID and bleed cached segments between +shells. The generated conf therefore strips that line from the cache and exports a +fresh per-shell value using fork-free zsh builtins (`$$` + `$RANDOM`). + +--- + +## Verified after patching + +- oh-my-posh prompt renders; `_omp_precmd` hook installed +- `POSH_SESSION_ID` unique per shell +- `fuck` stub correctly replaces itself with the real thefuck function on first call +- 1733 completions loaded +- PATH: 85 → 41 entries, 0 duplicates + +--- + +## Not addressed + +- **`zimfw init.zsh` (54ms)** — would need module pruning; left alone. +- **Global `/etc/zshrc` + `/etc/zprofile` (90ms)** — system files, mostly + `path_helper`. Not ours to edit. +- **`~/.mrsauravsahu/dotfiles/darwin.zshrc`** lives in a separate repo + (`mrsauravsahu/dotfiles`). It still duplicates 11 things from `.zshrc` + (aliases `cat`/`ll`/`l`/`h`/`k`, the `nvim` function, ruby/dotnet PATH entries, + both asdf `set-env` sources) and adds a second, non-existent `~/.asdf/shims`. + Deduping it was deliberately deferred. + +--- + +## ⚠️ Requires a paired dotfiles PR + +`darwin.zshrc` is sourced from the **middle** of `.zshrc`, so for anything defined +in both files, whichever defines it *later* silently wins. Both files defined +`colima_start` with **different flags**, and `.zshrc` defined it after the source — +so the `.zshrc` version won and the effective alias never had `--with-kubernetes`. + +Removing that duplicate here would have silently flipped Kubernetes **on**. To keep +behavior identical, a paired change drops the flag in `darwin.zshrc`: + +- branch: `colima-no-kubernetes` in `mrsauravsahu/dotfiles` (commit `68d77dc`) +- PR: https://github.com/mrsauravsahu/dotfiles/pull/new/colima-no-kubernetes + +**Merge the dotfiles PR together with this one.** Verified: with both applied, the +effective `colima_start` is byte-identical to today's. diff --git a/src/installers/ohmyposh.configure.zsh b/src/installers/ohmyposh.configure.zsh index 7d09bd0..11e312f 100755 --- a/src/installers/ohmyposh.configure.zsh +++ b/src/installers/ohmyposh.configure.zsh @@ -2,7 +2,19 @@ TOOL=ohmyposh CONF=$CLI_CONFIG_CONF_LOCATION/$TOOL.conf.sh echo -n >$CONF -printf 'eval "$($CLI_CONFIG_ROOT/current/ohmyposh/oh-my-posh init zsh --config $CLI_CONFIG_ROOT/current/ohmyposh/themes/$CLI_CONFIG_THEME.omp.json)"\n' >>$CONF +# `oh-my-posh init zsh` costs ~170ms per shell start. Its output only changes when +# the binary or the theme changes, so cache it and re-generate when either is newer. +printf '_omp_bin=$CLI_CONFIG_ROOT/current/ohmyposh/oh-my-posh\n' >>$CONF +printf '_omp_theme=$CLI_CONFIG_ROOT/current/ohmyposh/themes/$CLI_CONFIG_THEME.omp.json\n' >>$CONF +printf '_omp_cache=$CLI_CONFIG_ROOT/current/ohmyposh/init.$CLI_CONFIG_THEME.zsh\n' >>$CONF +printf 'if [[ ! -s $_omp_cache || $_omp_bin -nt $_omp_cache || $_omp_theme -nt $_omp_cache ]]; then\n' >>$CONF +# POSH_SESSION_ID is a fresh UUID per `init` call and keys oh-my-posh's segment +# cache, so it must stay per-shell rather than being frozen into the cached file. +printf ' $_omp_bin init zsh --config $_omp_theme | grep -v "^export POSH_SESSION_ID=" >| $_omp_cache\n' >>$CONF +printf 'fi\n' >>$CONF +printf 'source $_omp_cache\n' >>$CONF +printf 'export POSH_SESSION_ID="cli-config-$$-${RANDOM}${RANDOM}"\n' >>$CONF +printf 'unset _omp_bin _omp_theme _omp_cache\n' >>$CONF printf 'if [[ -n "$NVIM" ]]; then\n' >>$CONF printf ' _omp_strip_zwsp() {\n' >>$CONF printf '%s\n' " local zwsp=\$'\\xe2\\x80\\x8b'" >>$CONF diff --git a/src/scripts/setup.programs-conf.zsh b/src/scripts/setup.programs-conf.zsh index f23107c..e19a415 100755 --- a/src/scripts/setup.programs-conf.zsh +++ b/src/scripts/setup.programs-conf.zsh @@ -7,11 +7,17 @@ echo '# init all cli-config tools' >> $CLI_CONFIG_PROGRAMS_CONF { echo 'autoload -Uz compinit' echo 'setopt extendedglob' - echo 'if [[ -n ${ZDOTDIR:-$HOME}/.zcompdump(#qN.mh+24) ]]; then' + echo '_cli_config_zcompdump=${ZDOTDIR:-$HOME}/.zcompdump' + echo 'if [[ -n ${_cli_config_zcompdump}(#qN.mh+24) ]]; then' echo ' compinit' + echo ' # compinit only rewrites the dump when completions actually changed, so its' + echo ' # mtime can stay stale forever - touch it so the 24h check resets and the' + echo ' # next start takes the fast -C path.' + echo ' touch ${_cli_config_zcompdump}' echo 'else' echo ' compinit -C' echo 'fi' + echo 'unset _cli_config_zcompdump' echo 'unsetopt extendedglob' } >> $CLI_CONFIG_PROGRAMS_CONF @@ -24,4 +30,13 @@ for tool in $(find $CLI_CONFIG_CONF_LOCATION -type f \( -name '*.conf.sh' -o -na echo ". $tool" >>$CLI_CONFIG_PROGRAMS_CONF done -echo eval "\$(thefuck --alias)" >>$CLI_CONFIG_PROGRAMS_CONF +# `thefuck --alias` costs ~370ms per shell start (python interpreter startup), which +# was roughly half of total startup time. Define a stub that pays that cost only on +# the first `fuck` invocation, then replaces itself with the real alias function. +{ + echo 'fuck() {' + echo ' unset -f fuck' + echo ' eval "$(thefuck --alias)"' + echo ' fuck "$@"' + echo '}' +} >>$CLI_CONFIG_PROGRAMS_CONF