1'use strict';23require('@babel/register')({4 presets: [['@babel/preset-react', {runtime: 'automatic'}]],5 plugins: ['@babel/plugin-transform-modules-commonjs'],6 only: [/\/src\//],7});89const path = require('path');10const fs = require('fs');11const webpack = require('webpack');12const inspector = require('node:inspector');1314const {clientManifest, ssrManifest} = require('./webpack-mock');1516const PROFILE_MODE = process.argv.includes('--profile');17const CONCURRENT_MODE = process.argv.includes('--concurrent');18const INJECT = !process.argv.includes('--no-injection');1920// ---------------------------------------------------------------------------21// Build22// ---------------------------------------------------------------------------2324function build() {25 const config = require('./webpack.config');26 return new Promise(function (resolve, reject) {27 webpack(config, function (err, stats) {28 if (err) {29 reject(err);30 return;31 }32 if (stats.hasErrors()) {33 reject(new Error(stats.toString({errors: true})));34 return;35 }36 console.log(37 stats.toString({colors: true, modules: false, entrypoints: false})38 );39 resolve();40 });41 });42}4344// ---------------------------------------------------------------------------45// Render helpers46// ---------------------------------------------------------------------------4748const {49 renderFizzNode: renderFizzNodeStream,50 renderFizzEdge: renderFizzEdgeStream,51 renderFlightFizzNode: renderFlightFizzNodeStream,52 renderFlightFizzEdge: renderFlightFizzEdgeStream,53 nodeStreamToString,54 webStreamToString,55} = require('./render-helpers');56const {printGrid} = require('./print-helpers');5758function renderFizzNode(AppComponent, itemCount) {59 return nodeStreamToString(renderFizzNodeStream(AppComponent, itemCount));60}6162function renderFizzEdge(AppComponent, itemCount) {63 return renderFizzEdgeStream(AppComponent, itemCount).then(webStreamToString);64}6566function renderFlightFizzNode(renderRSCNode, AppComponent, itemCount) {67 return nodeStreamToString(68 renderFlightFizzNodeStream(69 renderRSCNode,70 AppComponent,71 itemCount,72 clientManifest,73 ssrManifest,74 {inject: INJECT}75 )76 );77}7879function renderFlightFizzEdge(renderRSCEdge, AppComponent, itemCount) {80 return renderFlightFizzEdgeStream(81 renderRSCEdge,82 AppComponent,83 itemCount,84 clientManifest,85 ssrManifest,86 {inject: INJECT}87 ).then(webStreamToString);88}8990// ---------------------------------------------------------------------------91// Benchmarking92// ---------------------------------------------------------------------------9394const canGC = typeof globalThis.gc === 'function';9596async function runBenchmark(name, fn, iterations, warmup) {97 if (canGC) globalThis.gc();9899 // Warmup100 for (let i = 0; i < warmup; i++) {101 await fn();102 }103104 // Collect GC pauses during timed iterations.105 let gcCount = 0;106 let gcTotalMs = 0;107 const gcObs = new PerformanceObserver(list => {108 for (const entry of list.getEntries()) {109 gcCount++;110 gcTotalMs += entry.duration;111 }112 });113 gcObs.observe({entryTypes: ['gc']});114115 // Timed iterations116 const times = [];117 for (let i = 0; i < iterations; i++) {118 const start = performance.now();119 await fn();120 times.push(performance.now() - start);121 }122 gcObs.disconnect();123124 // Trim top/bottom 5% to remove outliers125 const sorted = [...times].sort((a, b) => a - b);126 const trimCount = Math.floor(sorted.length * 0.05);127 const trimmed = sorted.slice(trimCount, sorted.length - trimCount);128129 const mean = trimmed.reduce((s, t) => s + t, 0) / trimmed.length;130 const median = sorted[Math.floor(sorted.length / 2)];131 const stddev = Math.sqrt(132 trimmed.reduce((s, t) => s + (t - mean) ** 2, 0) / trimmed.length133 );134 const p95 = sorted[Math.floor(sorted.length * 0.95)];135 const min = sorted[0];136 const max = sorted[sorted.length - 1];137138 return {139 name,140 mean,141 median,142 stddev,143 p95,144 min,145 max,146 iterations,147 gcCount,148 gcTotalMs,149 };150}151152function printResult(result) {153 console.log(' %s:', result.name);154 console.log(' Mean: %s ms', result.mean.toFixed(2));155 console.log(' Median: %s ms', result.median.toFixed(2));156 console.log(' Stddev: %s ms', result.stddev.toFixed(2));157 console.log(' P95: %s ms', result.p95.toFixed(2));158 console.log(' Min: %s ms', result.min.toFixed(2));159 console.log(' Max: %s ms', result.max.toFixed(2));160 console.log(161 ' GC: %d pauses, %s ms total (%s ms/iter)',162 result.gcCount,163 result.gcTotalMs.toFixed(1),164 (result.gcTotalMs / result.iterations).toFixed(2)165 );166}167168async function runConcurrent(name, fn, total, concurrency, warmup) {169 if (canGC) globalThis.gc();170171 for (let i = 0; i < warmup; i++) {172 await fn();173 }174175 let gcCount = 0;176 let gcTotalMs = 0;177 const gcObs = new PerformanceObserver(list => {178 for (const entry of list.getEntries()) {179 gcCount++;180 gcTotalMs += entry.duration;181 }182 });183 gcObs.observe({entryTypes: ['gc']});184185 const latencies = new Array(total);186 let completed = 0;187 let launched = 0;188189 const start = performance.now();190 await new Promise(resolve => {191 function launch() {192 while (launched < total && launched - completed < concurrency) {193 const idx = launched++;194 const t0 = performance.now();195 fn().then(() => {196 latencies[idx] = performance.now() - t0;197 completed++;198 if (completed === total) {199 resolve();200 } else {201 launch();202 }203 });204 }205 }206 launch();207 });208 const elapsed = performance.now() - start;209 gcObs.disconnect();210211 const sorted = [...latencies].sort((a, b) => a - b);212 const mean = sorted.reduce((s, t) => s + t, 0) / sorted.length;213 const p95 = sorted[Math.floor(sorted.length * 0.95)];214215 return {216 name,217 reqPerSec: (total / elapsed) * 1000,218 mean,219 p95,220 total,221 concurrency,222 gcCount,223 gcTotalMs,224 };225}226227function printConcurrentResult(result) {228 console.log(' %s:', result.name);229 console.log(' Req/s: %s', result.reqPerSec.toFixed(1));230 console.log(' Mean: %s ms', result.mean.toFixed(2));231 console.log(' P95: %s ms', result.p95.toFixed(2));232 console.log(233 ' GC: %d pauses, %s ms total (%s ms/req)',234 result.gcCount,235 result.gcTotalMs.toFixed(1),236 (result.gcTotalMs / result.total).toFixed(2)237 );238}239240// ---------------------------------------------------------------------------241// CPU Profiling242// ---------------------------------------------------------------------------243244function startProfiler() {245 const session = new inspector.Session();246 session.connect();247 return new Promise(function (resolve, reject) {248 session.post('Profiler.enable', function (err) {249 if (err) {250 reject(err);251 return;252 }253 session.post('Profiler.start', function (err2) {254 if (err2) {255 reject(err2);256 return;257 }258 resolve(session);259 });260 });261 });262}263264function stopProfiler(session, outputPath) {265 return new Promise(function (resolve, reject) {266 session.post('Profiler.stop', function (err, {profile}) {267 if (err) {268 reject(err);269 return;270 }271 fs.mkdirSync(path.dirname(outputPath), {recursive: true});272 fs.writeFileSync(outputPath, JSON.stringify(profile));273 session.post('Profiler.disable');274 session.disconnect();275 resolve(profile);276 });277 });278}279280function printTopFunctions(profile, topN) {281 // Aggregate self-time per function from the profile nodes.282 const selfTimes = new Map();283 for (const node of profile.nodes) {284 const name = node.callFrame.functionName || '(anonymous)';285 const loc = node.callFrame.url286 ? node.callFrame.url.replace(/.*\//, '') + ':' + node.callFrame.lineNumber287 : '(native)';288 const key = name + ' @ ' + loc;289 const hitCount = node.hitCount || 0;290 selfTimes.set(key, (selfTimes.get(key) || 0) + hitCount);291 }292293 const sorted = [...selfTimes.entries()]294 .sort((a, b) => b[1] - a[1])295 .slice(0, topN);296297 const totalSamples = profile.nodes.reduce((s, n) => s + (n.hitCount || 0), 0);298299 console.log(' Top %d functions by self-time:', topN);300 for (const [key, hits] of sorted) {301 const pct = ((hits / totalSamples) * 100).toFixed(1);302 console.log(' %s%% - %s', pct, key);303 }304}305306async function profileRun(name, fn, warmup, iterations, outputPath) {307 // Warmup (unprofiled)308 for (let i = 0; i < warmup; i++) {309 await fn();310 }311312 // Collect GC pauses during the profiled run.313 let gcCount = 0;314 let gcTotalMs = 0;315 const gcObs = new PerformanceObserver(list => {316 for (const entry of list.getEntries()) {317 gcCount++;318 gcTotalMs += entry.duration;319 }320 });321 gcObs.observe({entryTypes: ['gc']});322323 // Profiled run324 const session = await startProfiler();325 for (let i = 0; i < iterations; i++) {326 await fn();327 }328 const profile = await stopProfiler(session, outputPath);329 gcObs.disconnect();330331 console.log(' %s → %s', name, outputPath);332 printTopFunctions(profile, 10);333 console.log(334 ' GC: %d pauses, %s ms total (%s ms/iter)',335 gcCount,336 gcTotalMs.toFixed(1),337 (gcTotalMs / iterations).toFixed(2)338 );339}340341// ---------------------------------------------------------------------------342// Main343// ---------------------------------------------------------------------------344345async function main() {346 console.log('Building RSC bundle...\n');347 await build();348349 const {350 renderRSCNode,351 renderRSCEdge,352 App: RSCApp,353 AppAsync: RSCAppAsync,354 } = require('./build/rsc-bundle.js');355 const App = require('./src/App.js').default;356 const AppAsync = require('./src/AppAsync.js').default;357358 const ITEM_COUNT = 200;359360 const WARMUP = 50;361 const ITERATIONS = 1000;362 const PROFILE_WARMUP = 50;363 const PROFILE_ITERATIONS = 500;364365 // --- Verify renders ---366 console.log('\n--- Verifying renders ---\n');367368 const fizzNodeHtml = await renderFizzNode(App, ITEM_COUNT);369 console.log('Fizz (Node, sync): %d bytes', fizzNodeHtml.length);370371 const flightFizzNodeHtml = await renderFlightFizzNode(372 renderRSCNode,373 RSCApp,374 ITEM_COUNT375 );376 console.log(377 'Flight + Fizz (Node, sync): %d bytes',378 flightFizzNodeHtml.length379 );380381 const fizzNodeAsyncHtml = await renderFizzNode(AppAsync, ITEM_COUNT);382 console.log('Fizz (Node, async): %d bytes', fizzNodeAsyncHtml.length);383384 const flightFizzNodeAsyncHtml = await renderFlightFizzNode(385 renderRSCNode,386 RSCAppAsync,387 ITEM_COUNT388 );389 console.log(390 'Flight + Fizz (Node, async):%d bytes',391 flightFizzNodeAsyncHtml.length392 );393394 const fizzEdgeHtml = await renderFizzEdge(App, ITEM_COUNT);395 console.log('Fizz (Edge, sync): %d bytes', fizzEdgeHtml.length);396397 const fizzEdgeAsyncHtml = await renderFizzEdge(AppAsync, ITEM_COUNT);398 console.log('Fizz (Edge, async): %d bytes', fizzEdgeAsyncHtml.length);399400 const flightFizzEdgeHtml = await renderFlightFizzEdge(401 renderRSCEdge,402 RSCApp,403 ITEM_COUNT404 );405 console.log(406 'Flight + Fizz (Edge, sync): %d bytes',407 flightFizzEdgeHtml.length408 );409410 const flightFizzEdgeAsyncHtml = await renderFlightFizzEdge(411 renderRSCEdge,412 RSCAppAsync,413 ITEM_COUNT414 );415 console.log(416 'Flight + Fizz (Edge, async):%d bytes',417 flightFizzEdgeAsyncHtml.length418 );419420 // --- CPU Profiling ---421 if (PROFILE_MODE) {422 console.log(423 '\n--- CPU Profiling (%d warmup, %d iterations) ---\n',424 PROFILE_WARMUP,425 PROFILE_ITERATIONS426 );427428 const profileDir = path.resolve(__dirname, 'build/profiles');429430 await profileRun(431 'Fizz (Node, sync)',432 () => renderFizzNode(App, ITEM_COUNT),433 PROFILE_WARMUP,434 PROFILE_ITERATIONS,435 path.join(profileDir, 'fizz-node-sync.cpuprofile')436 );437438 await profileRun(439 'Flight + Fizz (Node, sync)',440 () => renderFlightFizzNode(renderRSCNode, RSCApp, ITEM_COUNT),441 PROFILE_WARMUP,442 PROFILE_ITERATIONS,443 path.join(profileDir, 'flight-fizz-node-sync.cpuprofile')444 );445446 await profileRun(447 'Fizz (Node, async)',448 () => renderFizzNode(AppAsync, ITEM_COUNT),449 PROFILE_WARMUP,450 PROFILE_ITERATIONS,451 path.join(profileDir, 'fizz-node-async.cpuprofile')452 );453454 await profileRun(455 'Flight + Fizz (Node, async)',456 () => renderFlightFizzNode(renderRSCNode, RSCAppAsync, ITEM_COUNT),457 PROFILE_WARMUP,458 PROFILE_ITERATIONS,459 path.join(profileDir, 'flight-fizz-node-async.cpuprofile')460 );461462 await profileRun(463 'Fizz (Edge, sync)',464 () => renderFizzEdge(App, ITEM_COUNT),465 PROFILE_WARMUP,466 PROFILE_ITERATIONS,467 path.join(profileDir, 'fizz-edge-sync.cpuprofile')468 );469470 await profileRun(471 'Flight + Fizz (Edge, sync)',472 () => renderFlightFizzEdge(renderRSCEdge, RSCApp, ITEM_COUNT),473 PROFILE_WARMUP,474 PROFILE_ITERATIONS,475 path.join(profileDir, 'flight-fizz-edge-sync.cpuprofile')476 );477478 await profileRun(479 'Fizz (Edge, async)',480 () => renderFizzEdge(AppAsync, ITEM_COUNT),481 PROFILE_WARMUP,482 PROFILE_ITERATIONS,483 path.join(profileDir, 'fizz-edge-async.cpuprofile')484 );485486 await profileRun(487 'Flight + Fizz (Edge, async)',488 () => renderFlightFizzEdge(renderRSCEdge, RSCAppAsync, ITEM_COUNT),489 PROFILE_WARMUP,490 PROFILE_ITERATIONS,491 path.join(profileDir, 'flight-fizz-edge-async.cpuprofile')492 );493494 console.log(495 '\nProfiles saved to build/profiles/. Open in Chrome DevTools or speedscope.app.'496 );497498 return;499 }500501 // --- Concurrent Benchmark ---502 if (CONCURRENT_MODE) {503 const CONCURRENCY = 50;504 const TOTAL = 1000;505 const CONC_WARMUP = 20;506507 console.log(508 '\n--- Concurrent Benchmark (%d warmup, %d concurrency, %d requests, %d items) ---\n',509 CONC_WARMUP,510 CONCURRENCY,511 TOTAL,512 ITEM_COUNT513 );514515 const fizzNodeSync = await runConcurrent(516 'Fizz (Node, sync)',517 () => renderFizzNode(App, ITEM_COUNT),518 TOTAL,519 CONCURRENCY,520 CONC_WARMUP521 );522 printConcurrentResult(fizzNodeSync);523524 const flightFizzNodeSync = await runConcurrent(525 'Flight + Fizz (Node, sync)',526 () => renderFlightFizzNode(renderRSCNode, RSCApp, ITEM_COUNT),527 TOTAL,528 CONCURRENCY,529 CONC_WARMUP530 );531 printConcurrentResult(flightFizzNodeSync);532533 const fizzNodeAsync = await runConcurrent(534 'Fizz (Node, async)',535 () => renderFizzNode(AppAsync, ITEM_COUNT),536 TOTAL,537 CONCURRENCY,538 CONC_WARMUP539 );540 printConcurrentResult(fizzNodeAsync);541542 const flightFizzNodeAsync = await runConcurrent(543 'Flight + Fizz (Node, async)',544 () => renderFlightFizzNode(renderRSCNode, RSCAppAsync, ITEM_COUNT),545 TOTAL,546 CONCURRENCY,547 CONC_WARMUP548 );549 printConcurrentResult(flightFizzNodeAsync);550551 const fizzEdgeSync = await runConcurrent(552 'Fizz (Edge, sync)',553 () => renderFizzEdge(App, ITEM_COUNT),554 TOTAL,555 CONCURRENCY,556 CONC_WARMUP557 );558 printConcurrentResult(fizzEdgeSync);559560 const flightFizzEdgeSync = await runConcurrent(561 'Flight + Fizz (Edge, sync)',562 () => renderFlightFizzEdge(renderRSCEdge, RSCApp, ITEM_COUNT),563 TOTAL,564 CONCURRENCY,565 CONC_WARMUP566 );567 printConcurrentResult(flightFizzEdgeSync);568569 const fizzEdgeAsync = await runConcurrent(570 'Fizz (Edge, async)',571 () => renderFizzEdge(AppAsync, ITEM_COUNT),572 TOTAL,573 CONCURRENCY,574 CONC_WARMUP575 );576 printConcurrentResult(fizzEdgeAsync);577578 const flightFizzEdgeAsync = await runConcurrent(579 'Flight + Fizz (Edge, async)',580 () => renderFlightFizzEdge(renderRSCEdge, RSCAppAsync, ITEM_COUNT),581 TOTAL,582 CONCURRENCY,583 CONC_WARMUP584 );585 printConcurrentResult(flightFizzEdgeAsync);586587 const rps = r => r.reqPerSec;588589 console.log('\n--- Flight overhead ---\n');590 printGrid(591 ['Fizz', 'Flight+Fizz'],592 [593 ['Node sync', fizzNodeSync, flightFizzNodeSync],594 ['Node async', fizzNodeAsync, flightFizzNodeAsync],595 ['Edge sync', fizzEdgeSync, flightFizzEdgeSync],596 ['Edge async', fizzEdgeAsync, flightFizzEdgeAsync],597 ],598 rps,599 'req/s',600 'higher is better'601 );602603 console.log('\n--- Edge vs Node ---\n');604 printGrid(605 ['Node', 'Edge'],606 [607 ['Fizz sync', fizzNodeSync, fizzEdgeSync],608 ['Fizz async', fizzNodeAsync, fizzEdgeAsync],609 ['Flight+Fizz sync', flightFizzNodeSync, flightFizzEdgeSync],610 ['Flight+Fizz async', flightFizzNodeAsync, flightFizzEdgeAsync],611 ],612 rps,613 'req/s',614 'higher is better'615 );616617 return;618 }619620 // --- Benchmark ---621 console.log(622 '\n--- Benchmark (%d warmup, %d iterations, %d items) ---\n',623 WARMUP,624 ITERATIONS,625 ITEM_COUNT626 );627628 const fizzNodeSync = await runBenchmark(629 'Fizz (Node, sync)',630 () => renderFizzNode(App, ITEM_COUNT),631 ITERATIONS,632 WARMUP633 );634 printResult(fizzNodeSync);635636 const flightFizzNodeSync = await runBenchmark(637 'Flight + Fizz (Node, sync)',638 () => renderFlightFizzNode(renderRSCNode, RSCApp, ITEM_COUNT),639 ITERATIONS,640 WARMUP641 );642 printResult(flightFizzNodeSync);643644 const fizzNodeAsync = await runBenchmark(645 'Fizz (Node, async)',646 () => renderFizzNode(AppAsync, ITEM_COUNT),647 ITERATIONS,648 WARMUP649 );650 printResult(fizzNodeAsync);651652 const flightFizzNodeAsync = await runBenchmark(653 'Flight + Fizz (Node, async)',654 () => renderFlightFizzNode(renderRSCNode, RSCAppAsync, ITEM_COUNT),655 ITERATIONS,656 WARMUP657 );658 printResult(flightFizzNodeAsync);659660 const fizzEdgeSync = await runBenchmark(661 'Fizz (Edge, sync)',662 () => renderFizzEdge(App, ITEM_COUNT),663 ITERATIONS,664 WARMUP665 );666 printResult(fizzEdgeSync);667668 const flightFizzEdgeSync = await runBenchmark(669 'Flight + Fizz (Edge, sync)',670 () => renderFlightFizzEdge(renderRSCEdge, RSCApp, ITEM_COUNT),671 ITERATIONS,672 WARMUP673 );674 printResult(flightFizzEdgeSync);675676 const fizzEdgeAsync = await runBenchmark(677 'Fizz (Edge, async)',678 () => renderFizzEdge(AppAsync, ITEM_COUNT),679 ITERATIONS,680 WARMUP681 );682 printResult(fizzEdgeAsync);683684 const flightFizzEdgeAsync = await runBenchmark(685 'Flight + Fizz (Edge, async)',686 () => renderFlightFizzEdge(renderRSCEdge, RSCAppAsync, ITEM_COUNT),687 ITERATIONS,688 WARMUP689 );690 printResult(flightFizzEdgeAsync);691692 const median = r => r.median;693694 console.log('\n--- Flight overhead ---\n');695 printGrid(696 ['Fizz', 'Flight+Fizz'],697 [698 ['Node sync', fizzNodeSync, flightFizzNodeSync],699 ['Node async', fizzNodeAsync, flightFizzNodeAsync],700 ['Edge sync', fizzEdgeSync, flightFizzEdgeSync],701 ['Edge async', fizzEdgeAsync, flightFizzEdgeAsync],702 ],703 median,704 'ms',705 'median, lower is better'706 );707708 console.log('\n--- Edge vs Node ---\n');709 printGrid(710 ['Node', 'Edge'],711 [712 ['Fizz sync', fizzNodeSync, fizzEdgeSync],713 ['Fizz async', fizzNodeAsync, fizzEdgeAsync],714 ['Flight+Fizz sync', flightFizzNodeSync, flightFizzEdgeSync],715 ['Flight+Fizz async', flightFizzNodeAsync, flightFizzEdgeAsync],716 ],717 median,718 'ms',719 'median, lower is better'720 );721}722723main().catch(function (err) {724 console.error(err);725 process.exit(1);726});
Findings
✓ No findings reported for this file.