profiler.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const browser = require('@sentry/browser');
  3. const core = require('@sentry/core');
  4. const utils = require('@sentry/utils');
  5. const hoistNonReactStatics = require('hoist-non-react-statics');
  6. const React = require('react');
  7. const constants = require('./constants.js');
  8. const _interopDefault = e => e && e.__esModule ? e.default : e;
  9. function _interopNamespace(e) {
  10. if (e && e.__esModule) return e;
  11. const n = Object.create(null);
  12. if (e) {
  13. for (const k in e) {
  14. n[k] = e[k];
  15. }
  16. }
  17. n.default = e;
  18. return n;
  19. }
  20. const hoistNonReactStatics__default = /*#__PURE__*/_interopDefault(hoistNonReactStatics);
  21. const React__namespace = /*#__PURE__*/_interopNamespace(React);
  22. const _jsxFileName = "/home/runner/work/sentry-javascript/sentry-javascript/packages/react/src/profiler.tsx";
  23. const UNKNOWN_COMPONENT = 'unknown';
  24. /**
  25. * The Profiler component leverages Sentry's Tracing integration to generate
  26. * spans based on component lifecycles.
  27. */
  28. class Profiler extends React__namespace.Component {
  29. /**
  30. * The span of the mount activity
  31. * Made protected for the React Native SDK to access
  32. */
  33. /**
  34. * The span that represents the duration of time between shouldComponentUpdate and componentDidUpdate
  35. */
  36. // eslint-disable-next-line @typescript-eslint/member-ordering
  37. static __initStatic() {this.defaultProps = {
  38. disabled: false,
  39. includeRender: true,
  40. includeUpdates: true,
  41. };}
  42. constructor(props) {
  43. super(props);
  44. const { name, disabled = false } = this.props;
  45. if (disabled) {
  46. return;
  47. }
  48. this._mountSpan = browser.startInactiveSpan({
  49. name: `<${name}>`,
  50. onlyIfParent: true,
  51. op: constants.REACT_MOUNT_OP,
  52. origin: 'auto.ui.react.profiler',
  53. attributes: { 'ui.component_name': name },
  54. });
  55. }
  56. // If a component mounted, we can finish the mount activity.
  57. componentDidMount() {
  58. if (this._mountSpan) {
  59. this._mountSpan.end();
  60. }
  61. }
  62. shouldComponentUpdate({ updateProps, includeUpdates = true }) {
  63. // Only generate an update span if includeUpdates is true, if there is a valid mountSpan,
  64. // and if the updateProps have changed. It is ok to not do a deep equality check here as it is expensive.
  65. // We are just trying to give baseline clues for further investigation.
  66. if (includeUpdates && this._mountSpan && updateProps !== this.props.updateProps) {
  67. // See what props haved changed between the previous props, and the current props. This is
  68. // set as data on the span. We just store the prop keys as the values could be potenially very large.
  69. const changedProps = Object.keys(updateProps).filter(k => updateProps[k] !== this.props.updateProps[k]);
  70. if (changedProps.length > 0) {
  71. const now = utils.timestampInSeconds();
  72. this._updateSpan = core.withActiveSpan(this._mountSpan, () => {
  73. return browser.startInactiveSpan({
  74. name: `<${this.props.name}>`,
  75. onlyIfParent: true,
  76. op: constants.REACT_UPDATE_OP,
  77. origin: 'auto.ui.react.profiler',
  78. startTimestamp: now,
  79. attributes: {
  80. 'ui.component_name': this.props.name,
  81. 'ui.react.changed_props': changedProps,
  82. },
  83. });
  84. });
  85. }
  86. }
  87. return true;
  88. }
  89. componentDidUpdate() {
  90. if (this._updateSpan) {
  91. this._updateSpan.end();
  92. this._updateSpan = undefined;
  93. }
  94. }
  95. // If a component is unmounted, we can say it is no longer on the screen.
  96. // This means we can finish the span representing the component render.
  97. componentWillUnmount() {
  98. const endTimestamp = utils.timestampInSeconds();
  99. const { name, includeRender = true } = this.props;
  100. if (this._mountSpan && includeRender) {
  101. const startTimestamp = core.spanToJSON(this._mountSpan).timestamp;
  102. core.withActiveSpan(this._mountSpan, () => {
  103. const renderSpan = browser.startInactiveSpan({
  104. onlyIfParent: true,
  105. name: `<${name}>`,
  106. op: constants.REACT_RENDER_OP,
  107. origin: 'auto.ui.react.profiler',
  108. startTimestamp,
  109. attributes: { 'ui.component_name': name },
  110. });
  111. if (renderSpan) {
  112. // Have to cast to Span because the type of _mountSpan is Span | undefined
  113. // and not getting narrowed properly
  114. renderSpan.end(endTimestamp);
  115. }
  116. });
  117. }
  118. }
  119. render() {
  120. return this.props.children;
  121. }
  122. } Profiler.__initStatic();
  123. /**
  124. * withProfiler is a higher order component that wraps a
  125. * component in a {@link Profiler} component. It is recommended that
  126. * the higher order component be used over the regular {@link Profiler} component.
  127. *
  128. * @param WrappedComponent component that is wrapped by Profiler
  129. * @param options the {@link ProfilerProps} you can pass into the Profiler
  130. */
  131. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  132. function withProfiler(
  133. WrappedComponent,
  134. // We do not want to have `updateProps` given in options, it is instead filled through the HOC.
  135. options,
  136. ) {
  137. const componentDisplayName =
  138. (options && options.name) || WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;
  139. const Wrapped = (props) => (
  140. React__namespace.createElement(Profiler, { ...options, name: componentDisplayName, updateProps: props, __self: this, __source: {fileName: _jsxFileName, lineNumber: 159}}
  141. , React__namespace.createElement(WrappedComponent, { ...props, __self: this, __source: {fileName: _jsxFileName, lineNumber: 160}} )
  142. )
  143. );
  144. Wrapped.displayName = `profiler(${componentDisplayName})`;
  145. // Copy over static methods from Wrapped component to Profiler HOC
  146. // See: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over
  147. hoistNonReactStatics__default(Wrapped, WrappedComponent);
  148. return Wrapped;
  149. }
  150. /**
  151. *
  152. * `useProfiler` is a React hook that profiles a React component.
  153. *
  154. * Requires React 16.8 or above.
  155. * @param name displayName of component being profiled
  156. */
  157. function useProfiler(
  158. name,
  159. options = {
  160. disabled: false,
  161. hasRenderSpan: true,
  162. },
  163. ) {
  164. const [mountSpan] = React__namespace.useState(() => {
  165. if (options && options.disabled) {
  166. return undefined;
  167. }
  168. return browser.startInactiveSpan({
  169. name: `<${name}>`,
  170. onlyIfParent: true,
  171. op: constants.REACT_MOUNT_OP,
  172. origin: 'auto.ui.react.profiler',
  173. attributes: { 'ui.component_name': name },
  174. });
  175. });
  176. React__namespace.useEffect(() => {
  177. if (mountSpan) {
  178. mountSpan.end();
  179. }
  180. return () => {
  181. if (mountSpan && options.hasRenderSpan) {
  182. const startTimestamp = core.spanToJSON(mountSpan).timestamp;
  183. const endTimestamp = utils.timestampInSeconds();
  184. const renderSpan = browser.startInactiveSpan({
  185. name: `<${name}>`,
  186. onlyIfParent: true,
  187. op: constants.REACT_RENDER_OP,
  188. origin: 'auto.ui.react.profiler',
  189. startTimestamp,
  190. attributes: { 'ui.component_name': name },
  191. });
  192. if (renderSpan) {
  193. // Have to cast to Span because the type of _mountSpan is Span | undefined
  194. // and not getting narrowed properly
  195. renderSpan.end(endTimestamp);
  196. }
  197. }
  198. };
  199. // We only want this to run once.
  200. // eslint-disable-next-line react-hooks/exhaustive-deps
  201. }, []);
  202. }
  203. exports.Profiler = Profiler;
  204. exports.UNKNOWN_COMPONENT = UNKNOWN_COMPONENT;
  205. exports.useProfiler = useProfiler;
  206. exports.withProfiler = withProfiler;
  207. //# sourceMappingURL=profiler.js.map