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

feat(ios): add time Profiler #188

Merged
merged 21 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fc3308b
feat: add ios profiler
GuillaumeEgret Jan 15, 2024
69c1bde
feat: create a device manager for ios profiler
GuillaumeEgret Jan 15, 2024
3ff9d9e
feat: kill the ios app before test
GuillaumeEgret Jan 15, 2024
32f2960
feat(test): allow test command for ios profiler
GuillaumeEgret Jan 15, 2024
088a412
fix CI
GuillaumeEgret Jan 15, 2024
85092ff
feat: add readme
GuillaumeEgret Jan 16, 2024
ee04594
fix(test): prevent breaking change for android users
GuillaumeEgret Jan 17, 2024
135ca12
chore(readme): add dependencies in readme
GuillaumeEgret Jan 17, 2024
50118ed
chore(ios profiler): replace default values by null
GuillaumeEgret Jan 17, 2024
6aad828
feat(ios profiler): poll data more often
GuillaumeEgret Jan 17, 2024
763c3d6
fix(web-reporter): report crashed if series was empty
GuillaumeEgret Jan 17, 2024
41af1d5
feat(CPUReport): hide Thread graph if there is only one thread
GuillaumeEgret Jan 17, 2024
a66e167
chore(ios profiler): set lastFps and lastCpu to null
GuillaumeEgret Jan 17, 2024
9eb7423
feat(profiler): add stop app function in profiler
GuillaumeEgret Jan 17, 2024
5462e58
chore(ios profiler): set same polling interval than android
GuillaumeEgret Jan 18, 2024
912252c
feat(ReportSummaryCard): hide high cpu usage if there is no thread pe…
GuillaumeEgret Jan 19, 2024
74e98ff
feat(readme): add know limits in readme
GuillaumeEgret Jan 19, 2024
129f79d
test(measure): update snapshot when hig cpu usage is hidden
GuillaumeEgret Jan 19, 2024
9f6b859
feat(highCpu): add function to enable/disable high cpu
GuillaumeEgret Jan 19, 2024
3d02413
feat(ReportSummaryCard): display high cpu by default
GuillaumeEgret Jan 19, 2024
6479ccd
feat(getScore): remove high cpu from score calculation if needed
GuillaumeEgret Jan 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/commands/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@perf-profiler/profiler": "^0.10.7",
"@perf-profiler/reporter": "^0.7.0",
"@perf-profiler/types": "^0.6.0",
"@perf-profiler/ios-instruments": "^0.2.0",
"commander": "^9.4.0"
}
}
17 changes: 9 additions & 8 deletions packages/commands/test/src/SingleIterationTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class SingleIterationTester {
}

