introduce FPS graph to visualize slow UIs
Summary: This diff creates a small FPS graph to be able to see where we slow down the app. This visualizes two things 1. The amount of FPS we render at (from tracking.fps). 2. If we were not able to render at all (due to the main thread being blocked fully), we interpolate the graph and draw it in red. Reviewed By: nikoant Differential Revision: D19579115 fbshipit-source-id: 2421d724c6d514986759bc9d68b92a5e4f51e401
This commit is contained in:
committed by
Facebook Github Bot
parent
33ad41c98c
commit
31df1db74f
90
src/chrome/FpsGraph.tsx
Normal file
90
src/chrome/FpsGraph.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import {fpsEmitter} from '../dispatcher/tracking';
|
||||
|
||||
export default function FpsGraph({
|
||||
width,
|
||||
height,
|
||||
sampleRate = 200,
|
||||
}: {
|
||||
width: number;
|
||||
height: number;
|
||||
sampleRate?: number;
|
||||
}) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const graphWidth = width - 20;
|
||||
const fps: number[] = new Array<number>(graphWidth).fill(0, 0, graphWidth);
|
||||
let lastFps = 0;
|
||||
let lastDraw = Date.now();
|
||||
|
||||
const handler = (xfps: number) => {
|
||||
// at any interval, take the lowest to better show slow downs
|
||||
lastFps = Math.min(lastFps, xfps);
|
||||
};
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const ctx = canvasRef.current!.getContext('2d')!;
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.strokeStyle = '#ccc';
|
||||
|
||||
const now = Date.now();
|
||||
let missedFrames = 0;
|
||||
// check if we missed some measurements, in that case the CPU was fully choked!
|
||||
for (let i = 0; i < Math.floor((now - lastDraw) / sampleRate) - 1; i++) {
|
||||
fps.push(0);
|
||||
fps.shift();
|
||||
missedFrames++;
|
||||
}
|
||||
lastDraw = now;
|
||||
|
||||
// latest measurement
|
||||
fps.push(lastFps);
|
||||
fps.shift();
|
||||
|
||||
ctx.strokeText(
|
||||
'' +
|
||||
(missedFrames
|
||||
? // if we were chocked, show FPS based on frames missed
|
||||
Math.floor((1000 / sampleRate) * missedFrames)
|
||||
: lastFps),
|
||||
width - 15,
|
||||
5 + height / 2,
|
||||
);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, height);
|
||||
ctx.lineWidth = 1;
|
||||
fps.forEach((num, idx) => {
|
||||
ctx.lineTo(idx, height - (Math.min(60, num) / 60) * height);
|
||||
});
|
||||
|
||||
ctx.strokeStyle = missedFrames ? '#ff0000' : '#ccc';
|
||||
|
||||
ctx.stroke();
|
||||
lastFps = 60;
|
||||
}, sampleRate);
|
||||
|
||||
fpsEmitter.on('fps', handler);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
fpsEmitter.off('fps', handler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<canvas ref={canvasRef} width={width} height={height} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import {clipboard} from 'electron';
|
||||
import React from 'react';
|
||||
import {State} from 'src/reducers';
|
||||
import {reportUsage} from '../utils/metrics';
|
||||
import FpsGraph from './FpsGraph';
|
||||
|
||||
const AppTitleBar = styled(FlexRow)<{focused?: boolean}>(({focused}) => ({
|
||||
background: focused
|
||||
@@ -160,6 +161,9 @@ class TitleBar extends React.Component<Props, StateFromProps> {
|
||||
share != null ? share.statusComponent : undefined,
|
||||
)}
|
||||
<Spacer />
|
||||
|
||||
{!isProduction() && <FpsGraph height={20} width={60} />}
|
||||
|
||||
{config.showFlipperRating ? <RatingButton /> : null}
|
||||
<Version>{this.props.version + (isProduction() ? '' : '-dev')}</Version>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import {ipcRenderer} from 'electron';
|
||||
import {performance} from 'perf_hooks';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import {Store} from '../reducers/index';
|
||||
import {Logger} from '../fb-interfaces/Logger';
|
||||
@@ -37,6 +38,8 @@ export type UsageSummary = {
|
||||
[pluginName: string]: {focusedTime: number; unfocusedTime: number};
|
||||
};
|
||||
|
||||
export const fpsEmitter = new EventEmitter();
|
||||
|
||||
export default (store: Store, logger: Logger) => {
|
||||
let droppedFrames: number = 0;
|
||||
let largeFrameDrops: number = 0;
|
||||
@@ -46,7 +49,9 @@ export default (store: Store, logger: Logger) => {
|
||||
) {
|
||||
const now = performance.now();
|
||||
requestAnimationFrame(() => droppedFrameDetection(now, isWindowFocused));
|
||||
const dropped = Math.round((now - past) / (1000 / 60) - 1);
|
||||
const delta = now - past;
|
||||
const dropped = Math.round(delta / (1000 / 60) - 1);
|
||||
fpsEmitter.emit('fps', delta > 1000 ? 0 : Math.round(1000 / (now - past)));
|
||||
if (!isWindowFocused() || dropped < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user