1
1
import { getCurrentHub , Hub } from '@sentry/browser' ;
2
- import { Span , Transaction } from '@sentry/types' ;
2
+ import { Integration , IntegrationClass , Span , Transaction } from '@sentry/types' ;
3
3
import { timestampWithMs } from '@sentry/utils' ;
4
4
import * as hoistNonReactStatic from 'hoist-non-react-statics' ;
5
5
import * as React from 'react' ;
6
6
7
7
export const UNKNOWN_COMPONENT = 'unknown' ;
8
8
9
+ const TRACING_GETTER = ( {
10
+ id : 'Tracing' ,
11
+ } as any ) as IntegrationClass < Integration > ;
12
+
13
+ let globalTracingIntegration : Integration | null = null ;
14
+ /** @deprecated remove when @sentry/apm no longer used */
15
+ const getTracingIntegration = ( ) => {
16
+ if ( globalTracingIntegration ) {
17
+ return globalTracingIntegration ;
18
+ }
19
+
20
+ globalTracingIntegration = getCurrentHub ( ) . getIntegration ( TRACING_GETTER ) ;
21
+ return globalTracingIntegration ;
22
+ } ;
23
+
24
+ /**
25
+ * pushActivity creates an new react activity.
26
+ * Is a no-op if Tracing integration is not valid
27
+ * @param name displayName of component that started activity
28
+ * @deprecated remove when @sentry/apm no longer used
29
+ */
30
+ function pushActivity ( name : string , op : string ) : number | null {
31
+ if ( globalTracingIntegration === null ) {
32
+ return null ;
33
+ }
34
+
35
+ // tslint:disable-next-line:no-unsafe-any
36
+ return ( globalTracingIntegration as any ) . constructor . pushActivity ( name , {
37
+ description : `<${ name } >` ,
38
+ op : `react.${ op } ` ,
39
+ } ) ;
40
+ }
41
+
42
+ /**
43
+ * popActivity removes a React activity.
44
+ * Is a no-op if Tracing integration is not valid.
45
+ * @param activity id of activity that is being popped
46
+ * @deprecated remove when @sentry/apm no longer used
47
+ */
48
+ function popActivity ( activity : number | null ) : void {
49
+ if ( activity === null || globalTracingIntegration === null ) {
50
+ return ;
51
+ }
52
+
53
+ // tslint:disable-next-line:no-unsafe-any
54
+ ( globalTracingIntegration as any ) . constructor . popActivity ( activity ) ;
55
+ }
56
+
57
+ /**
58
+ * Obtain a span given an activity id.
59
+ * Is a no-op if Tracing integration is not valid.
60
+ * @param activity activity id associated with obtained span
61
+ * @deprecated remove when @sentry/apm no longer used
62
+ */
63
+ function getActivitySpan ( activity : number | null ) : Span | undefined {
64
+ if ( activity === null || globalTracingIntegration === null ) {
65
+ return undefined ;
66
+ }
67
+
68
+ // tslint:disable-next-line:no-unsafe-any
69
+ return ( globalTracingIntegration as any ) . constructor . getActivitySpan ( activity ) as Span | undefined ;
70
+ }
71
+
9
72
export type ProfilerProps = {
10
73
// The name of the component being profiled.
11
74
name : string ;
@@ -25,8 +88,10 @@ export type ProfilerProps = {
25
88
* spans based on component lifecycles.
26
89
*/
27
90
class Profiler extends React . Component < ProfilerProps > {
28
- // The span representing how long it takes to mount a component
29
- public mountSpan : Span | undefined = undefined ;
91
+ // The activity representing how long it takes to mount a component.
92
+ private _mountActivity : number | null = null ;
93
+ // The span of the mount activity
94
+ private _mountSpan : Span | undefined = undefined ;
30
95
31
96
public static defaultProps : Partial < ProfilerProps > = {
32
97
disabled : false ,
@@ -42,35 +107,48 @@ class Profiler extends React.Component<ProfilerProps> {
42
107
return ;
43
108
}
44
109
45
- const activeTransaction = getActiveTransaction ( ) ;
46
- if ( activeTransaction ) {
47
- this . mountSpan = activeTransaction . startChild ( {
48
- description : `<${ name } >` ,
49
- op : 'react.mount' ,
50
- } ) ;
110
+ // If they are using @sentry /apm, we need to push/pop activities
111
+ // tslint:disable-next-line: deprecation
112
+ if ( getTracingIntegration ( ) ) {
113
+ // tslint:disable-next-line: deprecation
114
+ this . _mountActivity = pushActivity ( name , 'mount' ) ;
115
+ } else {
116
+ const activeTransaction = getActiveTransaction ( ) ;
117
+ if ( activeTransaction ) {
118
+ this . _mountSpan = activeTransaction . startChild ( {
119
+ description : `<${ name } >` ,
120
+ op : 'react.mount' ,
121
+ } ) ;
122
+ }
51
123
}
52
124
}
53
125
54
126
// If a component mounted, we can finish the mount activity.
55
127
public componentDidMount ( ) : void {
56
- if ( this . mountSpan ) {
57
- this . mountSpan . finish ( ) ;
128
+ if ( this . _mountSpan ) {
129
+ this . _mountSpan . finish ( ) ;
130
+ } else {
131
+ // tslint:disable-next-line: deprecation
132
+ this . _mountSpan = getActivitySpan ( this . _mountActivity ) ;
133
+ // tslint:disable-next-line: deprecation
134
+ popActivity ( this . _mountActivity ) ;
135
+ this . _mountActivity = null ;
58
136
}
59
137
}
60
138
61
139
public componentDidUpdate ( { updateProps, includeUpdates = true } : ProfilerProps ) : void {
62
140
// Only generate an update span if hasUpdateSpan is true, if there is a valid mountSpan,
63
141
// and if the updateProps have changed. It is ok to not do a deep equality check here as it is expensive.
64
142
// We are just trying to give baseline clues for further investigation.
65
- if ( includeUpdates && this . mountSpan && updateProps !== this . props . updateProps ) {
143
+ if ( includeUpdates && this . _mountSpan && updateProps !== this . props . updateProps ) {
66
144
// See what props haved changed between the previous props, and the current props. This is
67
145
// set as data on the span. We just store the prop keys as the values could be potenially very large.
68
146
const changedProps = Object . keys ( updateProps ) . filter ( k => updateProps [ k ] !== this . props . updateProps [ k ] ) ;
69
147
if ( changedProps . length > 0 ) {
70
148
// The update span is a point in time span with 0 duration, just signifying that the component
71
149
// has been updated.
72
150
const now = timestampWithMs ( ) ;
73
- this . mountSpan . startChild ( {
151
+ this . _mountSpan . startChild ( {
74
152
data : {
75
153
changedProps,
76
154
} ,
@@ -88,14 +166,14 @@ class Profiler extends React.Component<ProfilerProps> {
88
166
public componentWillUnmount ( ) : void {
89
167
const { name, includeRender = true } = this . props ;
90
168
91
- if ( this . mountSpan && includeRender ) {
169
+ if ( this . _mountSpan && includeRender ) {
92
170
// If we were able to obtain the spanId of the mount activity, we should set the
93
171
// next activity as a child to the component mount activity.
94
- this . mountSpan . startChild ( {
172
+ this . _mountSpan . startChild ( {
95
173
description : `<${ name } >` ,
96
174
endTimestamp : timestampWithMs ( ) ,
97
175
op : `react.render` ,
98
- startTimestamp : this . mountSpan . endTimestamp ,
176
+ startTimestamp : this . _mountSpan . endTimestamp ,
99
177
} ) ;
100
178
}
101
179
}
0 commit comments