11import { getCurrentHub , Hub } from '@sentry/browser' ;
2- import { Span , Transaction } from '@sentry/types' ;
2+ import { Integration , IntegrationClass , Span , Transaction } from '@sentry/types' ;
33import { timestampWithMs } from '@sentry/utils' ;
44import * as hoistNonReactStatic from 'hoist-non-react-statics' ;
55import * as React from 'react' ;
66
77export const UNKNOWN_COMPONENT = 'unknown' ;
88
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+
972export type ProfilerProps = {
1073 // The name of the component being profiled.
1174 name : string ;
@@ -25,8 +88,10 @@ export type ProfilerProps = {
2588 * spans based on component lifecycles.
2689 */
2790class 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 ;
3095
3196 public static defaultProps : Partial < ProfilerProps > = {
3297 disabled : false ,
@@ -42,35 +107,48 @@ class Profiler extends React.Component<ProfilerProps> {
42107 return ;
43108 }
44109
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+ }
51123 }
52124 }
53125
54126 // If a component mounted, we can finish the mount activity.
55127 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 ;
58136 }
59137 }
60138
61139 public componentDidUpdate ( { updateProps, includeUpdates = true } : ProfilerProps ) : void {
62140 // Only generate an update span if hasUpdateSpan is true, if there is a valid mountSpan,
63141 // and if the updateProps have changed. It is ok to not do a deep equality check here as it is expensive.
64142 // 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 ) {
66144 // See what props haved changed between the previous props, and the current props. This is
67145 // set as data on the span. We just store the prop keys as the values could be potenially very large.
68146 const changedProps = Object . keys ( updateProps ) . filter ( k => updateProps [ k ] !== this . props . updateProps [ k ] ) ;
69147 if ( changedProps . length > 0 ) {
70148 // The update span is a point in time span with 0 duration, just signifying that the component
71149 // has been updated.
72150 const now = timestampWithMs ( ) ;
73- this . mountSpan . startChild ( {
151+ this . _mountSpan . startChild ( {
74152 data : {
75153 changedProps,
76154 } ,
@@ -88,14 +166,14 @@ class Profiler extends React.Component<ProfilerProps> {
88166 public componentWillUnmount ( ) : void {
89167 const { name, includeRender = true } = this . props ;
90168
91- if ( this . mountSpan && includeRender ) {
169+ if ( this . _mountSpan && includeRender ) {
92170 // If we were able to obtain the spanId of the mount activity, we should set the
93171 // next activity as a child to the component mount activity.
94- this . mountSpan . startChild ( {
172+ this . _mountSpan . startChild ( {
95173 description : `<${ name } >` ,
96174 endTimestamp : timestampWithMs ( ) ,
97175 op : `react.render` ,
98- startTimestamp : this . mountSpan . endTimestamp ,
176+ startTimestamp : this . _mountSpan . endTimestamp ,
99177 } ) ;
100178 }
101179 }
0 commit comments