1/**
2 * Throughput benchmarks against a **deployed** git-service stack
3 * (DESIGN.md §14–15): turns the estimated scaling table into measured
4 * numbers, one benchmark per named bottleneck.
5 *
6 * These are measurements, not assertions about performance: each case
7 * prints a line and asserts only *correctness* (every operation succeeded),
8 * so the suite can never fail because a datacenter had a slow minute. Read
9 * the printed table to see where the service actually stands.
10 *
11 * Run:
12 * bun run test test/GitBench.e2e.test.ts --profile testing
13 * BENCH_SCALE=4 ... to make every workload 4x bigger
14 *
15 * Skipped under `--fast`.
16 */
17import * as Cloudflare from "@/Cloudflare";
18import * as Test from "@/Test/Alchemy";
19import { expect } from "alchemy-test";
20import * as Data from "effect/Data";
21import * as Effect from "effect/Effect";
22import * as Fiber from "effect/Fiber";
23import * as FileSystem from "effect/FileSystem";
24import * as Path from "effect/Path";
25import { MinimumLogLevel } from "effect/References";
26import * as Schedule from "effect/Schedule";
27import * as Stream from "effect/Stream";
28import * as HttpClient from "effect/http/HttpClient";
29import * as HttpClientRequest from "effect/http/HttpClientRequest";
30import * as HttpApiClient from "effect/http-api/HttpApiClient";
31import * as ChildProcess from "effect/process/ChildProcess";
32import { GitApi } from "@/Git/Api.ts";
33import { verifyPackResponse } from "./harness/pack.ts";
34import { makeTestStack, TEST_SECRET } from "./fixtures/stack.ts";
35
36const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
37 providers: Cloudflare.providers(),
38});
39
40const logLevel = Effect.provideService(
41 MinimumLogLevel,
42 process.env.DEBUG ? "Debug" : "Info",
43);
44
45const Stack = makeTestStack("GitBenchStack");
46
47/** Scales every workload; 1 keeps a full run inside the alarm/test budget. */
48const SCALE = Number.parseInt(process.env.BENCH_SCALE ?? "1", 10) || 1;
49
50const skipBench = !!process.env.FAST;
51
52// ── plumbing ────────────────────────────────────────────────────────────────
53
54const makeClient = (url: string, token: string) =>
55 HttpApiClient.make(GitApi, {
56 baseUrl: url,
57 transformClient: HttpClient.mapRequest((request) =>
58 request.pipe(HttpClientRequest.bearerToken(token)),
59 ),
60 });
61
62class ShellError extends Data.TaggedError("ShellError")<{
63 readonly script: string;
64 readonly exitCode: number;
65 readonly stdout: string;
66 readonly stderr: string;
67}> {
68 override get message(): string {
69 return `bench shell exited ${this.exitCode}\n${this.script}\n${this.stderr}`;
70 }
71}
72
73const sh = Effect.fn(function* (cwd: string, script: string) {
74 const handle = yield* ChildProcess.make("sh", ["-ec", script], {
75 cwd,
76 env: {
77 GIT_TERMINAL_PROMPT: "0",
78 GIT_CONFIG_NOSYSTEM: "1",
79 GIT_CONFIG_GLOBAL: "/dev/null",
80 GIT_AUTHOR_NAME: "Bench Bot",
81 GIT_AUTHOR_EMAIL: "bench@example.com",
82 GIT_COMMITTER_NAME: "Bench Bot",
83 GIT_COMMITTER_EMAIL: "bench@example.com",
84 GIT_AUTHOR_DATE: "2026-01-01T00:00:00Z",
85 GIT_COMMITTER_DATE: "2026-01-01T00:00:00Z",
86 },
87 extendEnv: true,
88 });
89 const [exitCode, stdout, stderr] = yield* Effect.all(
90 [
91 handle.exitCode,
92 Stream.mkString(Stream.decodeText(handle.stdout)),
93 Stream.mkString(Stream.decodeText(handle.stderr)),
94 ],
95 { concurrency: 3 },
96 );
97 return { script, exitCode, stdout: stdout.trim(), stderr };
98}, Effect.timeout("240 seconds"));
99
100const mustSh = Effect.fn(function* (cwd: string, script: string) {
101 const result = yield* sh(cwd, script);
102 if (result.exitCode !== 0) return yield* new ShellError(result);
103 return result;
104});
105
106const tempDir = Effect.gen(function* () {
107 const fs = yield* FileSystem.FileSystem;
108 return yield* fs.makeTempDirectory({ prefix: "git-bench-" });
109});
110
111const edgeRetry = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
112 effect.pipe(
113 Effect.timeout("15 seconds"),
114 Effect.retry({ schedule: Schedule.spaced("1500 millis"), times: 40 }),
115 );
116
117const purgeRepo = Effect.fn(function* (
118 url: string,
119 owner: string,
120 repo: string,
121) {
122 const admin = yield* makeClient(url, TEST_SECRET);
123 yield* admin.repos.delete({ params: { owner, repo } }).pipe(
124 Effect.catchTag("RepoNotFound", () => Effect.void),
125 edgeRetry,
126 );
127 yield* admin.repos.get({ params: { owner, repo } }).pipe(
128 edgeRetry,
129 Effect.as(false),
130 Effect.catchTag("RepoNotFound", () => Effect.succeed(true)),
131 Effect.repeat({
132 schedule: Schedule.spaced("1 second"),
133 until: (gone) => gone,
134 times: 60,
135 }),
136 );
137});
138
139const freshRepo = Effect.fn(function* (
140 url: string,
141 owner: string,
142 name: string,
143 options?: { readonly public?: boolean },
144) {
145 const admin = yield* makeClient(url, TEST_SECRET);
146 const created = yield* Effect.gen(function* () {
147 yield* purgeRepo(url, owner, name);
148 return yield* admin.repos
149 .create({ payload: { owner, name, public: options?.public } })
150 .pipe(edgeRetry);
151 }).pipe(
152 Effect.retry({
153 while: (error: { readonly _tag?: string }) =>
154 error._tag === "RepoAlreadyExists",
155 schedule: Schedule.spaced("1 second"),
156 times: 3,
157 }),
158 );
159 const parsed = new URL(url);
160 return {
161 admin,
162 token: TEST_SECRET,
163 remote: `${parsed.protocol}//x:${TEST_SECRET}@${parsed.host}/${owner}/${name}.git`,
164 };
165});
166
167// ── measurement ─────────────────────────────────────────────────────────────
168
169const results: Array<string> = [];
170
171/** Times an effect, records a bench line, and returns the value. */
172const measure = <A, E, R>(
173 label: string,
174 effect: Effect.Effect<A, E, R>,
175 describe: (value: A, ms: number) => string,
176) =>
177 Effect.gen(function* () {
178 const started = yield* Effect.sync(() => performance.now());
179 const value = yield* effect;
180 const ms = yield* Effect.sync(() => performance.now() - started);
181 const line = `${label.padEnd(46)} ${describe(value, ms)}`;
182 results.push(line);
183 yield* Effect.logInfo(`[bench] ${line}`);
184 return value;
185 });
186
187const perSecond = (count: number, ms: number) =>
188 `${((count / ms) * 1000).toFixed(1)}/s`;
189
190const stack = beforeAll(
191 deploy(Stack).pipe(
192 Effect.tap(({ url }) =>
193 Effect.gen(function* () {
194 yield* Effect.logInfo(`git-service deployed at ${url}`);
195 const admin = yield* makeClient(url, TEST_SECRET);
196 yield* admin.repos.list({ query: {} }).pipe(
197 edgeRetry,
198 Effect.retry({
199 schedule: Schedule.spaced("1500 millis"),
200 times: 40,
201 }),
202 );
203 }),
204 ),
205 logLevel,
206 ),
207);
208
209afterAll(
210 Effect.gen(function* () {
211 if (results.length > 0) {
212 yield* Effect.logInfo(
213 ["", "═══ git-service throughput ═══", ...results, ""].join("\n"),
214 );
215 }
216 }),
217);
218afterAll.skipIf(!!process.env.NO_DESTROY)(destroy(Stack));
219
220// ═══════════════════════════════════════════════════════════════════════════
221// Bottleneck 1 — closure computation / clone bandwidth on deep history
222// ═══════════════════════════════════════════════════════════════════════════
223
224test.skipIf(skipBench)(
225 "bench: deep history — push, cold clone, bundle clone, incremental fetch",
226 Effect.gen(function* () {
227 const { url } = yield* stack;
228 const repo = yield* freshRepo(url, "bench", "deep");
229 const tmp = yield* tempDir;
230 const commits = 300 * SCALE;
231
232 // Build the history locally first so the measurement is server-side.
233 yield* mustSh(
234 tmp,
235 `
236 rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work
237 cd work
238 i=1
239 while [ $i -le ${commits} ]; do
240 printf 'line %s\\n' $i >> history.txt
241 printf 'file %s\\n' $i > "f$i.txt"
242 git add -A && git commit -q -m "c$i"
243 i=$((i+1))
244 done
245 `,
246 );
247
248 yield* measure(
249 `push ${commits} commits`,
250 mustSh(tmp, `cd work && git push origin main`),
251 (_, ms) =>
252 `${(ms / 1000).toFixed(1)}s (${perSecond(commits, ms)} commits)`,
253 );
254
255 // Cold clone: no bundle yet (the post-push alarm needs a moment), so
256 // this measures the dynamic closure walk + pack emission path.
257 yield* measure(
258 "clone (dynamic closure walk)",
259 mustSh(tmp, `rm -rf cold && git clone -q '${repo.remote}' cold`),
260 (_, ms) => `${(ms / 1000).toFixed(2)}s`,
261 );
262
263 // Give the bundle alarm time to cut, then clone again: same repo, same
264 // client, but served as bytes from R2 (DESIGN §12.2).
265 yield* Effect.sleep("6 seconds");
266 yield* measure(
267 "clone (R2 bundle fast path)",
268 mustSh(tmp, `rm -rf warm && git clone -q '${repo.remote}' warm`),
269 (_, ms) => `${(ms / 1000).toFixed(2)}s`,
270 );
271
272 // Incremental fetch of a single new commit — the negotiation path.
273 yield* mustSh(
274 tmp,
275 `cd work && echo tip >> history.txt && git add -A && git commit -q -m tip && git push -q origin main`,
276 );
277 yield* measure(
278 "incremental fetch (1 commit)",
279 mustSh(tmp, `cd cold && git fetch -q origin main`),
280 (_, ms) => `${ms.toFixed(0)}ms`,
281 );
282
283 yield* mustSh(tmp, `cd warm && git fsck --strict`);
284 }).pipe(logLevel),
285 {
286 tags: [
287 "provider:cloudflare",
288 "provider:cloudflare:r2",
289 "provider:cloudflare:worker",
290 "live",
291 ],
292 timeout: 900_000,
293 },
294);
295
296// ═══════════════════════════════════════════════════════════════════════════
297// Bottleneck 1/2 — clone TPS and bandwidth under concurrency
298// ═══════════════════════════════════════════════════════════════════════════
299
300test.skipIf(skipBench)(
301 "bench: concurrent clone storm (clone TPS on one repo)",
302 Effect.gen(function* () {
303 const { url } = yield* stack;
304 const repo = yield* freshRepo(url, "bench", "storm");
305 const tmp = yield* tempDir;
306 const blobKiB = 256 * SCALE;
307 const clones = 8 * SCALE;
308
309 yield* mustSh(
310 tmp,
311 `
312 rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work
313 cd work
314 head -c ${blobKiB * 1024} /dev/urandom > blob.bin
315 i=1; while [ $i -le 20 ]; do printf 'f %s\\n' $i > "f$i.txt"; i=$((i+1)); done
316 git add -A && git commit -q -m payload
317 git push -q origin main
318 `,
319 );
320 // Let the bundle land so the storm hits the fast path.
321 yield* Effect.sleep("6 seconds");
322
323 yield* measure(
324 `${clones} concurrent clones (~${blobKiB} KiB each)`,
325 Effect.all(
326 Array.from({ length: clones }, (_, i) =>
327 mustSh(tmp, `rm -rf c${i} && git clone -q '${repo.remote}' c${i}`),
328 ),
329 { concurrency: clones },
330 ),
331 (_, ms) =>
332 `${(ms / 1000).toFixed(2)}s total, ${perSecond(clones, ms)} clones, ` +
333 `~${((clones * blobKiB) / 1024 / (ms / 1000)).toFixed(1)} MiB/s`,
334 );
335 }).pipe(logLevel),
336 {
337 tags: [
338 "provider:cloudflare",
339 "provider:cloudflare:r2",
340 "provider:cloudflare:worker",
341 "live",
342 ],
343 timeout: 900_000,
344 },
345);
346
347// ═══════════════════════════════════════════════════════════════════════════
348// Bottleneck 3 — push serialization (inherent: a ref is a serialization point)
349// ═══════════════════════════════════════════════════════════════════════════
350
351test.skipIf(skipBench)(
352 "bench: push TPS, serial and concurrent-to-distinct-branches",
353 Effect.gen(function* () {
354 const { url } = yield* stack;
355 const repo = yield* freshRepo(url, "bench", "push");
356 const tmp = yield* tempDir;
357 const pushes = 10 * SCALE;
358
359 yield* mustSh(
360 tmp,
361 `
362 rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work
363 cd work && echo base > base.txt && git add -A && git commit -q -m base
364 git push -q origin main
365 `,
366 );
367
368 yield* measure(
369 `${pushes} serial pushes (same branch)`,
370 mustSh(
371 tmp,
372 `
373 cd work
374 i=1
375 while [ $i -le ${pushes} ]; do
376 printf '%s\\n' $i >> base.txt
377 git add -A && git commit -q -m "p$i"
378 git push -q origin main
379 i=$((i+1))
380 done
381 `,
382 ),
383 (_, ms) => `${(ms / 1000).toFixed(1)}s (${perSecond(pushes, ms)})`,
384 );
385
386 // Distinct branches: admission is by ingest MEMORY, not by count
387 // (DESIGN.md §2.1), so ordinary kilobyte-sized pushes to different
388 // branches should now overlap instead of queueing behind each other.
389 const branches = 4 * SCALE;
390 yield* mustSh(
391 tmp,
392 `
393 cd work
394 i=1
395 while [ $i -le ${branches} ]; do
396 git branch -f "side$i" HEAD
397 printf 'side %s\\n' $i > "side$i.txt"
398 i=$((i+1))
399 done
400 `,
401 );
402 yield* measure(
403 `${branches} concurrent pushes (distinct branches)`,
404 Effect.all(
405 Array.from({ length: branches }, (_, i) =>
406 mustSh(tmp, `cd work && git push -q origin side${i + 1}`),
407 ),
408 { concurrency: branches },
409 ),
410 (_, ms) => `${(ms / 1000).toFixed(2)}s (${perSecond(branches, ms)})`,
411 );
412 }).pipe(logLevel),
413 {
414 tags: [
415 "provider:cloudflare",
416 "provider:cloudflare:r2",
417 "provider:cloudflare:worker",
418 "live",
419 ],
420 timeout: 900_000,
421 },
422);
423
424// ═══════════════════════════════════════════════════════════════════════════
425// Bottleneck 4 — Registry singleton (control-plane TPS)
426// ═══════════════════════════════════════════════════════════════════════════
427
428test.skipIf(skipBench)(
429 "bench: control plane — repo create/list/delete through the Registry",
430 Effect.gen(function* () {
431 const { url } = yield* stack;
432 const admin = yield* makeClient(url, TEST_SECRET);
433 const repos = 10 * SCALE;
434 const names = Array.from({ length: repos }, (_, i) => `ctl-${i}`);
435
436 yield* Effect.all(
437 names.map((name) => purgeRepo(url, "bench", name)),
438 { concurrency: 5 },
439 );
440
441 yield* measure(
442 `${repos} concurrent repo creates`,
443 Effect.all(
444 names.map((name) =>
445 admin.repos.create({ payload: { owner: "bench", name } }),
446 ),
447 { concurrency: repos },
448 ),
449 (created, ms) => {
450 expect(created.length).toBe(repos);
451 return `${(ms / 1000).toFixed(2)}s (${perSecond(repos, ms)})`;
452 },
453 );
454
455 yield* measure(
456 "repo list (single Registry read)",
457 admin.repos.list({ query: { owner: "bench" } }),
458 (page, ms) => `${page.items.length} rows in ${ms.toFixed(0)}ms`,
459 );
460
461 yield* measure(
462 "resolve-cached repo reads (20 sequential GETs)",
463 Effect.all(
464 Array.from({ length: 20 }, () =>
465 admin.repos.get({ params: { owner: "bench", repo: names[0]! } }),
466 ),
467 { concurrency: 1 },
468 ),
469 (_, ms) => `${(ms / 20).toFixed(0)}ms each (${perSecond(20, ms)})`,
470 );
471
472 yield* Effect.all(
473 names.map((name) =>
474 admin.repos
475 .delete({ params: { owner: "bench", repo: name } })
476 .pipe(Effect.catchTag("RepoNotFound", () => Effect.void)),
477 ),
478 { concurrency: 5 },
479 );
480 }).pipe(logLevel),
481 {
482 tags: [
483 "provider:cloudflare",
484 "provider:cloudflare:r2",
485 "provider:cloudflare:worker",
486 "live",
487 ],
488 timeout: 900_000,
489 },
490);
491
492// ═══════════════════════════════════════════════════════════════════════════
493// Bottleneck 6 — compaction throughput (R2 write path)
494// ═══════════════════════════════════════════════════════════════════════════
495
496test.skipIf(skipBench)(
497 "bench: compaction — loose rows into an R2 pack, then packed reads",
498 Effect.gen(function* () {
499 const { url } = yield* stack;
500 const repo = yield* freshRepo(url, "bench", "compact");
501 const tmp = yield* tempDir;
502 const files = 200 * SCALE;
503
504 yield* mustSh(
505 tmp,
506 `
507 rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work
508 cd work
509 i=1
510 while [ $i -le ${files} ]; do
511 head -c 4096 /dev/urandom | base64 > "b$i.txt"
512 i=$((i+1))
513 done
514 git add -A && git commit -q -m payload
515 git push -q origin main
516 `,
517 );
518
519 const before = yield* repo.admin.repos.get({
520 params: { owner: "bench", repo: "compact" },
521 });
522
523 yield* measure(
524 `compact ${before.objects.loose} loose objects into R2`,
525 Effect.gen(function* () {
526 yield* repo.admin.repos.compact({
527 params: { owner: "bench", repo: "compact" },
528 });
529 return yield* repo.admin.repos
530 .get({ params: { owner: "bench", repo: "compact" } })
531 .pipe(
532 Effect.map((found) => found.objects),
533 Effect.repeat({
534 schedule: Schedule.spaced("500 millis"),
535 until: (objects: { loose: number; packed: number }) =>
536 objects.packed > 0 && objects.loose === 0,
537 times: 120,
538 }),
539 );
540 }),
541 (objects, ms) =>
542 `${objects.packed} packed, ${(objects.bytes / 1024 / 1024).toFixed(1)} MiB in ${(ms / 1000).toFixed(1)}s`,
543 );
544
545 // Every object now reads through ranged R2 GETs.
546 yield* measure(
547 "clone from a fully packed repo (ranged R2 reads)",
548 mustSh(tmp, `rm -rf packed && git clone -q '${repo.remote}' packed`),
549 (_, ms) => `${(ms / 1000).toFixed(2)}s`,
550 );
551 yield* mustSh(tmp, `cd packed && git fsck --strict`);
552 }).pipe(logLevel),
553 {
554 tags: [
555 "provider:cloudflare",
556 "provider:cloudflare:r2",
557 "provider:cloudflare:worker",
558 "live",
559 ],
560 timeout: 900_000,
561 },
562);
563
564// ═══════════════════════════════════════════════════════════════════════════
565// Bottleneck 1/2 — SERVER-side clone capacity (no git process in the way)
566// ═══════════════════════════════════════════════════════════════════════════
567
568test.skipIf(skipBench)(
569 "bench: raw upload-pack throughput (server capacity, no git CLI)",
570 Effect.gen(function* () {
571 const { url } = yield* stack;
572 const repo = yield* freshRepo(url, "bench", "raw");
573 const tmp = yield* tempDir;
574 const blobKiB = 512 * SCALE;
575
576 yield* mustSh(
577 tmp,
578 `
579 rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work
580 cd work
581 head -c ${blobKiB * 1024} /dev/urandom | base64 > payload.txt
582 i=1; while [ $i -le 50 ]; do printf 'f %s\\n' $i > "f$i.txt"; i=$((i+1)); done
583 git add -A && git commit -q -m payload
584 git push -q origin main
585 `,
586 );
587 const head = (yield* mustSh(tmp, `cd work && git rev-parse HEAD`)).stdout;
588 yield* Effect.sleep("6 seconds"); // let the bundle land
589
590 const client = yield* HttpClient.HttpClient;
591 const pkt = (line: string) =>
592 `${(line.length + 4).toString(16).padStart(4, "0")}${line}`;
593 const body = `${pkt(`want ${head}\n`)}0000${pkt("done\n")}`;
594
595 /** One full clone over raw HTTP; resolves to bytes received. */
596 const rawClone = client
597 .execute(
598 HttpClientRequest.post(`${url}/bench/raw.git/git-upload-pack`).pipe(
599 HttpClientRequest.setHeaders({
600 authorization: `Bearer ${repo.token}`,
601 "content-type": "application/x-git-upload-pack-request",
602 }),
603 HttpClientRequest.bodyText(body),
604 ),
605 )
606 .pipe(
607 Effect.flatMap((response) => response.arrayBuffer),
608 Effect.map((buffer) => buffer.byteLength),
609 );
610
611 // Warm one request so the measurements exclude cold start.
612 const packBytes = yield* rawClone;
613 expect(packBytes).toBeGreaterThan(1024);
614
615 // The same clone, but with a `have` the server does not know. That
616 // disqualifies the bundle (bundleCovers refuses any request carrying
617 // haves) while producing the SAME pack, so the two lines below are a
618 // like-for-like v1-dynamic vs v2-bundle comparison.
619 const unknownHave = "f".repeat(40);
620 const dynamicClone = client
621 .execute(
622 HttpClientRequest.post(`${url}/bench/raw.git/git-upload-pack`).pipe(
623 HttpClientRequest.setHeaders({
624 authorization: `Bearer ${repo.token}`,
625 "content-type": "application/x-git-upload-pack-request",
626 }),
627 HttpClientRequest.bodyText(
628 `${pkt(`want ${head}\n`)}${pkt(`have ${unknownHave}\n`)}0000${pkt("done\n")}`,
629 ),
630 ),
631 )
632 .pipe(
633 Effect.flatMap((response) => response.arrayBuffer),
634 Effect.map((buffer) => buffer.byteLength),
635 );
636
637 const throughput = (
638 sizes: ReadonlyArray<number>,
639 ms: number,
640 n: number,
641 ) => {
642 const total = sizes.reduce((sum, bytes) => sum + bytes, 0);
643 return (
644 `${perSecond(n, ms)} clones, ` +
645 `${(total / 1024 / 1024 / (ms / 1000)).toFixed(1)} MiB/s`
646 );
647 };
648
649 for (const concurrency of [1, 8, 32]) {
650 yield* measure(
651 `raw clone x${concurrency} — bundle (${(packBytes / 1024).toFixed(0)} KiB)`,
652 Effect.all(
653 Array.from({ length: concurrency }, () => rawClone),
654 { concurrency },
655 ),
656 (sizes, ms) => throughput(sizes, ms, concurrency),
657 );
658 yield* measure(
659 `raw clone x${concurrency} — dynamic closure walk`,
660 Effect.all(
661 Array.from({ length: concurrency }, () => dynamicClone),
662 { concurrency },
663 ),
664 (sizes, ms) => throughput(sizes, ms, concurrency),
665 );
666 }
667 }).pipe(logLevel),
668 {
669 tags: [
670 "provider:cloudflare",
671 "provider:cloudflare:r2",
672 "provider:cloudflare:worker",
673 "live",
674 ],
675 timeout: 900_000,
676 },
677);
678
679// ═══════════════════════════════════════════════════════════════════════════
680// Bottleneck 2 — subrequest limit vs. fragmented packs (DESIGN §15.2)
681// ═══════════════════════════════════════════════════════════════════════════
682
683test.skipIf(skipBench)(
684 "bench: dynamic fetch over a compacted repo with >1000 objects",
685 Effect.gen(function* () {
686 const { url } = yield* stack;
687 const repo = yield* freshRepo(url, "bench", "subreq");
688 const tmp = yield* tempDir;
689 // Comfortably past the Workers 1000-subrequest cap once compacted:
690 // one ranged R2 GET per object would fail outright.
691 const files = 1200;
692
693 yield* mustSh(
694 tmp,
695 `
696 rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work
697 cd work
698 i=1
699 while [ $i -le ${files} ]; do
700 printf 'payload %s\\n' $i > "f$i.txt"
701 i=$((i+1))
702 done
703 git add -A && git commit -q -m bulk
704 git push -q origin main
705 `,
706 );
707
708 // Force everything into an R2 pack.
709 yield* repo.admin.repos.compact({
710 params: { owner: "bench", repo: "subreq" },
711 });
712 const packed = yield* repo.admin.repos
713 .get({ params: { owner: "bench", repo: "subreq" } })
714 .pipe(
715 Effect.map((found) => found.objects),
716 Effect.repeat({
717 schedule: Schedule.spaced("1 second"),
718 until: (objects: { loose: number; packed: number }) =>
719 objects.packed > 1000 && objects.loose === 0,
720 times: 120,
721 }),
722 );
723 expect(packed.packed).toBeGreaterThan(1000);
724
725 const head = (yield* mustSh(tmp, `cd work && git rev-parse HEAD`)).stdout;
726 const client = yield* HttpClient.HttpClient;
727 const pkt = (line: string) =>
728 `${(line.length + 4).toString(16).padStart(4, "0")}${line}`;
729
730 // A `have` the server does not know bypasses the bundle, so this is the
731 // dynamic path reading every object out of the pack.
732 yield* measure(
733 `dynamic fetch, ${packed.packed} packed objects`,
734 client
735 .execute(
736 HttpClientRequest.post(
737 `${url}/bench/subreq.git/git-upload-pack`,
738 ).pipe(
739 HttpClientRequest.setHeaders({
740 authorization: `Bearer ${repo.token}`,
741 "content-type": "application/x-git-upload-pack-request",
742 }),
743 HttpClientRequest.bodyText(
744 `${pkt(`want ${head}\n`)}${pkt(`have ${"f".repeat(40)}\n`)}0000${pkt("done\n")}`,
745 ),
746 ),
747 )
748 .pipe(
749 Effect.flatMap((response) =>
750 response.arrayBuffer.pipe(
751 Effect.map((buffer) => ({
752 status: response.status,
753 bytes: buffer.byteLength,
754 })),
755 ),
756 ),
757 ),
758 (result, ms) =>
759 `status ${result.status}, ${(result.bytes / 1024).toFixed(0)} KiB in ${(ms / 1000).toFixed(2)}s`,
760 ).pipe(
761 Effect.tap((result) => {
762 // The whole point: this must NOT fail on the subrequest cap.
763 expect(result.status).toBe(200);
764 expect(result.bytes).toBeGreaterThan(10_000);
765 return Effect.void;
766 }),
767 );
768
769 // And the repo still clones cleanly through the same path.
770 yield* mustSh(tmp, `rm -rf verify && git clone -q '${repo.remote}' verify`);
771 yield* mustSh(tmp, `cd verify && git fsck --strict`);
772 }).pipe(logLevel),
773 {
774 tags: [
775 "provider:cloudflare",
776 "provider:cloudflare:r2",
777 "provider:cloudflare:worker",
778 "live",
779 ],
780 timeout: 900_000,
781 },
782);
783
784// ═══════════════════════════════════════════════════════════════════════════
785// Bottleneck 1 — push admission. The Repo DO bounds ingest by MEMORY, not
786// by push count, so a small push no longer waits out a large upload. Both
787// arms run in ONE case so the comparison is same-repo, same-minute.
788// ═══════════════════════════════════════════════════════════════════════════
789
790test.skipIf(skipBench)(
791 "bench: small-push latency alone vs. behind a large upload",
792 Effect.gen(function* () {
793 const { url } = yield* stack;
794 const repo = yield* freshRepo(url, "bench", "admit");
795 const tmp = yield* tempDir;
796 const bigMiB = 12 * SCALE;
797
798 yield* mustSh(
799 tmp,
800 `
801 rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work
802 cd work
803 printf 'seed\n' > seed.txt
804 git add -A && git commit -q -m seed
805 git push -q origin main
806 `,
807 );
808
809 /** A one-file push to its own branch — kilobytes of actual work. */
810 const smallPush = (n: number) =>
811 mustSh(
812 tmp,
813 `
814 cd work
815 git checkout -q -B "tiny${n}" main
816 printf 'tiny %s\n' ${n} > "tiny${n}.txt"
817 git add -A && git commit -q -m "tiny${n}"
818 git push -q origin "tiny${n}"
819 git checkout -q main
820 `,
821 );
822
823 // Uncontended baseline.
824 yield* smallPush(1);
825 yield* measure(
826 "small push, uncontended",
827 smallPush(2),
828 (_, ms) => `${ms.toFixed(0)} ms`,
829 );
830
831 // Now the same push while a large body is still uploading. Under
832 // count-based admission the small push waited for the whole upload;
833 // under a byte budget it only needs its own MiB.
834 yield* mustSh(
835 tmp,
836 `
837 cd work
838 git checkout -q -B heavy main
839 head -c ${bigMiB * 1024 * 1024} /dev/urandom > heavy.bin
840 git add -A && git commit -q -m heavy
841 git checkout -q main
842 `,
843 );
844 // Fork the heavy push so the measurement covers the SMALL push alone,
845 // not the pair — timing both together just reports the heavy one.
846 const heavy = yield* Effect.forkChild(
847 mustSh(tmp, `cd work && git push -q origin heavy`),
848 );
849 yield* measure(
850 `small push, while a ${bigMiB} MiB upload is in flight`,
851 smallPush(3),
852 (_, ms) => `${ms.toFixed(0)} ms`,
853 );
854 yield* Fiber.join(heavy);
855 }).pipe(logLevel),
856 {
857 tags: [
858 "provider:cloudflare",
859 "provider:cloudflare:r2",
860 "provider:cloudflare:worker",
861 "live",
862 ],
863 timeout: 900_000,
864 },
865);
866
867// ═══════════════════════════════════════════════════════════════════════════
868// Bottleneck 2 — bundle staleness. A ref moving after the bundle was cut
869// used to drop every clone onto the dynamic closure walk; the bundle is now
870// spliced with a small delta pack instead. Fresh and stale are measured in
871// the same run against the same repo.
872// ═══════════════════════════════════════════════════════════════════════════
873
874test.skipIf(skipBench)(
875 "bench: clone TPS against a FRESH vs a STALE bundle",
876 Effect.gen(function* () {
877 const { url } = yield* stack;
878 const repo = yield* freshRepo(url, "bench", "stale");
879 const tmp = yield* tempDir;
880 const blobKiB = 512 * SCALE;
881
882 yield* mustSh(
883 tmp,
884 `
885 rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work
886 cd work
887 head -c ${blobKiB * 1024} /dev/urandom | base64 > payload.txt
888 i=1; while [ $i -le 50 ]; do printf 'f %s\n' $i > "f$i.txt"; i=$((i+1)); done
889 git add -A && git commit -q -m payload
890 git push -q origin main
891 `,
892 );
893 yield* Effect.sleep("8 seconds"); // let the bundle land
894
895 const client = yield* HttpClient.HttpClient;
896 const pkt = (line: string) =>
897 `${(line.length + 4).toString(16).padStart(4, "0")}${line}`;
898 const request = (head: string) =>
899 client.execute(
900 HttpClientRequest.post(`${url}/bench/stale.git/git-upload-pack`).pipe(
901 HttpClientRequest.setHeaders({
902 authorization: `Bearer ${repo.token}`,
903 "content-type": "application/x-git-upload-pack-request",
904 }),
905 HttpClientRequest.bodyText(
906 `${pkt(`want ${head}\n`)}0000${pkt("done\n")}`,
907 ),
908 ),
909 );
910 const cloneAt = (head: string) =>
911 request(head).pipe(
912 Effect.flatMap((response) => response.arrayBuffer),
913 Effect.map((buffer) => buffer.byteLength),
914 );
915 /** Which plane answered — `do-bundle:bundle`, `…:spliced`, or none. */
916 const planeAt = (head: string) =>
917 request(head).pipe(
918 Effect.map(
919 (response) => response.headers["x-git-served-by"] ?? "dynamic",
920 ),
921 );
922
923 const N = 32 * SCALE;
924 const throughput = (sizes: ReadonlyArray<number>, ms: number) => {
925 const total = sizes.reduce((sum, bytes) => sum + bytes, 0);
926 return (
927 `${perSecond(sizes.length, ms)} clones, ` +
928 `${(total / 1024 / 1024 / (ms / 1000)).toFixed(1)} MiB/s`
929 );
930 };
931
932 const fresh = (yield* mustSh(tmp, `cd work && git rev-parse HEAD`)).stdout;
933 yield* cloneAt(fresh); // warm
934 const freshPlane = yield* planeAt(fresh);
935 results.push(` fresh arm served by: ${freshPlane}`);
936 yield* measure(
937 `${N} clones, FRESH bundle, concurrency 32`,
938 Effect.all(
939 Array.from({ length: N }, () => cloneAt(fresh)),
940 { concurrency: 32 },
941 ),
942 throughput,
943 );
944
945 // Move main past the bundle and clone immediately: the bundle on disk
946 // no longer names this tip.
947 yield* mustSh(
948 tmp,
949 `
950 cd work
951 printf 'more\n' > more.txt
952 git add -A && git commit -q -m more
953 git push -q origin main
954 `,
955 );
956 const stale = (yield* mustSh(tmp, `cd work && git rev-parse HEAD`)).stdout;
957 const warmStale = yield* cloneAt(stale);
958 expect(warmStale).toBeGreaterThan(1024);
959 const stalePlane = yield* planeAt(stale);
960 results.push(` stale arm served by: ${stalePlane}`);
961 yield* measure(
962 `${N} clones, STALE bundle (dynamic fallback), concurrency 32`,
963 Effect.all(
964 Array.from({ length: N }, () => cloneAt(stale)),
965 { concurrency: 32 },
966 ),
967 throughput,
968 );
969 }).pipe(logLevel),
970 {
971 tags: [
972 "provider:cloudflare",
973 "provider:cloudflare:r2",
974 "provider:cloudflare:worker",
975 "live",
976 ],
977 timeout: 900_000,
978 },
979);
980
981// ═══════════════════════════════════════════════════════════════════════════
982// Single large clone — where does the time go? Splits time-to-first-byte
983// (planning: Worker -> DO -> R2 HEAD -> marker) from transfer (bytes on
984// the wire), per serving path, on a real ~36 MiB repository. This is the
985// instrument for "clones feel slow": a large TTFB is planning latency, a
986// low MiB/s with small TTFB is the streaming path itself.
987// ═══════════════════════════════════════════════════════════════════════════
988
989test.skipIf(skipBench)(
990 "bench: single large clone — TTFB vs transfer, by serving path",
991 Effect.gen(function* () {
992 const { url } = yield* stack;
993 const tmp = yield* tempDir;
994
995 // Same source as the real-world suite: this monorepo, depth-1, as one
996 // root commit — ~13.6k objects, ~36 MiB.
997 const root = (yield* mustSh(process.cwd(), `git rev-parse --show-toplevel`))
998 .stdout;
999 yield* mustSh(
1000 tmp,
1001 `
1002 rm -rf src
1003 git clone -q --depth 1 "file://${root}" src
1004 cd src && rm -rf .git && git init -q -b main && git add -A
1005 git commit -q -m "alchemy snapshot"
1006 `,
1007 );
1008 const repo = yield* freshRepo(url, "bench", "large", { public: true });
1009 yield* mustSh(
1010 tmp,
1011 `cd src && git remote add origin '${repo.remote}' && git push -q origin main`,
1012 );
1013 const head = (yield* mustSh(tmp, `cd src && git rev-parse HEAD`)).stdout;
1014
1015 const client = yield* HttpClient.HttpClient;
1016 const pkt = (line: string) =>
1017 `${(line.length + 4).toString(16).padStart(4, "0")}${line}`;
1018 const request = (body: string) =>
1019 client.execute(
1020 HttpClientRequest.post(`${url}/bench/large.git/git-upload-pack`).pipe(
1021 HttpClientRequest.setHeaders({
1022 authorization: `Bearer ${repo.token}`,
1023 "content-type": "application/x-git-upload-pack-request",
1024 }),
1025 HttpClientRequest.bodyText(body),
1026 ),
1027 );
1028 const bodies = {
1029 passthrough: `${pkt(`want ${head}\n`)}0000${pkt("done\n")}`,
1030 sideband: `${pkt(`want ${head} side-band-64k\n`)}0000${pkt("done\n")}`,
1031 dynamic: `${pkt(`want ${head}\n`)}${pkt(`have ${"f".repeat(40)}\n`)}0000${pkt("done\n")}`,
1032 };
1033
1034 /**
1035 * One clone, timed in two halves: headers (TTFB) and body drain — and
1036 * VERIFIED: the pack is de-framed and its SHA-1 trailer checked. A
1037 * stream that aborts mid-body can reach a lenient HTTP client as a
1038 * clean end (seen once: 0.5 MiB of a 40.6 MiB pack, test green), so
1039 * bytes alone prove nothing.
1040 */
1041 const timedClone = (body: string) =>
1042 Effect.gen(function* () {
1043 const t0 = performance.now();
1044 const response = yield* request(body);
1045 const ttfbMs = performance.now() - t0;
1046 const raw = new Uint8Array(yield* response.arrayBuffer);
1047 const totalMs = performance.now() - t0;
1048 const pack = yield* Effect.sync(() =>
1049 verifyPackResponse(raw, body.includes("side-band-64k")),
1050 );
1051 expect(pack.error, `pack response: ${pack.error}`).toBeUndefined();
1052 return {
1053 ttfbMs,
1054 totalMs,
1055 bytes: raw.byteLength,
1056 objects: pack.objects,
1057 via: response.headers["x-git-served-by"] ?? "dynamic",
1058 };
1059 });
1060 const describe = (r: {
1061 ttfbMs: number;
1062 totalMs: number;
1063 bytes: number;
1064 objects: number;
1065 via: string;
1066 }) =>
1067 `ttfb ${r.ttfbMs.toFixed(0)}ms, total ${(r.totalMs / 1000).toFixed(2)}s, ` +
1068 `${(r.bytes / 1048576).toFixed(1)} MiB (${r.objects} objects, trailer ok) @ ` +
1069 `${(r.bytes / 1048576 / ((r.totalMs - r.ttfbMs) / 1000)).toFixed(1)} MiB/s transfer ` +
1070 `[${r.via}]`;
1071
1072 // Wait for the bundle to land (a 36 MiB cut takes a while), bounded.
1073 yield* timedClone(bodies.passthrough).pipe(
1074 Effect.repeat({
1075 schedule: Schedule.spaced("5 seconds"),
1076 until: (r) => r.via.startsWith("do-bundle"),
1077 times: 36,
1078 }),
1079 );
1080
1081 for (const [name, body] of Object.entries(bodies)) {
1082 yield* timedClone(body); // warm
1083 yield* measure(`large clone x1 — ${name}`, timedClone(body), (r) =>
1084 describe(r),
1085 );
1086 }
1087
1088 // Does the streaming path scale with parallel streams, or is it a
1089 // per-stream ceiling? 4 passthrough clones at once.
1090 yield* measure(
1091 "large clone x4 — passthrough, concurrent",
1092 Effect.all(
1093 Array.from({ length: 4 }, () => timedClone(bodies.passthrough)),
1094 { concurrency: 4 },
1095 ),
1096 (rs, ms) => {
1097 const total = rs.reduce((n, r) => n + r.bytes, 0);
1098 return `${(total / 1048576 / (ms / 1000)).toFixed(1)} MiB/s aggregate over ${(ms / 1000).toFixed(2)}s`;
1099 },
1100 );
1101 }).pipe(logLevel),
1102 {
1103 tags: [
1104 "provider:cloudflare",
1105 "provider:cloudflare:r2",
1106 "provider:cloudflare:worker",
1107 "live",
1108 ],
1109 timeout: 1_200_000,
1110 },
1111);
1112
1113// ═══════════════════════════════════════════════════════════════════════════
1114// Bottleneck 8 — the DO on the anonymous read path (Continuity learnings,
1115// DESIGN.md §21): every advertisement and bundle-planning hop wakes the
1116// repo DO today, so anonymous public reads share one single-threaded
1117// object. Baseline for the DO-less head-object fast path.
1118// ═══════════════════════════════════════════════════════════════════════════
1119
1120test.skipIf(skipBench)(
1121 "bench: anonymous reads on a PUBLIC repo (advertisement + raw clone TPS)",
1122 Effect.gen(function* () {
1123 const { url } = yield* stack;
1124 const repo = yield* freshRepo(url, "bench", "pubread", { public: true });
1125 const tmp = yield* tempDir;
1126 const blobKiB = 256 * SCALE;
1127
1128 yield* mustSh(
1129 tmp,
1130 `
1131 rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work
1132 cd work
1133 head -c ${blobKiB * 1024} /dev/urandom > blob.bin
1134 i=1; while [ $i -le 20 ]; do printf 'f %s\n' $i > "f$i.txt"; i=$((i+1)); done
1135 git add -A && git commit -q -m payload
1136 git push -q origin main
1137 `,
1138 );
1139 const head = (yield* mustSh(tmp, `cd work && git rev-parse HEAD`)).stdout;
1140 yield* Effect.sleep("6 seconds"); // let the bundle land
1141
1142 const client = yield* HttpClient.HttpClient;
1143 const pkt = (line: string) =>
1144 `${(line.length + 4).toString(16).padStart(4, "0")}${line}`;
1145
1146 // TOKENLESS advertisement — GET info/refs with no Authorization.
1147 const advertise = client
1148 .get(`${url}/bench/pubread.git/info/refs?service=git-upload-pack`)
1149 .pipe(
1150 Effect.flatMap((response) => response.arrayBuffer),
1151 Effect.map((buffer) => buffer.byteLength),
1152 );
1153 // warm + correctness: the advertisement carries the tip AND proves
1154 // the DO-less path served it (not an accidental DO fall-through).
1155 const warmAdv = yield* client.get(
1156 `${url}/bench/pubread.git/info/refs?service=git-upload-pack`,
1157 );
1158 expect(warmAdv.headers["x-git-served-by"]).toBe("head-snapshot");
1159 const advBytes = (yield* warmAdv.arrayBuffer).byteLength;
1160 expect(advBytes).toBeGreaterThan(40);
1161
1162 const SERIAL_N = 20;
1163 yield* measure(
1164 `${SERIAL_N} serial anonymous advertisements`,
1165 Effect.forEach(Array.from({ length: SERIAL_N }), () => advertise),
1166 (_, ms) => `${(ms / SERIAL_N).toFixed(0)} ms each`,
1167 );
1168
1169 const ADV_N = 128 * SCALE;
1170 yield* measure(
1171 `${ADV_N} anonymous advertisements, concurrency 32`,
1172 Effect.all(
1173 Array.from({ length: ADV_N }, () => advertise),
1174 { concurrency: 32 },
1175 ),
1176 (sizes, ms) => `${perSecond(sizes.length, ms)} advertisements`,
1177 );
1178
1179 // TOKENLESS full clone over raw HTTP (want tip, done — bundle-eligible).
1180 const rawClone = client
1181 .execute(
1182 HttpClientRequest.post(`${url}/bench/pubread.git/git-upload-pack`).pipe(
1183 HttpClientRequest.setHeaders({
1184 "content-type": "application/x-git-upload-pack-request",
1185 }),
1186 HttpClientRequest.bodyText(
1187 `${pkt(`want ${head}\n`)}0000${pkt("done\n")}`,
1188 ),
1189 ),
1190 )
1191 .pipe(
1192 Effect.flatMap((response) => response.arrayBuffer),
1193 Effect.map((buffer) => buffer.byteLength),
1194 );
1195 // warm + prove the bundle was served DO-lessly from the snapshot
1196 const warmClone = yield* client.execute(
1197 HttpClientRequest.post(`${url}/bench/pubread.git/git-upload-pack`).pipe(
1198 HttpClientRequest.setHeaders({
1199 "content-type": "application/x-git-upload-pack-request",
1200 }),
1201 HttpClientRequest.bodyText(
1202 `${pkt(`want ${head}\n`)}0000${pkt("done\n")}`,
1203 ),
1204 ),
1205 );
1206 expect(warmClone.headers["x-git-served-by"]).toBe("head-snapshot:bundle");
1207 const first = (yield* warmClone.arrayBuffer).byteLength;
1208 expect(first).toBeGreaterThan(blobKiB * 1024 * 0.5);
1209
1210 const CLONE_N = 64 * SCALE;
1211 yield* measure(
1212 `${CLONE_N} anonymous raw clones, concurrency 32`,
1213 Effect.all(
1214 Array.from({ length: CLONE_N }, () => rawClone),
1215 { concurrency: 32 },
1216 ),
1217 (sizes, ms) => {
1218 const total = sizes.reduce((sum, bytes) => sum + bytes, 0);
1219 return (
1220 `${perSecond(sizes.length, ms)} clones, ` +
1221 `${(total / 1024 / 1024 / (ms / 1000)).toFixed(1)} MiB/s`
1222 );
1223 },
1224 );
1225
1226 // and a real git clone with NO credentials still works
1227 const parsed = new URL(url);
1228 yield* mustSh(
1229 tmp,
1230 `rm -rf anon && git clone -q '${parsed.protocol}//${parsed.host}/bench/pubread.git' anon && cd anon && git fsck --strict`,
1231 );
1232 }).pipe(logLevel),
1233 {
1234 tags: [
1235 "provider:cloudflare",
1236 "provider:cloudflare:r2",
1237 "provider:cloudflare:worker",
1238 "live",
1239 ],
1240 timeout: 900_000,
1241 },
1242);
1243
1244// ═══════════════════════════════════════════════════════════════════════════
1245// Bottleneck 9 — pack-count growth (Continuity learnings, DESIGN.md §21):
1246// each compaction run writes a NEW pack and never merges, so reads over an
1247// old repo fan out across ever more packs. Baseline for geometric merging.
1248// ═══════════════════════════════════════════════════════════════════════════
1249
1250test.skipIf(skipBench)(
1251 "bench: dynamic fetch over a repo compacted in 6 increments (pack fan-out)",
1252 Effect.gen(function* () {
1253 const { url } = yield* stack;
1254 const repo = yield* freshRepo(url, "bench", "npack");
1255 const tmp = yield* tempDir;
1256 const ROUNDS = 6;
1257
1258 yield* mustSh(
1259 tmp,
1260 `rm -rf work && git -c init.defaultBranch=main clone '${repo.remote}' work`,
1261 );
1262
1263 // ROUNDS x (push a batch, compact it) => ROUNDS separate packs today.
1264 for (let round = 1; round <= ROUNDS; round++) {
1265 yield* mustSh(
1266 tmp,
1267 `
1268 cd work
1269 i=1; while [ $i -le 12 ]; do head -c ${32 * 1024} /dev/urandom > "r${round}-f$i.bin"; i=$((i+1)); done
1270 git add -A && git commit -q -m "round ${round}"
1271 git push -q origin main
1272 `,
1273 );
1274 yield* repo.admin.repos
1275 .compact({ params: { owner: "bench", repo: "npack" } })
1276 .pipe(edgeRetry);
1277 // wait until the loose rows have drained into the pack
1278 yield* repo.admin.repos
1279 .get({ params: { owner: "bench", repo: "npack" } })
1280 .pipe(
1281 edgeRetry,
1282 Effect.repeat({
1283 schedule: Schedule.spaced("2 seconds"),
1284 until: (meta) => meta.objects.loose === 0,
1285 times: 45,
1286 }),
1287 );
1288 }
1289
1290 const after = yield* repo.admin.repos
1291 .get({ params: { owner: "bench", repo: "npack" } })
1292 .pipe(edgeRetry);
1293 results.push(
1294 `npack repo: ${after.objects.packed} packed objects after ${ROUNDS} compactions`.padEnd(
1295 46,
1296 ),
1297 );
1298
1299 const head = (yield* mustSh(tmp, `cd work && git rev-parse HEAD`)).stdout;
1300 const client = yield* HttpClient.HttpClient;
1301 const pkt = (line: string) =>
1302 `${(line.length + 4).toString(16).padStart(4, "0")}${line}`;
1303
1304 // Unknown have forces the dynamic path (bundle refused): the closure
1305 // walk + object reads must range-read across every pack.
1306 const unknownHave = "f".repeat(40);
1307 const dynamicFetch = client
1308 .execute(
1309 HttpClientRequest.post(`${url}/bench/npack.git/git-upload-pack`).pipe(
1310 HttpClientRequest.setHeaders({
1311 authorization: `Bearer ${repo.token}`,
1312 "content-type": "application/x-git-upload-pack-request",
1313 }),
1314 HttpClientRequest.bodyText(
1315 `${pkt(`want ${head}\n`)}${pkt(`have ${unknownHave}\n`)}0000${pkt("done\n")}`,
1316 ),
1317 ),
1318 )
1319 .pipe(
1320 Effect.flatMap((response) => response.arrayBuffer),
1321 Effect.map((buffer) => buffer.byteLength),
1322 );
1323
1324 const warm = yield* dynamicFetch;
1325 expect(warm).toBeGreaterThan(100_000);
1326
1327 const N = 8;
1328 yield* measure(
1329 `${N} serial dynamic fetches over ${ROUNDS}-increment repo`,
1330 Effect.forEach(Array.from({ length: N }), () => dynamicFetch),
1331 (sizes, ms) =>
1332 `${(ms / N).toFixed(0)} ms/fetch, ` +
1333 `${((sizes[0] ?? 0) / 1024 / 1024).toFixed(1)} MiB pack`,
1334 );
1335
1336 // correctness: clone back clean through the same packs
1337 yield* mustSh(tmp, `rm -rf verify && git clone -q '${repo.remote}' verify`);
1338 yield* mustSh(tmp, `cd verify && git fsck --strict`);
1339 }).pipe(logLevel),
1340 {
1341 tags: [
1342 "provider:cloudflare",
1343 "provider:cloudflare:r2",
1344 "provider:cloudflare:worker",
1345 "live",
1346 ],
1347 timeout: 900_000,
1348 },
1349);