Skip to content

Commit ae0bff0

Browse files
feat: add Support for High-FPS Readings in Flashlight for Devices Over 60 FPS (#316)
* feat: read refresh rate specs from adb shell * refactor(profiler): use a concise algorithm for extracting fps details Use regular expression to find all matches of fps details from the adb command response. A contrived algorithm was used previously, relying on different regex constructs to isolate sections of the adb response strings such as strings that had the following format - "refreshRate %d", "fps=%d". The "refreshRate" string was dropped for the more common "fps". chore(profiler): update base types with new method Adding detectDeviceRefreshRate method to the base and sub classes. An implementation templation that can be adopted across multiple platforms to provide GPU refresh rate of the devices being profiled * feat(webapp): send refresh rate to webapp from profiler by emiting an event in a socket * chore(web-reporter): pass refresh rate numbers to reporter ui chore: use device specs type A new device spec has been created for use to model device spec relevant to performance test: update snapshots and test cases * refactor: extract socket events into an enum * test: add unit test for verifying device refresh rate retrieval from ADB command refactor: remove unnecessary catch block * fix(profiler): accomodate varying refresh rate configurations Some devices come with the capabilities of switching between 60,90, 120 fps. The renderFrameRate reports the currently configured refresh rate. * test: add new specs for testing refresh rates on Pixels * refactor(results): Add refresh rate to results model For consistency, refresh rate is added to the DeviceSpecs property of the TestCaseResult and AveragedTestCaseResult chore(test): update snapshots refactor: remove unneeded changes refactor: use logger for error * refactor(sockets): place device refresh rates calls in start events
1 parent 59cb115 commit ae0bff0

File tree

24 files changed

+261
-69
lines changed

24 files changed

+261
-69
lines changed

packages/commands/measure/src/__tests__/__snapshots__/measure.test.tsx.snap

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ Time taken to run the test.
1212
Can be helpful to measure Time To Interactive of your app, if the test is checking app start for instance.
1313
Average FPS
1414
60 FPS
15-
Frame Per Second. Your app should display 60 Frames Per Second to give an impression of fluidity. This number should be close to 60, otherwise it will seem laggy.
15+
Frame Per Second. Your app should display 60 Frames Per Second to give an impression of fluidity. This number should be close to 60, otherwise it will seem laggy.
16+
1617
See
1718
1819
this video
1920
2021
for more details
2122
Average CPU usage
2223
83 %
23-
An app might run at 60FPS but might be using too much processing power, so it's important to check CPU usage.
24+
An app might run at high frame rates, such as 60 FPS or higher, but might be using too much processing power, so it's important to check CPU usage.
2425
Depending on the device, this value can go up to
2526
100% x number of cores
2627
. For instance, a Samsung A10s has 4 cores, so the max value would be 400%.
@@ -855,7 +856,7 @@ exports[`flashlight measure interactive it displays measures: Web app with measu
855856
<div
856857
class="text-neutral-400 text-sm"
857858
>
858-
An app might run at 60FPS but might be using too much processing power, so it's important to check CPU usage.
859+
An app might run at high frame rates, such as 60 FPS or higher, but might be using too much processing power, so it's important to check CPU usage.
859860
<br />
860861
Depending on the device, this value can go up to
861862
<code>
@@ -3812,15 +3813,16 @@ Time taken to run the test.
38123813
Can be helpful to measure Time To Interactive of your app, if the test is checking app start for instance.
38133814
Average FPS
38143815
-
3815-
Frame Per Second. Your app should display 60 Frames Per Second to give an impression of fluidity. This number should be close to 60, otherwise it will seem laggy.
3816+
Frame Per Second. Your app should display 60 Frames Per Second to give an impression of fluidity. This number should be close to 60, otherwise it will seem laggy.
3817+
38163818
See
38173819
38183820
this video
38193821
38203822
for more details
38213823
Average CPU usage
38223824
-
3823-
An app might run at 60FPS but might be using too much processing power, so it's important to check CPU usage.
3825+
An app might run at high frame rates, such as 60 FPS or higher, but might be using too much processing power, so it's important to check CPU usage.
38243826
Depending on the device, this value can go up to
38253827
100% x number of cores
38263828
. For instance, a Samsung A10s has 4 cores, so the max value would be 400%.
@@ -4218,7 +4220,7 @@ exports[`flashlight measure interactive it displays measures: Web app with no me
42184220
<div
42194221
class="text-neutral-400 text-sm"
42204222
>
4221-
An app might run at 60FPS but might be using too much processing power, so it's important to check CPU usage.
4223+
An app might run at high frame rates, such as 60 FPS or higher, but might be using too much processing power, so it's important to check CPU usage.
42224224
<br />
42234225
Depending on the device, this value can go up to
42244226
<code>

packages/commands/measure/src/server/ServerSocketConnectionApp.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { PerformanceMeasurer } from "@perf-profiler/e2e";
22
import { Logger } from "@perf-profiler/logger";
3+
import { profiler } from "@perf-profiler/profiler";
34
import { Measure } from "@perf-profiler/types";
45
import React, { useCallback, useEffect } from "react";
56
import { HostAndPortInfo } from "./components/HostAndPortInfo";
6-
import { SocketType } from "./socket/socketInterface";
7+
import { SocketType, SocketEvents } from "./socket/socketInterface";
78
import { useSocketState, updateMeasuresReducer, addNewResultReducer } from "./socket/socketState";
89
import { useBundleIdControls } from "./useBundleIdControls";
910
import { useLogSocketEvents } from "../common/useLogSocketEvents";
@@ -33,9 +34,11 @@ export const ServerSocketConnectionApp = ({ socket, url }: { socket: SocketType;
3334
)
3435
);
3536

36-
socket.on("start", async () => {
37+
socket.on(SocketEvents.START, async () => {
38+
const refreshRate = profiler.detectDeviceRefreshRate();
3739
setState({
3840
isMeasuring: true,
41+
refreshRate,
3942
});
4043

4144
if (!state.bundleId) {
@@ -55,19 +58,19 @@ export const ServerSocketConnectionApp = ({ socket, url }: { socket: SocketType;
5558
);
5659
});
5760

58-
socket.on("stop", stop);
61+
socket.on(SocketEvents.STOP, stop);
5962

60-
socket.on("reset", () => {
63+
socket.on(SocketEvents.RESET, () => {
6164
stop();
6265
setState({
6366
results: [],
6467
});
6568
});
6669

6770
return () => {
68-
socket.removeAllListeners("start");
69-
socket.removeAllListeners("stop");
70-
socket.removeAllListeners("reset");
71+
socket.removeAllListeners(SocketEvents.START);
72+
socket.removeAllListeners(SocketEvents.STOP);
73+
socket.removeAllListeners(SocketEvents.RESET);
7174
};
7275
}, [setState, socket, state.bundleId, stop]);
7376

packages/commands/measure/src/server/socket/socketInterface.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface SocketData {
55
isMeasuring: boolean;
66
bundleId: string | null;
77
results: TestCaseResult[];
8+
refreshRate: number;
89
}
910

1011
export interface ServerToClientEvents {
@@ -18,6 +19,7 @@ export interface ClientToServerEvents {
1819
reset: () => void;
1920
autodetectBundleId: () => void;
2021
setBundleId: (bundleId: string) => void;
22+
autodetectRefreshRate: () => void;
2123
}
2224

2325
interface InterServerEvents {
@@ -37,3 +39,17 @@ export type SocketType = Socket<
3739
InterServerEvents,
3840
SocketData
3941
>;
42+
43+
export enum SocketEvents {
44+
START = "start",
45+
STOP = "stop",
46+
RESET = "reset",
47+
AUTODETECT_BUNDLE_ID = "autodetectBundleId",
48+
SET_BUNDLE_ID = "setBundleId",
49+
AUTODETECT_REFRESH_RATE = "autodetectRefreshRate",
50+
UPDATE_STATE = "updateState",
51+
SEND_ERROR = "sendError",
52+
PING = "ping",
53+
CONNECT = "connect",
54+
DISCONNECT = "disconnect",
55+
}

packages/commands/measure/src/server/socket/socketState.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Measure, POLLING_INTERVAL } from "@perf-profiler/types";
22
import { useState, useEffect } from "react";
3-
import { SocketType, SocketData } from "./socketInterface";
3+
import { SocketType, SocketData, SocketEvents } from "./socketInterface";
44

55
export const useSocketState = (socket: SocketType) => {
66
const [state, _setState] = useState<SocketData>({
77
isMeasuring: false,
88
bundleId: null,
99
results: [],
10+
refreshRate: 60,
1011
});
1112

1213
const setState = (
@@ -23,7 +24,7 @@ export const useSocketState = (socket: SocketType) => {
2324
};
2425

2526
useEffect(() => {
26-
socket.emit("updateState", state);
27+
socket.emit(SocketEvents.UPDATE_STATE, state);
2728
}, [state, socket]);
2829

2930
return [state, setState] as const;
@@ -54,6 +55,9 @@ export const addNewResultReducer = (state: SocketData, name: string): SocketData
5455
name,
5556
iterations: [],
5657
status: "SUCCESS",
58+
specs: {
59+
refreshRate: state.refreshRate,
60+
},
5761
},
5862
],
5963
});
Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import { profiler } from "@perf-profiler/profiler";
22
import { useEffect } from "react";
3-
import { SocketType, SocketData } from "./socket/socketInterface";
3+
import { SocketType, SocketData, SocketEvents } from "./socket/socketInterface";
44

55
export const useBundleIdControls = (
66
socket: SocketType,
77
setState: (state: Partial<SocketData>) => void,
88
stop: () => void
99
) => {
1010
useEffect(() => {
11-
socket.on("setBundleId", (bundleId) => {
11+
socket.on(SocketEvents.SET_BUNDLE_ID, (bundleId) => {
1212
setState({
1313
bundleId,
1414
});
1515
});
1616

17-
socket.on("autodetectBundleId", () => {
17+
socket.on(SocketEvents.AUTODETECT_BUNDLE_ID, () => {
1818
stop();
1919

2020
try {
@@ -23,13 +23,26 @@ export const useBundleIdControls = (
2323
bundleId,
2424
});
2525
} catch (error) {
26-
socket.emit("sendError", error instanceof Error ? error.message : "unknown error");
26+
socket.emit(
27+
SocketEvents.SEND_ERROR,
28+
error instanceof Error ? error.message : "unknown error"
29+
);
2730
}
2831
});
2932

33+
socket.on(SocketEvents.AUTODETECT_REFRESH_RATE, () => {
34+
stop();
35+
36+
const refreshRate = profiler.detectDeviceRefreshRate();
37+
setState({
38+
refreshRate,
39+
});
40+
});
41+
3042
return () => {
31-
socket.removeAllListeners("setBundleId");
32-
socket.removeAllListeners("autodetectBundleId");
43+
socket.removeAllListeners(SocketEvents.SET_BUNDLE_ID);
44+
socket.removeAllListeners(SocketEvents.AUTODETECT_BUNDLE_ID);
45+
socket.removeAllListeners(SocketEvents.AUTODETECT_REFRESH_RATE);
3346
};
3447
}, [setState, socket, stop]);
3548
};

packages/commands/measure/src/webapp/components/SocketState.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Button from "@mui/material/Button";
88
import { Logger } from "@perf-profiler/logger";
99
import { socket } from "../socket";
1010
import { useLogSocketEvents } from "../../common/useLogSocketEvents";
11+
import { SocketEvents } from "../../server/socket/socketInterface";
1112

1213
const useSocketState = (onError: (error: string) => void) => {
1314
useLogSocketEvents(socket);
@@ -28,14 +29,14 @@ const useSocketState = (onError: (error: string) => void) => {
2829
}
2930
}
3031

31-
socket.on("connect", onConnect);
32-
socket.on("disconnect", onDisconnect);
33-
socket.on("sendError", onError);
32+
socket.on(SocketEvents.CONNECT, onConnect);
33+
socket.on(SocketEvents.DISCONNECT, onDisconnect);
34+
socket.on(SocketEvents.SEND_ERROR, onError);
3435

3536
return () => {
36-
socket.off("connect", onConnect);
37-
socket.off("disconnect", onDisconnect);
38-
socket.off("sendError", onError);
37+
socket.off(SocketEvents.CONNECT, onConnect);
38+
socket.off(SocketEvents.DISCONNECT, onDisconnect);
39+
socket.off(SocketEvents.SEND_ERROR, onError);
3940
};
4041
}, [onError]);
4142

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { io, Socket } from "socket.io-client";
2-
import { ServerToClientEvents, ClientToServerEvents } from "../server/socket/socketInterface";
2+
import {
3+
ServerToClientEvents,
4+
ClientToServerEvents,
5+
SocketEvents,
6+
} from "../server/socket/socketInterface";
37

48
export const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
59
window.__FLASHLIGHT_DATA__.socketServerUrl
610
);
711

8-
socket.on("disconnect", () => socket.close());
12+
socket.on(SocketEvents.DISCONNECT, () => socket.close());
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,38 @@
11
import { useEffect, useState } from "react";
2-
import type { SocketData } from "../server/socket/socketInterface";
2+
import { SocketData, SocketEvents } from "../server/socket/socketInterface";
33
import { socket } from "./socket";
44

55
export const useMeasures = () => {
66
const [state, setState] = useState<SocketData>();
77

88
useEffect(() => {
9-
socket.on("updateState", setState);
9+
socket.on(SocketEvents.UPDATE_STATE, setState);
1010

1111
return () => {
12-
socket.off("updateState", setState);
12+
socket.off(SocketEvents.UPDATE_STATE, setState);
1313
};
1414
}, []);
1515

1616
return {
1717
bundleId: state?.bundleId ?? null,
18+
refreshRate: state?.refreshRate ?? 60,
1819
autodetect: () => {
19-
socket.emit("autodetectBundleId");
20+
socket.emit(SocketEvents.AUTODETECT_BUNDLE_ID);
21+
socket.emit(SocketEvents.AUTODETECT_REFRESH_RATE);
2022
},
2123
setBundleId: (bundleId: string) => {
22-
socket.emit("setBundleId", bundleId);
24+
socket.emit(SocketEvents.SET_BUNDLE_ID, bundleId);
2325
},
2426
results: state?.results ?? [],
2527
isMeasuring: state?.isMeasuring ?? false,
2628
start: () => {
27-
socket.emit("start");
29+
socket.emit(SocketEvents.START);
2830
},
2931
stop: () => {
30-
socket.emit("stop");
32+
socket.emit(SocketEvents.STOP);
3133
},
3234
reset: () => {
33-
socket.emit("reset");
35+
socket.emit(SocketEvents.RESET);
3436
},
3537
};
3638
};

packages/core/reporter/src/reporting/Report.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,8 @@ export class Report {
117117
threads: getThreadsStats(iterations),
118118
};
119119
}
120+
121+
public getRefreshRate() {
122+
return this.result.specs?.refreshRate ?? 60;
123+
}
120124
}

packages/core/reporter/src/reporting/getScore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const getScore = (result: AveragedTestCaseResult) => {
2626
const scores = [cpuScore];
2727

2828
if (averageUIFPS !== undefined) {
29-
const fpsScore = (averageUIFPS * 100) / 60;
29+
const fpsScore = (averageUIFPS * 100) / (result?.specs?.refreshRate ?? 60);
3030
scores.push(fpsScore);
3131
}
3232

packages/core/types/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface TestCaseResult {
3939
status: TestCaseResultStatus;
4040
iterations: TestCaseIterationResult[];
4141
type?: TestCaseResultType;
42+
specs?: DeviceSpecs;
4243
}
4344

4445
export interface AveragedTestCaseResult {
@@ -49,6 +50,7 @@ export interface AveragedTestCaseResult {
4950
average: TestCaseIterationResult;
5051
averageHighCpuUsage: { [processName: string]: number };
5152
type?: TestCaseResultType;
53+
specs?: DeviceSpecs;
5254
}
5355

5456
// Shouldn't really be here but @perf-profiler/types is imported by everyone and doesn't contain any logic
@@ -97,4 +99,9 @@ export interface Profiler {
9799
cleanup: () => void;
98100
getScreenRecorder: (videoPath: string) => ScreenRecorder | undefined;
99101
stopApp: (bundleId: string) => Promise<void>;
102+
detectDeviceRefreshRate: () => number;
103+
}
104+
105+
export interface DeviceSpecs {
106+
refreshRate: number;
100107
}

0 commit comments

Comments
 (0)