Skip to content

Commit 0569873

Browse files
authored
feat(profiling): generate chrome compatible timeline data (#5686)
1 parent 363db96 commit 0569873

File tree

6 files changed

+138
-2
lines changed

6 files changed

+138
-2
lines changed

lib/bootstrap.ts

+4
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ injector.require(
155155
"./services/android-device-debug-service"
156156
);
157157

158+
injector.require(
159+
"timelineProfilerService",
160+
"./services/timeline-profiler-service"
161+
);
158162
injector.require("userSettingsService", "./services/user-settings-service");
159163
injector.requirePublic(
160164
"analyticsSettingsService",

lib/common/mobile/device-log-provider.ts

+5
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import * as chalk from "chalk";
66
import { LoggerConfigData } from "../../constants";
77
import { IOptions } from "../../declarations";
88

9+
import { ITimelineProfilerService } from "../../services/timeline-profiler-service";
10+
911
export class DeviceLogProvider extends DeviceLogProviderBase {
1012
constructor(
1113
protected $logFilter: Mobile.ILogFilter,
1214
protected $logger: ILogger,
1315
protected $logSourceMapService: Mobile.ILogSourceMapService,
16+
protected $timelineProfilerService: ITimelineProfilerService,
1417
protected $options: IOptions
1518
) {
1619
super($logFilter, $logger, $logSourceMapService);
@@ -29,7 +32,9 @@ export class DeviceLogProvider extends DeviceLogProviderBase {
2932
data,
3033
loggingOptions
3134
);
35+
3236
if (data) {
37+
this.$timelineProfilerService.processLogData(data, deviceIdentifier);
3338
this.logDataCore(data, deviceIdentifier);
3439
this.emit(DEVICE_LOG_EVENT_NAME, lineText, deviceIdentifier, platform);
3540
}

lib/common/test/unit-tests/mobile/device-log-provider.ts

+4
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ const createTestInjector = (): IInjector => {
7676
},
7777
});
7878

79+
testInjector.register("timelineProfilerService", {
80+
processLogData() {},
81+
});
82+
7983
const logger = testInjector.resolve<CommonLoggerStub>("logger");
8084
logger.info = (...args: any[]): void => {
8185
args = args.filter((arg) => Object.keys(arg).indexOf("skipNewLine") === -1);

lib/nativescript-cli.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ installUncaughtExceptionListener(
2222
);
2323

2424
const logger: ILogger = injector.resolve("logger");
25-
const originalProcessOn = process.on;
25+
export const originalProcessOn = process.on.bind(process);
2626

2727
process.on = (event: string, listener: any): any => {
2828
if (event === "SIGINT") {
@@ -33,7 +33,7 @@ process.on = (event: string, listener: any): any => {
3333
const stackTrace = new Error(msg).stack || "";
3434
logger.trace(stackTrace.replace(`Error: ${msg}`, msg));
3535
} else {
36-
return originalProcessOn.apply(process, [event, listener]);
36+
return originalProcessOn(event, listener);
3737
}
3838
};
3939

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { IFileSystem } from "../common/declarations";
2+
import { cache } from "../common/decorators";
3+
import { injector } from "../common/yok";
4+
import { IProjectConfigService } from "../definitions/project";
5+
import * as path from "path";
6+
import { originalProcessOn } from "../nativescript-cli";
7+
8+
export interface ITimelineProfilerService {
9+
processLogData(data: string, deviceIdentifier: string): void;
10+
}
11+
12+
const TIMELINE_LOG_RE = /Timeline:\s*(\d*.?\d*ms:\s*)?([^\:]*\:)?(.*)\((\d*.?\d*)ms\.?\s*-\s*(\d*.\d*)ms\.?\)/;
13+
14+
enum ChromeTraceEventPhase {
15+
BEGIN = "B",
16+
END = "E",
17+
INSTANT = "i",
18+
COMPLETE = "X",
19+
}
20+
21+
interface ChromeTraceEvent {
22+
ts: number;
23+
pid: number;
24+
tid: number;
25+
/** event phase */
26+
ph?: ChromeTraceEventPhase | string;
27+
[otherData: string]: any;
28+
}
29+
30+
interface DeviceTimeline {
31+
startPoint: number;
32+
timeline: ChromeTraceEvent[];
33+
}
34+
35+
export class TimelineProfilerService implements ITimelineProfilerService {
36+
private timelines: Map<string, DeviceTimeline> = new Map();
37+
private attachedExitHandler: boolean = false;
38+
constructor(
39+
private $projectConfigService: IProjectConfigService,
40+
private $fs: IFileSystem,
41+
private $logger: ILogger
42+
) {}
43+
44+
private attachExitHanlder() {
45+
if (!this.attachedExitHandler) {
46+
this.$logger.info('attached "SIGINT" handler to write timeline data.');
47+
originalProcessOn("SIGINT", this.writeTimelines.bind(this));
48+
this.attachedExitHandler = true;
49+
}
50+
}
51+
52+
public processLogData(data: string, deviceIdentifier: string) {
53+
if (!this.isEnabled()) {
54+
return;
55+
}
56+
this.attachExitHanlder();
57+
58+
if (!this.timelines.has(deviceIdentifier)) {
59+
this.timelines.set(deviceIdentifier, {
60+
startPoint: null,
61+
timeline: [],
62+
});
63+
}
64+
65+
const deviceTimeline = this.timelines.get(deviceIdentifier);
66+
67+
data.split("\n").forEach((line) => {
68+
const trace = this.toTrace(line.trim());
69+
if (trace) {
70+
deviceTimeline.startPoint ??= trace.from;
71+
deviceTimeline.timeline.push(trace);
72+
}
73+
});
74+
}
75+
76+
@cache()
77+
private isEnabled() {
78+
return this.$projectConfigService.getValue("profiling") === "timeline";
79+
}
80+
81+
private toTrace(text: string): ChromeTraceEvent | undefined {
82+
const result = text.match(TIMELINE_LOG_RE);
83+
if (!result) {
84+
return;
85+
}
86+
87+
const trace = {
88+
domain: result[2]?.trim().replace(":", ""),
89+
name: result[3].trim(),
90+
from: parseFloat(result[4]),
91+
to: parseFloat(result[5]),
92+
};
93+
94+
return {
95+
pid: 1,
96+
tid: 1,
97+
ts: trace.from * 1000,
98+
dur: (trace.to - trace.from) * 1000,
99+
name: trace.name,
100+
cat: trace.domain ?? "default",
101+
ph: ChromeTraceEventPhase.COMPLETE,
102+
};
103+
}
104+
105+
private writeTimelines() {
106+
this.$logger.info("\n\nWriting timeline data to json...");
107+
this.timelines.forEach((deviceTimeline, deviceIdentifier) => {
108+
const deviceTimelineFileName = `timeline-${deviceIdentifier}.json`;
109+
this.$fs.writeJson(
110+
path.resolve(process.cwd(), deviceTimelineFileName),
111+
deviceTimeline.timeline
112+
);
113+
this.$logger.info(
114+
`Timeline data for device ${deviceIdentifier} written to ${deviceTimelineFileName}`
115+
);
116+
});
117+
118+
process.exit();
119+
}
120+
}
121+
122+
injector.register("timelineProfilerService", TimelineProfilerService);

test/ios-project-service.ts

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ function createTestInjector(
129129
testInjector.register("messages", Messages);
130130
testInjector.register("mobileHelper", MobileHelper);
131131
testInjector.register("deviceLogProvider", DeviceLogProvider);
132+
testInjector.register("timelineProfilerService", {});
132133
testInjector.register("logFilter", LogFilter);
133134
testInjector.register("loggingLevels", LoggingLevels);
134135
testInjector.register("utils", Utils);

0 commit comments

Comments
 (0)