diff --git a/src/chrome/FpsGraph.tsx b/src/chrome/FpsGraph.tsx new file mode 100644 index 000000000..ccdc711f8 --- /dev/null +++ b/src/chrome/FpsGraph.tsx @@ -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(null); + + useEffect(() => { + const graphWidth = width - 20; + const fps: number[] = new Array(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 ( +
+ +
+ ); +} diff --git a/src/chrome/TitleBar.tsx b/src/chrome/TitleBar.tsx index 5f1fd35b3..03fa2a625 100644 --- a/src/chrome/TitleBar.tsx +++ b/src/chrome/TitleBar.tsx @@ -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 { share != null ? share.statusComponent : undefined, )} + + {!isProduction() && } + {config.showFlipperRating ? : null} {this.props.version + (isProduction() ? '' : '-dev')} diff --git a/src/dispatcher/tracking.tsx b/src/dispatcher/tracking.tsx index 6ae71f831..27545363f 100644 --- a/src/dispatcher/tracking.tsx +++ b/src/dispatcher/tracking.tsx @@ -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; }