Skip to content

Commit

Permalink
Add app profiling feature
Browse files Browse the repository at this point in the history
  • Loading branch information
sudharsan-selvaraj committed Apr 10, 2024
1 parent 41a995d commit 9f0148d
Show file tree
Hide file tree
Showing 15 changed files with 452 additions and 109 deletions.
2 changes: 2 additions & 0 deletions prisma/migrations/20240410102723_app_profiling/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Session" ADD COLUMN "app_profiling" TEXT;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ model Session {
hasLiveVideo Boolean @map("has_live_video")
videoRecording String? @map("video_recording")
deviceLogs String? @map("device_logs")
appProfiling String? @map("app_profiling")
startTime DateTime @default(now()) @map("start_time")
endTime DateTime? @map("end_time")
failureReason String? @map("failure_reason")
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/IDeviceFarmSession.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ADB from 'appium-adb';
import { IDevice } from './IDevice';

export type IDeviceFarmSessionOptions = {
Expand All @@ -7,4 +8,5 @@ export type IDeviceFarmSessionOptions = {
sessionResponse: Record<string, any>;
pluginNodeId: string;
driver: any;
adb: ADB;
};
2 changes: 1 addition & 1 deletion src/modules
1 change: 1 addition & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ class DevicePlugin extends BasePlugin {
deviceFarmCapabilities,
pluginNodeId: DevicePlugin.NODE_ID,
driver: driver,
adb: DevicePlugin.adbInstance,
}),
);

Expand Down
46 changes: 46 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.14",
"@mui/material": "^5.15.14",
"chart.js": "^4.4.2",
"pretty-ms": "^8.0.0",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-json-view": "^1.21.3",
"react-router-dom": "^6.21.1"
Expand Down
4 changes: 4 additions & 0 deletions web/src/api-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,8 @@ export default class DeviceFarmApiService {
);
return response?.logs || [];
}

public static async getAppProfiling(sessionId: string) {
return apiClient.makeGETRequest(`/dashboard/session/${sessionId}/app_profiling`, {});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.app-profiling-container {
max-width: 700px !important;
display: flex;
flex-direction: column;
margin: auto;
align-items: center;
height: 100%;
}

.chart-row {
margin: 25px 0 40px 0;
height: 100%;
width: 100%;
padding: 0 50px 0 50px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import './app-profiling.css';
import { IAppProfilingLogs } from '../../../../interfaces/IAppProfilingLogs';
import { ISession } from '../../../../interfaces/ISession';
import _ from 'lodash';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler,
} from 'chart.js';
import { Line } from 'react-chartjs-2';

const colors = {
profiling_chart_system_cpu_border: 'rgb(194, 230, 153)',
profiling_chart_system_cpu_background: 'rgba(194, 230, 153, 0.5)',
profiling_chart_app_cpu_border: 'rgb(49, 163, 84)',
profiling_chart_app_cpu_background: 'rgba(49, 163, 84, 0.5)',
profiling_chart_system_memory_border: 'rgb(65, 182, 196)',
profiling_chart_system_memory_background: 'rgba(65, 182, 196, 0.5)',
profiling_chart_app_memory_border: 'rgb(34, 94, 168)',
profiling_chart_app_memory_background: 'rgba(34, 94, 168, 0.5)',
};

ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler,
);

function toMegaByte(value: any) {
return Math.ceil(Number(value) / 1024);
}

function getTimeDiffInSecs(startDate: Date, endDate: Date) {
return Math.ceil((endDate.getTime() - startDate.getTime()) / 1000);
}

/* Cpu Usage data */
function getCpuChartOptions(appProfilingLogs: IAppProfilingLogs) {
return {
responsive: true,
aspectRatio: 3,
plugins: {
legend: {
position: 'top' as const,
labels: {
usePointStyle: true,
},
},
title: {
display: true,
text: `CPU [${appProfilingLogs.device_info.total_cpu} processors = ${
appProfilingLogs.device_info.total_cpu * 100
} %]`,
},
tooltip: {
callbacks: {
label: function (context: any) {
return context.dataset.label + ' : ' + context.parsed.y;
},
},
},
},
parsing: {
xAxisKey: 'timestamp',
yAxisKey: 'cpu',
},
scales: {
y: {
min: -1,
max: appProfilingLogs.device_info.total_cpu * 100,
},
},
};
}

