@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 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
@@ -0,0 +1,5 @@
1
+ // @flow
2
+ export {default as SamplingProfiler} from './SamplingProfiler';
3
+ export {default as Trace} from './Trace';
4
+ export {tracer, PluginTracer} from './Tracer';
5
+ export type {TraceMeasurement, TraceMeasurementData} from './types';
package/src/types.js ADDED
@@ -0,0 +1,10 @@
1
+ // @flow
2
+
3
+ export interface TraceMeasurement {
4
+ end(): void;
5
+ }
6
+
7
+ export type TraceMeasurementData = {|
8
+ +categories: string[],
9
+ +args?: {[key: string]: mixed},
10
+ |};
@@ -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
+ });