fixtures/flight-ssr-bench/bench.js JAVASCRIPT 727 lines View on github.com → Search inside
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.