@@ -26,7 +26,7 @@ import type {
26
26
StateHandlerPayloads ,
27
27
} from './typeUtils'
28
28
29
- interface CreateTraceRecordingConfig < ScopeT extends ScopeBase > {
29
+ interface FinalState < ScopeT extends ScopeBase > {
30
30
transitionFromState : NonTerminalTraceStates
31
31
interruptionReason ?: TraceInterruptionReason
32
32
cpuIdleSpanAndAnnotation ?: SpanAndAnnotation < ScopeT >
@@ -47,8 +47,7 @@ interface OnEnterInterrupted {
47
47
interruptionReason : TraceInterruptionReason
48
48
}
49
49
50
- interface OnEnterComplete < ScopeT extends ScopeBase >
51
- extends CreateTraceRecordingConfig < ScopeT > {
50
+ interface OnEnterComplete < ScopeT extends ScopeBase > extends FinalState < ScopeT > {
52
51
transitionToState : 'complete'
53
52
}
54
53
@@ -73,9 +72,7 @@ export type Transition<ScopeT extends ScopeBase> = DistributiveOmit<
73
72
'transitionFromState'
74
73
>
75
74
76
- type FinalizeFn < ScopeT extends ScopeBase > = (
77
- config : CreateTraceRecordingConfig < ScopeT > ,
78
- ) => void
75
+ type FinalizeFn < ScopeT extends ScopeBase > = ( config : FinalState < ScopeT > ) => void
79
76
80
77
export type States < ScopeT extends ScopeBase > =
81
78
TraceStateMachine < ScopeT > [ 'states' ]
@@ -108,7 +105,7 @@ export class TraceStateMachine<ScopeT extends ScopeBase> {
108
105
readonly requiredToEndIndexChecklist : Set < number >
109
106
}
110
107
readonly sideEffectFns : {
111
- readonly finalize : FinalizeFn < ScopeT >
108
+ readonly storeFinalizeState : FinalizeFn < ScopeT >
112
109
}
113
110
currentState : TraceStates = 'recording'
114
111
/** the span that ended at the furthest point in time */
@@ -165,6 +162,12 @@ export class TraceStateMachine<ScopeT extends ScopeBase> {
165
162
}
166
163
167
164
for ( let i = 0 ; i < this . context . definition . requiredToEnd . length ; i ++ ) {
165
+ if ( ! this . context . requiredToEndIndexChecklist . has ( i ) ) {
166
+ // we previously checked off this index
167
+ // eslint-disable-next-line no-continue
168
+ continue
169
+ }
170
+
168
171
const definition = this . context . definition . requiredToEnd [ i ] !
169
172
if (
170
173
doesEntryMatchDefinition (
@@ -173,24 +176,29 @@ export class TraceStateMachine<ScopeT extends ScopeBase> {
173
176
this . context . input . scope ,
174
177
)
175
178
) {
179
+ console . log (
180
+ '# got a match!' ,
181
+ 'span' ,
182
+ spanAndAnnotation ,
183
+ 'matches' ,
184
+ definition ,
185
+ 'remaining items' ,
186
+ this . context . requiredToEndIndexChecklist ,
187
+ )
176
188
// remove the index of this definition from the list of requiredToEnd
177
189
this . context . requiredToEndIndexChecklist . delete ( i )
178
190
179
191
// Sometimes spans are processed out of order, we update the lastRelevant if this span ends later
180
192
if (
193
+ ! this . lastRelevant ||
181
194
spanAndAnnotation . annotation . operationRelativeEndTime >
182
- ( this . lastRelevant ?. annotation . operationRelativeEndTime ?? 0 )
195
+ ( this . lastRelevant ?. annotation . operationRelativeEndTime ?? 0 )
183
196
) {
184
197
this . lastRelevant = spanAndAnnotation
185
198
}
186
199
}
187
200
}
188
201
189
- console . log (
190
- '# requiredToEndIndexChecklist' ,
191
- this . context . requiredToEndIndexChecklist ,
192
- spanAndAnnotation ,
193
- )
194
202
if ( this . context . requiredToEndIndexChecklist . size === 0 ) {
195
203
return { transitionToState : 'debouncing' }
196
204
}
@@ -212,17 +220,23 @@ export class TraceStateMachine<ScopeT extends ScopeBase> {
212
220
// the final, settled state of the component
213
221
debouncing : {
214
222
onEnterState : ( payload : OnEnterDebouncing ) => {
223
+ if ( ! this . lastRelevant ) {
224
+ // this should never happen
225
+ return {
226
+ transitionToState : 'interrupted' ,
227
+ interruptionReason : 'invalid-state-transition' ,
228
+ }
229
+ }
215
230
if ( ! this . context . definition . debounceOn ) {
216
231
return { transitionToState : 'waiting-for-interactive' }
217
232
}
218
- if ( this . lastRelevant ) {
219
- // set the first debounce deadline
220
- this . debounceDeadline =
221
- this . lastRelevant . span . startTime . epoch +
222
- this . lastRelevant . span . duration +
223
- ( this . context . definition . debounceDuration ??
224
- DEFAULT_DEBOUNCE_DURATION )
225
- }
233
+ // set the first debounce deadline
234
+ this . debounceDeadline =
235
+ this . lastRelevant . span . startTime . epoch +
236
+ this . lastRelevant . span . duration +
237
+ ( this . context . definition . debounceDuration ??
238
+ DEFAULT_DEBOUNCE_DURATION )
239
+
226
240
return undefined
227
241
} ,
228
242
@@ -306,6 +320,14 @@ export class TraceStateMachine<ScopeT extends ScopeBase> {
306
320
307
321
'waiting-for-interactive' : {
308
322
onEnterState : ( payload : OnEnterWaitingForInteractive ) => {
323
+ if ( ! this . lastRelevant ) {
324
+ // this should never happen
325
+ return {
326
+ transitionToState : 'interrupted' ,
327
+ interruptionReason : 'invalid-state-transition' ,
328
+ }
329
+ }
330
+
309
331
this . lastRequiredSpan = this . lastRelevant
310
332
const interactiveConfig = this . context . definition . captureInteractive
311
333
if ( ! interactiveConfig ) {
@@ -316,17 +338,12 @@ export class TraceStateMachine<ScopeT extends ScopeBase> {
316
338
}
317
339
}
318
340
319
- if ( this . lastRequiredSpan ) {
320
- this . interactiveDeadline =
321
- this . lastRequiredSpan . span . startTime . epoch +
322
- this . lastRequiredSpan . span . duration +
323
- ( ( typeof interactiveConfig === 'object' &&
324
- interactiveConfig . timeout ) ||
325
- DEFAULT_INTERACTIVE_TIMEOUT_DURATION )
326
- } else {
327
- // TODO: do we want an error state? will this condition every be true?
328
- return { transitionToState : 'complete' }
329
- }
341
+ this . interactiveDeadline =
342
+ this . lastRequiredSpan . span . startTime . epoch +
343
+ this . lastRequiredSpan . span . duration +
344
+ ( ( typeof interactiveConfig === 'object' &&
345
+ interactiveConfig . timeout ) ||
346
+ DEFAULT_INTERACTIVE_TIMEOUT_DURATION )
330
347
331
348
this . cpuIdleLongTaskProcessor = createCPUIdleProcessor <
332
349
EntryType < ScopeT >
@@ -340,7 +357,6 @@ export class TraceStateMachine<ScopeT extends ScopeBase> {
340
357
typeof interactiveConfig === 'object' ? interactiveConfig : { } ,
341
358
)
342
359
343
- // TODO: start the timer for tti debouncing
344
360
return undefined
345
361
} ,
346
362
@@ -419,15 +435,14 @@ export class TraceStateMachine<ScopeT extends ScopeBase> {
419
435
420
436
// terminal states:
421
437
interrupted : {
422
- onEnterState : ( payload : OnEnterInterrupted ) => {
423
- this . sideEffectFns . finalize ( payload )
438
+ onEnterState : ( _payload : OnEnterInterrupted ) => {
439
+ // terminal state, but we reuse the payload for generating the report in ActiveTrace
424
440
} ,
425
441
} ,
426
442
427
443
complete : {
428
- onEnterState : ( payload : OnEnterComplete < ScopeT > ) => {
429
- console . log ( '# complete payload' , payload )
430
- this . sideEffectFns . finalize ( payload )
444
+ onEnterState : ( _payload : OnEnterComplete < ScopeT > ) => {
445
+ // terminal state, but we reuse the payload for generating the report in ActiveTrace
431
446
} ,
432
447
} ,
433
448
} satisfies StatesBase < ScopeT >
@@ -490,6 +505,8 @@ export class ActiveTrace<ScopeT extends ScopeBase> {
490
505
SpanAndAnnotation < ScopeT >
491
506
> = new WeakMap ( )
492
507
508
+ finalState : FinalState < ScopeT > | undefined
509
+
493
510
constructor (
494
511
definition : CompleteTraceDefinition < ScopeT > ,
495
512
input : ActiveTraceConfig < ScopeT > ,
@@ -501,15 +518,13 @@ export class ActiveTrace<ScopeT extends ScopeBase> {
501
518
definition,
502
519
input,
503
520
sideEffectFns : {
504
- finalize : this . finalize ,
521
+ storeFinalizeState : this . storeFinalizeState ,
505
522
} ,
506
523
} )
507
524
}
508
525
509
- finalize = ( config : CreateTraceRecordingConfig < ScopeT > ) => {
510
- const traceRecording = this . createTraceRecording ( config )
511
- console . log ( '# recording?' , traceRecording )
512
- this . input . onEnd ( traceRecording )
526
+ storeFinalizeState = ( config : FinalState < ScopeT > ) => {
527
+ this . finalState = config
513
528
}
514
529
515
530
// this is public API only and should not be called internally
@@ -564,23 +579,29 @@ export class ActiveTrace<ScopeT extends ScopeBase> {
564
579
spanAndAnnotation . span = span
565
580
}
566
581
567
- const transitionPayload = this . stateMachine . emit (
582
+ const transition = this . stateMachine . emit (
568
583
'onProcessSpan' ,
569
584
spanAndAnnotation ,
570
585
)
571
- // console.log('transitionPayload', transitionPayload)
572
- // if the final state is interrupted,
573
- // we decided that we should not record the entry nor annotate it externally
574
- // TODO: this if statement needs to be validated/rethought
575
- if (
586
+
587
+ const shouldRecord =
576
588
! existingAnnotation &&
577
- ( ! transitionPayload ||
578
- transitionPayload . transitionToState === 'complete' ||
579
- // TODO: this condition doesn't make sense
580
- transitionPayload . transitionToState !== 'interrupted' )
581
- ) {
582
- console . log ( '# PUSH INTO RECORDED ITEMS' , spanAndAnnotation )
589
+ ( ! transition || transition . transitionToState !== 'interrupted' )
590
+
591
+ // DECISION: if the final state is interrupted, we should not record the entry nor annotate it externally
592
+ if ( shouldRecord ) {
583
593
this . recordedItems . push ( spanAndAnnotation )
594
+ }
595
+
596
+ if (
597
+ transition ?. transitionToState === 'interrupted' ||
598
+ transition ?. transitionToState === 'complete'
599
+ ) {
600
+ const traceRecording = this . createTraceRecording ( transition )
601
+ this . input . onEnd ( traceRecording )
602
+ }
603
+
604
+ if ( shouldRecord ) {
584
605
return {
585
606
[ this . definition . name ] : spanAndAnnotation . annotation ,
586
607
}
@@ -673,7 +694,9 @@ export class ActiveTrace<ScopeT extends ScopeBase> {
673
694
private createTraceRecording = ( {
674
695
transitionFromState,
675
696
interruptionReason,
676
- } : CreateTraceRecordingConfig < ScopeT > ) : TraceRecording < ScopeT > => {
697
+ cpuIdleSpanAndAnnotation,
698
+ lastRequiredSpanAndAnnotation,
699
+ } : FinalState < ScopeT > ) : TraceRecording < ScopeT > => {
677
700
const { id, scope } = this . input
678
701
const { name } = this . definition
679
702
const { computedSpans, computedValues, spanAttributes, attributes } = this
@@ -701,8 +724,8 @@ export class ActiveTrace<ScopeT extends ScopeBase> {
701
724
interruptionReason && transitionFromState !== 'waiting-for-interactive'
702
725
? 'interrupted'
703
726
: anyErrors
704
- ? 'error'
705
- : 'ok' ,
727
+ ? 'error'
728
+ : 'ok' ,
706
729
computedSpans,
707
730
computedValues,
708
731
attributes,
0 commit comments