Skip to content

perf(pm): drop per-package debug logs from resolve hot path#2880

Merged
elrrrrrrr merged 4 commits intonextfrom
perf/drop-hot-path-debug-logs
May 1, 2026
Merged

perf(pm): drop per-package debug logs from resolve hot path#2880
elrrrrrrr merged 4 commits intonextfrom
perf/drop-hot-path-debug-logs

Conversation

@elrrrrrrr
Copy link
Copy Markdown
Contributor

Summary

Strip ~17 tracing::debug!() callsites from ruborist's resolve hot path. Each per-package event paid 3 costs on resolver threads: (1) EnvFilter dispatch, (2) fmt::layer format with target + line + thread_id, (3) tracing_appender::non_blocking channel send. With 600+ packages on a typical project that adds up to thousands of events per cold install — and the resulting multi-MB file log was ~95% dedup/cache-hit noise no operator ever reads.

Failure paths already produce structured anyhow!() chains with URL + name + status, so tracing::debug!(\"Fetching ... from {url}\") adds nothing the error chain doesn't already carry.

Measured impact (96-package cold install, UTOO_FILE_LOG=debug)

before after Δ
log lines 2634 715 −73%
log size 375 KB 90 KB −76%
ruborist events ~2200 14 −99%

The 14 remaining ruborist events are all phase markers (preload start/stats, build elapsed, registry/cache config). The other ~700 lines come from external crates (hyper / rustls / reqwest debug). Default file filter (utoo=info after #2872's log filter change, or utoo=debug today) emits 0 lines from ruborist hot path.

What's dropped

File Callsite Frequency
service/cache.rs 6× memory hit/insert per-package very high
service/manifest.rs 2× "Fetching ... from {url}" per request once per fetch
service/registry.rs ETag/store-hit/cache-miss per resolve per package
resolver/preload.rs "Preloaded {}@{}" per package per package
resolver/builder.rs "Processing/Reusing/Creating/Need to resolve/Resolved" per BFS step per edge (highest)
resolver/version.rs "Using dist-tags 'latest'" per resolve per package
model/graph.rs "found existing deps reuse" per BFS edge per dedup hit

What's kept

  • Phase markers (preload start, stats, complete; build elapsed)
  • Rare/informative: Failed to preload, Skipped optional, Override matched, Conflict deps
  • Startup config: registry URL, cache dir, override-rules count

Why ship as a standalone PR (not bundled with #2872)

This is a pure log hygiene change applied directly on top of next, with no dependency on worker-pool or the rest of the #2818 perf bundle. Isolating it lets us measure the bench delta cleanly:

Test plan

  • cargo fmt clean
  • cargo clippy -p utoo-ruborist -p utoo-pm --all-targets -- -D warnings --no-deps clean
  • cargo test -p utoo-ruborist: 162 pass
  • cargo test -p utoo-pm: 244 pass (1 pre-existing flaky network test in isolation)
  • Local utoo deps smoke test on 96-pkg cold install: works, log size −76%
  • CI pm-bench-phases-linux — main interest is whether ratio vs bun moves and σ collapses

🤖 Generated with Claude Code

The hot path emitted ~5-10 `tracing::debug!()` calls per resolved
manifest — cache hit/miss decisions, BFS dedup decisions, "Fetching"
notices, "Preloaded" markers, "Reusing existing" / "Resolved" /
"Creating node" traces. With 600+ packages on a typical project
that's thousands of events per cold install, each paying:

  1. EnvFilter dispatch (callsite cache lookup),
  2. fmt::layer format (target + line + thread_id + message),
  3. tracing_appender::non_blocking channel send.

Steps 2-3 run on resolver threads; the format output landed in a
multi-megabyte file even though most lines are pure dedup noise the
operator never reads.

Failure paths already produce structured `anyhow!()` chains with full
URL + name + status context — `tracing::debug!("Fetching ... from
{url}")` is redundant at start, the error chain has the same data on
failure. Cache hit/miss, "Reusing", "found existing deps" are
implementation details that never inform user-facing diagnosis;
removing them keeps the file log focused on phase markers + statistics.

Measured on a 96-package cold install with `UTOO_FILE_LOG=debug`:

  before:  2634 lines, 375 KB
  after:    715 lines,  90 KB   (-73% lines, -76% size)

ruborist's own emissions drop from ~2200 events to 14 (phase markers
only); the remainder is hyper / rustls / reqwest debug noise unaffected
by this change.

Kept (rare, informative for diagnosis):
- Phase markers: preload start/stats, build phase elapsed
- Override matched / Skipped optional / Conflict / Failed to preload

Dropped:
- service/cache.rs: 6× memory hit/insert per-package
- service/manifest.rs: 2× "Fetching ... from {url}" per request
- service/registry.rs: ETag/store-hit/cache-miss per resolve
- resolver/preload.rs: "Preloaded {}@{}" per package
- resolver/builder.rs: "Processing/Reusing/Creating/Need to resolve/Resolved" per BFS step
- resolver/version.rs: "Using dist-tags 'latest'" per resolve
- model/graph.rs: "found existing deps reuse" per BFS edge

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@elrrrrrrr elrrrrrrr added the benchmark Run pm-bench on PR label Apr 30, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request removes numerous tracing::debug! logging statements across the codebase, specifically within the dependency graph, resolver, and service modules. These changes streamline the code by removing verbose debug output during dependency resolution, manifest fetching, and cache operations. I have no feedback to provide.

elrrrrrrr and others added 3 commits April 30, 2026 21:41
…text

Companion to #2880's ruborist hot-path log cleanup. Extends the same
discipline to the pm side and fixes three real bugs in failure paths.

## Network retry: URL spam (downloader.rs)

`download_bytes` retried up to 10 times and printed
`tracing::warn!("Retry N/10 - Network error: ..., url: {url}")` on
every attempt — plus an outer `inspect_err(|e| warn!("Download
failed: name@version: e"))` at the call site, plus `.context(
"Download failed after retries")` at the bottom that didn't even
include the URL. One transient network hiccup = 12 log lines, URL
repeated 10x.

Fix: drop per-attempt warns. The retry framework already retries
silently; final failure surfaces through `with_context(|| format!(
"Download failed after retries: {url}"))` once. Per-package
"Cache hit" / "Downloaded" / "URL not found" debug noise also
dropped (same class as ruborist's recent BFS log cleanup).

## Link failure: lost paths (install.rs)

The pre-fix code logged `source={resolved}, target={path}` only at
debug level, then returned `anyhow!("Link failed: {e}")` which
dropped both paths. User saw `Link failed: Permission denied (os
error 13)` with no idea which file. Paths only landed in
`UTOO_FILE_LOG=debug` capture, invisible to default CLI output.

Fix: `link(...).await.with_context(|| format!("Link failed:
{resolved} -> {path}"))?;` — paths now ride in the returned error's
context chain.

## Hardlink fallback: per-file warn spam (cloner.rs)

EXDEV cross-device failure already latched `force_copy = true` so
subsequent files used copy. But the non-EXDEV branch (EMLINK / EPERM
/ ...) had no such latch — every failing file in a multi-thousand-file
package fired its own warn line.

Fix: latch a per-package `warned_per_file` bool, warn once with
"further per-file failures suppressed" suffix, silently fall back
for the rest.

## Optional dep: error chain dropped (install.rs)

`tracing::warn!("Optional dependency {name} failed (ignored)")` had
no `e`, user couldn't tell why. Inconsistent with the sibling at
line 263 which does include `e`. Now: `... failed (ignored): {e:#}`
with `{:#}` for full anyhow chain.

## classify_status: tracing/anyhow duplication (ruborist/fetch.rs)

Each `tracing::warn!` in the match arms carried the same data the
`anyhow!` immediately constructed — but the warn fires per retry
attempt while the anyhow chain only surfaces on final failure.
Stacked with downloader's per-attempt warns at the outer layer, one
HTTP 500 was 6 separate emissions for the same fact.

Fix: drop the per-attempt warns. anyhow chain owns the URL+status;
caller's `with_context` adds the boundary annotation on final failure.

## Per-package debug noise (cloner.rs / install.rs / downloader.rs)

Same class as #2880's ruborist cleanup — `Cloned: name@version`,
`validating failed, since it's not a directory`, `cpu skipped`,
`os skipped`, `Skipping omitted dependency`, etc. all fire per
package on cold installs. None of them inform user-facing diagnosis
and the extraction-thread CPU + non_blocking channel send isn't free.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nventions

New review dimension covering the patterns the recent log audit
(#2880 + follow-up) surfaced:

- 14.1 Log level discipline — never per-package debug! in BFS hot
  paths (rule of thumb: if a project has 600+ packages and a
  callsite fires once per package, it produces multi-MB logs and
  CPU on resolver / extractor threads via non_blocking format/send)
- 14.2 Don't double-log a failure — pick exactly one of warn vs
  anyhow chain; sibling layers are duplication
- 14.3 Preserve context when wrapping errors — paths/URLs in
  with_context, not in opt-in debug
- 14.4 anyhow idioms — with_context lazy closure, bail!, {:#} for
  full chain
- 14.5 Per-incident warns inside loops — latch a "warned once" bool;
  retry frameworks should swallow per-attempt failures silently
- 14.6 Failure paths must include the resource — URL for network,
  path for fs, source+target for link, command+args for spawn

Adds 7 new anti-patterns A27-A33 to the quick-reference grep table:
- A27 per-package debug in hot path
- A28 retry-loop warn duplication
- A29 lost context in returned error
- A30 eager .context(format!())
- A31 source chain stripped via map_err
- A32 outer-only error format ({} vs {:#})
- A33 per-iteration warn spam

Updates the dimension count (12 -> 14) and anti-pattern range (A1-A26
-> A1-A33) in cross-references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@elrrrrrrr elrrrrrrr added the A-Pkg Manager Area: Package Manager label Apr 30, 2026
Copy link
Copy Markdown
Contributor

@killagu killagu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@github-actions
Copy link
Copy Markdown

📊 pm-bench-phases · b0221ab · linux (ubuntu-latest)

Workflow run — ant-design

PMs: utoo (this branch) · utoo-npm (latest published) · bun (latest)

npmjs.org

p0_full_cold

PM wall ±σ user sys RSS pgMinor
bun 15.50s 2.12s 9.64s 9.60s 556M 322.2K
utoo-next 13.58s 1.11s 11.69s 13.92s 1.15G 159.0K
utoo-npm 13.83s 1.09s 12.02s 13.89s 1.28G 155.1K
utoo 12.20s 2.12s 11.33s 13.60s 1.19G 158.7K
PM vCtx iCtx netRX netTX cache node_mod lock
bun 35.3K 9.5K 1.16G 7M 1.83G 1.72G 1M
utoo-next 204.6K 158.0K 1.13G 5M 1.68G 1.68G 2M
utoo-npm 205.4K 161.7K 1.14G 6M 1.68G 1.68G 2M
utoo 187.5K 140.4K 1.13G 5M 1.68G 1.68G 2M

p1_resolve

PM wall ±σ user sys RSS pgMinor
bun 5.59s 0.62s 3.65s 1.15s 471M 217.3K
utoo-next 10.00s 0.37s 6.52s 1.49s 421M 74.0K
utoo-npm 10.92s 0.58s 6.39s 1.06s 426M 73.3K
utoo 8.61s 1.08s 5.86s 1.28s 423M 69.5K
PM vCtx iCtx netRX netTX cache node_mod lock
bun 20.0K 1.2K 202M 3M 104M - 1M
utoo-next 103.0K 7.2K 197M 2M 7M 3M 2M
utoo-npm 74.1K 1.8K 208M 2M 9M 5M 2M
utoo 82.1K 4.0K 197M 2M 7M 3M 2M

p3_cold_install

PM wall ±σ user sys RSS pgMinor
bun 7.78s 0.61s 6.13s 9.98s 451M 194.6K
utoo-next 12.31s 4.51s 5.71s 12.08s 821M 115.5K
utoo-npm 10.68s 1.03s 5.73s 11.81s 746M 92.5K
utoo 12.76s 2.57s 5.71s 12.02s 758M 106.1K
PM vCtx iCtx netRX netTX cache node_mod lock
bun 11.9K 8.0K 993M 4M 1.73G 1.73G 1M
utoo-next 151.2K 108.9K 965M 4M 1.67G 1.67G 2M
utoo-npm 142.5K 111.1K 965M 4M 1.67G 1.67G 2M
utoo 147.2K 103.0K 965M 4M 1.67G 1.67G 2M

p4_warm_link

PM wall ±σ user sys RSS pgMinor
bun 3.28s 0.06s 0.22s 2.33s 135M 32.5K
utoo-next 2.33s 0.20s 0.61s 3.92s 83M 18.6K
utoo-npm 2.25s 0.15s 0.59s 3.83s 83M 18.9K
utoo 2.23s 0.11s 0.51s 3.77s 82M 19.2K
PM vCtx iCtx netRX netTX cache node_mod lock
bun 354 24 7M 27K 1.88G 1.72G 1M
utoo-next 49.0K 21.1K 15K 12K 1.67G 1.67G 2M
utoo-npm 47.1K 21.3K 16K 33K 1.67G 1.67G 2M
utoo 46.8K 19.7K 16K 34K 1.68G 1.67G 2M

npmmirror.com: no output captured.

@elrrrrrrr elrrrrrrr merged commit 3c8e3e5 into next May 1, 2026
46 checks passed
@elrrrrrrr elrrrrrrr deleted the perf/drop-hot-path-debug-logs branch May 1, 2026 01:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Pkg Manager Area: Package Manager benchmark Run pm-bench on PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants