@atlaspack/profiler 2.14.35-dev-compiled-hash-e5f8a1735.0 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/NativeProfiler.js +205 -0
- package/dist/index.js +3 -1
- package/lib/NativeProfiler.js +235 -0
- package/lib/index.js +7 -0
- package/lib/types/NativeProfiler.d.ts +7 -0
- package/lib/types/index.d.ts +2 -0
- package/package.json +8 -6
- package/src/NativeProfiler.ts +245 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +6 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/LICENSE +0 -201
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @atlaspack/profiler
|
|
2
2
|
|
|
3
|
+
## 2.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#892](https://github.com/atlassian-labs/atlaspack/pull/892) [`617a318`](https://github.com/atlassian-labs/atlaspack/commit/617a318ddc9419b38360257353fec50b9051ee13) Thanks [@marcins](https://github.com/marcins)! - Added a new `shouldProfileNative` option that provides a way to pause and connect a native profiler to Atlaspack.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`8eb84ee`](https://github.com/atlassian-labs/atlaspack/commit/8eb84ee61a42bfe87c58079b610802b07a6a13e4), [`73168c2`](https://github.com/atlassian-labs/atlaspack/commit/73168c275a5d9abff9907bcf536b340bca1ed5f0), [`617a318`](https://github.com/atlassian-labs/atlaspack/commit/617a318ddc9419b38360257353fec50b9051ee13)]:
|
|
12
|
+
- @atlaspack/types-internal@2.21.0
|
|
13
|
+
- @atlaspack/utils@3.2.0
|
|
14
|
+
- @atlaspack/logger@2.14.31
|
|
15
|
+
|
|
16
|
+
## 2.14.35
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Updated dependencies []:
|
|
21
|
+
- @atlaspack/types-internal@2.20.8
|
|
22
|
+
|
|
3
23
|
## 2.14.34
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = require("@atlaspack/utils");
|
|
7
|
+
const logger_1 = __importDefault(require("@atlaspack/logger"));
|
|
8
|
+
const readline_1 = __importDefault(require("readline"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const util_1 = require("util");
|
|
12
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
13
|
+
class NativeProfiler {
|
|
14
|
+
startProfiling(profilerType) {
|
|
15
|
+
const pid = process.pid;
|
|
16
|
+
const timeId = (0, utils_1.getTimeId)();
|
|
17
|
+
let filename;
|
|
18
|
+
let command;
|
|
19
|
+
logger_1.default.info({
|
|
20
|
+
origin: '@atlaspack/profiler',
|
|
21
|
+
message: 'Starting native profiling...',
|
|
22
|
+
});
|
|
23
|
+
if (profilerType === 'instruments') {
|
|
24
|
+
filename = `native-profile-${timeId}.trace`;
|
|
25
|
+
command = `xcrun xctrace record --template "CPU Profiler" --output ${filename} --attach ${pid}`;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
filename = `native-profile-${timeId}.json`;
|
|
29
|
+
command = `samply record --save-only --output ${filename} --pid ${pid}`;
|
|
30
|
+
}
|
|
31
|
+
// Display banner with PID and command
|
|
32
|
+
// Strip ANSI codes for length calculation
|
|
33
|
+
// eslint-disable-next-line no-control-regex
|
|
34
|
+
const stripAnsi = (str) => str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
35
|
+
const boxWidth = Math.max(60, stripAnsi(command).length + 6);
|
|
36
|
+
const title = 'Native Profiling';
|
|
37
|
+
const titlePadding = Math.floor((boxWidth - title.length - 2) / 2);
|
|
38
|
+
const isTTY = process.stdin.isTTY;
|
|
39
|
+
const maxWaitTime = 30; // seconds
|
|
40
|
+
const padLine = (content) => {
|
|
41
|
+
const contentLength = stripAnsi(content).length;
|
|
42
|
+
const padding = Math.max(0, boxWidth - contentLength - 2);
|
|
43
|
+
return (chalk_1.default.blue('│') +
|
|
44
|
+
' ' +
|
|
45
|
+
content +
|
|
46
|
+
' '.repeat(padding) +
|
|
47
|
+
' ' +
|
|
48
|
+
chalk_1.default.blue('│'));
|
|
49
|
+
};
|
|
50
|
+
// Make the command visually distinct and easy to copy
|
|
51
|
+
// Note: Hyperlinks can cause issues with commands (words become separate links)
|
|
52
|
+
// So we just make it visually prominent with colors
|
|
53
|
+
const makeCommandDisplay = (cmd) => {
|
|
54
|
+
return chalk_1.default.cyan.bold(cmd);
|
|
55
|
+
};
|
|
56
|
+
// Contextual message based on TTY
|
|
57
|
+
const continueMessage = isTTY
|
|
58
|
+
? 'Press Enter or start the profiler to continue'
|
|
59
|
+
: `Build will continue when profiler has started, or after ${maxWaitTime}s`;
|
|
60
|
+
const banner = [
|
|
61
|
+
'',
|
|
62
|
+
chalk_1.default.blue('┌' + '─'.repeat(boxWidth) + '┐'),
|
|
63
|
+
chalk_1.default.blue('│') +
|
|
64
|
+
' '.repeat(titlePadding) +
|
|
65
|
+
chalk_1.default.blue.bold(title) +
|
|
66
|
+
' '.repeat(boxWidth - title.length - titlePadding) +
|
|
67
|
+
chalk_1.default.blue('│'),
|
|
68
|
+
chalk_1.default.blue('├' + '─'.repeat(boxWidth) + '┤'),
|
|
69
|
+
padLine(`${chalk_1.default.gray('PID:')} ${chalk_1.default.white.bold(String(pid))}`),
|
|
70
|
+
padLine(''),
|
|
71
|
+
padLine(chalk_1.default.gray('Command:')),
|
|
72
|
+
padLine(makeCommandDisplay(command)),
|
|
73
|
+
padLine(''),
|
|
74
|
+
padLine(chalk_1.default.gray('Run the command above to start profiling.')),
|
|
75
|
+
padLine(chalk_1.default.gray(continueMessage)),
|
|
76
|
+
chalk_1.default.blue('└' + '─'.repeat(boxWidth) + '┘'),
|
|
77
|
+
'',
|
|
78
|
+
].join('\n');
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.log(banner);
|
|
81
|
+
// In both interactive and non-interactive mode, detect when profiler is running
|
|
82
|
+
// In interactive mode, also allow user to press Enter to continue
|
|
83
|
+
if (!process.stdin.isTTY) {
|
|
84
|
+
return this.waitForProfiler(profilerType, pid);
|
|
85
|
+
}
|
|
86
|
+
// Interactive mode: wait for either user to press Enter OR profiler to be detected
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
let resolved = false;
|
|
89
|
+
const doResolve = () => {
|
|
90
|
+
if (resolved)
|
|
91
|
+
return;
|
|
92
|
+
resolved = true;
|
|
93
|
+
logger_1.default.info({
|
|
94
|
+
origin: '@atlaspack/profiler',
|
|
95
|
+
message: 'Native profiling setup complete',
|
|
96
|
+
});
|
|
97
|
+
resolve();
|
|
98
|
+
};
|
|
99
|
+
const rl = readline_1.default.createInterface({
|
|
100
|
+
input: process.stdin,
|
|
101
|
+
output: process.stdout,
|
|
102
|
+
});
|
|
103
|
+
// User presses Enter
|
|
104
|
+
rl.on('line', () => {
|
|
105
|
+
rl.close();
|
|
106
|
+
doResolve();
|
|
107
|
+
});
|
|
108
|
+
// Also poll for profiler in the background
|
|
109
|
+
this.pollForProfiler(profilerType, pid, doResolve);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
waitForProfiler(profilerType, pid) {
|
|
113
|
+
logger_1.default.info({
|
|
114
|
+
origin: '@atlaspack/profiler',
|
|
115
|
+
message: 'Non-interactive mode: waiting for profiler to attach...',
|
|
116
|
+
});
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
this.pollForProfiler(profilerType, pid, () => {
|
|
119
|
+
logger_1.default.info({
|
|
120
|
+
origin: '@atlaspack/profiler',
|
|
121
|
+
message: 'Native profiling setup complete',
|
|
122
|
+
});
|
|
123
|
+
resolve();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async pollForProfiler(profilerType, pid, onDetected) {
|
|
128
|
+
const maxAttempts = 60; // 60 attempts * 500ms = 30 seconds max
|
|
129
|
+
const pollInterval = 500; // 500ms between checks
|
|
130
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
131
|
+
const isRunning = await this.checkProfilerRunning(profilerType, pid);
|
|
132
|
+
if (isRunning) {
|
|
133
|
+
// Instruments takes longer to start up (~5s), samply needs ~1s
|
|
134
|
+
const waitTime = profilerType === 'instruments' ? 5000 : 1000;
|
|
135
|
+
logger_1.default.info({
|
|
136
|
+
origin: '@atlaspack/profiler',
|
|
137
|
+
message: `Profiler detected, waiting ${waitTime}ms before continuing...`,
|
|
138
|
+
});
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
140
|
+
onDetected();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
144
|
+
}
|
|
145
|
+
// If we couldn't detect the profiler after 30 seconds, log a warning and continue anyway
|
|
146
|
+
logger_1.default.warn({
|
|
147
|
+
origin: '@atlaspack/profiler',
|
|
148
|
+
message: 'Could not detect profiler after 30 seconds, continuing anyway...',
|
|
149
|
+
});
|
|
150
|
+
onDetected();
|
|
151
|
+
}
|
|
152
|
+
async checkProfilerRunning(profilerType, pid) {
|
|
153
|
+
try {
|
|
154
|
+
// Get all processes and filter in JavaScript
|
|
155
|
+
const { stdout } = await execAsync('ps aux');
|
|
156
|
+
const lines = stdout.split('\n').filter((line) => line.trim().length > 0);
|
|
157
|
+
// Use word boundaries to match the PID as a complete number
|
|
158
|
+
const pidRegex = new RegExp(`\\b${pid}\\b`);
|
|
159
|
+
// Determine the profiler process name to look for
|
|
160
|
+
const profilerName = profilerType === 'instruments' ? 'xctrace' : 'samply';
|
|
161
|
+
for (const line of lines) {
|
|
162
|
+
const lowerLine = line.toLowerCase();
|
|
163
|
+
// Skip lines that are part of our own process checking (avoid false positives)
|
|
164
|
+
// Skip lines containing "ps aux" or "grep" to avoid matching our own commands
|
|
165
|
+
if (lowerLine.includes('ps aux') || lowerLine.includes(' grep ')) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Skip our own process (the Atlaspack process itself)
|
|
169
|
+
// The PID column is the second field in ps aux output
|
|
170
|
+
const fields = line.trim().split(/\s+/);
|
|
171
|
+
if (fields.length >= 2 && fields[1] === String(pid)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
// Check if this line contains the profiler name as a command
|
|
175
|
+
const profilerRegex = new RegExp(`\\b${profilerName}\\b`);
|
|
176
|
+
if (!profilerRegex.test(lowerLine)) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
// Now check if our PID appears in the command arguments (not in the PID column)
|
|
180
|
+
// The PID should appear after the profiler command, typically as --pid <pid> or --attach <pid>
|
|
181
|
+
// We need to check the command portion, which starts around column 11 in ps aux
|
|
182
|
+
// For safety, check if PID appears after the profiler name in the line
|
|
183
|
+
const profilerIndex = lowerLine.indexOf(profilerName);
|
|
184
|
+
if (profilerIndex === -1) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
// Check if PID appears in the command portion (after the profiler name)
|
|
188
|
+
const commandPortion = line.substring(profilerIndex);
|
|
189
|
+
if (pidRegex.test(commandPortion)) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
// If the command fails, log and return false
|
|
197
|
+
logger_1.default.warn({
|
|
198
|
+
origin: '@atlaspack/profiler',
|
|
199
|
+
message: `Error checking profiler status: ${error.message}`,
|
|
200
|
+
});
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.default = NativeProfiler;
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PluginTracer = exports.tracer = exports.Trace = exports.SamplingProfiler = void 0;
|
|
6
|
+
exports.NativeProfiler = exports.PluginTracer = exports.tracer = exports.Trace = exports.SamplingProfiler = void 0;
|
|
7
7
|
var SamplingProfiler_1 = require("./SamplingProfiler");
|
|
8
8
|
Object.defineProperty(exports, "SamplingProfiler", { enumerable: true, get: function () { return __importDefault(SamplingProfiler_1).default; } });
|
|
9
9
|
var Trace_1 = require("./Trace");
|
|
@@ -11,3 +11,5 @@ Object.defineProperty(exports, "Trace", { enumerable: true, get: function () { r
|
|
|
11
11
|
var Tracer_1 = require("./Tracer");
|
|
12
12
|
Object.defineProperty(exports, "tracer", { enumerable: true, get: function () { return Tracer_1.tracer; } });
|
|
13
13
|
Object.defineProperty(exports, "PluginTracer", { enumerable: true, get: function () { return Tracer_1.PluginTracer; } });
|
|
14
|
+
var NativeProfiler_1 = require("./NativeProfiler");
|
|
15
|
+
Object.defineProperty(exports, "NativeProfiler", { enumerable: true, get: function () { return __importDefault(NativeProfiler_1).default; } });
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
function _utils() {
|
|
8
|
+
const data = require("@atlaspack/utils");
|
|
9
|
+
_utils = function () {
|
|
10
|
+
return data;
|
|
11
|
+
};
|
|
12
|
+
return data;
|
|
13
|
+
}
|
|
14
|
+
function _logger() {
|
|
15
|
+
const data = _interopRequireDefault(require("@atlaspack/logger"));
|
|
16
|
+
_logger = function () {
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
return data;
|
|
20
|
+
}
|
|
21
|
+
function _readline() {
|
|
22
|
+
const data = _interopRequireDefault(require("readline"));
|
|
23
|
+
_readline = function () {
|
|
24
|
+
return data;
|
|
25
|
+
};
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
function _chalk() {
|
|
29
|
+
const data = _interopRequireDefault(require("chalk"));
|
|
30
|
+
_chalk = function () {
|
|
31
|
+
return data;
|
|
32
|
+
};
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
function _child_process() {
|
|
36
|
+
const data = require("child_process");
|
|
37
|
+
_child_process = function () {
|
|
38
|
+
return data;
|
|
39
|
+
};
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
function _util() {
|
|
43
|
+
const data = require("util");
|
|
44
|
+
_util = function () {
|
|
45
|
+
return data;
|
|
46
|
+
};
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
50
|
+
const execAsync = (0, _util().promisify)(_child_process().exec);
|
|
51
|
+
class NativeProfiler {
|
|
52
|
+
startProfiling(profilerType) {
|
|
53
|
+
const pid = process.pid;
|
|
54
|
+
const timeId = (0, _utils().getTimeId)();
|
|
55
|
+
let filename;
|
|
56
|
+
let command;
|
|
57
|
+
_logger().default.info({
|
|
58
|
+
origin: '@atlaspack/profiler',
|
|
59
|
+
message: 'Starting native profiling...'
|
|
60
|
+
});
|
|
61
|
+
if (profilerType === 'instruments') {
|
|
62
|
+
filename = `native-profile-${timeId}.trace`;
|
|
63
|
+
command = `xcrun xctrace record --template "CPU Profiler" --output ${filename} --attach ${pid}`;
|
|
64
|
+
} else {
|
|
65
|
+
filename = `native-profile-${timeId}.json`;
|
|
66
|
+
command = `samply record --save-only --output ${filename} --pid ${pid}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Display banner with PID and command
|
|
70
|
+
// Strip ANSI codes for length calculation
|
|
71
|
+
// eslint-disable-next-line no-control-regex
|
|
72
|
+
const stripAnsi = str => str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
73
|
+
const boxWidth = Math.max(60, stripAnsi(command).length + 6);
|
|
74
|
+
const title = 'Native Profiling';
|
|
75
|
+
const titlePadding = Math.floor((boxWidth - title.length - 2) / 2);
|
|
76
|
+
const isTTY = process.stdin.isTTY;
|
|
77
|
+
const maxWaitTime = 30; // seconds
|
|
78
|
+
|
|
79
|
+
const padLine = content => {
|
|
80
|
+
const contentLength = stripAnsi(content).length;
|
|
81
|
+
const padding = Math.max(0, boxWidth - contentLength - 2);
|
|
82
|
+
return _chalk().default.blue('│') + ' ' + content + ' '.repeat(padding) + ' ' + _chalk().default.blue('│');
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Make the command visually distinct and easy to copy
|
|
86
|
+
// Note: Hyperlinks can cause issues with commands (words become separate links)
|
|
87
|
+
// So we just make it visually prominent with colors
|
|
88
|
+
const makeCommandDisplay = cmd => {
|
|
89
|
+
return _chalk().default.cyan.bold(cmd);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Contextual message based on TTY
|
|
93
|
+
const continueMessage = isTTY ? 'Press Enter or start the profiler to continue' : `Build will continue when profiler has started, or after ${maxWaitTime}s`;
|
|
94
|
+
const banner = ['', _chalk().default.blue('┌' + '─'.repeat(boxWidth) + '┐'), _chalk().default.blue('│') + ' '.repeat(titlePadding) + _chalk().default.blue.bold(title) + ' '.repeat(boxWidth - title.length - titlePadding) + _chalk().default.blue('│'), _chalk().default.blue('├' + '─'.repeat(boxWidth) + '┤'), padLine(`${_chalk().default.gray('PID:')} ${_chalk().default.white.bold(String(pid))}`), padLine(''), padLine(_chalk().default.gray('Command:')), padLine(makeCommandDisplay(command)), padLine(''), padLine(_chalk().default.gray('Run the command above to start profiling.')), padLine(_chalk().default.gray(continueMessage)), _chalk().default.blue('└' + '─'.repeat(boxWidth) + '┘'), ''].join('\n');
|
|
95
|
+
|
|
96
|
+
// eslint-disable-next-line no-console
|
|
97
|
+
console.log(banner);
|
|
98
|
+
|
|
99
|
+
// In both interactive and non-interactive mode, detect when profiler is running
|
|
100
|
+
// In interactive mode, also allow user to press Enter to continue
|
|
101
|
+
if (!process.stdin.isTTY) {
|
|
102
|
+
return this.waitForProfiler(profilerType, pid);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Interactive mode: wait for either user to press Enter OR profiler to be detected
|
|
106
|
+
return new Promise(resolve => {
|
|
107
|
+
let resolved = false;
|
|
108
|
+
const doResolve = () => {
|
|
109
|
+
if (resolved) return;
|
|
110
|
+
resolved = true;
|
|
111
|
+
_logger().default.info({
|
|
112
|
+
origin: '@atlaspack/profiler',
|
|
113
|
+
message: 'Native profiling setup complete'
|
|
114
|
+
});
|
|
115
|
+
resolve();
|
|
116
|
+
};
|
|
117
|
+
const rl = _readline().default.createInterface({
|
|
118
|
+
input: process.stdin,
|
|
119
|
+
output: process.stdout
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// User presses Enter
|
|
123
|
+
rl.on('line', () => {
|
|
124
|
+
rl.close();
|
|
125
|
+
doResolve();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Also poll for profiler in the background
|
|
129
|
+
this.pollForProfiler(profilerType, pid, doResolve);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
waitForProfiler(profilerType, pid) {
|
|
133
|
+
_logger().default.info({
|
|
134
|
+
origin: '@atlaspack/profiler',
|
|
135
|
+
message: 'Non-interactive mode: waiting for profiler to attach...'
|
|
136
|
+
});
|
|
137
|
+
return new Promise(resolve => {
|
|
138
|
+
this.pollForProfiler(profilerType, pid, () => {
|
|
139
|
+
_logger().default.info({
|
|
140
|
+
origin: '@atlaspack/profiler',
|
|
141
|
+
message: 'Native profiling setup complete'
|
|
142
|
+
});
|
|
143
|
+
resolve();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
async pollForProfiler(profilerType, pid, onDetected) {
|
|
148
|
+
const maxAttempts = 60; // 60 attempts * 500ms = 30 seconds max
|
|
149
|
+
const pollInterval = 500; // 500ms between checks
|
|
150
|
+
|
|
151
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
152
|
+
const isRunning = await this.checkProfilerRunning(profilerType, pid);
|
|
153
|
+
if (isRunning) {
|
|
154
|
+
// Instruments takes longer to start up (~5s), samply needs ~1s
|
|
155
|
+
const waitTime = profilerType === 'instruments' ? 5000 : 1000;
|
|
156
|
+
_logger().default.info({
|
|
157
|
+
origin: '@atlaspack/profiler',
|
|
158
|
+
message: `Profiler detected, waiting ${waitTime}ms before continuing...`
|
|
159
|
+
});
|
|
160
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
161
|
+
onDetected();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// If we couldn't detect the profiler after 30 seconds, log a warning and continue anyway
|
|
168
|
+
_logger().default.warn({
|
|
169
|
+
origin: '@atlaspack/profiler',
|
|
170
|
+
message: 'Could not detect profiler after 30 seconds, continuing anyway...'
|
|
171
|
+
});
|
|
172
|
+
onDetected();
|
|
173
|
+
}
|
|
174
|
+
async checkProfilerRunning(profilerType, pid) {
|
|
175
|
+
try {
|
|
176
|
+
// Get all processes and filter in JavaScript
|
|
177
|
+
const {
|
|
178
|
+
stdout
|
|
179
|
+
} = await execAsync('ps aux');
|
|
180
|
+
const lines = stdout.split('\n').filter(line => line.trim().length > 0);
|
|
181
|
+
|
|
182
|
+
// Use word boundaries to match the PID as a complete number
|
|
183
|
+
const pidRegex = new RegExp(`\\b${pid}\\b`);
|
|
184
|
+
|
|
185
|
+
// Determine the profiler process name to look for
|
|
186
|
+
const profilerName = profilerType === 'instruments' ? 'xctrace' : 'samply';
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
const lowerLine = line.toLowerCase();
|
|
189
|
+
|
|
190
|
+
// Skip lines that are part of our own process checking (avoid false positives)
|
|
191
|
+
// Skip lines containing "ps aux" or "grep" to avoid matching our own commands
|
|
192
|
+
if (lowerLine.includes('ps aux') || lowerLine.includes(' grep ')) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Skip our own process (the Atlaspack process itself)
|
|
197
|
+
// The PID column is the second field in ps aux output
|
|
198
|
+
const fields = line.trim().split(/\s+/);
|
|
199
|
+
if (fields.length >= 2 && fields[1] === String(pid)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check if this line contains the profiler name as a command
|
|
204
|
+
const profilerRegex = new RegExp(`\\b${profilerName}\\b`);
|
|
205
|
+
if (!profilerRegex.test(lowerLine)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Now check if our PID appears in the command arguments (not in the PID column)
|
|
210
|
+
// The PID should appear after the profiler command, typically as --pid <pid> or --attach <pid>
|
|
211
|
+
// We need to check the command portion, which starts around column 11 in ps aux
|
|
212
|
+
// For safety, check if PID appears after the profiler name in the line
|
|
213
|
+
const profilerIndex = lowerLine.indexOf(profilerName);
|
|
214
|
+
if (profilerIndex === -1) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check if PID appears in the command portion (after the profiler name)
|
|
219
|
+
const commandPortion = line.substring(profilerIndex);
|
|
220
|
+
if (pidRegex.test(commandPortion)) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// If the command fails, log and return false
|
|
227
|
+
_logger().default.warn({
|
|
228
|
+
origin: '@atlaspack/profiler',
|
|
229
|
+
message: `Error checking profiler status: ${error.message}`
|
|
230
|
+
});
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.default = NativeProfiler;
|
package/lib/index.js
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
Object.defineProperty(exports, "NativeProfiler", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _NativeProfiler.default;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
6
12
|
Object.defineProperty(exports, "PluginTracer", {
|
|
7
13
|
enumerable: true,
|
|
8
14
|
get: function () {
|
|
@@ -30,4 +36,5 @@ Object.defineProperty(exports, "tracer", {
|
|
|
30
36
|
var _SamplingProfiler = _interopRequireDefault(require("./SamplingProfiler"));
|
|
31
37
|
var _Trace = _interopRequireDefault(require("./Trace"));
|
|
32
38
|
var _Tracer = require("./Tracer");
|
|
39
|
+
var _NativeProfiler = _interopRequireDefault(require("./NativeProfiler"));
|
|
33
40
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
package/lib/types/index.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ export { default as SamplingProfiler } from './SamplingProfiler';
|
|
|
2
2
|
export { default as Trace } from './Trace';
|
|
3
3
|
export { tracer, PluginTracer } from './Tracer';
|
|
4
4
|
export type { TraceMeasurement, TraceMeasurementData } from './types';
|
|
5
|
+
export { default as NativeProfiler } from './NativeProfiler';
|
|
6
|
+
export type { NativeProfilerType } from './NativeProfiler';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaspack/profiler",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.0",
|
|
4
4
|
"description": "Blazing fast, zero configuration web application bundler",
|
|
5
5
|
"license": "(MIT OR Apache-2.0)",
|
|
6
6
|
"publishConfig": {
|
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
"build:lib": "gulp build --gulpfile ../../../gulpfile.js --cwd ."
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@atlaspack/diagnostic": "2.14.
|
|
24
|
-
"@atlaspack/events": "2.14.
|
|
25
|
-
"@atlaspack/types-internal": "2.
|
|
23
|
+
"@atlaspack/diagnostic": "2.14.4",
|
|
24
|
+
"@atlaspack/events": "2.14.4",
|
|
25
|
+
"@atlaspack/types-internal": "2.21.0",
|
|
26
|
+
"@atlaspack/logger": "2.14.31",
|
|
27
|
+
"@atlaspack/utils": "3.2.0",
|
|
28
|
+
"chalk": "^4.1.2",
|
|
26
29
|
"chrome-trace-event": "^1.0.2"
|
|
27
30
|
},
|
|
28
|
-
"type": "commonjs"
|
|
29
|
-
"gitHead": "e5f8a173505611c1fafafd6e7dddb2f6b483f67c"
|
|
31
|
+
"type": "commonjs"
|
|
30
32
|
}
|