pm-perf-tracing
GitHub用于追踪 nub 包管理器安装性能,通过开启内置的相位计时和结构化诊断日志,定位网络、解析或链接等环节的性能瓶颈。
Trigger Scenarios
Install
npx skills add nubjs/nub --skill pm-perf-tracing -g -y
SKILL.md
Frontmatter
{
"name": "pm-perf-tracing",
"metadata": {
"internal": true
},
"description": "Performance-trace Nub package-manager installs using the existing phase timings, structured diagnostics, and sampling-profiler workflow. Use when an install or package-manager operation is unexpectedly slow and the bottleneck needs to be localized before changing code."
}
pm-perf-tracing
How to performance-trace the nub package manager (install/resolve/fetch/link) and find where the time actually goes — the method that cracked the hoisted-linker slowness (10.8s → root-caused to a per-file copy loop). Reach for this any time a nub install / PM operation is "mysteriously slow" — do NOT reverse-engineer from source; the instrumentation already exists, turn it on.
The two layers that already exist
-
Phase timings — works under nub TODAY.
RUST_LOG=debug nub installemitsphase:<name> <elapsed>lines on stderr:phase:resolve,phase:fetch (N packages),phase:link (N files),phase:link_bins. This alone tells you the coarse split (is it network/resolve, or linking?). The aube engine wires these through thetracingcrate. -
Rich diagnostics layer — native under nub via
NUB_DIAG_*.aube_util::diagemits structured JSONL toNUB_DIAG_FILE=<path>— including, for the linker, one event per file naming the strategy used (link_clonedir,link_reflink,link_macos_small_copy,link_copy,link_hardlink). This is the load-independent crux for a link-perf question. The full knob set (all off by default, zero cost when unset):NUB_DIAG_FILE=<path>— JSONL events to a file.NUB_DIAG_PRINT=1— live per-span lines to stderr.NUB_DIAG_SUMMARY=1— end-of-run aggregate table.NUB_DIAG_CRITPATH=1— retain records for the critical-path / lifecycle / starvation analyzers.NUB_DIAG_THRESHOLD_MS=<n>— filter live prints below<n>ms.NUB_DIAG_KERNEL=1—getrusagekernel deltas around phases.NUB_BENCH_PHASES_FILE=<path>— per-run phase-timing JSON for the bench harness.
cargo build -p nub-cli --profile fast # dev binary at <worktree>-target/fast/nub
NUB=<worktree>-target/fast/nub
NUB_DIAG_FILE=/tmp/d.jsonl NUB_DIAG_SUMMARY=1 "$NUB" install --offline
grep -o '"name":"link_[a-z_]*"' /tmp/d.jsonl | sort | uniq -c # per-strategy tally
(The diag layer reads NUB_DIAG_* under the nub embedder profile via the diag_env_prefix hook — no source edit needed. AUBE_DIAG_* is NOT read under nub. The dev-tracing-telemetry thread / wiki/research/dev-tracing-telemetry.md tracks the optional chrome-trace/flamegraph export layer on top.)
The measurement discipline (load-independent — this host is permanently contended)
The dev box runs load ~30–50 and never goes quiet, so absolute wall-clock is untrustworthy. Measure things contention can't ruin:
- Verified-clean warm loop:
rm -rf node_modulesand assert it's gone, warm store already populated,--offline(proves zero network:phase:fetchshows0 packages), and check rc=0 on every run (a timing from an errored install — e.g. npm'srm: Directory not emptypurge failures → rc=254 — is garbage). - Strategy tally, not seconds: "75,079/76,167 files took
link_macos_small_copy, 0 tooklink_clonedir" is a fact regardless of load. That's what proves a design gap. - Back-to-back A/B on the same box, report the RATIO: e.g. hoisted vs
--node-linker isolatedon the same fixture, same load window → the relative delta (≈26×) is robust even when both absolutes are inflated. - For a real clean wall-clock number, hand it to a quiet machine / CI runner — never block on this box settling.
Layout matters — always check which linker path runs
nub mirrors the incumbent layout: an npm/yarn/bun lockfile → hoisted layout (link.rs → hoisted::link_hoisted_importer); nub-identity / --node-linker isolated → isolated layout (materialize_into, the only path with the whole-dir clonefile(2) fast path). They have completely different perf characteristics. When a perf question is about linking, run BOTH (--node-linker isolated vs default) and diff — that A/B is what localized the hoisted-linker gap.
When spans aren't enough — sampling profiler
Span/JSONL instrumentation can distort a syscall-bound, parallel pass (observer effect). For "where inside the link phase do the syscalls go" use a sampling profiler on a release build: samply record -- <NUB> install --offline (macOS/Linux), or cargo flamegraph. Spans tell you which phase/strategy; the sampler tells you which syscalls/functions dominate.
Fixtures
/tmp/coffee2-demo— CoffeeScript 2.0.1, npmlockfileVersion:1, 519 pkgs / ~76k hoisted files. The canonical heavy-hoisted-layout fixture.- A minimal hoisted repro: a
package.json+package-lock.jsonwithwebpack@3.6.0+underscore(nonode_modules).
The one-liner to remember
RUST_LOG=debug nub install for the phase split; if it's phase:link, set NUB_DIAG_FILE=… for the per-file strategy tally; A/B against --node-linker isolated; judge by strategy-tally + ratio, never the contended absolute.
Version History
- 4fd083f Current 2026-07-19 12:03


