@@ -2,119 +2,128 @@ import { InternalPlugin } from '../InternalPlugin';
22import {
33 getResourceFileType ,
44 isPutRumEventsCall ,
5- isResourceSupported
5+ shuffle
66} from '../../utils/common-utils' ;
7+ import { ResourceEvent } from '../../events/resource-event' ;
78import { PERFORMANCE_RESOURCE_EVENT_TYPE } from '../utils/constant' ;
8- import { PerformanceResourceTimingEvent } from '../../events/performance-resource-timing' ;
99import {
1010 defaultPerformancePluginConfig ,
11- PerformancePluginConfig ,
12- PerformanceResourceTimingPolyfill
11+ PerformancePluginConfig
1312} from '../utils/performance-utils' ;
1413
1514export const RESOURCE_EVENT_PLUGIN_ID = 'resource' ;
15+
1616const RESOURCE = 'resource' ;
1717
1818/**
1919 * This plugin records resource performance timing events generated during every page load/re-load.
2020 */
2121export class ResourcePlugin extends InternalPlugin {
2222 private config : PerformancePluginConfig ;
23- private resourceObserver ? : PerformanceObserver ;
24- private sampleCount : number ;
23+ private resourceObserver : PerformanceObserver ;
24+ private eventCount : number ;
2525
2626 constructor ( config ?: Partial < PerformancePluginConfig > ) {
2727 super ( RESOURCE_EVENT_PLUGIN_ID ) ;
2828 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+ ) ;
3333 }
3434
3535 enable ( ) : void {
3636 if ( this . enabled ) {
3737 return ;
3838 }
3939 this . enabled = true ;
40- this . observe ( ) ;
40+ this . resourceObserver . observe ( {
41+ type : RESOURCE ,
42+ buffered : true
43+ } ) ;
4144 }
4245
4346 disable ( ) : void {
4447 if ( ! this . enabled ) {
4548 return ;
4649 }
4750 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 ( ) ;
5952 }
6053
6154 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+ } ;
7357
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 ++ ;
8785 }
8886 } ;
8987
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+ }
115118 } ;
116119
117120 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+ } ) ;
119128 }
120129}
0 commit comments