Skip to content

Commit e1802ed

Browse files
committed
[INTERNAL] Add CPU profile generation capability
Can be activated by setting the `UI5LINT_PROFILE` environment variable to a truthy value. Based on SAP/ui5-cli#638
1 parent d15313d commit e1802ed

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed

packages/ui5-linter/src/cli/base.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ async function handleLint(argv: yargs.ArgumentsCamelCase) {
9595
format
9696
} = <{ coverage: boolean, filePaths: string[], details: boolean, format: string }><unknown>argv;
9797

98+
let profile;
99+
if (process.env.UI5LINT_PROFILE) {
100+
profile = await import("./utils/profile.js");
101+
await profile.start();
102+
}
103+
98104
const res = await lintProject({
99105
rootDir: path.join(process.cwd()),
100106
filePaths: filePaths && filePaths.map((filePath) => path.resolve(process.cwd(), filePath)),
@@ -113,7 +119,10 @@ async function handleLint(argv: yargs.ArgumentsCamelCase) {
113119
const textFormatter = new Text();
114120
process.stderr.write(textFormatter.format(res, details));
115121
}
116-
122+
// Stop profiling after CLI finished execution
123+
if (profile) {
124+
await profile.stop();
125+
}
117126
}
118127

119128
export default function base(cli: yargs.Argv) {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {writeFile} from "node:fs/promises";
2+
import {Session, Profiler} from "node:inspector";
3+
import {getLogger} from "@ui5/logger";
4+
const log = getLogger("cli:utils:profile");
5+
6+
let session: Session | null;
7+
type ProcessSignals = Record<string, (exitCode: number) => void>
8+
let processSignals: ProcessSignals | null;
9+
10+
export async function start() {
11+
if (session) {
12+
return;
13+
}
14+
session = new Session();
15+
session.connect();
16+
await new Promise<void>((resolve) => {
17+
session?.post("Profiler.enable", () => {
18+
log.info(`Recording CPU profile...`);
19+
session?.post("Profiler.start", () => {
20+
processSignals = registerSigHooks();
21+
resolve();
22+
});
23+
});
24+
});
25+
}
26+
27+
async function writeProfile(profile: Profiler.Profile) {
28+
const formatter = new Intl.DateTimeFormat("en-GB", {
29+
year: "numeric",
30+
month: "2-digit",
31+
day: "2-digit",
32+
hour: "2-digit",
33+
minute: "2-digit",
34+
second: "2-digit",
35+
});
36+
const dateParts = Object.create(null);
37+
const parts = formatter.formatToParts(new Date());
38+
parts.forEach((p) => {
39+
dateParts[p.type] = p.value;
40+
});
41+
42+
const fileName = `./ui5_${dateParts.year}-${dateParts.month}-${dateParts.day}_` +
43+
`${dateParts.hour}-${dateParts.minute}-${dateParts.second}.cpuprofile`;
44+
log.info(`\nSaving CPU profile to ${fileName}...`);
45+
await writeFile(fileName, JSON.stringify(profile));
46+
}
47+
48+
export async function stop() {
49+
if (!session) {
50+
return;
51+
}
52+
if (processSignals) {
53+
deregisterSigHooks(processSignals);
54+
processSignals = null;
55+
}
56+
const profile = await new Promise<Profiler.Profile | null>((resolve) => {
57+
session?.post("Profiler.stop", (err, {profile}) => {
58+
if (err) {
59+
resolve(null);
60+
} else {
61+
resolve(profile);
62+
}
63+
});
64+
session = null;
65+
});
66+
if (profile) {
67+
await writeProfile(profile);
68+
}
69+
}
70+
71+
function registerSigHooks() {
72+
function createListener(exitCode: number) {
73+
return function() {
74+
// Gracefully end profiling, then exit
75+
stop().then(() => {
76+
process.exit(exitCode);
77+
});
78+
};
79+
}
80+
81+
const processSignals: ProcessSignals = {
82+
"SIGHUP": createListener(128 + 1),
83+
"SIGINT": createListener(128 + 2),
84+
"SIGTERM": createListener(128 + 15),
85+
"SIGBREAK": createListener(128 + 21)
86+
};
87+
88+
for (const signal of Object.keys(processSignals)) {
89+
process.on(signal, processSignals[signal]);
90+
}
91+
return processSignals;
92+
}
93+
94+
function deregisterSigHooks(signals: ProcessSignals) {
95+
for (const signal of Object.keys(signals)) {
96+
process.removeListener(signal, signals[signal]);
97+
}
98+
}

0 commit comments

Comments
 (0)