@gnsx/three 0.184.18 → 0.184.20
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/build/three.cjs +271 -41
- package/build/three.core.js +271 -41
- package/build/three.core.min.js +1 -1
- package/package.json +2 -2
- package/src/profiler/ProfilerService.js +271 -41
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gnsx/three",
|
|
3
|
-
"version": "0.184.
|
|
3
|
+
"version": "0.184.20",
|
|
4
4
|
"description": "JavaScript 3D library",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./build/three.cjs",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"jsdoc": "^4.0.5",
|
|
109
109
|
"magic-string": "^0.30.0",
|
|
110
110
|
"pngjs": "^7.0.0",
|
|
111
|
-
"puppeteer": "^
|
|
111
|
+
"puppeteer": "^25.1.0",
|
|
112
112
|
"qunit": "^2.19.4",
|
|
113
113
|
"rollup": "^4.6.0",
|
|
114
114
|
"three": "file:.",
|
|
@@ -9,14 +9,22 @@
|
|
|
9
9
|
* __gnsx_profiler.downloadTrace() // download gnsx-trace.json for Speedscope / Perfetto
|
|
10
10
|
* __gnsx_profiler.reset() // clear samples and trace
|
|
11
11
|
* __gnsx_profiler.disable()
|
|
12
|
+
*
|
|
13
|
+
* Chrome trace: `tid=1` is synchronous work; `@profile` on async methods records the
|
|
14
|
+
* promise lifetime on `tid=2` so long async does not flatten the main row. For manual
|
|
15
|
+
* spans, use `beginSpan` / `endSpan` with `{ asyncTimeline: true }` in a `.finally()`.
|
|
12
16
|
*/
|
|
13
17
|
|
|
14
18
|
const RING_SIZE = 120;
|
|
15
19
|
const FRAME_BUDGET_MS = 1000 / 60;
|
|
16
|
-
|
|
17
|
-
const
|
|
20
|
+
const TRACE_TID_MAIN = 1;
|
|
21
|
+
const TRACE_TID_ASYNC = 2;
|
|
18
22
|
const NOOP = () => {};
|
|
19
23
|
|
|
24
|
+
const DUMMY_SPAN = Object.freeze( { label: '', t0: 0, _seq: 0 } );
|
|
25
|
+
const NOOP_BEGIN_SPAN = () => DUMMY_SPAN;
|
|
26
|
+
const NOOP_END_SPAN = () => {};
|
|
27
|
+
|
|
20
28
|
class ProfilerServiceClass {
|
|
21
29
|
|
|
22
30
|
constructor() {
|
|
@@ -24,24 +32,40 @@ class ProfilerServiceClass {
|
|
|
24
32
|
this._profile = 'full';
|
|
25
33
|
this._enabled = false;
|
|
26
34
|
|
|
27
|
-
/** @type {Map<string, Float64Array>} */
|
|
35
|
+
/** @type {Map<string, Float64Array>} Inclusive time ring buffers (one per label). */
|
|
28
36
|
this.buffers = new Map();
|
|
29
37
|
/** @type {Map<string, number>} */
|
|
30
38
|
this.cursors = new Map();
|
|
31
39
|
/** @type {Map<string, number>} */
|
|
32
40
|
this.counts = new Map();
|
|
33
|
-
/** @type {Map<string, Array<{ startTime: number, startMark: string
|
|
41
|
+
/** @type {Map<string, Array<{ startTime: number, startMark: string }>>} */
|
|
34
42
|
this.marks = new Map();
|
|
35
43
|
/** @type {ChromeTraceEvent[]} */
|
|
36
44
|
this.traceEvents = [];
|
|
37
45
|
this.traceStartTime = 0;
|
|
38
46
|
this._markId = 0;
|
|
39
47
|
|
|
48
|
+
/** @type {Map<string, Float64Array>} Exclusive (self) time ring buffers (one per label). */
|
|
49
|
+
this.selfBuffers = new Map();
|
|
50
|
+
/** @type {Map<string, number>} */
|
|
51
|
+
this.selfCursors = new Map();
|
|
52
|
+
/** @type {Map<string, number>} */
|
|
53
|
+
this.selfCounts = new Map();
|
|
54
|
+
/**
|
|
55
|
+
* Global call stack tracking nesting across all labels for exclusive-time computation.
|
|
56
|
+
* @type {Array<{ label: string, childTime: number }>}
|
|
57
|
+
*/
|
|
58
|
+
this.callStack = [];
|
|
59
|
+
/** @type {Map<string, number>} Total (uncapped) invocation count since last reset, for calls-per-frame. */
|
|
60
|
+
this.invocations = new Map();
|
|
61
|
+
|
|
40
62
|
if ( this._enabled ) {
|
|
41
63
|
|
|
42
64
|
this.traceStartTime = performance.now();
|
|
43
65
|
this.begin = this._beginImpl.bind( this );
|
|
44
66
|
this.end = this._endImpl.bind( this );
|
|
67
|
+
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
68
|
+
this.endSpan = this._endSpanImpl.bind( this );
|
|
45
69
|
this._exposeGlobal();
|
|
46
70
|
console.log( `[ProfilerService] auto-started from env (profile: ${this._profile})` );
|
|
47
71
|
|
|
@@ -49,6 +73,8 @@ class ProfilerServiceClass {
|
|
|
49
73
|
|
|
50
74
|
this.begin = NOOP;
|
|
51
75
|
this.end = NOOP;
|
|
76
|
+
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
77
|
+
this.endSpan = NOOP_END_SPAN;
|
|
52
78
|
|
|
53
79
|
}
|
|
54
80
|
|
|
@@ -80,6 +106,8 @@ class ProfilerServiceClass {
|
|
|
80
106
|
this._enabled = true;
|
|
81
107
|
this.begin = this._beginImpl.bind( this );
|
|
82
108
|
this.end = this._endImpl.bind( this );
|
|
109
|
+
this.beginSpan = this._beginSpanImpl.bind( this );
|
|
110
|
+
this.endSpan = this._endSpanImpl.bind( this );
|
|
83
111
|
this._exposeGlobal();
|
|
84
112
|
console.log( `[ProfilerService] enabled (profile: ${this._profile}) — call __gnsx_profiler.report() or downloadTrace() from the console` );
|
|
85
113
|
|
|
@@ -91,6 +119,8 @@ class ProfilerServiceClass {
|
|
|
91
119
|
this._enabled = false;
|
|
92
120
|
this.begin = NOOP;
|
|
93
121
|
this.end = NOOP;
|
|
122
|
+
this.beginSpan = NOOP_BEGIN_SPAN;
|
|
123
|
+
this.endSpan = NOOP_END_SPAN;
|
|
94
124
|
console.log( '[ProfilerService] disabled' );
|
|
95
125
|
|
|
96
126
|
}
|
|
@@ -126,22 +156,11 @@ class ProfilerServiceClass {
|
|
|
126
156
|
|
|
127
157
|
}
|
|
128
158
|
|
|
129
|
-
const traced = this._profile === 'full' && this.traceEvents.length < MAX_TRACE_EVENTS;
|
|
130
|
-
if ( traced ) {
|
|
131
|
-
|
|
132
|
-
this.traceEvents.push( {
|
|
133
|
-
name: label,
|
|
134
|
-
ph: 'B',
|
|
135
|
-
ts: this._getTraceTimestamp( startTime ),
|
|
136
|
-
pid: 1,
|
|
137
|
-
tid: 1,
|
|
138
|
-
cat: 'gnsx',
|
|
139
|
-
} );
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
159
|
performance.mark( startMark );
|
|
144
|
-
stack.push( { startTime, startMark
|
|
160
|
+
stack.push( { startTime, startMark } );
|
|
161
|
+
|
|
162
|
+
// Push onto the global call stack for exclusive-time tracking.
|
|
163
|
+
this.callStack.push( { label, childTime: 0 } );
|
|
145
164
|
|
|
146
165
|
}
|
|
147
166
|
|
|
@@ -156,13 +175,61 @@ class ProfilerServiceClass {
|
|
|
156
175
|
if ( stack.length === 0 ) this.marks.delete( label );
|
|
157
176
|
|
|
158
177
|
const now = performance.now();
|
|
159
|
-
const { startTime, startMark
|
|
178
|
+
const { startTime, startMark } = mark;
|
|
160
179
|
const duration = now - startTime;
|
|
161
180
|
const endMark = `gnsx:${label}:end:${++ this._markId}`;
|
|
162
181
|
|
|
163
182
|
performance.mark( endMark );
|
|
164
183
|
performance.measure( `gnsx:${label}`, startMark, endMark );
|
|
165
184
|
|
|
185
|
+
this._commitDurationSample( label, startTime, duration, TRACE_TID_MAIN );
|
|
186
|
+
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Per-invocation span start (pair with {@link ProfilerServiceClass#endSpan}).
|
|
191
|
+
* Safe for concurrent async with the same label.
|
|
192
|
+
*
|
|
193
|
+
* @param {string} label
|
|
194
|
+
* @return {import('./ProfilerService.js').SpanHandle}
|
|
195
|
+
*/
|
|
196
|
+
_beginSpanImpl( label ) {
|
|
197
|
+
|
|
198
|
+
const seq = ++ this._markId;
|
|
199
|
+
const t0 = performance.now();
|
|
200
|
+
const startMark = `gnsx:${label}:s${seq}:start`;
|
|
201
|
+
performance.mark( startMark );
|
|
202
|
+
return { label, t0, _seq: seq, _startMark: startMark };
|
|
203
|
+
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {import('./ProfilerService.js').SpanHandle} handle
|
|
208
|
+
* @param {import('./ProfilerService.js').EndSpanOptions} [opts]
|
|
209
|
+
*/
|
|
210
|
+
_endSpanImpl( handle, opts ) {
|
|
211
|
+
|
|
212
|
+
if ( handle._seq === 0 ) return;
|
|
213
|
+
|
|
214
|
+
const now = performance.now();
|
|
215
|
+
const duration = now - handle.t0;
|
|
216
|
+
const endMark = `gnsx:${handle.label}:s${handle._seq}:end`;
|
|
217
|
+
performance.mark( endMark );
|
|
218
|
+
performance.measure( `gnsx:${handle.label}#${handle._seq}`, handle._startMark, endMark );
|
|
219
|
+
|
|
220
|
+
const traceTid = opts?.asyncTimeline === true ? TRACE_TID_ASYNC : TRACE_TID_MAIN;
|
|
221
|
+
this._commitDurationSample( handle.label, handle.t0, duration, traceTid );
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @param {string} label
|
|
227
|
+
* @param {number} startTime
|
|
228
|
+
* @param {number} durationMs
|
|
229
|
+
* @param {typeof TRACE_TID_MAIN|typeof TRACE_TID_ASYNC} traceTid
|
|
230
|
+
*/
|
|
231
|
+
_commitDurationSample( label, startTime, durationMs, traceTid ) {
|
|
232
|
+
|
|
166
233
|
let buffer = this.buffers.get( label );
|
|
167
234
|
if ( ! buffer ) {
|
|
168
235
|
|
|
@@ -174,18 +241,56 @@ class ProfilerServiceClass {
|
|
|
174
241
|
}
|
|
175
242
|
|
|
176
243
|
const cursor = this.cursors.get( label );
|
|
177
|
-
buffer[ cursor ] =
|
|
244
|
+
buffer[ cursor ] = durationMs;
|
|
178
245
|
this.cursors.set( label, ( cursor + 1 ) % RING_SIZE );
|
|
179
246
|
this.counts.set( label, Math.min( ( this.counts.get( label ) + 1 ), RING_SIZE ) );
|
|
180
247
|
|
|
181
|
-
|
|
248
|
+
// Track total invocations (uncapped) for calls-per-frame computation.
|
|
249
|
+
this.invocations.set( label, ( this.invocations.get( label ) ?? 0 ) + 1 );
|
|
182
250
|
|
|
251
|
+
// Exclusive (self) time via the global call stack.
|
|
252
|
+
const top = this.callStack.length > 0 ? this.callStack[ this.callStack.length - 1 ] : undefined;
|
|
253
|
+
if ( top !== undefined && top.label === label ) {
|
|
254
|
+
|
|
255
|
+
this.callStack.pop();
|
|
256
|
+
const selfTime = Math.max( 0, durationMs - top.childTime );
|
|
257
|
+
|
|
258
|
+
let selfBuffer = this.selfBuffers.get( label );
|
|
259
|
+
if ( ! selfBuffer ) {
|
|
260
|
+
|
|
261
|
+
selfBuffer = new Float64Array( RING_SIZE );
|
|
262
|
+
this.selfBuffers.set( label, selfBuffer );
|
|
263
|
+
this.selfCursors.set( label, 0 );
|
|
264
|
+
this.selfCounts.set( label, 0 );
|
|
265
|
+
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const selfCursor = this.selfCursors.get( label );
|
|
269
|
+
selfBuffer[ selfCursor ] = selfTime;
|
|
270
|
+
this.selfCursors.set( label, ( selfCursor + 1 ) % RING_SIZE );
|
|
271
|
+
this.selfCounts.set( label, Math.min( ( this.selfCounts.get( label ) + 1 ), RING_SIZE ) );
|
|
272
|
+
|
|
273
|
+
// Propagate inclusive duration to the parent scope's child accumulator.
|
|
274
|
+
const parent = this.callStack.length > 0 ? this.callStack[ this.callStack.length - 1 ] : undefined;
|
|
275
|
+
if ( parent !== undefined ) parent.childTime += durationMs;
|
|
276
|
+
|
|
277
|
+
} else {
|
|
278
|
+
|
|
279
|
+
// Label mismatch — likely async interleaving. Reset to avoid corruption.
|
|
280
|
+
this.callStack.length = 0;
|
|
281
|
+
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if ( traceTid ) {
|
|
285
|
+
|
|
286
|
+
const traceName = traceTid === TRACE_TID_ASYNC ? `${label} (promise)` : label;
|
|
183
287
|
this.traceEvents.push( {
|
|
184
|
-
name:
|
|
185
|
-
ph: '
|
|
186
|
-
ts: this._getTraceTimestamp(
|
|
288
|
+
name: traceName,
|
|
289
|
+
ph: 'X',
|
|
290
|
+
ts: Math.round( this._getTraceTimestamp( startTime ) ),
|
|
291
|
+
dur: Math.max( 1, Math.round( durationMs * 1000 ) ),
|
|
187
292
|
pid: 1,
|
|
188
|
-
tid:
|
|
293
|
+
tid: traceTid,
|
|
189
294
|
cat: 'gnsx',
|
|
190
295
|
} );
|
|
191
296
|
|
|
@@ -220,6 +325,23 @@ class ProfilerServiceClass {
|
|
|
220
325
|
const sorted = [ ...samples ].sort( ( a, b ) => a - b );
|
|
221
326
|
const avg = sorted.reduce( ( a, b ) => a + b, 0 ) / sorted.length;
|
|
222
327
|
|
|
328
|
+
// Exclusive (self) time stats.
|
|
329
|
+
const selfCount = this.selfCounts.get( label ) ?? 0;
|
|
330
|
+
let selfAvg, selfMin, selfMax, selfP95, selfFrameBudget;
|
|
331
|
+
if ( selfCount > 0 ) {
|
|
332
|
+
|
|
333
|
+
const selfBuffer = this.selfBuffers.get( label );
|
|
334
|
+
const selfSamples = [ ...( selfCount < RING_SIZE
|
|
335
|
+
? selfBuffer.subarray( 0, selfCount )
|
|
336
|
+
: selfBuffer ) ].sort( ( a, b ) => a - b );
|
|
337
|
+
selfAvg = selfSamples.reduce( ( a, b ) => a + b, 0 ) / selfSamples.length;
|
|
338
|
+
selfMin = selfSamples[ 0 ];
|
|
339
|
+
selfMax = selfSamples[ selfSamples.length - 1 ];
|
|
340
|
+
selfP95 = selfSamples[ Math.floor( selfSamples.length * 0.95 ) ];
|
|
341
|
+
selfFrameBudget = ( selfAvg / FRAME_BUDGET_MS ) * 100;
|
|
342
|
+
|
|
343
|
+
}
|
|
344
|
+
|
|
223
345
|
return {
|
|
224
346
|
label,
|
|
225
347
|
samples: samples.length,
|
|
@@ -228,6 +350,12 @@ class ProfilerServiceClass {
|
|
|
228
350
|
max: sorted[ sorted.length - 1 ],
|
|
229
351
|
p95: sorted[ Math.floor( sorted.length * 0.95 ) ],
|
|
230
352
|
frameBudget: ( avg / FRAME_BUDGET_MS ) * 100,
|
|
353
|
+
totalInvocations: this.invocations.get( label ) ?? 0,
|
|
354
|
+
selfAvg,
|
|
355
|
+
selfMin,
|
|
356
|
+
selfMax,
|
|
357
|
+
selfP95,
|
|
358
|
+
selfFrameBudget,
|
|
231
359
|
};
|
|
232
360
|
|
|
233
361
|
}
|
|
@@ -273,13 +401,53 @@ class ProfilerServiceClass {
|
|
|
273
401
|
*/
|
|
274
402
|
exportChromeTrace() {
|
|
275
403
|
|
|
404
|
+
const slices = [ ...this.traceEvents ];
|
|
405
|
+
slices.sort( ( a, b ) => {
|
|
406
|
+
|
|
407
|
+
if ( a.ts !== b.ts ) return a.ts - b.ts;
|
|
408
|
+
if ( a.tid !== b.tid ) return a.tid - b.tid;
|
|
409
|
+
return a.name.localeCompare( b.name );
|
|
410
|
+
|
|
411
|
+
} );
|
|
412
|
+
const hasAsync = slices.some( e => e.tid === TRACE_TID_ASYNC );
|
|
413
|
+
/** @type {import('./ProfilerService.js').ChromeTraceMetadataEvent[]} */
|
|
414
|
+
const prefix = [
|
|
415
|
+
{
|
|
416
|
+
cat: '__metadata',
|
|
417
|
+
name: 'process_name',
|
|
418
|
+
ph: 'M',
|
|
419
|
+
pid: 1,
|
|
420
|
+
tid: 0,
|
|
421
|
+
ts: 0,
|
|
422
|
+
args: { name: 'Genesys Profiler' },
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
cat: '__metadata',
|
|
426
|
+
name: 'thread_name',
|
|
427
|
+
ph: 'M',
|
|
428
|
+
pid: 1,
|
|
429
|
+
tid: TRACE_TID_MAIN,
|
|
430
|
+
ts: 0,
|
|
431
|
+
args: { name: 'Main thread' },
|
|
432
|
+
},
|
|
433
|
+
];
|
|
434
|
+
if ( hasAsync ) {
|
|
435
|
+
|
|
436
|
+
prefix.push( {
|
|
437
|
+
cat: '__metadata',
|
|
438
|
+
name: 'thread_name',
|
|
439
|
+
ph: 'M',
|
|
440
|
+
pid: 1,
|
|
441
|
+
tid: TRACE_TID_ASYNC,
|
|
442
|
+
ts: 0,
|
|
443
|
+
args: { name: 'Async (promise lifetime)' },
|
|
444
|
+
} );
|
|
445
|
+
|
|
446
|
+
}
|
|
447
|
+
|
|
276
448
|
return {
|
|
277
449
|
displayTimeUnit: 'ms',
|
|
278
|
-
traceEvents: [
|
|
279
|
-
{ name: 'process_name', ph: 'M', pid: 1, args: { name: 'Genesys Profiler' } },
|
|
280
|
-
{ name: 'thread_name', ph: 'M', pid: 1, tid: 1, args: { name: 'Main Thread' } },
|
|
281
|
-
...this.traceEvents,
|
|
282
|
-
],
|
|
450
|
+
traceEvents: [ ...prefix, ...slices ],
|
|
283
451
|
};
|
|
284
452
|
|
|
285
453
|
}
|
|
@@ -350,6 +518,11 @@ class ProfilerServiceClass {
|
|
|
350
518
|
this.traceEvents = [];
|
|
351
519
|
this.traceStartTime = performance.now();
|
|
352
520
|
this._markId = 0;
|
|
521
|
+
this.selfBuffers.clear();
|
|
522
|
+
this.selfCursors.clear();
|
|
523
|
+
this.selfCounts.clear();
|
|
524
|
+
this.callStack.length = 0;
|
|
525
|
+
this.invocations.clear();
|
|
353
526
|
|
|
354
527
|
}
|
|
355
528
|
|
|
@@ -371,24 +544,46 @@ class ProfilerServiceClass {
|
|
|
371
544
|
* @property {number} max
|
|
372
545
|
* @property {number} p95
|
|
373
546
|
* @property {number} frameBudget Percentage of a 60 fps frame budget (16.67 ms)
|
|
547
|
+
* @property {number} totalInvocations Total (uncapped) call count since last reset, for calls-per-frame computation.
|
|
548
|
+
* @property {number|undefined} selfAvg Exclusive (self) avg ms — inclusive time minus child scope time.
|
|
549
|
+
* @property {number|undefined} selfMin
|
|
550
|
+
* @property {number|undefined} selfMax
|
|
551
|
+
* @property {number|undefined} selfP95
|
|
552
|
+
* @property {number|undefined} selfFrameBudget Exclusive time as percentage of a 60 fps frame budget.
|
|
553
|
+
*/
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* @typedef {Object} SpanHandle
|
|
557
|
+
* @property {string} label
|
|
558
|
+
* @property {number} t0
|
|
559
|
+
* @property {number} _seq
|
|
560
|
+
* @property {string} _startMark
|
|
561
|
+
*/
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* @typedef {Object} EndSpanOptions
|
|
565
|
+
* @property {boolean} [asyncTimeline] When true, trace slice uses `tid=2` (virtual async row).
|
|
374
566
|
*/
|
|
375
567
|
|
|
376
568
|
/**
|
|
377
569
|
* @typedef {Object} ChromeTraceEvent
|
|
378
570
|
* @property {string} name
|
|
379
|
-
* @property {'
|
|
571
|
+
* @property {'X'} ph
|
|
380
572
|
* @property {number} ts
|
|
573
|
+
* @property {number} dur
|
|
381
574
|
* @property {1} pid
|
|
382
|
-
* @property {
|
|
575
|
+
* @property {number} tid
|
|
383
576
|
* @property {'gnsx'} cat
|
|
384
577
|
*/
|
|
385
578
|
|
|
386
579
|
/**
|
|
387
580
|
* @typedef {Object} ChromeTraceMetadataEvent
|
|
581
|
+
* @property {'__metadata'} cat
|
|
388
582
|
* @property {'process_name'|'thread_name'} name
|
|
389
583
|
* @property {'M'} ph
|
|
390
584
|
* @property {1} pid
|
|
391
|
-
* @property {
|
|
585
|
+
* @property {number} [tid]
|
|
586
|
+
* @property {0} ts
|
|
392
587
|
* @property {{ name: string }} args
|
|
393
588
|
*/
|
|
394
589
|
|
|
@@ -404,28 +599,38 @@ class ProfilerServiceClass {
|
|
|
404
599
|
|
|
405
600
|
const ProfilerService = new ProfilerServiceClass();
|
|
406
601
|
|
|
602
|
+
function isThenable( x ) {
|
|
603
|
+
|
|
604
|
+
return (
|
|
605
|
+
( typeof x === 'object' || typeof x === 'function' ) &&
|
|
606
|
+
x !== null &&
|
|
607
|
+
typeof x.then === 'function'
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
}
|
|
611
|
+
|
|
407
612
|
function applyProfileToMethod( label, descriptor ) {
|
|
408
613
|
|
|
409
614
|
const original = descriptor.value;
|
|
410
615
|
|
|
411
616
|
function profiled( ...args ) {
|
|
412
617
|
|
|
413
|
-
ProfilerService.
|
|
618
|
+
const span = ProfilerService.beginSpan( label );
|
|
414
619
|
try {
|
|
415
620
|
|
|
416
621
|
const result = original.apply( this, args );
|
|
417
|
-
if ( result
|
|
622
|
+
if ( isThenable( result ) ) {
|
|
418
623
|
|
|
419
|
-
return result.finally( () => ProfilerService.
|
|
624
|
+
return Promise.resolve( result ).finally( () => ProfilerService.endSpan( span, { asyncTimeline: true } ) );
|
|
420
625
|
|
|
421
626
|
}
|
|
422
627
|
|
|
423
|
-
ProfilerService.
|
|
628
|
+
ProfilerService.endSpan( span );
|
|
424
629
|
return result;
|
|
425
630
|
|
|
426
631
|
} catch ( error ) {
|
|
427
632
|
|
|
428
|
-
ProfilerService.
|
|
633
|
+
ProfilerService.endSpan( span );
|
|
429
634
|
throw error;
|
|
430
635
|
|
|
431
636
|
}
|
|
@@ -439,14 +644,39 @@ function applyProfileToMethod( label, descriptor ) {
|
|
|
439
644
|
|
|
440
645
|
/**
|
|
441
646
|
* Method decorator that profiles the decorated method.
|
|
442
|
-
*
|
|
647
|
+
* Default label: `ClassName.methodName`. Pass a custom tag via `@profile('My tag')`.
|
|
443
648
|
*
|
|
649
|
+
* @param {Object|string} [targetOrTag]
|
|
650
|
+
* @param {string|symbol} [propertyKey]
|
|
651
|
+
* @param {PropertyDescriptor} [descriptor]
|
|
652
|
+
* @return {PropertyDescriptor|function(Object, string|symbol, PropertyDescriptor): PropertyDescriptor}
|
|
653
|
+
*/
|
|
654
|
+
function profile( targetOrTag, propertyKey, descriptor ) {
|
|
655
|
+
|
|
656
|
+
if ( arguments.length === 0 ) {
|
|
657
|
+
|
|
658
|
+
return defaultProfileDecorator;
|
|
659
|
+
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if ( arguments.length === 1 && typeof targetOrTag === 'string' ) {
|
|
663
|
+
|
|
664
|
+
const customTag = targetOrTag;
|
|
665
|
+
return ( target, key, desc ) => applyProfileToMethod( customTag, desc );
|
|
666
|
+
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return defaultProfileDecorator( targetOrTag, propertyKey, descriptor );
|
|
670
|
+
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
444
674
|
* @param {Object} target
|
|
445
675
|
* @param {string|symbol} propertyKey
|
|
446
676
|
* @param {PropertyDescriptor} descriptor
|
|
447
677
|
* @return {PropertyDescriptor}
|
|
448
678
|
*/
|
|
449
|
-
function
|
|
679
|
+
function defaultProfileDecorator( target, propertyKey, descriptor ) {
|
|
450
680
|
|
|
451
681
|
const className = target.constructor?.name ?? 'Unknown';
|
|
452
682
|
return applyProfileToMethod( `${className}.${String( propertyKey )}`, descriptor );
|