/
profiler.tsx
114 lines (93 loc) · 3.33 KB
/
profiler.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { getCurrentHub } from '@sentry/browser';
import { Integration, IntegrationClass } from '@sentry/types';
import { logger } from '@sentry/utils';
import * as hoistNonReactStatic from 'hoist-non-react-statics';
import * as React from 'react';
export const UNKNOWN_COMPONENT = 'unknown';
const TRACING_GETTER = ({
id: 'Tracing',
} as any) as IntegrationClass<Integration>;
/**
*
* Based on implementation from Preact:
* https:github.com/preactjs/preact/blob/9a422017fec6dab287c77c3aef63c7b2fef0c7e1/hooks/src/index.js#L301-L313
*
* Schedule a callback to be invoked after the browser has a chance to paint a new frame.
* Do this by combining requestAnimationFrame (rAF) + setTimeout to invoke a callback after
* the next browser frame.
*
* Also, schedule a timeout in parallel to the the rAF to ensure the callback is invoked
* even if RAF doesn't fire (for example if the browser tab is not visible)
*
* This is what we use to tell if a component activity has finished
*
*/
function afterNextFrame(callback: Function): void {
let timeout: number | undefined;
let raf: number;
const done = () => {
window.clearTimeout(timeout);
window.cancelAnimationFrame(raf);
window.setTimeout(callback);
};
raf = window.requestAnimationFrame(done);
timeout = window.setTimeout(done, 100);
}
const getInitActivity = (name: string): number | null => {
const tracingIntegration = getCurrentHub().getIntegration(TRACING_GETTER);
if (tracingIntegration !== null) {
// tslint:disable-next-line:no-unsafe-any
return (tracingIntegration as any).constructor.pushActivity(name, {
description: `<${name}>`,
op: 'react',
});
}
logger.warn(
`Unable to profile component ${name} due to invalid Tracing Integration. Please make sure to setup the Tracing integration.`,
);
return null;
};
export type ProfilerProps = {
name: string;
};
class Profiler extends React.Component<ProfilerProps> {
public activity: number | null;
public constructor(props: ProfilerProps) {
super(props);
this.activity = getInitActivity(this.props.name);
}
public componentDidMount(): void {
afterNextFrame(this.finishProfile);
}
public componentWillUnmount(): void {
afterNextFrame(this.finishProfile);
}
public finishProfile = () => {
if (!this.activity) {
return;
}
const tracingIntegration = getCurrentHub().getIntegration(TRACING_GETTER);
if (tracingIntegration !== null) {
// tslint:disable-next-line:no-unsafe-any
(tracingIntegration as any).constructor.popActivity(this.activity);
this.activity = null;
}
};
public render(): React.ReactNode {
return this.props.children;
}
}
function withProfiler<P extends object>(WrappedComponent: React.ComponentType<P>): React.FC<P> {
const componentDisplayName = WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;
const Wrapped: React.FC<P> = (props: P) => (
<Profiler name={componentDisplayName}>
<WrappedComponent {...props} />
</Profiler>
);
Wrapped.displayName = `profiler(${componentDisplayName})`;
// Copy over static methods from Wrapped component to Profiler HOC
// See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
hoistNonReactStatic(Wrapped, WrappedComponent);
return Wrapped;
}
export { withProfiler, Profiler };