@parcel/profiler 2.8.4-nightly.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/LICENSE +21 -0
- package/lib/SamplingProfiler.js +80 -0
- package/lib/Trace.js +131 -0
- package/lib/Tracer.js +181 -0
- package/lib/index.js +37 -0
- package/lib/types.js +1 -0
- package/package.json +28 -0
- package/src/SamplingProfiler.js +93 -0
- package/src/Trace.js +125 -0
- package/src/Tracer.js +163 -0
- package/src/index.js +5 -0
- package/src/types.js +10 -0
- package/test/Tracer.test.js +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-present Devon Govett
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
|
|
8
|
+
function _assert() {
|
|
9
|
+
const data = _interopRequireDefault(require("assert"));
|
|
10
|
+
|
|
11
|
+
_assert = function () {
|
|
12
|
+
return data;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function _diagnostic() {
|
|
19
|
+
const data = _interopRequireDefault(require("@parcel/diagnostic"));
|
|
20
|
+
|
|
21
|
+
_diagnostic = function () {
|
|
22
|
+
return data;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
29
|
+
|
|
30
|
+
class SamplingProfiler {
|
|
31
|
+
startProfiling() {
|
|
32
|
+
let inspector;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
inspector = require('inspector');
|
|
36
|
+
} catch (err) {
|
|
37
|
+
throw new (_diagnostic().default)({
|
|
38
|
+
diagnostic: {
|
|
39
|
+
message: `The inspector module isn't available`,
|
|
40
|
+
origin: '@parcel/workers',
|
|
41
|
+
hints: ['Disable build profiling']
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.session = new inspector.Session();
|
|
47
|
+
this.session.connect();
|
|
48
|
+
return Promise.all([this.sendCommand('Profiler.setSamplingInterval', {
|
|
49
|
+
interval: 100
|
|
50
|
+
}), this.sendCommand('Profiler.enable'), this.sendCommand('Profiler.start')]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
sendCommand(method, params) {
|
|
54
|
+
(0, _assert().default)(this.session != null);
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
this.session.post(method, params, (err, params) => {
|
|
57
|
+
if (err == null) {
|
|
58
|
+
resolve(params);
|
|
59
|
+
} else {
|
|
60
|
+
reject(err);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
destroy() {
|
|
67
|
+
if (this.session != null) {
|
|
68
|
+
this.session.disconnect();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async stopProfiling() {
|
|
73
|
+
let res = await this.sendCommand('Profiler.stop');
|
|
74
|
+
this.destroy();
|
|
75
|
+
return res.profile;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
exports.default = SamplingProfiler;
|
package/lib/Trace.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
|
|
8
|
+
function _chromeTraceEvent() {
|
|
9
|
+
const data = require("chrome-trace-event");
|
|
10
|
+
|
|
11
|
+
_chromeTraceEvent = function () {
|
|
12
|
+
return data;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class Trace {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.tracer = new (_chromeTraceEvent().Tracer)();
|
|
21
|
+
this.tid = 0;
|
|
22
|
+
this.eventId = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getEventId() {
|
|
26
|
+
return this.eventId++;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
init(ts) {
|
|
30
|
+
this.tracer.instantEvent({
|
|
31
|
+
name: 'TracingStartedInPage',
|
|
32
|
+
id: this.getEventId(),
|
|
33
|
+
ts,
|
|
34
|
+
cat: ['disabled-by-default-devtools.timeline'],
|
|
35
|
+
args: {
|
|
36
|
+
data: {
|
|
37
|
+
sessionId: '-1',
|
|
38
|
+
page: '0xfff',
|
|
39
|
+
frames: [{
|
|
40
|
+
frame: '0xfff',
|
|
41
|
+
url: 'parcel',
|
|
42
|
+
name: ''
|
|
43
|
+
}]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
this.tracer.instantEvent({
|
|
48
|
+
name: 'TracingStartedInBrowser',
|
|
49
|
+
id: this.getEventId(),
|
|
50
|
+
ts,
|
|
51
|
+
cat: ['disabled-by-default-devtools.timeline'],
|
|
52
|
+
args: {
|
|
53
|
+
data: {
|
|
54
|
+
sessionId: '-1'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
addCPUProfile(name, profile) {
|
|
61
|
+
if (this.eventId === 0) {
|
|
62
|
+
this.init(profile.startTime);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const trace = this.tracer;
|
|
66
|
+
const tid = this.tid;
|
|
67
|
+
this.tid++;
|
|
68
|
+
const cpuStartTime = profile.startTime;
|
|
69
|
+
const cpuEndTime = profile.endTime;
|
|
70
|
+
trace.instantEvent({
|
|
71
|
+
tid,
|
|
72
|
+
id: this.getEventId(),
|
|
73
|
+
cat: ['toplevel'],
|
|
74
|
+
name: 'TaskQueueManager::ProcessTaskFromWorkQueue',
|
|
75
|
+
args: {
|
|
76
|
+
src_file: '../../ipc/ipc_moji_bootstrap.cc',
|
|
77
|
+
src_func: 'Accept'
|
|
78
|
+
},
|
|
79
|
+
ts: cpuStartTime
|
|
80
|
+
});
|
|
81
|
+
trace.completeEvent({
|
|
82
|
+
tid,
|
|
83
|
+
name: 'EvaluateScript',
|
|
84
|
+
id: this.getEventId(),
|
|
85
|
+
cat: ['devtools.timeline'],
|
|
86
|
+
ts: cpuStartTime,
|
|
87
|
+
dur: cpuEndTime - cpuStartTime,
|
|
88
|
+
args: {
|
|
89
|
+
data: {
|
|
90
|
+
url: 'parcel',
|
|
91
|
+
lineNumber: 1,
|
|
92
|
+
columnNumber: 1,
|
|
93
|
+
frame: '0xFFF'
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
trace.instantEvent({
|
|
98
|
+
tid,
|
|
99
|
+
ts: 0,
|
|
100
|
+
ph: 'M',
|
|
101
|
+
cat: ['__metadata'],
|
|
102
|
+
name: 'thread_name',
|
|
103
|
+
args: {
|
|
104
|
+
name
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
trace.instantEvent({
|
|
108
|
+
tid,
|
|
109
|
+
name: 'CpuProfile',
|
|
110
|
+
id: this.getEventId(),
|
|
111
|
+
cat: ['disabled-by-default-devtools.timeline'],
|
|
112
|
+
ts: cpuEndTime,
|
|
113
|
+
args: {
|
|
114
|
+
data: {
|
|
115
|
+
cpuProfile: profile
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
pipe(writable) {
|
|
122
|
+
return this.tracer.pipe(writable);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
flush() {
|
|
126
|
+
this.tracer.push(null);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
exports.default = Trace;
|
package/lib/Tracer.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.tracer = exports.default = exports.PluginTracer = void 0;
|
|
7
|
+
|
|
8
|
+
function _events() {
|
|
9
|
+
const data = require("@parcel/events");
|
|
10
|
+
|
|
11
|
+
_events = function () {
|
|
12
|
+
return data;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function _perf_hooks() {
|
|
19
|
+
const data = require("perf_hooks");
|
|
20
|
+
|
|
21
|
+
_perf_hooks = function () {
|
|
22
|
+
return data;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// $FlowFixMe
|
|
29
|
+
let tid;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
tid = require('worker_threads').threadId;
|
|
33
|
+
} catch {
|
|
34
|
+
tid = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const performance = _perf_hooks().performance;
|
|
38
|
+
|
|
39
|
+
const pid = process.pid;
|
|
40
|
+
|
|
41
|
+
class TraceMeasurement {
|
|
42
|
+
#active
|
|
43
|
+
/* boolean */
|
|
44
|
+
= true;
|
|
45
|
+
#name
|
|
46
|
+
/* string */
|
|
47
|
+
;
|
|
48
|
+
#pid
|
|
49
|
+
/* number */
|
|
50
|
+
;
|
|
51
|
+
#tid
|
|
52
|
+
/* number */
|
|
53
|
+
;
|
|
54
|
+
#start
|
|
55
|
+
/* number */
|
|
56
|
+
;
|
|
57
|
+
#data
|
|
58
|
+
/* any */
|
|
59
|
+
;
|
|
60
|
+
|
|
61
|
+
constructor(tracer, name, pid, tid, data) {
|
|
62
|
+
this.#name = name;
|
|
63
|
+
this.#pid = pid;
|
|
64
|
+
this.#tid = tid;
|
|
65
|
+
this.#start = performance.now();
|
|
66
|
+
this.#data = data;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
end() {
|
|
70
|
+
if (!this.#active) return;
|
|
71
|
+
const duration = performance.now() - this.#start;
|
|
72
|
+
tracer.trace({
|
|
73
|
+
type: 'trace',
|
|
74
|
+
name: this.#name,
|
|
75
|
+
pid: this.#pid,
|
|
76
|
+
tid: this.#tid,
|
|
77
|
+
duration,
|
|
78
|
+
ts: this.#start,
|
|
79
|
+
...this.#data
|
|
80
|
+
});
|
|
81
|
+
this.#active = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class Tracer {
|
|
87
|
+
#traceEmitter
|
|
88
|
+
/* ValueEmitter<TraceEvent> */
|
|
89
|
+
= new (_events().ValueEmitter)();
|
|
90
|
+
#enabled
|
|
91
|
+
/* boolean */
|
|
92
|
+
= false;
|
|
93
|
+
|
|
94
|
+
onTrace(cb) {
|
|
95
|
+
return this.#traceEmitter.addListener(cb);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async wrap(name, fn) {
|
|
99
|
+
let measurement = this.createMeasurement(name);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await fn();
|
|
103
|
+
} finally {
|
|
104
|
+
measurement && measurement.end();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
createMeasurement(name, category = 'Core', argumentName, otherArgs) {
|
|
109
|
+
if (!this.enabled) return null; // We create `args` in a fairly verbose way to avoid object
|
|
110
|
+
// allocation where not required.
|
|
111
|
+
|
|
112
|
+
let args;
|
|
113
|
+
|
|
114
|
+
if (typeof argumentName === 'string') {
|
|
115
|
+
args = {
|
|
116
|
+
name: argumentName
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (typeof otherArgs === 'object') {
|
|
121
|
+
if (typeof args == 'undefined') {
|
|
122
|
+
args = {};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const [k, v] of Object.entries(otherArgs)) {
|
|
126
|
+
args[k] = v;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const data = {
|
|
131
|
+
categories: [category],
|
|
132
|
+
args
|
|
133
|
+
};
|
|
134
|
+
return new TraceMeasurement(this, name, pid, tid, data);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get enabled() {
|
|
138
|
+
return this.#enabled;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
enable() {
|
|
142
|
+
this.#enabled = true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
disable() {
|
|
146
|
+
this.#enabled = false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
trace(event) {
|
|
150
|
+
if (!this.#enabled) return;
|
|
151
|
+
this.#traceEmitter.emit(event);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
exports.default = Tracer;
|
|
157
|
+
const tracer = new Tracer();
|
|
158
|
+
exports.tracer = tracer;
|
|
159
|
+
|
|
160
|
+
class PluginTracer {
|
|
161
|
+
/** @private */
|
|
162
|
+
|
|
163
|
+
/** @private */
|
|
164
|
+
|
|
165
|
+
/** @private */
|
|
166
|
+
constructor(opts) {
|
|
167
|
+
this.origin = opts.origin;
|
|
168
|
+
this.category = opts.category;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
get enabled() {
|
|
172
|
+
return tracer.enabled;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
createMeasurement(name, category, argumentName, otherArgs) {
|
|
176
|
+
return tracer.createMeasurement(name, `${this.category}:${this.origin}${typeof category === 'string' ? `:${category}` : ''}`, argumentName, otherArgs);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
exports.PluginTracer = PluginTracer;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "PluginTracer", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _Tracer.PluginTracer;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "SamplingProfiler", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _SamplingProfiler.default;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "Trace", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _Trace.default;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(exports, "tracer", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _Tracer.tracer;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
var _SamplingProfiler = _interopRequireDefault(require("./SamplingProfiler"));
|
|
32
|
+
|
|
33
|
+
var _Trace = _interopRequireDefault(require("./Trace"));
|
|
34
|
+
|
|
35
|
+
var _Tracer = require("./Tracer");
|
|
36
|
+
|
|
37
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@parcel/profiler",
|
|
3
|
+
"version": "2.8.4-nightly.0+7b79c6d",
|
|
4
|
+
"description": "Blazing fast, zero configuration web application bundler",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"funding": {
|
|
10
|
+
"type": "opencollective",
|
|
11
|
+
"url": "https://opencollective.com/parcel"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/parcel-bundler/parcel.git"
|
|
16
|
+
},
|
|
17
|
+
"main": "lib/index.js",
|
|
18
|
+
"source": "src/index.js",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">= 12.0.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@parcel/diagnostic": "2.8.4-nightly.0+7b79c6d",
|
|
24
|
+
"@parcel/events": "2.8.4-nightly.0+7b79c6d",
|
|
25
|
+
"chrome-trace-event": "^1.0.2"
|
|
26
|
+
},
|
|
27
|
+
"gitHead": "7b79c6d69ffabef89810a8db61e9abdeb70d6990"
|
|
28
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type {Session} from 'inspector';
|
|
3
|
+
import invariant from 'assert';
|
|
4
|
+
import ThrowableDiagnostic from '@parcel/diagnostic';
|
|
5
|
+
|
|
6
|
+
// https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-Profile
|
|
7
|
+
export type Profile = {|
|
|
8
|
+
nodes: Array<ProfileNode>,
|
|
9
|
+
startTime: number,
|
|
10
|
+
endTime: number,
|
|
11
|
+
samples?: Array<number>,
|
|
12
|
+
timeDeltas?: Array<number>,
|
|
13
|
+
|};
|
|
14
|
+
|
|
15
|
+
// https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ProfileNode
|
|
16
|
+
type ProfileNode = {|
|
|
17
|
+
id: number,
|
|
18
|
+
callFrame: CallFrame,
|
|
19
|
+
hitCount?: number,
|
|
20
|
+
children?: Array<number>,
|
|
21
|
+
deoptReason?: string,
|
|
22
|
+
positionTicks?: PositionTickInfo,
|
|
23
|
+
|};
|
|
24
|
+
|
|
25
|
+
// https://chromedevtools.github.io/devtools-protocol/tot/Runtime#type-CallFrame
|
|
26
|
+
type CallFrame = {|
|
|
27
|
+
functionName: string,
|
|
28
|
+
scriptId: string,
|
|
29
|
+
url: string,
|
|
30
|
+
lineNumber: string,
|
|
31
|
+
columnNumber: string,
|
|
32
|
+
|};
|
|
33
|
+
|
|
34
|
+
// https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-PositionTickInfo
|
|
35
|
+
type PositionTickInfo = {|
|
|
36
|
+
line: number,
|
|
37
|
+
ticks: number,
|
|
38
|
+
|};
|
|
39
|
+
|
|
40
|
+
export default class SamplingProfiler {
|
|
41
|
+
session: Session;
|
|
42
|
+
|
|
43
|
+
startProfiling(): Promise<mixed> {
|
|
44
|
+
let inspector;
|
|
45
|
+
try {
|
|
46
|
+
inspector = require('inspector');
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new ThrowableDiagnostic({
|
|
49
|
+
diagnostic: {
|
|
50
|
+
message: `The inspector module isn't available`,
|
|
51
|
+
origin: '@parcel/workers',
|
|
52
|
+
hints: ['Disable build profiling'],
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.session = new inspector.Session();
|
|
58
|
+
this.session.connect();
|
|
59
|
+
|
|
60
|
+
return Promise.all([
|
|
61
|
+
this.sendCommand('Profiler.setSamplingInterval', {
|
|
62
|
+
interval: 100,
|
|
63
|
+
}),
|
|
64
|
+
this.sendCommand('Profiler.enable'),
|
|
65
|
+
this.sendCommand('Profiler.start'),
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
sendCommand(method: string, params: mixed): Promise<{profile: Profile, ...}> {
|
|
70
|
+
invariant(this.session != null);
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
this.session.post(method, params, (err, params) => {
|
|
73
|
+
if (err == null) {
|
|
74
|
+
resolve(params);
|
|
75
|
+
} else {
|
|
76
|
+
reject(err);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
destroy() {
|
|
83
|
+
if (this.session != null) {
|
|
84
|
+
this.session.disconnect();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async stopProfiling(): Promise<Profile> {
|
|
89
|
+
let res = await this.sendCommand('Profiler.stop');
|
|
90
|
+
this.destroy();
|
|
91
|
+
return res.profile;
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/Trace.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type {Profile} from './SamplingProfiler';
|
|
3
|
+
import type {Writable} from 'stream';
|
|
4
|
+
import {Tracer} from 'chrome-trace-event';
|
|
5
|
+
|
|
6
|
+
export default class Trace {
|
|
7
|
+
tracer: Tracer;
|
|
8
|
+
tid: number;
|
|
9
|
+
eventId: number;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.tracer = new Tracer();
|
|
13
|
+
this.tid = 0;
|
|
14
|
+
this.eventId = 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getEventId(): number {
|
|
18
|
+
return this.eventId++;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
init(ts: number) {
|
|
22
|
+
this.tracer.instantEvent({
|
|
23
|
+
name: 'TracingStartedInPage',
|
|
24
|
+
id: this.getEventId(),
|
|
25
|
+
ts,
|
|
26
|
+
cat: ['disabled-by-default-devtools.timeline'],
|
|
27
|
+
args: {
|
|
28
|
+
data: {
|
|
29
|
+
sessionId: '-1',
|
|
30
|
+
page: '0xfff',
|
|
31
|
+
frames: [
|
|
32
|
+
{
|
|
33
|
+
frame: '0xfff',
|
|
34
|
+
url: 'parcel',
|
|
35
|
+
name: '',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.tracer.instantEvent({
|
|
43
|
+
name: 'TracingStartedInBrowser',
|
|
44
|
+
id: this.getEventId(),
|
|
45
|
+
ts,
|
|
46
|
+
cat: ['disabled-by-default-devtools.timeline'],
|
|
47
|
+
args: {
|
|
48
|
+
data: {
|
|
49
|
+
sessionId: '-1',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
addCPUProfile(name: string, profile: Profile) {
|
|
56
|
+
if (this.eventId === 0) {
|
|
57
|
+
this.init(profile.startTime);
|
|
58
|
+
}
|
|
59
|
+
const trace = this.tracer;
|
|
60
|
+
const tid = this.tid;
|
|
61
|
+
this.tid++;
|
|
62
|
+
|
|
63
|
+
const cpuStartTime = profile.startTime;
|
|
64
|
+
const cpuEndTime = profile.endTime;
|
|
65
|
+
|
|
66
|
+
trace.instantEvent({
|
|
67
|
+
tid,
|
|
68
|
+
id: this.getEventId(),
|
|
69
|
+
cat: ['toplevel'],
|
|
70
|
+
name: 'TaskQueueManager::ProcessTaskFromWorkQueue',
|
|
71
|
+
args: {
|
|
72
|
+
src_file: '../../ipc/ipc_moji_bootstrap.cc',
|
|
73
|
+
src_func: 'Accept',
|
|
74
|
+
},
|
|
75
|
+
ts: cpuStartTime,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
trace.completeEvent({
|
|
79
|
+
tid,
|
|
80
|
+
name: 'EvaluateScript',
|
|
81
|
+
id: this.getEventId(),
|
|
82
|
+
cat: ['devtools.timeline'],
|
|
83
|
+
ts: cpuStartTime,
|
|
84
|
+
dur: cpuEndTime - cpuStartTime,
|
|
85
|
+
args: {
|
|
86
|
+
data: {
|
|
87
|
+
url: 'parcel',
|
|
88
|
+
lineNumber: 1,
|
|
89
|
+
columnNumber: 1,
|
|
90
|
+
frame: '0xFFF',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
trace.instantEvent({
|
|
96
|
+
tid,
|
|
97
|
+
ts: 0,
|
|
98
|
+
ph: 'M',
|
|
99
|
+
cat: ['__metadata'],
|
|
100
|
+
name: 'thread_name',
|
|
101
|
+
args: {name},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
trace.instantEvent({
|
|
105
|
+
tid,
|
|
106
|
+
name: 'CpuProfile',
|
|
107
|
+
id: this.getEventId(),
|
|
108
|
+
cat: ['disabled-by-default-devtools.timeline'],
|
|
109
|
+
ts: cpuEndTime,
|
|
110
|
+
args: {
|
|
111
|
+
data: {
|
|
112
|
+
cpuProfile: profile,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
pipe(writable: Writable): stream$Writable {
|
|
119
|
+
return this.tracer.pipe(writable);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
flush() {
|
|
123
|
+
this.tracer.push(null);
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/Tracer.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// @flow strict-local
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
TraceEvent,
|
|
5
|
+
IDisposable,
|
|
6
|
+
PluginTracer as IPluginTracer,
|
|
7
|
+
} from '@parcel/types';
|
|
8
|
+
import type {
|
|
9
|
+
TraceMeasurement as ITraceMeasurement,
|
|
10
|
+
TraceMeasurementData,
|
|
11
|
+
} from './types';
|
|
12
|
+
import {ValueEmitter} from '@parcel/events';
|
|
13
|
+
|
|
14
|
+
// $FlowFixMe
|
|
15
|
+
import {performance as _performance} from 'perf_hooks';
|
|
16
|
+
|
|
17
|
+
let tid;
|
|
18
|
+
try {
|
|
19
|
+
tid = require('worker_threads').threadId;
|
|
20
|
+
} catch {
|
|
21
|
+
tid = 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const performance: Performance = _performance;
|
|
25
|
+
const pid = process.pid;
|
|
26
|
+
|
|
27
|
+
class TraceMeasurement implements ITraceMeasurement {
|
|
28
|
+
#active /* boolean */ = true;
|
|
29
|
+
#name /* string */;
|
|
30
|
+
#pid /* number */;
|
|
31
|
+
#tid /* number */;
|
|
32
|
+
#start /* number */;
|
|
33
|
+
#data /* any */;
|
|
34
|
+
constructor(tracer: Tracer, name, pid, tid, data) {
|
|
35
|
+
this.#name = name;
|
|
36
|
+
this.#pid = pid;
|
|
37
|
+
this.#tid = tid;
|
|
38
|
+
this.#start = performance.now();
|
|
39
|
+
this.#data = data;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
end() {
|
|
43
|
+
if (!this.#active) return;
|
|
44
|
+
const duration = performance.now() - this.#start;
|
|
45
|
+
tracer.trace({
|
|
46
|
+
type: 'trace',
|
|
47
|
+
name: this.#name,
|
|
48
|
+
pid: this.#pid,
|
|
49
|
+
tid: this.#tid,
|
|
50
|
+
duration,
|
|
51
|
+
ts: this.#start,
|
|
52
|
+
...this.#data,
|
|
53
|
+
});
|
|
54
|
+
this.#active = false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default class Tracer {
|
|
59
|
+
#traceEmitter /* ValueEmitter<TraceEvent> */ = new ValueEmitter();
|
|
60
|
+
|
|
61
|
+
#enabled /* boolean */ = false;
|
|
62
|
+
|
|
63
|
+
onTrace(cb: (event: TraceEvent) => mixed): IDisposable {
|
|
64
|
+
return this.#traceEmitter.addListener(cb);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async wrap(name: string, fn: () => mixed): Promise<void> {
|
|
68
|
+
let measurement = this.createMeasurement(name);
|
|
69
|
+
try {
|
|
70
|
+
await fn();
|
|
71
|
+
} finally {
|
|
72
|
+
measurement && measurement.end();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
createMeasurement(
|
|
77
|
+
name: string,
|
|
78
|
+
category?: string = 'Core',
|
|
79
|
+
argumentName?: string,
|
|
80
|
+
otherArgs?: {[key: string]: mixed},
|
|
81
|
+
): ITraceMeasurement | null {
|
|
82
|
+
if (!this.enabled) return null;
|
|
83
|
+
|
|
84
|
+
// We create `args` in a fairly verbose way to avoid object
|
|
85
|
+
// allocation where not required.
|
|
86
|
+
let args: {[key: string]: mixed};
|
|
87
|
+
if (typeof argumentName === 'string') {
|
|
88
|
+
args = {name: argumentName};
|
|
89
|
+
}
|
|
90
|
+
if (typeof otherArgs === 'object') {
|
|
91
|
+
if (typeof args == 'undefined') {
|
|
92
|
+
args = {};
|
|
93
|
+
}
|
|
94
|
+
for (const [k, v] of Object.entries(otherArgs)) {
|
|
95
|
+
args[k] = v;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const data: TraceMeasurementData = {
|
|
100
|
+
categories: [category],
|
|
101
|
+
args,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return new TraceMeasurement(this, name, pid, tid, data);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get enabled(): boolean {
|
|
108
|
+
return this.#enabled;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
enable(): void {
|
|
112
|
+
this.#enabled = true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
disable(): void {
|
|
116
|
+
this.#enabled = false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
trace(event: TraceEvent): void {
|
|
120
|
+
if (!this.#enabled) return;
|
|
121
|
+
this.#traceEmitter.emit(event);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const tracer: Tracer = new Tracer();
|
|
126
|
+
|
|
127
|
+
type TracerOpts = {|
|
|
128
|
+
origin: string,
|
|
129
|
+
category: string,
|
|
130
|
+
|};
|
|
131
|
+
export class PluginTracer implements IPluginTracer {
|
|
132
|
+
/** @private */
|
|
133
|
+
origin: string;
|
|
134
|
+
|
|
135
|
+
/** @private */
|
|
136
|
+
category: string;
|
|
137
|
+
|
|
138
|
+
/** @private */
|
|
139
|
+
constructor(opts: TracerOpts) {
|
|
140
|
+
this.origin = opts.origin;
|
|
141
|
+
this.category = opts.category;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get enabled(): boolean {
|
|
145
|
+
return tracer.enabled;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
createMeasurement(
|
|
149
|
+
name: string,
|
|
150
|
+
category?: string,
|
|
151
|
+
argumentName?: string,
|
|
152
|
+
otherArgs?: {[key: string]: mixed},
|
|
153
|
+
): ITraceMeasurement | null {
|
|
154
|
+
return tracer.createMeasurement(
|
|
155
|
+
name,
|
|
156
|
+
`${this.category}:${this.origin}${
|
|
157
|
+
typeof category === 'string' ? `:${category}` : ''
|
|
158
|
+
}`,
|
|
159
|
+
argumentName,
|
|
160
|
+
otherArgs,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
package/src/index.js
ADDED
package/src/types.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {tracer, PluginTracer} from '../src/Tracer';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import assert from 'assert';
|
|
4
|
+
|
|
5
|
+
describe('Tracer', () => {
|
|
6
|
+
let onTrace;
|
|
7
|
+
let traceDisposable;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
onTrace = sinon.spy();
|
|
10
|
+
traceDisposable = tracer.onTrace(onTrace);
|
|
11
|
+
tracer.enable();
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
traceDisposable.dispose();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns no measurement when disabled', () => {
|
|
18
|
+
tracer.disable();
|
|
19
|
+
const measurement = tracer.createMeasurement('test');
|
|
20
|
+
assert(measurement == null);
|
|
21
|
+
assert(onTrace.notCalled);
|
|
22
|
+
});
|
|
23
|
+
it('emits a basic trace event', () => {
|
|
24
|
+
const measurement = tracer.createMeasurement('test');
|
|
25
|
+
measurement.end();
|
|
26
|
+
sinon.assert.calledWith(
|
|
27
|
+
onTrace,
|
|
28
|
+
sinon.match({
|
|
29
|
+
type: 'trace',
|
|
30
|
+
name: 'test',
|
|
31
|
+
args: undefined,
|
|
32
|
+
duration: sinon.match.number,
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
it('emits a complex trace event', () => {
|
|
37
|
+
const measurement = tracer.createMeasurement('test', 'myPlugin', 'aaargh', {
|
|
38
|
+
extra: 'data',
|
|
39
|
+
});
|
|
40
|
+
measurement.end();
|
|
41
|
+
sinon.assert.calledWith(
|
|
42
|
+
onTrace,
|
|
43
|
+
sinon.match({
|
|
44
|
+
type: 'trace',
|
|
45
|
+
name: 'test',
|
|
46
|
+
categories: ['myPlugin'],
|
|
47
|
+
args: {extra: 'data', name: 'aaargh'},
|
|
48
|
+
duration: sinon.match.number,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
it('calling end twice on measurment should be a no-op', () => {
|
|
53
|
+
const measurement = tracer.createMeasurement('test');
|
|
54
|
+
measurement.end();
|
|
55
|
+
measurement.end();
|
|
56
|
+
sinon.assert.calledOnce(onTrace);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('PluginTracer', () => {
|
|
60
|
+
it('emits events with proper origin/category', () => {
|
|
61
|
+
const pluginTracer = new PluginTracer({
|
|
62
|
+
origin: 'origin',
|
|
63
|
+
category: 'cat',
|
|
64
|
+
});
|
|
65
|
+
const measurement = pluginTracer.createMeasurement('test', 'customCat');
|
|
66
|
+
measurement.end();
|
|
67
|
+
sinon.assert.calledWith(
|
|
68
|
+
onTrace,
|
|
69
|
+
sinon.match({
|
|
70
|
+
type: 'trace',
|
|
71
|
+
name: 'test',
|
|
72
|
+
categories: ['cat:origin:customCat'],
|
|
73
|
+
duration: sinon.match.number,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|