Skip to content

Commit c1b2936

Browse files
feat(profiler): add 'hack' functions for instruments profiler
1 parent 8abb68f commit c1b2936

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed

packages/core/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,6 @@ export interface Profiler {
8585
cleanup: () => void;
8686
getScreenRecorder: (videoPath: string) => ScreenRecorder | undefined;
8787
stopApp: (bundleId: string) => Promise<void>;
88+
waitUntilReady: (bundleId: string) => Promise<void>;
89+
getMeasures: () => Promise<void>;
8890
}

packages/platforms/android/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ export { waitFor } from "./utils/waitFor";
1212
export { executeAsync, executeCommand } from "./commands/shell";
1313

1414
export class AndroidProfiler implements Profiler {
15+
waitUntilReady = (bundleId: string) => {
16+
return new Promise<void>((resolve) => resolve());
17+
};
18+
getMeasures = () => {
19+
return new Promise<void>((resolve) => resolve());
20+
};
1521
pollPerformanceMeasures = pollPerformanceMeasures;
1622
detectCurrentBundleId = profiler.detectCurrentBundleId;
1723
installProfilerOnDevice = ensureCppProfilerIsInstalled;

packages/platforms/ios-instruments/src/index.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,64 @@
11
import { Measure, Profiler, ProfilerPollingOptions, ScreenRecorder } from "@perf-profiler/types";
2+
import { ChildProcess } from "child_process";
3+
// TODO: refactor so that these functions are not in android
4+
// eslint-disable-next-line import/no-extraneous-dependencies
5+
import { executeAsync, executeCommand } from "@perf-profiler/android";
6+
import { IdbDevice, getConnectedDevice, killApp, launchApp } from "./utils/DeviceManager";
7+
import { computeMeasures } from "./XcodePerfParser";
8+
import { getTmpFilePath, removeTmpFiles } from "./utils/tmpFileManager";
29
export { killApp } from "./utils/DeviceManager";
310

11+
const startRecord = async (
12+
deviceUdid: string,
13+
appPid: number,
14+
traceFile: string
15+
): Promise<ChildProcess> => {
16+
const templateFilePath = `${__dirname}/../Flashlight.tracetemplate`;
17+
const recordingProcess = executeAsync(
18+
`xcrun xctrace record --device ${deviceUdid} --template ${templateFilePath} --attach ${appPid} --output ${traceFile}`
19+
);
20+
await new Promise<void>((resolve) => {
21+
recordingProcess.stdout?.on("data", (data) => {
22+
if (data.toString().includes("Ctrl-C to stop")) {
23+
resolve();
24+
}
25+
});
26+
});
27+
return recordingProcess;
28+
};
29+
30+
const saveTraceFile = (traceFile: string): string => {
31+
const xmlOutputFile = getTmpFilePath("report.xml");
32+
executeCommand(
33+
`xctrace export --input ${traceFile} --xpath '/trace-toc/run[@number="1"]/data/table[@schema="time-profile"]' --output ${xmlOutputFile}`
34+
);
35+
return xmlOutputFile;
36+
};
37+
38+
const stopPerfRecord = async (
39+
recordingProcess: ChildProcess,
40+
traceFile: string,
41+
onMeasure: (measure: Measure) => void
42+
) => {
43+
try {
44+
await new Promise<void>((resolve) => {
45+
recordingProcess.stdout?.on("data", (data) => {
46+
if (data.toString().includes("Output file saved as")) {
47+
resolve();
48+
}
49+
});
50+
});
51+
} catch (e) {
52+
console.log("Error while recording: ", e);
53+
}
54+
const xmlFile = saveTraceFile(traceFile);
55+
const measures = computeMeasures(xmlFile);
56+
measures.forEach((measure) => {
57+
onMeasure(measure);
58+
});
59+
removeTmpFiles();
60+
};
61+
462
export class IOSInstrumentsProfiler implements Profiler {
563
connectedDevice: IdbDevice | undefined;
664
recordingProcess: ChildProcess | undefined;
@@ -33,6 +91,28 @@ export class IOSInstrumentsProfiler implements Profiler {
3391
cleanup: () => void = () => {
3492
// Do we need anything here?
3593
};
94+
95+
async waitUntilReady(bundleId: string): Promise<void> {
96+
this.connectedDevice = getConnectedDevice();
97+
if (!this.connectedDevice) {
98+
throw new Error("No device connected");
99+
}
100+
this.bundleId = bundleId;
101+
this.pid = launchApp(bundleId);
102+
const traceFile = `report_${new Date().getTime()}.trace`;
103+
this.traceFile = traceFile;
104+
this.recordingProcess = await startRecord(this.connectedDevice.udid, this.pid, traceFile);
105+
}
106+
107+
async getMeasures(): Promise<void> {
108+
if (!this.recordingProcess || !this.traceFile || !this.pid || !this.onMeasure || !this.bundleId)
109+
throw new Error("Profiler is not ready to get measures");
110+
const recordingProcess = this.recordingProcess;
111+
const traceFile = this.traceFile;
112+
killApp(this.bundleId);
113+
await stopPerfRecord(recordingProcess, traceFile, this.onMeasure);
114+
}
115+
36116
async stopApp(bundleId: string): Promise<void> {
37117
killApp(bundleId);
38118
return new Promise<void>((resolve) => resolve());

packages/platforms/ios/src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,12 @@ export class IOSProfiler implements Profiler {
104104
killApp(bundleId);
105105
return new Promise<void>((resolve) => resolve());
106106
}
107+
108+
async waitUntilReady(bundleId: string): Promise<void> {
109+
return new Promise<void>((resolve) => resolve());
110+
}
111+
112+
async getMeasures(): Promise<void> {
113+
return new Promise<void>((resolve) => resolve());
114+
}
107115
}

0 commit comments

Comments
 (0)