Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

benchmark remotion #1358

Merged
merged 28 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c4aa64b
add basic benchmark command
uragirii Oct 2, 2022
8245604
add progress bars
uragirii Oct 3, 2022
c7bcbe0
fixed formatting on outputs
uragirii Oct 3, 2022
db40d04
make output closer to hyperfine
uragirii Oct 4, 2022
dbcee75
fixed logging problems
uragirii Oct 4, 2022
45c3c34
added docs
uragirii Oct 4, 2022
18e4e3b
Merge branch 'main' into pr/1358
JonnyBurger Oct 5, 2022
d2caf76
Merge branch 'main' into pr/1358
JonnyBurger Oct 5, 2022
de73c37
Merge branch 'main' into pr/1358
JonnyBurger Oct 5, 2022
73f58ad
Merge branch 'main' into pr/1358
JonnyBurger Oct 5, 2022
4556f3d
Update old-version.test.ts
JonnyBurger Oct 5, 2022
276f58e
Merge branch 'main' into pr/1358
JonnyBurger Oct 5, 2022
ad10ed6
Merge branch 'main' into pr/1358
JonnyBurger Oct 5, 2022
cd17a7e
allow all options from render to be also passed to benchmark
JonnyBurger Oct 5, 2022
d6e67f4
Merge branch 'main' into pr/1358
JonnyBurger Oct 5, 2022
af3705f
fixed docs layout
uragirii Oct 6, 2022
90d33a8
Merge branch 'main' into pr/1358
JonnyBurger Oct 7, 2022
7caeaa9
replace --compositions with composition command
JonnyBurger Oct 7, 2022
883d0b0
proresprofile is inherited
JonnyBurger Oct 7, 2022
4030f80
update docs
JonnyBurger Oct 7, 2022
fad8e87
work with new codec option
JonnyBurger Oct 7, 2022
15cbf93
simplify concurrencies by removing if/else statement
JonnyBurger Oct 7, 2022
0f7f445
add benchmark to help command and update copyright
JonnyBurger Oct 7, 2022
4837319
don't log `command undefined not found`
JonnyBurger Oct 7, 2022
5511a1d
inherit benchmark options from render command
JonnyBurger Oct 7, 2022
1e6f2be
better docs
JonnyBurger Oct 7, 2022
e5c087f
cleanup benchmark, same options as in render
JonnyBurger Oct 7, 2022
51cb77f
remove downloadmap, it does not work for multiple renders without cle…
JonnyBurger Oct 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
265 changes: 265 additions & 0 deletions packages/cli/src/benchmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import type {RenderMediaOptions} from '@remotion/renderer';
import {getCompositions, renderMedia} from '@remotion/renderer';
import path from 'path';
import {chalk} from './chalk';
import {Log} from './log';
import {makeProgressBar} from './make-progress-bar';
import {parsedCli, quietFlagProvided} from './parse-command-line';
import {createOverwriteableCliOutput} from './progress-bar';
import {bundleOnCli} from './setup-cache';

const DEFUALT_RUNS = 3;
const DEFAULT_COMP_ID = 'Main';
JonnyBurger marked this conversation as resolved.
Show resolved Hide resolved
const DEFAULT_FILE_PATH = './src/index';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make the user explicitly pass the path: npx remotion benchmark src/index.tsx ... for now.
We will tackle this in another issue where this can be omitted for all commands.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we don't need to download (or clone) benchmark repo?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it can be a entry point, URL, or filepath to a bundle (Through #1048 in the future)


const getValidCompositions = async (bundleLoc: string) => {
const compositionArg: string = parsedCli.compositions ?? DEFAULT_COMP_ID;
JonnyBurger marked this conversation as resolved.
Show resolved Hide resolved

const comps = await getCompositions(bundleLoc);

const ids = compositionArg.split(',').map((c) => c.trim());

return ids.map((compId) => {
const composition = comps.find((c) => c.id === compId);

if (!composition) {
throw new Error(`No composition with the ID "${compId}" found.`);
}

return composition;
});
};

const getValidConcurrency = () => {
const {concurrencies} = parsedCli;

if (!concurrencies) {
return undefined;
}

return (concurrencies as string)
.split(',')
.map((c) => parseInt(c.trim(), 10));
};

const runBenchmark = async (
runs: number,
options: RenderMediaOptions,
onProgress?: (run: number, progress: number) => void
) => {
const timeTaken: number[] = [];
for (let run = 0; run < runs; ++run) {
const startTime = performance.now();
await renderMedia({
...options,
onProgress: ({progress}) => onProgress?.(run, progress),
});
const endTime = performance.now();

timeTaken.push(endTime - startTime);
}

return timeTaken;
};

const formatTime = (time: number) => {
let ret = '';
const hours = Math.floor(time / (60 * 60 * 1000));
if (hours) {
ret = `${hours}h`;
}

time %= 60 * 60 * 1000;
const minutes = Math.floor(time / (60 * 1000));

if (minutes) {
ret = `${ret}${minutes}m`;
}

time %= 60 * 1000;
const seconds = (time / 1000).toFixed(5);

if (seconds) {
ret = `${ret}${seconds}s`;
}

return ret;
};

