Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
73cc5a9
perf(pm): sequential writes within tarball extraction
elrrrrrrr Apr 21, 2026
9379eb3
ci(pm-bench): robust output + warm skip + json fault tolerance
elrrrrrrr Apr 21, 2026
c5ec8bf
perf(pm): chunked parallel writes within tarball extraction
elrrrrrrr Apr 21, 2026
5464a76
perf(pm): surface per-phase cumulative timings in install summary
elrrrrrrr Apr 21, 2026
dde11b3
ci(pm-bench): surface cold install stdout via hyperfine --show-output
elrrrrrrr Apr 21, 2026
f776c9f
ci(pm-bench-phases): phase-isolated cold install bench
elrrrrrrr Apr 22, 2026
3d272e2
ci(pm-bench-phases): trigger on PR label so it runs before default-br…
elrrrrrrr Apr 22, 2026
ccd948c
ci(pm-bench-phases): robust per-iteration prepare via generated scripts
elrrrrrrr Apr 22, 2026
7a92749
ci(pm-bench-phases): use utoo clean / bun pm cache rm for reliable ca…
elrrrrrrr Apr 22, 2026
d8e01ee
ci(pm-bench-phases): scope caches to bench dirs via --cache-dir / env
elrrrrrrr Apr 22, 2026
4f9f210
debug(pm-bench-phases): log prepare script + before/after cache sizes
elrrrrrrr Apr 22, 2026
786dee2
fix(pm-bench-phases): match full phase name in case patterns
elrrrrrrr Apr 22, 2026
5cb27a7
ci(pm-bench-phases): drop debug wrapper now that prepare is verified
elrrrrrrr Apr 22, 2026
0d9628a
feat(pm-bench-phases): collect RSS / ctx / IO per phase via /usr/bin/…
elrrrrrrr Apr 22, 2026
3d59b2e
feat(pm-bench-phases): capture detailed IO, network, CPU per phase
elrrrrrrr Apr 22, 2026
e364194
fix(pm-bench-phases): track disk writes by path, drop noisy pgpg counter
elrrrrrrr Apr 23, 2026
2929826
fix(pm-bench-phases): move du out of timed window
elrrrrrrr Apr 23, 2026
9554b1a
fix(pm-bench-phases): skip footprint JSON when loading hyperfine results
elrrrrrrr Apr 23, 2026
1ef5fa8
perf(ruborist): enable gzip + brotli on manifest HTTP client
elrrrrrrr Apr 23, 2026
cbf8d33
perf(ruborist): use HTTP/1.1 + connection pool for manifest fetching
elrrrrrrr Apr 23, 2026
499af54
Revert "perf(ruborist): use HTTP/1.1 + connection pool for manifest f…
elrrrrrrr Apr 23, 2026
df69b5c
debug(ruborist): per-request latency histogram for manifest fetches
elrrrrrrr Apr 23, 2026
a14b17e
debug(ruborist): switch histogram output to stdout (survives metrics …
elrrrrrrr Apr 23, 2026
bb8ce9e
perf(ruborist): use HTTP/1.1 + connection pool for manifest fetching
elrrrrrrr Apr 23, 2026
9e7f25f
perf(ruborist): dedup concurrent full-manifest fetches
elrrrrrrr Apr 23, 2026
09950ee
chore(ruborist): remove debug histogram instrumentation
elrrrrrrr Apr 23, 2026
80e5c66
ci(pm-bench-phases): add p0_full_cold phase
elrrrrrrr Apr 23, 2026
973d287
perf(ruborist): round-robin DNS addresses across connection pool
elrrrrrrr Apr 23, 2026
7ce528d
ci(pm-bench-phases): capture pcap of resolve phase for investigation
elrrrrrrr Apr 23, 2026
934f67c
perf(ruborist): raise manifest fetch concurrency to 256
elrrrrrrr Apr 23, 2026
08b104b
perf(ruborist): preheat HTTP connection pool before resolve phase
elrrrrrrr Apr 23, 2026
b0590d5
perf(ruborist): offload transitive-dep extraction to blocking pool
elrrrrrrr Apr 23, 2026
fd3f245
perf(ruborist): offload manifest JSON parse to blocking pool, drop pr…
elrrrrrrr Apr 23, 2026
1b82312
perf(ruborist): fire-and-forget disk cache writes
elrrrrrrr Apr 23, 2026
739a386
perf(pm): raise manifests-concurrency-limit default from 64 to 256
elrrrrrrr Apr 23, 2026
41010de
Revert "perf(pm): raise manifests-concurrency-limit default from 64 t…
elrrrrrrr Apr 23, 2026
39b58d3
perf(ruborist): multi-client HTTP pool + real preheat
elrrrrrrr Apr 23, 2026
76ac3fd
perf(ruborist): halve client count and preheat footprint
elrrrrrrr Apr 23, 2026
84a4095
perf(ruborist): OnceMap dedup + proc_us instrumentation, drop multi-c…
elrrrrrrr Apr 23, 2026
a813db9
debug(ruborist): per-request send/body/parse histograms
elrrrrrrr Apr 23, 2026
acb0af9
debug(ruborist): instrument get_core_version latency
elrrrrrrr Apr 23, 2026
a025f7f
perf(ruborist): eager-parse FullManifest.versions into map lookup
elrrrrrrr Apr 23, 2026
44c885a
perf(ruborist): drop spawn_blocking wraps from preload hot path
elrrrrrrr Apr 23, 2026
3cb7e71
debug(ruborist): in_flight saturation histogram
elrrrrrrr Apr 23, 2026
0818c30
perf(pm): raise manifests-concurrency-limit from 64 to 128
elrrrrrrr Apr 23, 2026
4fd088d
perf(ruborist): drop redundant skip_on_error from CoreVersionManifest
elrrrrrrr Apr 23, 2026
fbadec9
fix(ruborist): restore skip_on_error on non-Value fields
elrrrrrrr Apr 23, 2026
9d2db0c
perf(ruborist): keep preload HTTP connections hot
elrrrrrrr Apr 23, 2026
5423805
perf(ruborist): lazy per-version CoreVersionManifest conversion
elrrrrrrr Apr 24, 2026
67700a8
Revert "perf(ruborist): lazy per-version CoreVersionManifest conversion"
elrrrrrrr Apr 24, 2026
da8e194
chore(ruborist): remove preload/fetch latency instrumentation
elrrrrrrr Apr 24, 2026
014c366
perf(ruborist): rotate DNS addresses per-family
elrrrrrrr Apr 24, 2026
b374e00
debug(ruborist): resolve_pkg / disk_precheck timers
elrrrrrrr Apr 24, 2026
45fe0e1
perf(ruborist): skip per-package disk ETag probe on preload
elrrrrrrr Apr 24, 2026
423b1a3
debug(ruborist): full per-future timing breakdown
elrrrrrrr Apr 24, 2026
2fb6671
perf(ruborist): lock-free pending queue for preload
elrrrrrrr Apr 24, 2026
339be21
chore(ruborist): remove preload timing instrumentation
elrrrrrrr Apr 24, 2026
0ac4410
perf(pm): cap tokio blocking pool at worker_threads
elrrrrrrr Apr 24, 2026
4fd0fd9
perf(ruborist): lazy per-version CoreVersionManifest via simd_json Ow…
elrrrrrrr Apr 24, 2026
64337ed
perf(pm): drop manifests-concurrency-limit 128 → 64
elrrrrrrr Apr 24, 2026
780a8fb
Revert "perf(pm): drop manifests-concurrency-limit 128 → 64"
elrrrrrrr Apr 24, 2026
f76ff35
debug(ruborist): info-level logs for build_deps phase timing
elrrrrrrr Apr 24, 2026
7a90abb
perf(ruborist): rustls with aws-lc-rs crypto provider instead of ring
elrrrrrrr Apr 24, 2026
6a62059
perf(ruborist): restore disk ETag probe via bulk-readdir index
elrrrrrrr Apr 24, 2026
6726e47
debug(ruborist): HTTP-only timing diagnostic for preload phase
elrrrrrrr Apr 24, 2026
63b7a23
debug(ruborist): parse queue-vs-exec timing diagnostic
elrrrrrrr Apr 24, 2026
cfe0d71
perf(pm): raise manifests-concurrency cap from 128 to 256
elrrrrrrr Apr 24, 2026
890d9f9
Revert "perf(pm): raise manifests-concurrency cap from 128 to 256"
elrrrrrrr Apr 24, 2026
585036b
perf(ruborist): per-IP client pool for registry connections
elrrrrrrr Apr 24, 2026
bf7dc72
fix(ruborist): prefer IPv4 for pinned client pool
elrrrrrrr Apr 24, 2026
b821553
perf(ruborist): enable HTTP/2 negotiation for multi-client pool
elrrrrrrr Apr 24, 2026
5b1f616
Revert "perf(ruborist): enable HTTP/2 negotiation for multi-client pool"
elrrrrrrr Apr 24, 2026
01555df
Revert "fix(ruborist): prefer IPv4 for pinned client pool"
elrrrrrrr Apr 24, 2026
50df08c
Revert "perf(ruborist): per-IP client pool for registry connections"
elrrrrrrr Apr 24, 2026
324ba29
perf(pm): drop manifests-concurrency cap from 128 to 64
elrrrrrrr Apr 25, 2026
6aa0704
chore(manifest-bench): isolated HTTP-only fetch benchmark
elrrrrrrr Apr 25, 2026
08cdae0
perf(pm): tune manifests-concurrency cap from 64 to 96
elrrrrrrr Apr 25, 2026
c6324f1
ci(pm-bench-phases): wire manifest-bench standalone HTTP sweep
elrrrrrrr Apr 25, 2026
f70ab45
perf(pm): drop per-package progress messages from preload hot path
elrrrrrrr Apr 25, 2026
1c12623
perf(ruborist): drop format!() from preload dedup hot path
elrrrrrrr Apr 25, 2026
a801264
Revert "perf(ruborist): drop format!() from preload dedup hot path"
elrrrrrrr Apr 25, 2026
6bc6ff8
perf(pm): drop indicatif inc()/inc_length() from preload hot path
elrrrrrrr Apr 25, 2026
f5bfa64
perf(ruborist): Arc<FullManifest> in memory cache + dedup result
elrrrrrrr Apr 25, 2026
c995c83
perf(pm): default file log filter to info+ instead of debug+
elrrrrrrr Apr 25, 2026
1615d8b
perf(ruborist): drop versions.keys.clone() on cached resolve hot path
elrrrrrrr Apr 25, 2026
5c2aacf
perf(ruborist): normalize_spec returns Cow, no alloc on common path
elrrrrrrr Apr 25, 2026
ce574d5
perf(ruborist): preload worker-pool replaces FuturesUnordered
elrrrrrrr Apr 25, 2026
732258b
perf(pm): raise manifests-concurrency cap 96 → 128 (worker-pool era)
elrrrrrrr Apr 25, 2026
d5386ba
perf(pm): manifests-concurrency cap 128 → 160 (worker-pool sweep)
elrrrrrrr Apr 25, 2026
3ba8b27
perf(ruborist): inline simd_json parse, drop spawn_blocking
elrrrrrrr Apr 25, 2026
662f20d
perf(pm): cap 160 → 128 (with inline parse, find sweet spot)
elrrrrrrr Apr 25, 2026
e185dae
perf(ruborist): parallelise workspace package.json reads
elrrrrrrr Apr 25, 2026
8295aeb
debug(pm): instrument save_package_lock sub-phases
elrrrrrrr Apr 25, 2026
f1abb55
debug(ruborist): instrument setup phase (workspace + graph init)
elrrrrrrr Apr 25, 2026
cbb22f4
chore(ruborist): drop trailing newline in preload.rs
elrrrrrrr Apr 25, 2026
d9819fb
perf(pm): raise tokio max_blocking_threads from N to max(N*4, 32)
elrrrrrrr Apr 25, 2026
4e598bb
Revert "perf(pm): raise tokio max_blocking_threads from N to max(N*4,…
elrrrrrrr Apr 25, 2026
7a83e9a
test(pm): drop intra-package rayon par_chunks in tarball extract
elrrrrrrr Apr 25, 2026
3014901
Revert "test(pm): drop intra-package rayon par_chunks in tarball extr…
elrrrrrrr Apr 25, 2026
1bbbaa4
perf(pm): clone_dir uses rayon (mirror extractor pattern)
elrrrrrrr Apr 25, 2026
6e3a658
Revert "perf(pm): clone_dir uses rayon (mirror extractor pattern)"
elrrrrrrr Apr 25, 2026
d579072
chore(pm): fix CI gates on rebased #2818
elrrrrrrr Apr 27, 2026
88221ad
Revert "perf(ruborist): preload worker-pool replaces FuturesUnordered"
elrrrrrrr Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions .github/workflows/pm-bench-phases.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
name: utoopm-benchmark-phases

on:
workflow_dispatch:
inputs:
project:
description: 'Project to benchmark (under github.com/ant-design/<project>)'
required: false
default: 'ant-design'
type: string
registry:
description: 'Registry URL'
required: false
default: 'https://registry.npmjs.org'
type: string
runs:
description: 'Runs per phase'
required: false
default: '3'
type: string
pull_request:
types: [labeled]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-linux:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && github.event.label.name == 'bench-phases')
runs-on: ubuntu-latest
container:
image: ubuntu:20.04
steps:
- name: Install dependencies
env:
DEBIAN_FRONTEND: noninteractive
run: apt-get update && apt-get install -y build-essential curl pkg-config libssl-dev git
- uses: actions/checkout@v4
- name: Configure git safe directory
run: git config --global --add safe.directory /__w/utoo/utoo
- name: Init git submodules
run: git submodule update --init --recursive --depth 1
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly-2026-04-02
- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: pm-bench-linux
- name: Build current branch utoo + manifest-bench
run: |
cargo build --release --bin utoo -p utoo-pm
cargo build --release -p manifest-bench
- name: Upload utoo binary
uses: actions/upload-artifact@v4
with:
name: utoo-linux-bench-phases
path: target/release/utoo
retention-days: 1
- name: Upload manifest-bench binary
uses: actions/upload-artifact@v4
with:
name: manifest-bench-linux
path: target/release/manifest-bench
retention-days: 1

