1
1
import { getCurrentHub } from '@sentry/core' ;
2
2
import type { Event , EventProcessor , Hub , Integration } from '@sentry/types' ;
3
3
import { logger } from '@sentry/utils' ;
4
- import { LRUMap } from 'lru_map' ;
5
4
5
+ import { addExtensionMethods } from './hubextensions' ;
6
6
import type { ProcessedJSSelfProfile } from './jsSelfProfiling' ;
7
7
import type { ProfiledEvent } from './utils' ;
8
8
import { createProfilingEventEnvelope } from './utils' ;
9
9
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 ) ;
15
80
/**
16
81
* Browser profiling integration. Stores any event that has contexts["profile"]["profile_id"]
17
82
* 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);
21
86
*/
22
87
export class BrowserProfilingIntegration implements Integration {
23
88
public readonly name : string = 'BrowserProfilingIntegration' ;
24
- public getCurrentHub ?: ( ) => Hub = undefined ;
25
89
26
90
/**
27
91
* @inheritDoc
28
92
*/
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
31
101
addGlobalEventProcessor ( this . handleGlobalEvent . bind ( this ) ) ;
32
102
}
33
103
34
104
/**
35
105
* @inheritDoc
36
106
*/
37
107
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' ] ;
39
109
40
- if ( profile_id && typeof profile_id === 'string' ) {
110
+ if ( profileId && typeof profileId === 'string' ) {
41
111
if ( __DEBUG_BUILD__ ) {
42
112
logger . log ( '[Profiling] Profiling event found, caching it.' ) ;
43
113
}
44
- PROFILING_EVENT_CACHE . set ( profile_id , event ) ;
114
+ PROFILING_EVENT_CACHE . add ( profileId , event ) ;
45
115
}
46
116
47
117
return event ;
@@ -53,8 +123,8 @@ export class BrowserProfilingIntegration implements Integration {
53
123
* If the profiled transaction event is found, we use the profiled transaction event and profile
54
124
* to construct a profile type envelope and send it to Sentry.
55
125
*/
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 ) ;
58
128
59
129
if ( ! event ) {
60
130
// We could not find a corresponding transaction event for this profile.
@@ -112,7 +182,7 @@ export function sendProfile(profile_id: string, profile: ProcessedJSSelfProfile)
112
182
const envelope = createProfilingEventEnvelope ( event as ProfiledEvent , dsn ) ;
113
183
114
184
// 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 ) ;
116
186
117
187
if ( ! envelope ) {
118
188
if ( __DEBUG_BUILD__ ) {
0 commit comments