const avg = (time: number[]) =>
time.reduce((prev, curr) => prev + curr) / time.length;

const logResults = (
results: Record<string, Record<string, number[]>>,
runs: number
) => {
for (const compId in results) {
if (Object.prototype.hasOwnProperty.call(results, compId)) {
const comp = results[compId];

Log.info(`Rendering time for ${compId} for ${runs} runs`);
if (comp.default) {
Log.info(
` Max : ${chalk.bold(formatTime(Math.max(...comp.default)))}`
);
Log.info(
` Min : ${chalk.bold(formatTime(Math.min(...comp.default)))}`
);
Log.info(` Average : ${chalk.bold(formatTime(avg(comp.default)))}`);
} else {
for (const con in comp) {
// eslint-disable-next-line max-depth
if (Object.prototype.hasOwnProperty.call(comp, con)) {
const concurrencyResult = comp[con];

Log.info();
Log.info(` Concurrency : ${con}`);
Log.info(
` Max : ${chalk.bold(
formatTime(Math.max(...concurrencyResult))
)}`
);
Log.info(
` Min : ${chalk.bold(
formatTime(Math.min(...concurrencyResult))
)}`
);
Log.info(
` Average : ${chalk.bold(formatTime(avg(concurrencyResult)))}`
);
}
}
}
}
}
};

type BenchmarkProgressBarOptions = {
totalRuns: number;
run: number;
progress: number;
concurrency: number | null;
compId: string;
doneIn: string | null;
};

const makeBenchmarkProgressBar = ({
totalRuns,
run,
progress,
doneIn,
compId,
concurrency,
}: BenchmarkProgressBarOptions) => {
const totalProgress = (run + progress) / totalRuns;

return [
`(2/2)`,
makeProgressBar(totalProgress),
doneIn === null
? `Rendering ${compId} frames${
concurrency === null ? '' : `(${concurrency}x)`
}`
: `Rendered ${compId} ${totalRuns} times`,
doneIn === null
? `${(totalProgress * 100).toFixed(2)}% ${chalk.gray(
` ${run + 1}th run`
)}`
: chalk.gray(doneIn),
].join(' ');
};

export const benchmarkCommand = async (remotionRoot: string) => {
const runs: number = parsedCli.runs ?? DEFUALT_RUNS;
const filePath: string = parsedCli.filePath ?? DEFAULT_FILE_PATH;

const fullPath = path.join(process.cwd(), filePath);

const bundleLocation = await bundleOnCli({
fullPath,
publicDir: null,
remotionRoot,
steps: ['bundling', 'rendering'],
});

const compositions = await getValidCompositions(bundleLocation);

const benchmark: Record<string, Record<string, number[]>> = {};

const concurrency = getValidConcurrency();

for (const composition of compositions) {
if (composition !== compositions[0]) {
Log.info();
}

const benchmarkProgress = createOverwriteableCliOutput(quietFlagProvided());

const start = Date.now();

benchmark[composition.id] = {};
if (concurrency) {
for (const con of concurrency) {
const timeTaken = await runBenchmark(
runs,
{
codec: 'h264',
JonnyBurger marked this conversation as resolved.
Show resolved Hide resolved
composition,
serveUrl: bundleLocation,
concurrency: con,
},
(run, progress) => {
benchmarkProgress.update(
makeBenchmarkProgressBar({
totalRuns: runs,
run,
compId: composition.id,
concurrency: con,
doneIn: null,
progress,
})
);
}
);

benchmark[composition.id][`${con}`] = timeTaken;
}
} else {
const timeTaken = await runBenchmark(
runs,
{
codec: 'h264',
composition,
serveUrl: bundleLocation,
},
(run, progress) => {
benchmarkProgress.update(
makeBenchmarkProgressBar({
totalRuns: runs,
run,
compId: composition.id,
concurrency: null,
doneIn: null,
progress,
})
);
}
);
benchmarkProgress.update(
makeBenchmarkProgressBar({
totalRuns: runs,
run: runs - 1,
compId: composition.id,
concurrency: null,
doneIn: formatTime(Date.now() - start),
progress: 1,
})
);

benchmark[composition.id].default = timeTaken;
}
}

Log.info();
logResults(benchmark, runs);
};
3 changes: 3 additions & 0 deletions packages/cli/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {RenderInternals} from '@remotion/renderer';
import {benchmarkCommand} from './benchmark';
import {chalk} from './chalk';
import {checkNodeVersion} from './check-version';
import {listCompositionsCommand} from './compositions';
Expand Down Expand Up @@ -67,6 +68,8 @@ export const cli = async () => {
await upgrade(remotionRoot);
} else if (command === VERSIONS_COMMAND) {
await versionsCommand(remotionRoot);
} else if (command === 'benchmark') {
await benchmarkCommand(remotionRoot);
} else if (command === 'help') {
printHelp();
process.exit(0);
Expand Down