private async maybeStartRecording() {
if (this.options.recordOptions.record) {
if (this.options.recordOptions.record && this.recorder) {
const { bitRate, size } = this.options.recordOptions;
await this.recorder.startRecording({ bitRate, size });
}
Expand All @@ -87,7 +87,7 @@ export class SingleIterationTester {
}

private async maybeStopRecording() {
if (this.options.recordOptions.record) {
if (this.options.recordOptions.record && this.recorder) {
await this.recorder.stopRecording();
await this.recorder.pullRecording(dirname(this.options.resultsFileOptions.path));
}
Expand All @@ -104,12 +104,13 @@ export class SingleIterationTester {
this.currentTestCaseIterationResult = {
...measures,
status,
videoInfos: this.options.recordOptions.record
? {
path: this.videoPath,
startOffset: Math.floor(measures.startTime - this.recorder.getRecordingStartTime()),
}
: undefined,
videoInfos:
this.options.recordOptions.record && this.recorder
? {
path: this.videoPath,
startOffset: Math.floor(measures.startTime - this.recorder.getRecordingStartTime()),
}
: undefined,
};
}
}
7 changes: 2 additions & 5 deletions packages/commands/test/src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env node

import { Option, program } from "commander";
import { execSync } from "child_process";
import { TestCase } from ".";
import { executeAsync } from "./executeAsync";
import { applyLogLevelOption, logLevelOption } from "./commands/logLevelOption";
import { PerformanceTester } from "./PerformanceTester";
import { Logger } from "@perf-profiler/logger";
import { profiler } from "@perf-profiler/profiler";

program
.command("test")
Expand Down Expand Up @@ -118,10 +118,7 @@ const runTest = async ({
const testCase: TestCase = {
beforeTest: async () => {
if (!skipRestart) {
// This is needed in case the e2e test script actually restarts the app
// So far this method of measuring only works if e2e test actually starts the app
execSync(`adb shell am force-stop ${bundleId}`);
await new Promise((resolve) => setTimeout(resolve, 3000));
await profiler.stopApp(bundleId);
}

if (beforeEachCommand) await executeAsync(beforeEachCommand);
Expand Down
1 change: 1 addition & 0 deletions packages/core/reporter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from "./reporting/Report";
export * from "./utils/sanitizeProcessName";
export * from "./utils/round";
export * from "./reporting/cpu";
export { canComputeHighCpuUsage } from "./reporting/highCpu";
5 changes: 4 additions & 1 deletion packages/core/reporter/src/reporting/getScore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { round } from "lodash";
import { average } from "./averageIterations";
import { getAverageCpuUsage } from "./cpu";
import { getAverageFPSUsage } from "./fps";
import { canComputeHighCpuUsage } from "./highCpu";

/**
* From https://www.mathcelebrity.com/3ptquad.php?p1=50%2C100&p2=200%2C50&p3=300%2C15&pl=Calculate+Equation
Expand Down Expand Up @@ -30,7 +31,9 @@ export const getScore = (result: AveragedTestCaseResult) => {
}

const totalMeasureTime = result.average.measures.length * POLLING_INTERVAL;
const timePercentageThreadlocked = totalTimeThreadlocked / totalMeasureTime;
const timePercentageThreadlocked = canComputeHighCpuUsage(result)
? totalTimeThreadlocked / totalMeasureTime
: 0;

return round(Math.max(0, average(scores) * (1 - timePercentageThreadlocked)), 0);
};
11 changes: 11 additions & 0 deletions packages/core/reporter/src/reporting/highCpu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,14 @@ export const getHighCpuStats = (
variationCoefficient: variationCoefficient(averageTotalHighCpu, standardDeviation.deviation),
};
};

// We compute every time unless there is only one thread and it's called "Total"
export const canComputeHighCpuUsage = (testCaseResult: AveragedTestCaseResult) => {
if (testCaseResult.average.measures.length === 0) {
return true;
}
const lastMeasure = testCaseResult.average.measures[testCaseResult.average.measures.length - 1];
const threads = Object.keys(lastMeasure.cpu.perName);
if (threads.length === 1 && threads[0] === "Total") return false;
return true;
};
3 changes: 2 additions & 1 deletion packages/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@ export interface Profiler {
detectCurrentBundleId: () => string;
installProfilerOnDevice: () => void;
cleanup: () => void;
getScreenRecorder: (videoPath: string) => ScreenRecorder;
getScreenRecorder: (videoPath: string) => ScreenRecorder | undefined;
stopApp: (bundleId: string) => Promise<void>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { setVideoCurrentTime } from "../../../videoCurrentTimeContext";
import { RangeAreaSeriesType, LineSeriesType } from "./types";

export const getLastX = (series: RangeAreaSeriesType | LineSeriesType) => {
if (series.length === 0) return undefined;
const lastDataPoint = series[0].data.at(-1);
return typeof lastDataPoint === "object" && lastDataPoint !== null && "x" in lastDataPoint
? lastDataPoint.x
Expand Down
66 changes: 39 additions & 27 deletions packages/core/web-reporter-ui/src/sections/CPUReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ const getAutoSelectedThreads = (results: AveragedTestCaseResult[]) => {
return autoSelectedThread ? [autoSelectedThread] : [];
};

export const getNumberOfThreads = (results: AveragedTestCaseResult[]) => {
if (results.length === 0 || results[0].average.measures.length === 0) {
return 0;
}
const lastMeasure = results[0].average.measures[results[0].average.measures.length - 1];
return Object.keys(lastMeasure.cpu.perName).length;
};

export const CPUReport = ({ results }: { results: AveragedTestCaseResult[] }) => {
const [selectedThreads, setSelectedThreads] = React.useState<string[]>(
getAutoSelectedThreads(results)
Expand Down Expand Up @@ -82,34 +90,38 @@ export const CPUReport = ({ results }: { results: AveragedTestCaseResult[] }) =>
series={totalCPUUsage}
annotationIntervalList={totalCpuAnnotationInterval}
/>
<ReportChart
title="CPU Usage per thread (%)"
height={500}
series={threads}
colors={results.length > 1 ? getColorPalette().slice(0, results.length) : undefined}
maxValue={100}
showLegendForSingleSeries
annotationIntervalList={perThreadCpuAnnotationInterval}
/>
<Collapsible
unmountOnExit
header={<div className="text-neutral-200 text-xl">{"Threads"}</div>}
className="border rounded-lg border-gray-800 py-4 px-4"
>
{results.length > 1 ? (
<ComparativeThreadTable
results={results}
selectedThreads={selectedThreads}
setSelectedThreads={setSelectedThreads}
/>
) : (
<ThreadTable
measures={results[0].average.measures}
selectedThreads={selectedThreads}
setSelectedThreads={setSelectedThreads}
{getNumberOfThreads(results) > 1 && (
<>
<ReportChart
title="CPU Usage per thread (%)"
height={500}
series={threads}
colors={results.length > 1 ? getColorPalette().slice(0, results.length) : undefined}
maxValue={100}
showLegendForSingleSeries
annotationIntervalList={perThreadCpuAnnotationInterval}
/>
)}
</Collapsible>
<Collapsible
unmountOnExit
header={<div className="text-neutral-200 text-xl">{"Threads"}</div>}
className="border rounded-lg border-gray-800 py-4 px-4"
>
{results.length > 1 ? (
<ComparativeThreadTable
results={results}
selectedThreads={selectedThreads}
setSelectedThreads={setSelectedThreads}
/>
) : (
<ThreadTable
measures={results[0].average.measures}
selectedThreads={selectedThreads}
setSelectedThreads={setSelectedThreads}
/>
)}
</Collapsible>
</>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { FunctionComponent } from "react";
import { Report } from "@perf-profiler/reporter";
import { Report, canComputeHighCpuUsage } from "@perf-profiler/reporter";
import { ReportSummaryCardInfoRow } from "./ReportSummaryCardInfoRow";
import { Score } from "../../components/Score";
import { Explanations } from "./Explanations";
Expand All @@ -20,6 +20,8 @@ export const ReportSummaryCard: FunctionComponent<Props> = ({ report, baselineRe
const shouldDisplayStats = report.getIterationCount() > 1;
const reportStats = shouldDisplayStats ? report.getStats() : undefined;

const averagedTestCaseResult = report.getAveragedResult();

return (
<div className="flex flex-col items-center py-6 px-10 bg-dark-charcoal border border-gray-800 rounded-lg w-[500px] flex-shrink-0">
<div className="flex flex-row items-center gap-2">
Expand Down Expand Up @@ -75,33 +77,35 @@ export const ReportSummaryCard: FunctionComponent<Props> = ({ report, baselineRe
/>

<div className="h-2" />
<ReportSummaryCardInfoRow
title="High CPU Usage"
value={
displayPlaceholder ? (
"-"
) : (
<div style={metrics.totalHighCpuTime > 0 ? { color: "red" } : {}}>
{metrics.totalHighCpuTime > 0 ? `${metrics.totalHighCpuTime} s` : "None ✅"}
</div>
)
}
difference={
<Difference
value={metrics.totalHighCpuTime}
baseline={baselineMetrics?.totalHighCpuTime}
/>
}
explanation={<Explanations.HighCPUUsageExplanation result={report.getAveragedResult()} />}
statistics={
reportStats ? (
<>
<SummaryStats stats={reportStats.highCpu} unit="ms" />{" "}
<ThreadStats stats={reportStats.highCpu.threads} />
</>
) : undefined
}
/>
{canComputeHighCpuUsage(averagedTestCaseResult) && (
<ReportSummaryCardInfoRow
title="High CPU Usage"
value={
displayPlaceholder ? (
"-"
) : (
<div style={metrics.totalHighCpuTime > 0 ? { color: "red" } : {}}>
{metrics.totalHighCpuTime > 0 ? `${metrics.totalHighCpuTime} s` : "None ✅"}
</div>
)
}
difference={
<Difference
value={metrics.totalHighCpuTime}
baseline={baselineMetrics?.totalHighCpuTime}
/>
}
explanation={<Explanations.HighCPUUsageExplanation result={averagedTestCaseResult} />}
statistics={
reportStats ? (
<>
<SummaryStats stats={reportStats.highCpu} unit="ms" />{" "}
<ThreadStats stats={reportStats.highCpu.threads} />
</>
) : undefined
}
/>
)}

{metrics.ram !== undefined || displayPlaceholder ? (
<>
Expand Down
6 changes: 6 additions & 0 deletions packages/platforms/android/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@ import { cleanup } from "./commands/shell";
import { ScreenRecorder } from "./commands/ScreenRecorder";
import { profiler } from "./commands/platforms/platformProfiler";
import { Profiler } from "@perf-profiler/types";
import { execSync } from "child_process";

export { Measure } from "@perf-profiler/types";
export { Measure as GfxInfoMeasure } from "./commands/gfxInfo/parseGfxInfo";
export { waitFor } from "./utils/waitFor";
export { executeAsync, executeCommand } from "./commands/shell";

export class AndroidProfiler implements Profiler {
pollPerformanceMeasures = pollPerformanceMeasures;
detectCurrentBundleId = profiler.detectCurrentBundleId;
installProfilerOnDevice = ensureCppProfilerIsInstalled;
getScreenRecorder = (videoPath: string) => new ScreenRecorder(videoPath);
cleanup = cleanup;
async stopApp(bundleId: string) {
execSync(`adb shell am force-stop ${bundleId}`);
await new Promise((resolve) => setTimeout(resolve, 3000));
}
}
3 changes: 2 additions & 1 deletion packages/platforms/ios-instruments/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"bin": {
"flashlight-ios-poc": "./dist/launchIOS.js"
},
"main": "index.js",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand All @@ -15,6 +15,7 @@
"dependencies": {
"@perf-profiler/profiler": "^0.10.7",
"@perf-profiler/types": "^0.6.0",
"@perf-profiler/logger": "0.3.1",
"commander": "^9.4.0",
"fast-xml-parser": "^4.2.7"
}
Expand Down
1 change: 1 addition & 0 deletions packages/platforms/ios-instruments/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { killApp } from "./utils/DeviceManager";
Loading