@@ -2,119 +2,128 @@ import { InternalPlugin } from '../InternalPlugin';
2
2
import {
3
3
getResourceFileType ,
4
4
isPutRumEventsCall ,
5
- isResourceSupported
5
+ shuffle
6
6
} from '../../utils/common-utils' ;
7
+ import { ResourceEvent } from '../../events/resource-event' ;
7
8
import { PERFORMANCE_RESOURCE_EVENT_TYPE } from '../utils/constant' ;
8
- import { PerformanceResourceTimingEvent } from '../../events/performance-resource-timing' ;
9
9
import {
10
10
defaultPerformancePluginConfig ,
11
- PerformancePluginConfig ,
12
- PerformanceResourceTimingPolyfill
11
+ PerformancePluginConfig
13
12
} from '../utils/performance-utils' ;
14
13
15
14
export const RESOURCE_EVENT_PLUGIN_ID = 'resource' ;
15
+
16
16
const RESOURCE = 'resource' ;
17
17
18
18
/**
19
19
* This plugin records resource performance timing events generated during every page load/re-load.
20
20
*/
21
21
export class ResourcePlugin extends InternalPlugin {
22
22
private config : PerformancePluginConfig ;
23
- private resourceObserver ? : PerformanceObserver ;
24
- private sampleCount : number ;
23
+ private resourceObserver : PerformanceObserver ;
24
+ private eventCount : number ;
25
25
26
26
constructor ( config ?: Partial < PerformancePluginConfig > ) {
27
27
super ( RESOURCE_EVENT_PLUGIN_ID ) ;
28
28
this . config = { ...defaultPerformancePluginConfig , ...config } ;
29
- this . sampleCount = 0 ;
30
- this . resourceObserver = isResourceSupported ( )
31
- ? new PerformanceObserver ( this . performanceEntryHandler )
32
- : undefined ;
29
+ this . eventCount = 0 ;
30
+ this . resourceObserver = new PerformanceObserver (
31
+ this . performanceEntryHandler
32
+ ) ;
33
33
}
34
34
35
35
enable ( ) : void {
36
36
if ( this . enabled ) {
37
37
return ;
38
38
}
39
39
this . enabled = true ;
40
- this . observe ( ) ;
40
+ this . resourceObserver . observe ( {
41
+ type : RESOURCE ,
42
+ buffered : true
43
+ } ) ;
41
44
}
42
45
43
46
disable ( ) : void {
44
47
if ( ! this . enabled ) {
45
48
return ;
46
49
}
47
50
this . enabled = false ;
48
- this . resourceObserver ?. disconnect ( ) ;
49
- }
50
-
51
- private observe ( ) {
52
- // We need to set `buffered: true`, so the observer also records past
53
- // resource entries. However, there is a limited buffer size, so we may
54
- // not be able to collect all resource entries.
55
- this . resourceObserver ?. observe ( {
56
- type : RESOURCE ,
57
- buffered : true
58
- } ) ;
51
+ this . resourceObserver . disconnect ( ) ;
59
52
}
60
53
61
54
performanceEntryHandler = ( list : PerformanceObserverEntryList ) : void => {
62
- for ( const entry of list . getEntries ( ) ) {
63
- const e = entry as PerformanceResourceTimingPolyfill ;
64
- if (
65
- this . config . ignore ( e ) ||
66
- // Ignore calls to PutRumEvents (i.e., the CloudWatch RUM data
67
- // plane), otherwise we end up in an infinite loop of recording
68
- // PutRumEvents.
69
- isPutRumEventsCall ( e . name , this . context . config . endpointUrl . host )
70
- ) {
71
- continue ;
72
- }
55
+ this . recordPerformanceEntries ( list . getEntries ( ) ) ;
56
+ } ;
73
57
74
- // Sampling logic
75
- const fileType = getResourceFileType ( e . initiatorType ) ;
76
- if ( this . config . recordAllTypes . includes ( fileType ) ) {
77
- // Always record
78
- this . recordResourceEvent ( e ) ;
79
- } else if (
80
- this . sampleCount < this . config . eventLimit &&
81
- this . config . sampleTypes . includes ( fileType )
82
- ) {
83
- // Only sample first N
84
- this . recordResourceEvent ( e ) ;
85
- this . sampleCount ++ ;
86
- }
58
+ recordPerformanceEntries = ( list : PerformanceEntryList ) => {
59
+ const recordAll : PerformanceEntry [ ] = [ ] ;
60
+ const sample : PerformanceEntry [ ] = [ ] ;
61
+
62
+ list . filter ( ( e ) => e . entryType === RESOURCE )
63
+ . filter ( ( e ) => ! this . config . ignore ( e ) )
64
+ . forEach ( ( event ) => {
65
+ const { name, initiatorType } =
66
+ event as PerformanceResourceTiming ;
67
+ const type = getResourceFileType ( name , initiatorType ) ;
68
+ if ( this . config . recordAllTypes . includes ( type ) ) {
69
+ recordAll . push ( event ) ;
70
+ } else if ( this . config . sampleTypes . includes ( type ) ) {
71
+ sample . push ( event ) ;
72
+ }
73
+ } ) ;
74
+
75
+ // Record all events for resources in recordAllTypes
76
+ recordAll . forEach ( ( r ) =>
77
+ this . recordResourceEvent ( r as PerformanceResourceTiming )
78
+ ) ;
79
+
80
+ // Record events from resources in sample until we hit the resource limit
81
+ shuffle ( sample ) ;
82
+ while ( sample . length > 0 && this . eventCount < this . config . eventLimit ) {
83
+ this . recordResourceEvent ( sample . pop ( ) as PerformanceResourceTiming ) ;
84
+ this . eventCount ++ ;
87
85
}
88
86
} ;
89
87
90
- recordResourceEvent = ( e : PerformanceResourceTimingPolyfill ) : void => {
91
- this . context ?. record ( PERFORMANCE_RESOURCE_EVENT_TYPE , {
92
- name : this . context . config . recordResourceUrl ? e . name : undefined ,
93
- entryType : RESOURCE ,
94
- startTime : e . startTime ,
95
- duration : e . duration ,
96
- connectStart : e . connectStart ,
97
- connectEnd : e . connectEnd ,
98
- decodedBodySize : e . decodedBodySize ,
99
- domainLookupEnd : e . domainLookupEnd ,
100
- domainLookupStart : e . domainLookupStart ,
101
- fetchStart : e . fetchStart ,
102
- encodedBodySize : e . encodedBodySize ,
103
- initiatorType : e . initiatorType ,
104
- nextHopProtocol : e . nextHopProtocol ,
105
- redirectEnd : e . redirectEnd ,
106
- redirectStart : e . redirectStart ,
107
- renderBlockingStatus : e . renderBlockingStatus ,
108
- requestStart : e . requestStart ,
109
- responseEnd : e . responseEnd ,
110
- responseStart : e . responseStart ,
111
- secureConnectionStart : e . secureConnectionStart ,
112
- transferSize : e . transferSize ,
113
- workerStart : e . workerStart
114
- } as PerformanceResourceTimingEvent ) ;
88
+ recordResourceEvent = ( {
89
+ name,
90
+ startTime,
91
+ initiatorType,
92
+ duration,
93
+ transferSize
94
+ } : PerformanceResourceTiming ) : void => {
95
+ if (
96
+ isPutRumEventsCall ( name , this . context . config . endpointUrl . hostname )
97
+ ) {
98
+ // Ignore calls to PutRumEvents (i.e., the CloudWatch RUM data
99
+ // plane), otherwise we end up in an infinite loop of recording
100
+ // PutRumEvents.
101
+ return ;
102
+ }
103
+
104
+ if ( this . context ?. record ) {
105
+ const eventData : ResourceEvent = {
106
+ version : '1.0.0' ,
107
+ initiatorType,
108
+ startTime,
109
+ duration,
110
+ fileType : getResourceFileType ( name , initiatorType ) ,
111
+ transferSize
112
+ } ;
113
+ if ( this . context . config . recordResourceUrl ) {
114
+ eventData . targetUrl = name ;
115
+ }
116
+ this . context . record ( PERFORMANCE_RESOURCE_EVENT_TYPE , eventData ) ;
117
+ }
115
118
} ;
116
119
117
120
protected onload ( ) : void {
118
- this . observe ( ) ;
121
+ // We need to set `buffered: true`, so the observer also records past
122
+ // resource entries. However, there is a limited buffer size, so we may
123
+ // not be able to collect all resource entries.
124
+ this . resourceObserver . observe ( {
125
+ type : RESOURCE ,
126
+ buffered : true
127
+ } ) ;
119
128
}
120
129
}
0 commit comments