bench:
needs: [build-linux]
if: >
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && github.event.label.name == 'bench-phases')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Download utoo binary
uses: actions/download-artifact@v4
with:
name: utoo-linux-bench-phases
path: dist
- name: Download manifest-bench binary
uses: actions/download-artifact@v4
with:
name: manifest-bench-linux
path: dist
- name: Install utoo
run: |
chmod +x dist/utoo dist/manifest-bench
echo "$GITHUB_WORKSPACE/dist" >> $GITHUB_PATH
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install hyperfine, GNU time, tcpdump and dnsutils
run: sudo apt-get update && sudo apt-get install -y hyperfine time tcpdump dnsutils
- name: Verify tools
run: |
hyperfine --version
utoo --version
bun --version
- name: Run phase-isolated benchmark
env:
PROJECT: ${{ github.event.inputs.project || 'ant-design' }}
REGISTRY: ${{ github.event.inputs.registry || 'https://registry.npmjs.org' }}
BENCH_RUNS: ${{ github.event.inputs.runs || '3' }}
PM_LIST: 'utoo,bun'
run: |
chmod +x e2e/pm-bench-phases.sh
bash e2e/pm-bench-phases.sh

- name: Standalone manifest-bench (HTTP-only fetch sweep)
env:
REGISTRY: ${{ github.event.inputs.registry || 'https://registry.npmjs.org' }}
PROJECT: ${{ github.event.inputs.project || 'ant-design' }}
run: |
set -eu
# Generate a project lockfile to source the manifest names from.
# Use bun (which we already have) to do a quick lockfile-only
# install so manifest-bench has a real workload to fire against.
PROJECT_DIR="/tmp/pm-bench/$PROJECT"
if [ ! -d "$PROJECT_DIR" ]; then
mkdir -p /tmp/pm-bench
git clone --depth 1 "https://github.com/ant-design/$PROJECT" "$PROJECT_DIR"
fi
cd "$PROJECT_DIR"
if [ ! -f package-lock.json ]; then
echo "==> generating lockfile via utoo"
utoo deps --registry "$REGISTRY" || true
fi
ls -la package-lock.json || { echo "no lockfile, skip"; exit 0; }

