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

Report slowest frames #1381

Merged
merged 15 commits into from
Oct 10, 2022
8 changes: 8 additions & 0 deletions packages/cli/src/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,18 @@ export const render = async (remotionRoot: string) => {
puppeteerInstance,
onDownload,
downloadMap,
onSlowestFrames: (slowestFrames) => {
Log.verbose();
Log.verbose(`Slowest frames:`);
slowestFrames.forEach(({frame, time}) => {
Log.verbose(`Frame ${frame} (${time.toFixed(3)}ms)`);
});
},
});

Log.info();
Log.info();

const seconds = Math.round((Date.now() - startTime) / 1000);
Log.info(
[
Expand Down
24 changes: 24 additions & 0 deletions packages/docs/docs/renderer/render-media.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,30 @@ Using this feature is discouraged. Before using it, we want to make you aware of
Before you use this hack, reach out to the Remotion team on [Discord](https://remotion.dev/discord) and ask us if we are open to implement the feature you need in a clean way - we often do implement new features quickly based on users feedback.
:::

### `onSlowestFrames?`

_available from v3.2.29_

Callback function that gets called right before `renderMedia()` resolves.
The only argument `slowestFrames` is an array of the 10 slowest frames in the shape of `{frame:<Frame number>, time:<Time to render frame ms>}`. You can use this information to optimise your render times.

```tsx twoslash
import type { OnSlowestFrames } from "@remotion/renderer";

const onSlowestFrames: OnSlowestFrames = (slowestFrames) => {
console.log("The slowest 10 frames are:");
for (const slowFrame of slowestFrames) {
console.log(`Frame ${slowFrame.frame} (${slowFrame.time}ms)`);
}
};
```

## Return Value

_since v3.0.26_

If `outputLocation` is not specified or `null`, the return value is a Promise that resolves a `Buffer`. If an output location is specified, the return value is a Promise that resolves no value.

## See also

- [Source code for this function](https://github.com/remotion-dev/remotion/blob/main/packages/renderer/src/render-media.ts)
Expand Down
11 changes: 8 additions & 3 deletions packages/lambda/src/functions/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,14 @@ const renderHandler = async (
downloadMap,
muted: params.muted,
enforceAudioTrack: true,
})
.then(() => resolve())
.catch((err) => reject(err));
onSlowestFrames: (slowestFrames) => {
console.log();
console.log(`Slowest frames:`);
slowestFrames.forEach(({frame, time}) => {
console.log(`Frame ${frame} (${time.toFixed(3)}ms)`);
});
},
}).catch((err) => reject(err));
});

const endRendered = Date.now();
Expand Down
2 changes: 2 additions & 0 deletions packages/renderer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ export {PixelFormat} from './pixel-format';
export {ProResProfile} from './prores-profile';
export {renderFrames} from './render-frames';
export {
OnSlowestFrames,
renderMedia,
RenderMediaOnProgress,
RenderMediaOptions,
SlowFrame,
StitchingState,
} from './render-media';
export {renderStill, RenderStillOptions} from './render-still';
Expand Down
51 changes: 50 additions & 1 deletion packages/renderer/src/render-media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {ExecaChildProcess} from 'execa';
import fs from 'fs';
import os from 'os';
import path from 'path';
import {performance} from 'perf_hooks';
import type {SmallTCompMetadata} from 'remotion';
import {Internals} from 'remotion';
import type {RenderMediaOnDownload} from './assets/download-and-map-assets-to-file';
Expand Down Expand Up @@ -48,6 +49,11 @@ import {validateScale} from './validate-scale';

export type StitchingState = 'encoding' | 'muxing';

const SLOWEST_FRAME_COUNT = 10;

export type SlowFrame = {frame: number; time: number};
export type OnSlowestFrames = (frames: SlowFrame[]) => void;

export type RenderMediaOnProgress = (progress: {
renderedFrames: number;
encodedFrames: number;
Expand Down Expand Up @@ -94,6 +100,7 @@ export type RenderMediaOptions = {
muted?: boolean;
enforceAudioTrack?: boolean;
ffmpegOverride?: FfmpegOverrideFn;
onSlowestFrames?: OnSlowestFrames;
} & ServeUrlOrWebpackBundle &
ConcurrencyOrParallelism;

Expand Down Expand Up @@ -155,6 +162,7 @@ export const renderMedia = ({
muted,
enforceAudioTrack,
ffmpegOverride,
onSlowestFrames,
...options
}: RenderMediaOptions): Promise<Buffer | null> => {
validateQuality(options.quality);
Expand Down Expand Up @@ -313,13 +321,53 @@ export const renderMedia = ({
const mediaSupport = codecSupportsMedia(codec);
const disableAudio = !mediaSupport.audio || muted;

const slowestFrames: SlowFrame[] = [];
let maxTime = 0;
let minTime = 0;
let startTime = performance.now();

const recordFrameTime = (frameIndex: number) => {
const time = performance.now() - startTime;
startTime = performance.now();

// ignore first frame
if (frameIndex <= 1) {
return;
}

const frameTime: SlowFrame = {frame: frameIndex, time};

if (time < minTime && slowestFrames.length === SLOWEST_FRAME_COUNT) {
return;
}

if (time > maxTime) {
// add at starting;
slowestFrames.unshift(frameTime);
maxTime = time;
} else {
// add frame at appropriate position
const index = slowestFrames.findIndex(
({time: indexTime}) => indexTime < time
);
slowestFrames.splice(index, 0, frameTime);
}

if (slowestFrames.length > SLOWEST_FRAME_COUNT) {
slowestFrames.pop();
}

minTime = slowestFrames[slowestFrames.length - 1]?.time ?? minTime;
};

const happyPath = createPrestitcherIfNecessary()
.then(() => {
const renderFramesProc = renderFrames({
config: composition,
onFrameUpdate: (frame: number) => {
onFrameUpdate: (frame: number, frameIndex: number) => {
renderedFrames = frame;
callUpdate();
recordFrameTime(frameIndex);
},
concurrency,
outputDir,
Expand Down Expand Up @@ -420,6 +468,7 @@ export const renderMedia = ({
encodedFrames = getFramesToRender(realFrameRange, everyNthFrame).length;
encodedDoneIn = Date.now() - stitchStart;
callUpdate();
onSlowestFrames?.(slowestFrames);
return buffer;
})
.catch((err) => {
Expand Down