Git's built-in tooling (plus ordinary Unix tools) gives you quite a bit already to understand your repo. You can time commands, capture repository layout, trace nested regions, log network chatter, and keep a small baseline for later comparison.
Start With One Reproducible Command
I start with one command I can run again without changing five other things at the same time — something like git status in the same checkout, git log -- path/to/file, or git fetch origin. Commands that mutate checkout state, like git switch, need a different measurement approach; Chapter 20 covers that case. Run the same command several times before you get fancy:
time git log --oneline -n 20 >/dev/null
time git log --oneline -n 20 >/dev/null
time git log --oneline -n 20 >/dev/null
Decide What "Slow" Means In This Context
There is no universal Git number that separates "fine" from "bad." The useful comparison is usually local: this checkout before and after a change, this repository on two machines, fetch --dry-run versus fetch, or the same command with and without a traceable feature enabled. That still leaves a practical question: what counts as enough to investigate? Under a few hundred milliseconds usually feels instant. One or two seconds is noticeable in an interactive command. Five seconds or more is usually disruptive if the command is part of normal development. And a 2x or 3x regression in the same environment is worth taking seriously even if the absolute number is smaller.
The point is to anchor the discussion in a real workload. git status taking 1.5 seconds in a very large monorepo may be acceptable for now. The same 1.5 seconds in a small repo after a config change is a regression.
Capture Repository Shape Before You Start Guessing
Timing alone is not enough. Before trusting a number, name the repository you're timing. Take a quick snapshot of the repository you are talking about:
git --version
git count-objects -vH
test -f "$(git rev-parse --git-path objects/info/commit-graph)" && echo "commit-graph: yes" || echo "commit-graph: no"
test -f "$(git rev-parse --git-path objects/pack/multi-pack-index)" && echo "midx: yes" || echo "midx: no"
ls "$(git rev-parse --git-path objects/pack)"/*.bitmap 2>/dev/null || true
git count-objects gives you a fast read on loose objects, packed objects, and pack count. That alone can explain more than you might expect. If the question looks storage-heavy, git verify-pack belongs nearby too.
Keep A Small Baseline On Disk
Do not trust yourself to remember what the repository looked like before you changed it. Keep some data around:
mkdir -p perf
git --version >perf/version.txt
git count-objects -vH >perf/count-objects.txt
git config --show-origin --get-regexp '^(core\.fsmonitor|index\.|gc\.|maintenance\.|commitGraph\.|feature\.)' >perf/config.txt || true
Use Trace2 When Timing Stops Being Enough
Git's Trace2 outputs are the first place to reach when wall-clock timing is not enough:
GIT_TRACE2_PERFfor a human-readable timing breakdown with nested regionsGIT_TRACE2_EVENTfor structured JSON you can inspect later or feed into tools
Examples:
GIT_TRACE2_PERF=/tmp/status.perf git status >/dev/null
GIT_TRACE2_EVENT=/tmp/status.json git status >/dev/null
If the command spawns child processes or hides in several layers of internal work, Trace2 breaks down where the time goes.
Use Narrow Traces For Narrow Questions
Sometimes you do not need full Trace2 output. A few useful examples:
GIT_TRACE_PERFORMANCE=1 git status >/dev/null
GIT_TRACE_SETUP=1 git status >/dev/null
GIT_TRACE_REFS=1 git for-each-ref --count=5 >/dev/null
GIT_TRACE_PACKET=/tmp/fetch.packet git fetch origin
Those answer different questions:
GIT_TRACE_PERFORMANCEgives a quick timing summaryGIT_TRACE_SETUPconfirms which repository and worktree Git thinks it is inGIT_TRACE_REFShelps when ref lookup or enumeration is part of the problemGIT_TRACE_PACKETis useful when fetch or push questions live in protocol exchange rather than local storage
Keep Trace Output Separate From Normal Output
Trace output gets messy fast if you mix it with ordinary command output. Write traces to files:
mkdir -p perf
GIT_TRACE2_PERF=perf/fetch.perf git fetch origin >perf/fetch.out 2>perf/fetch.err
GIT_TRACE_PACKET=perf/fetch.packet git fetch origin >/dev/null
Git also redacts some sensitive values by default when tracing is enabled, but trace files can still contain URLs, branch names, path names, and other useful context. Treat them like logs.
Instrument The Network Separately From The Checkout
Remember that clone and fetch often hide several costs under a single command. A slow clone can include server-side negotiation, transfer time, local unpack, and checkout and materialization. Packet tracing and Trace2 let you separate transport cost from local checkout cost before you start changing bundle strategy, bitmaps, or sparse settings.