echo
echo "============================================================"
echo "manifest-bench: HTTP-only fetch (no parse, no resolver)"
echo " Goal: isolate reqwest/rustls/tokio behaviour from"
echo " ruborist's resolver pipeline. Same numbers (wall, busy,"
echo " avg_conc, p50/p95/max) as ruborist's Preload HTTP diag."
echo "============================================================"
for CAP in 32 64 96 128 192 256; do
echo
echo "--- concurrency=$CAP, h1, full manifest, default UA ---"
manifest-bench --lockfile package-lock.json --registry "$REGISTRY" \
--concurrency "$CAP" --reps 2 --http1-only || true
done

echo
echo "--- concurrency=128, h2 negotiate, full manifest, default UA ---"
manifest-bench --lockfile package-lock.json --registry "$REGISTRY" \
--concurrency 128 --reps 2 || true

echo
echo "--- concurrency=128, h1, single-version endpoint ---"
manifest-bench --lockfile package-lock.json --registry "$REGISTRY" \
--concurrency 128 --reps 2 --http1-only --single-version || true

echo
echo "--- concurrency=128, h1, UA=Bun/1.2.21 ---"
manifest-bench --lockfile package-lock.json --registry "$REGISTRY" \
--concurrency 128 --reps 2 --http1-only --user-agent "Bun/1.2.21" || true