function getCpuChartData(appProfilingLogs: IAppProfilingLogs) {
return {
datasets: [
{
label: 'Total CPU Usage %',
data: appProfilingLogs.profiling_logs.map((v: any) => {
return {
timestamp: v.timestamp,
cpu: v.total_cpu_used,
};
}),
fill: true,
borderColor: colors.profiling_chart_system_cpu_border,
backgroundColor: colors.profiling_chart_system_cpu_background,
},
{
label: `${appProfilingLogs.device_info.app_package} %`,
data: appProfilingLogs.profiling_logs.map((v: any) => {
return {
timestamp: v.timestamp,
cpu: v.cpu,
};
}),
fill: true,
borderColor: colors.profiling_chart_app_cpu_border,
backgroundColor: colors.profiling_chart_app_cpu_background,
},
],
};
}

/* Memory usage data */
function getMemoryUsageChartOptions(appProfilingLogs: IAppProfilingLogs) {
const totalMemoryInMB = toMegaByte(appProfilingLogs.device_info.total_memory);
return {
responsive: true,
plugins: {
legend: {
position: 'top' as const,
labels: {
usePointStyle: true,
},
},
title: {
display: true,
text: `MEMORY [${totalMemoryInMB} MB]`,
},
tooltip: {
callbacks: {
label: function (context: any) {
return context.dataset.label + ' : ' + context.parsed.y;
},
},
},
},
parsing: {
xAxisKey: 'timestamp',
yAxisKey: 'memory',
},
aspectRatio: 3,
scales: {
y: {
min: 0,
max: Math.ceil(totalMemoryInMB / 500) * 500, //round to nearest five hundred
ticks: {
stepSize: 500,
},
},
},
};
}

function getMemoryChartData(appProfilingLogs: IAppProfilingLogs) {
return {
datasets: [
{
label: 'Total Memory Usage (MB)',
data: appProfilingLogs.profiling_logs.map((data) => {
return {
timestamp: data.timestamp,
memory: Math.ceil(Number(data.total_memory_used) / 1024),
};
}),
fill: true,
borderColor: colors.profiling_chart_system_memory_border,
backgroundColor: colors.profiling_chart_system_memory_background,
},
{
label: `${appProfilingLogs.device_info.app_package} (MB)`,
data: appProfilingLogs.profiling_logs.map((data) => {
return {
timestamp: data.timestamp,
memory: toMegaByte(data.memory),
};
}),
fill: true,
borderColor: colors.profiling_chart_app_memory_border,
backgroundColor: colors.profiling_chart_app_memory_background,
},
],
};
}

function useProfiling(session: ISession, appProfiling: IAppProfilingLogs): IAppProfilingLogs {
const processedData = appProfiling.profiling_logs.map((data: any) => {
return {
...data,
timestamp: getTimeDiffInSecs(new Date(session.createdAt), new Date(data.timestamp)) + 's',
};
});
return {
device_info: appProfiling.device_info,
profiling_logs: _.uniqBy(processedData, 'timestamp'),
};
}

export default function AppProfiling(props: {
session: ISession;
appProfilingLogs: IAppProfilingLogs;
}) {
const { appProfilingLogs, session } = props;

const processedData = useProfiling(session, appProfilingLogs);
return (
<div className="app-profiling-container">
<div className="chart-row">
<Line
options={getCpuChartOptions(processedData)}
data={getCpuChartData(processedData)}
></Line>
</div>
<div className="chart-row">
<Line
options={getMemoryUsageChartOptions(processedData)}
data={getMemoryChartData(processedData)}
></Line>
</div>
</div>
);
}
Loading

0 comments on commit 9f0148d

Please sign in to comment.