11import { getCurrentHub } from '@sentry/core' ;
22import type { Event , EventProcessor , Hub , Integration } from '@sentry/types' ;
33import { logger } from '@sentry/utils' ;
4- import { LRUMap } from 'lru_map' ;
54
5+ import { addExtensionMethods } from './hubextensions' ;
66import type { ProcessedJSSelfProfile } from './jsSelfProfiling' ;
77import type { ProfiledEvent } from './utils' ;
88import { createProfilingEventEnvelope } from './utils' ;
99
10- // We need this integration in order to actually send data to Sentry. We hook into the event processor
11- // and inspect each event to see if it is a transaction event and if that transaction event
12- // contains a profile on it's metadata. If that is the case, we create a profiling event envelope
13- // and delete the profile from the transaction metadata.
14- export const PROFILING_EVENT_CACHE = new LRUMap < string , Event > ( 20 ) ;
10+ /**
11+ * Creates a simple cache that evicts keys in fifo order
12+ * @param size {Number}
13+ */
14+ export function makeProfilingCache < Key extends string , Value extends Event > (
15+ size : number ,
16+ ) : {
17+ get : ( key : Key ) => Value | undefined ;
18+ add : ( key : Key , value : Value ) => void ;
19+ delete : ( key : Key ) => boolean ;
20+ clear : ( ) => void ;
21+ size : ( ) => number ;
22+ } {
23+ // Maintain a fifo queue of keys, we cannot rely on Object.keys as the browser may not support it.
24+ let evictionOrder : Key [ ] = [ ] ;
25+ let cache : Record < string , Value > = { } ;
26+
27+ return {
28+ add ( key : Key , value : Value ) {
29+ while ( evictionOrder . length >= size ) {
30+ // shift is O(n) but this is small size and only happens if we are
31+ // exceeding the cache size so it should be fine.
32+ const evictCandidate = evictionOrder . shift ( ) ;
33+
34+ if ( evictCandidate !== undefined ) {
35+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
36+ delete cache [ evictCandidate ] ;
37+ }
38+ }
39+
40+ // in case we have a collision, delete the old key.
41+ if ( cache [ key ] ) {
42+ this . delete ( key ) ;
43+ }
44+
45+ evictionOrder . push ( key ) ;
46+ cache [ key ] = value ;
47+ } ,
48+ clear ( ) {
49+ cache = { } ;
50+ evictionOrder = [ ] ;
51+ } ,
52+ get ( key : Key ) : Value | undefined {
53+ return cache [ key ] ;
54+ } ,
55+ size ( ) {
56+ return evictionOrder . length ;
57+ } ,
58+ // Delete cache key and return true if it existed, false otherwise.
59+ delete ( key : Key ) : boolean {
60+ if ( ! cache [ key ] ) {
61+ return false ;
62+ }
63+
64+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
65+ delete cache [ key ] ;
66+
67+ for ( let i = 0 ; i < evictionOrder . length ; i ++ ) {
68+ if ( evictionOrder [ i ] === key ) {
69+ evictionOrder . splice ( i , 1 ) ;
70+ break ;
71+ }
72+ }
73+
74+ return true ;
75+ } ,
76+ } ;
77+ }
78+
79+ export const PROFILING_EVENT_CACHE = makeProfilingCache < string , Event > ( 20 ) ;
1580/**
1681 * Browser profiling integration. Stores any event that has contexts["profile"]["profile_id"]
1782 * This exists because we do not want to await async profiler.stop calls as transaction.finish is called
@@ -21,27 +86,32 @@ export const PROFILING_EVENT_CACHE = new LRUMap<string, Event>(20);
2186 */
2287export class BrowserProfilingIntegration implements Integration {
2388 public readonly name : string = 'BrowserProfilingIntegration' ;
24- public getCurrentHub ?: ( ) => Hub = undefined ;
2589
2690 /**
2791 * @inheritDoc
2892 */
29- public setupOnce ( addGlobalEventProcessor : ( callback : EventProcessor ) => void , getCurrentHub : ( ) => Hub ) : void {
30- this . getCurrentHub = getCurrentHub ;
93+ public setupOnce ( addGlobalEventProcessor : ( callback : EventProcessor ) => void , _getCurrentHub : ( ) => Hub ) : void {
94+ // Patching the hub to add the extension methods.
95+ // Warning: we have an implicit dependency on import order and we will fail patching if the constructor of
96+ // BrowserProfilingIntegration is called before @sentry /tracing is imported. This is because we need to patch
97+ // the methods of @sentry /tracing which are patched as a side effect of importing @sentry /tracing.
98+ addExtensionMethods ( ) ;
99+
100+ // Add our event processor
31101 addGlobalEventProcessor ( this . handleGlobalEvent . bind ( this ) ) ;
32102 }
33103
34104 /**
35105 * @inheritDoc
36106 */
37107 public handleGlobalEvent ( event : Event ) : Event {
38- const profile_id = event . contexts && event . contexts [ 'profile' ] && event . contexts [ 'profile' ] [ 'profile_id' ] ;
108+ const profileId = event . contexts && event . contexts [ 'profile' ] && event . contexts [ 'profile' ] [ 'profile_id' ] ;
39109
40- if ( profile_id && typeof profile_id === 'string' ) {
110+ if ( profileId && typeof profileId === 'string' ) {
41111 if ( __DEBUG_BUILD__ ) {
42112 logger . log ( '[Profiling] Profiling event found, caching it.' ) ;
43113 }
44- PROFILING_EVENT_CACHE . set ( profile_id , event ) ;
114+ PROFILING_EVENT_CACHE . add ( profileId , event ) ;
45115 }
46116
47117 return event ;
@@ -53,8 +123,8 @@ export class BrowserProfilingIntegration implements Integration {
53123 * If the profiled transaction event is found, we use the profiled transaction event and profile
54124 * to construct a profile type envelope and send it to Sentry.
55125 */
56- export function sendProfile ( profile_id : string , profile : ProcessedJSSelfProfile ) : void {
57- const event = PROFILING_EVENT_CACHE . get ( profile_id ) ;
126+ export function sendProfile ( profileId : string , profile : ProcessedJSSelfProfile ) : void {
127+ const event = PROFILING_EVENT_CACHE . get ( profileId ) ;
58128
59129 if ( ! event ) {
60130 // We could not find a corresponding transaction event for this profile.
@@ -112,7 +182,7 @@ export function sendProfile(profile_id: string, profile: ProcessedJSSelfProfile)
112182 const envelope = createProfilingEventEnvelope ( event as ProfiledEvent , dsn ) ;
113183
114184 // Evict event from the cache - we want to prevent the LRU cache from prioritizing already sent events over new ones.
115- PROFILING_EVENT_CACHE . delete ( profile_id ) ;
185+ PROFILING_EVENT_CACHE . delete ( profileId ) ;
116186
117187 if ( ! envelope ) {
118188 if ( __DEBUG_BUILD__ ) {
0 commit comments