Files
Recordly/scripts/benchmark-export-queues.mjs

177 lines
6.0 KiB
JavaScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import electron from "electron";
import ffmpegStatic from "ffmpeg-static";
import { buildBenchmarkRequests, createBenchmarkConfig } from "./benchmark-export-queues/config.mjs";
import { createFixtureVideo, ensureBuildArtifacts } from "./benchmark-export-queues/fixtures.mjs";
import {
calculateDelta,
printBackendDetailTable,
printDeltaTable,
printRequestedConfigTable,
printTimingSummaryTable,
} from "./benchmark-export-queues/reporting.mjs";
import { runBenchmarkRequest } from "./benchmark-export-queues/runner.mjs";
function logBenchmarkConfig(config, benchmarkRequests) {
console.log("[benchmark-export-queues] Config");
console.log(
JSON.stringify({
width: config.width,
height: config.height,
frameRate: config.frameRate,
durationSeconds: config.durationSeconds,
timeoutMs: config.timeoutMs,
runsPerVariant: config.runsPerVariant,
requestedPipeline: config.exportPipeline,
requestedBackend: config.exportBackend,
requestedBackends: benchmarkRequests.map((request) => request.label),
backendSweepEnabled: benchmarkRequests.length > 1,
requestedEncodingMode: config.exportEncodingMode,
requestedShadowIntensity: config.exportShadowIntensity,
webcamEnabled: config.useWebcamOverlay,
requestedWebcamShadowIntensity: config.webcamShadowIntensity,
requestedWebcamSize: config.webcamSize,
}),
);
printRequestedConfigTable(config, benchmarkRequests);
}
function printJsonSummary(benchmarkResults, config) {
console.log("[benchmark-export-queues] Summary");
for (const result of benchmarkResults) {
for (const summary of result.summaries) {
console.log(
JSON.stringify({
requestedPipeline: result.request.pipeline,
requestedBackend: result.request.backend,
name: summary.variant.name,
webcamEnabled: config.useWebcamOverlay,
webcamShadowIntensity: config.webcamShadowIntensity,
webcamSize: config.webcamSize,
maxEncodeQueue: summary.variant.maxEncodeQueue,
maxDecodeQueue: summary.variant.maxDecodeQueue,
maxPendingFrames: summary.variant.maxPendingFrames,
averageElapsedMs: summary.averageElapsedMs,
medianElapsedMs: summary.medianElapsedMs,
minElapsedMs: summary.minElapsedMs,
maxElapsedMs: summary.maxElapsedMs,
averageSizeBytes: summary.averageSizeBytes,
averageOutputDurationSeconds: summary.averageOutputDurationSeconds,
averageSmokeElapsedMs: summary.averageSmokeElapsedMs,
observedRenderBackends: summary.observedRenderBackends,
observedEncodeBackends: summary.observedEncodeBackends,
observedEncoders: summary.observedEncoders,
runs: summary.runs.map((run) => ({
elapsedMs: run.elapsedMs,
sizeBytes: run.sizeBytes,
outputDuration: run.outputDuration,
smokeExportReport: run.smokeExportReport,
smokeProgressSummary: run.smokeProgressSummary,
})),
}),
);
}
}
}
function printVariantComparisons(benchmarkResults) {
for (const result of benchmarkResults) {
if (result.summaries.length < 2) {
continue;
}
const baseline = result.summaries[0];
const tuned = result.summaries[1];
const { deltaMs, deltaPercent } = calculateDelta(
baseline.averageElapsedMs,
tuned.averageElapsedMs,
);
const { deltaMs: medianDeltaMs, deltaPercent: medianPercent } = calculateDelta(
baseline.medianElapsedMs,
tuned.medianElapsedMs,
);
const backendLabel = result.request.backend ?? "default";
console.log(
`[benchmark-export-queues] ${backendLabel} tuned vs baseline: ${deltaMs}ms (${typeof deltaPercent === "number" ? deltaPercent.toFixed(1) : "-"}%)`,
);
console.log(
`[benchmark-export-queues] ${backendLabel} tuned vs baseline (median): ${medianDeltaMs}ms (${typeof medianPercent === "number" ? medianPercent.toFixed(1) : "-"}%)`,
);
}
}
async function main() {
if (typeof ffmpegStatic !== "string" || ffmpegStatic.length === 0) {
throw new Error("ffmpeg-static is unavailable for this platform");
}
if (typeof electron !== "string" || electron.length === 0) {
throw new Error("The Electron binary is unavailable in this workspace");
}
const config = createBenchmarkConfig();
await ensureBuildArtifacts(config);
const benchmarkRequests = buildBenchmarkRequests(config);
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "recordly-export-queue-bench-"));
const inputPath = path.join(tempDir, "input.mp4");
const webcamInputPath = config.useWebcamOverlay ? path.join(tempDir, "webcam.mp4") : null;
try {
logBenchmarkConfig(config, benchmarkRequests);
console.log(`[benchmark-export-queues] Generating fixture video: ${inputPath}`);
await createFixtureVideo(ffmpegStatic, inputPath, {
durationSeconds: config.durationSeconds,
frameRate: config.frameRate,
fixtureWidth: config.width,
fixtureHeight: config.height,
});
if (webcamInputPath) {
console.log(
`[benchmark-export-queues] Generating webcam fixture video: ${webcamInputPath}`,
);
await createFixtureVideo(ffmpegStatic, webcamInputPath, {
durationSeconds: config.durationSeconds,
frameRate: config.frameRate,
fixtureWidth: config.webcamWidth,
fixtureHeight: config.webcamHeight,
includeAudio: false,
videoFilter: `testsrc=size=${config.webcamWidth}x${config.webcamHeight}:rate=${config.frameRate}`,
});
}
const benchmarkResults = [];
for (const benchmarkRequest of benchmarkRequests) {
benchmarkResults.push(
await runBenchmarkRequest(
electron,
ffmpegStatic,
inputPath,
webcamInputPath,
benchmarkRequest,
config,
),
);
}
printJsonSummary(benchmarkResults, config);
printTimingSummaryTable(benchmarkResults);
printBackendDetailTable(benchmarkResults);
printDeltaTable(benchmarkResults);
printVariantComparisons(benchmarkResults);
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
}
main().catch((error) => {
console.error(
`[benchmark-export-queues] ${error instanceof Error ? error.message : String(error)}`,
);
process.exitCode = 1;
});