- name: Capture pcap (resolve phase, single run per PM)
env:
PROJECT: ${{ github.event.inputs.project || 'ant-design' }}
REGISTRY: ${{ github.event.inputs.registry || 'https://registry.npmjs.org' }}
run: |
chmod +x e2e/pm-bench-pcap.sh
bash e2e/pm-bench-pcap.sh

- name: Upload pcap artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: pm-bench-pcap
path: /tmp/pm-bench-pcap
retention-days: 3
57 changes: 57 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions bench/pm-bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,15 @@ run_cold_benchmarks() {
> "$metrics_file"

echo -e " ${CYAN}$pm${NC}..."
hyperfine \
if ! hyperfine \
--runs "$BENCH_COLD_RUNS" \
--prepare "bash $PREPARE_SCRIPT $project_dir $pm --cold" \
--export-json "$json_file" \
--show-output \
-n "$pm" \
"bash $METRICS_WRAPPER $metrics_file $cmd_script" \
2>&1 | tail -1 || echo -e " ${RED}$pm cold install failed${NC}"
"bash $METRICS_WRAPPER $metrics_file $cmd_script"; then
echo -e " ${RED}$pm cold install failed${NC}"
fi
done
}

Expand Down Expand Up @@ -372,7 +374,15 @@ print_results() {
// --- Parse hyperfine timing ---
const timeRows = [];
for (const file of jsonFiles) {
const data = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'));
let data;
try {
const raw = fs.readFileSync(path.join(dir, file), 'utf8');
if (!raw.trim()) { console.log(' skip empty: ' + file); continue; }
data = JSON.parse(raw);
} catch (err) {
console.log(' skip unparsable: ' + file + ' (' + err.message + ')');
continue;
}
const base = file.replace('.json', '');
const parts = base.split('_');
const typeIdx = parts.findIndex(p => p === 'cold' || p === 'warm');
Expand Down Expand Up @@ -406,7 +416,11 @@ print_results() {
.trim().split('\n').filter(Boolean);
if (lines.length === 0) continue;

const entries = lines.map(l => JSON.parse(l));
const entries = [];
for (const l of lines) {
try { entries.push(JSON.parse(l)); } catch (_) { /* skip malformed line */ }
}
if (entries.length === 0) continue;
const avg = {};
for (const key of ['rss', 'page_faults', 'vol_ctx', 'invol_ctx', 'io_in', 'io_out']) {
avg[key] = Math.round(entries.reduce((s, e) => s + (e[key] || 0), 0) / entries.length);
Expand Down Expand Up @@ -513,7 +527,11 @@ main() {
echo -e "${YELLOW}----------------------------------------${NC}"

run_cold_benchmarks "$project" "$registry" "$reg_short"
run_warm_benchmarks "$project" "$registry" "$reg_short"
if [ "$BENCH_WARM_RUNS" -gt 0 ]; then
run_warm_benchmarks "$project" "$registry" "$reg_short"
else
echo -e " ${YELLOW}Warm installs: skipped (BENCH_WARM_RUNS=0)${NC}"
fi

echo ""
done
Expand Down
4 changes: 2 additions & 2 deletions crates/pm/src/cmd/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub async fn view(package_spec: &str) -> Result<()> {
tracing::debug!("Fetched package info: {full_manifest:?}");

// Resolve version and get full VersionManifest (with all display fields)
let version_list: Vec<String> = full_manifest.versions.clone();
let version_list: Vec<String> = full_manifest.versions.keys.clone();
let resolved_version = utoo_ruborist::resolver::version::resolve_target_version(
&full_manifest.dist_tags,
&version_list,
Expand Down Expand Up @@ -94,7 +94,7 @@ fn print_package_header(full_manifest: &FullManifest, version_manifest: &Version
.as_ref()
.map(|d| d.len())
.unwrap_or(0);
let versions_count = full_manifest.versions.len();
let versions_count = full_manifest.versions.keys.len();

let deps_str = if deps_count == 0 {
"none".to_string()
Expand Down
Loading
Loading