diff --git a/packages/commands/measure/src/__tests__/__snapshots__/measure.test.tsx.snap b/packages/commands/measure/src/__tests__/__snapshots__/measure.test.tsx.snap index 5731da1f..a2abdff4 100644 --- a/packages/commands/measure/src/__tests__/__snapshots__/measure.test.tsx.snap +++ b/packages/commands/measure/src/__tests__/__snapshots__/measure.test.tsx.snap @@ -28,7 +28,7 @@ High CPU Usage 0.5 s Impacted threads: - -mqt_js +RN JS Thread for 0.5 @@ -43,13 +43,13 @@ here for more details ) Frame rate (FPS) Total CPU Usage (%) -CPU Usage per thread (%) -Threads +RN JS Thread CPU Usage (%) +Other threads Thread Average CPU Usage (%) sorted descending Current CPU Usage (%) -mqt_js +RN JS Thread 50 0 com.example @@ -70,7 +70,7 @@ OkHttp Dispatch mali-cmar-backe 1 0 -mqt_native_modu +RN Bridge Thread 1 0 Signal Catcher @@ -648,7 +648,7 @@ exports[`flashlight measure interactive it displays measures: Web app with measu

- - mqt_js for 0.5s + - RN JS Thread for 0.5s

High CPU usage by a single process can cause app unresponsiveness, even with low overall CPU usage. For instance, an overworked JS thread in a React Native app may lead to unresponsiveness despite maintaining 60 FPS. @@ -771,7 +771,45 @@ exports[`flashlight measure interactive it displays measures: Web app with measu
- CPU Usage per thread (%) +
+
+ + + + + + + + +
+ RN JS Thread CPU Usage (%) +
- Threads + Other threads - mqt_js + RN JS Thread - mqt_native_modu + RN Bridge Thread { await screen.findByText("47"); // expand threads - await screen.findByText("Threads"); - fireEvent.click(screen.getByText("Threads")); + await screen.findByText("Other threads"); + fireEvent.click(screen.getByText("Other threads")); expectWebAppToMatchSnapshot("Web app with measures and threads opened"); diff --git a/packages/core/web-reporter-ui/ReporterView.tsx b/packages/core/web-reporter-ui/ReporterView.tsx index ba2c2c45..ac4fe6ca 100644 --- a/packages/core/web-reporter-ui/ReporterView.tsx +++ b/packages/core/web-reporter-ui/ReporterView.tsx @@ -14,6 +14,7 @@ import { IterationSelector, useIterationSelector } from "./src/components/Iterat import { VideoSection } from "./src/sections/VideoSection"; import { VideoEnabledContext } from "./videoCurrentTimeContext"; import { HideSectionIfUndefinedValueFound } from "./src/sections/hideSectionForEmptyValue"; +import { mapThreadNames } from "./src/sections/threads"; const Padding = styled.div` height: 10px; @@ -27,12 +28,13 @@ const theme = createTheme({ }); const Report = ({ - results, + results: rawResults, additionalMenuOptions, }: { results: TestCaseResult[]; additionalMenuOptions?: MenuOption[]; }) => { + const results = mapThreadNames(rawResults); const reports = useMemo(() => results.map((result) => new ReportModel(result)), [results]); const minIterationCount = Math.min(...reports.map((report) => report.getIterationCount())); const iterationSelector = useIterationSelector(minIterationCount); diff --git a/packages/core/web-reporter-ui/__tests__/ReporterView.test.tsx b/packages/core/web-reporter-ui/__tests__/ReporterView.test.tsx index 2a69fdfa..6b06d9c0 100644 --- a/packages/core/web-reporter-ui/__tests__/ReporterView.test.tsx +++ b/packages/core/web-reporter-ui/__tests__/ReporterView.test.tsx @@ -16,7 +16,7 @@ describe("", () => { ); expect(screen.getAllByLabelText("Score")[0].textContent).toEqual("69"); - fireEvent.click(screen.getByText("Threads")); + fireEvent.click(screen.getByText("Other threads")); expect(getText(baseElement)).toMatchSnapshot(); expect(asFragment()).toMatchSnapshot(); diff --git a/packages/core/web-reporter-ui/__tests__/__snapshots__/ReporterView.test.tsx.snap b/packages/core/web-reporter-ui/__tests__/__snapshots__/ReporterView.test.tsx.snap index 595d6dd2..4ac6d4cc 100644 --- a/packages/core/web-reporter-ui/__tests__/__snapshots__/ReporterView.test.tsx.snap +++ b/packages/core/web-reporter-ui/__tests__/__snapshots__/ReporterView.test.tsx.snap @@ -91,7 +91,7 @@ High CPU Usage 3.2 s Impacted threads: - -mqt_js +RN JS Thread for 3.2 @@ -121,7 +121,7 @@ Coefficient of variation : % - -mqt_js +RN JS Thread - Min Max Range: @@ -268,7 +268,7 @@ High CPU Usage (-69%) Impacted threads: - -mqt_js +RN JS Thread for 1 @@ -298,7 +298,7 @@ Coefficient of variation : % - -mqt_js +RN JS Thread - Min Max Range: @@ -354,19 +354,19 @@ Coefficient of variation : % Frame rate (FPS) Total CPU Usage (%) -CPU Usage per thread (%) -Threads +RN JS Thread CPU Usage (%) +Other threads Thread 1-before-memo.apk sorted descending 2-after-memo.apk -mqt_js +RN JS Thread 32.2 12.3 culture.staging 10 8.7 -mqt_native_modu +RN Bridge Thread 7.2 4.2 RenderThread @@ -1406,7 +1406,7 @@ exports[` renders the comparison view 2`] = `

- - mqt_js for 3.2s + - RN JS Thread for 3.2s

High CPU usage by a single process can cause app unresponsiveness, even with low overall CPU usage. For instance, an overworked JS thread in a React Native app may lead to unresponsiveness despite maintaining 60 FPS. @@ -1457,7 +1457,7 @@ exports[` renders the comparison view 2`] = `
- - mqt_js - + - RN JS Thread -
@@ -2087,7 +2087,7 @@ exports[` renders the comparison view 2`] = `

- - mqt_js for 1s + - RN JS Thread for 1s

High CPU usage by a single process can cause app unresponsiveness, even with low overall CPU usage. For instance, an overworked JS thread in a React Native app may lead to unresponsiveness despite maintaining 60 FPS. @@ -2138,7 +2138,7 @@ exports[` renders the comparison view 2`] = `
- - mqt_js - + - RN JS Thread -
@@ -2338,7 +2338,45 @@ exports[` renders the comparison view 2`] = `
- CPU Usage per thread (%) +
+
+ + + + + + + + +
+ RN JS Thread CPU Usage (%) +
renders the comparison view 2`] = `
- Threads + Other threads
renders the comparison view 2`] = ` id="enhanced-table-checkbox-0" scope="row" > - mqt_js + RN JS Thread renders the comparison view 2`] = ` id="enhanced-table-checkbox-2" scope="row" > - mqt_native_modu + RN Bridge Thread renders the comparison view 4`] = `

- - mqt_js for 3.5s + - RN JS Thread for 3.5s

High CPU usage by a single process can cause app unresponsiveness, even with low overall CPU usage. For instance, an overworked JS thread in a React Native app may lead to unresponsiveness despite maintaining 60 FPS. @@ -13286,7 +13324,7 @@ exports[` renders the comparison view 4`] = `

- - mqt_js for 1s + - RN JS Thread for 1s

High CPU usage by a single process can cause app unresponsiveness, even with low overall CPU usage. For instance, an overworked JS thread in a React Native app may lead to unresponsiveness despite maintaining 60 FPS. @@ -13414,7 +13452,45 @@ exports[` renders the comparison view 4`] = `
- CPU Usage per thread (%) +
+
+ + + + + + + + +
+ RN JS Thread CPU Usage (%) +
renders the comparison view 4`] = `
- Threads + Other threads
renders the comparison view 4`] = ` id="enhanced-table-checkbox-0" scope="row" > - mqt_js + RN JS Thread renders the comparison view 4`] = ` id="enhanced-table-checkbox-2" scope="row" > - mqt_native_modu + RN Bridge Thread renders the comparison view with videos 2`] = `

- - mqt_js for 2.3s + - RN JS Thread for 2.3s

renders the comparison view with videos 2`] = `

- - mqt_js - + - RN JS Thread -
@@ -19875,7 +19951,7 @@ exports[` renders the comparison view with videos 2`] = `

- - mqt_js for 1.5s + - RN JS Thread for 1.5s

renders the comparison view with videos 2`] = `

- - mqt_js - + - RN JS Thread -
@@ -20207,7 +20283,45 @@ exports[` renders the comparison view with videos 2`] = `
- CPU Usage per thread (%) +
+
+ + + + + + + + +
+ RN JS Thread CPU Usage (%) +
renders the comparison view with videos 2`] = `
- Threads + Other threads
( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/packages/core/web-reporter-ui/src/components/icons/ReactNativeLogo.tsx b/packages/core/web-reporter-ui/src/components/icons/ReactNativeLogo.tsx new file mode 100644 index 00000000..dd48289a --- /dev/null +++ b/packages/core/web-reporter-ui/src/components/icons/ReactNativeLogo.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +export const RNLogo = ({ size }: { size: number }) => ( + + + + + + + + +); diff --git a/packages/core/web-reporter-ui/src/sections/CPUReport.tsx b/packages/core/web-reporter-ui/src/sections/CPUReport.tsx index 5152ad28..11c5bc51 100644 --- a/packages/core/web-reporter-ui/src/sections/CPUReport.tsx +++ b/packages/core/web-reporter-ui/src/sections/CPUReport.tsx @@ -1,15 +1,11 @@ import React from "react"; -import { - AveragedTestCaseResult, - Measure, - POLLING_INTERVAL, - ThreadNames, -} from "@perf-profiler/types"; +import { AveragedTestCaseResult, Measure, POLLING_INTERVAL } from "@perf-profiler/types"; import { ComparativeThreadTable, ThreadTable } from "../components/ThreadTable"; import { Collapsible } from "../components/Collapsible"; import { getColorPalette } from "../theme/colors"; import { getAverageCpuUsage, roundToDecimal } from "@perf-profiler/reporter"; import { ReportChart } from "../components/Charts/ReportChart"; +import { THREAD_ICON_MAPPING, getAutoSelectedThreads } from "./threads"; const buildSeriesData = (measures: Measure[], calculate: (measure: Measure) => number) => measures @@ -29,32 +25,6 @@ const totalCpuAnnotationInterval = [{ y: 300, y2: 1000, color: "#E62E2E", label: const perThreadCpuAnnotationInterval = [{ y: 90, y2: 100, color: "#E62E2E", label: "Danger Zone" }]; -const autoSelectedThreads = [ - ThreadNames.RN.JS_IOS, - ThreadNames.RN.JS_ANDROID, - ThreadNames.RN.JS_BRIDGELESS_ANDROID, - ThreadNames.FLUTTER.UI, - ThreadNames.ANDROID.UI, - ThreadNames.IOS.UI, -]; - -const getAutoSelectedThreads = (results: AveragedTestCaseResult[]) => { - const autoSelectedThread = autoSelectedThreads.find((threadName) => - results - .filter((result) => result.average.measures.length > 0) - .every((result) => { - const lastMeasure = result.average.measures[result.average.measures.length - 1]; - return ( - lastMeasure.cpu.perName[threadName] !== undefined || - // Support legacy json files with thread names in parenthesis - lastMeasure.cpu.perName[`(${threadName})`] !== undefined - ); - }) - ); - - return autoSelectedThread ? [autoSelectedThread] : []; -}; - export const getNumberOfThreads = (results: AveragedTestCaseResult[]) => { if (results.length === 0 || results[0].average.measures.length === 0) { return 0; @@ -82,6 +52,22 @@ export const CPUReport = ({ results }: { results: AveragedTestCaseResult[] }) => data: buildAverageCpuSeriesData(result.average.measures), })); + const TitleIcon = THREAD_ICON_MAPPING[selectedThreads[0]]; + + const chartTitle = + selectedThreads.length === 1 ? ( +
+ {TitleIcon ? ( +
+ +
+ ) : null} + {`${selectedThreads[0]} CPU Usage (%)`} +
+ ) : ( + "CPU Usage per thread (%)" + ); + return ( <> {getNumberOfThreads(results) > 1 && ( <> 1 ? getColorPalette().slice(0, results.length) : undefined} @@ -103,7 +89,7 @@ export const CPUReport = ({ results }: { results: AveragedTestCaseResult[] }) => /> {"Threads"}
} + header={
{"Other threads"}
} className="border rounded-lg border-gray-800 py-4 px-4" > {results.length > 1 ? ( diff --git a/packages/core/web-reporter-ui/src/sections/threads.tsx b/packages/core/web-reporter-ui/src/sections/threads.tsx new file mode 100644 index 00000000..f7cc7947 --- /dev/null +++ b/packages/core/web-reporter-ui/src/sections/threads.tsx @@ -0,0 +1,77 @@ +import React from "react"; +import { AveragedTestCaseResult, TestCaseResult, ThreadNames } from "@perf-profiler/types"; +import { RNLogo } from "../components/icons/ReactNativeLogo"; +import { FlutterLogo } from "../components/icons/FlutterLogo"; + +export const getAutoSelectedThreads = (results: AveragedTestCaseResult[]) => { + const autoSelectedThread = autoSelectedThreads.find((threadName) => + results + .filter((result) => result.average.measures.length > 0) + .every((result) => { + const lastMeasure = result.average.measures[result.average.measures.length - 1]; + return ( + lastMeasure.cpu.perName[threadName] !== undefined || + // Support legacy json files with thread names in parenthesis + lastMeasure.cpu.perName[`(${threadName})`] !== undefined + ); + }) + ); + + return autoSelectedThread ? [autoSelectedThread] : []; +}; + +const THREAD_NAME_MAPPING: { + [key: string]: string; +} = { + [ThreadNames.FLUTTER.UI]: "Flutter UI Thread", + [ThreadNames.FLUTTER.RASTER]: "Flutter Raster Thread", + [ThreadNames.FLUTTER.IO]: "Flutter IO Thread", + [ThreadNames.RN.JS_ANDROID]: "RN JS Thread", + [ThreadNames.RN.JS_BRIDGELESS_ANDROID]: "RN JS Thread", + [ThreadNames.RN.JS_IOS]: "RN JS Thread", + [ThreadNames.RN.OLD_BRIDGE]: "RN Bridge Thread", +}; + +export const THREAD_ICON_MAPPING = { + [THREAD_NAME_MAPPING[ThreadNames.FLUTTER.UI]]: FlutterLogo, + [THREAD_NAME_MAPPING[ThreadNames.FLUTTER.RASTER]]: FlutterLogo, + [THREAD_NAME_MAPPING[ThreadNames.FLUTTER.IO]]: FlutterLogo, + [THREAD_NAME_MAPPING[ThreadNames.RN.JS_ANDROID]]: RNLogo, + [THREAD_NAME_MAPPING[ThreadNames.RN.JS_BRIDGELESS_ANDROID]]: RNLogo, + [THREAD_NAME_MAPPING[ThreadNames.RN.JS_IOS]]: RNLogo, + [THREAD_NAME_MAPPING[ThreadNames.RN.OLD_BRIDGE]]: RNLogo, +}; + +const autoSelectedThreads = [ + ThreadNames.RN.JS_IOS, + ThreadNames.RN.JS_ANDROID, + ThreadNames.RN.JS_BRIDGELESS_ANDROID, + ThreadNames.FLUTTER.UI, + ThreadNames.ANDROID.UI, + ThreadNames.IOS.UI, +].map((threadName) => THREAD_NAME_MAPPING[threadName] || threadName); + +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 mapThreadNames = (results: TestCaseResult[]): TestCaseResult[] => + results.map((result) => ({ + ...result, + iterations: result.iterations.map((iteration) => ({ + ...iteration, + measures: iteration.measures.map((measure) => ({ + ...measure, + cpu: { + ...measure.cpu, + perName: Object.keys(measure.cpu.perName).reduce((acc, key) => { + return { ...acc, [THREAD_NAME_MAPPING[key] || key]: measure.cpu.perName[key] }; + }, {}), + }, + })), + })), + }));