Skip to content

ref(react): Use new span API in React Profiler #10104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export {
startSession,
endSession,
captureSession,
withActiveSpan,
} from './exports';
export {
// eslint-disable-next-line deprecation/deprecation
Expand Down
123 changes: 54 additions & 69 deletions packages/react/src/profiler.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! ❤️ less lint exceptions...

/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Hub } from '@sentry/browser';
import { getCurrentHub } from '@sentry/browser';
import { spanToJSON } from '@sentry/core';
import type { Span, Transaction } from '@sentry/types';
import { startInactiveSpan } from '@sentry/browser';
import { spanToJSON, withActiveSpan } from '@sentry/core';
import type { Span } from '@sentry/types';
import { timestampInSeconds } from '@sentry/utils';
import hoistNonReactStatics from 'hoist-non-react-statics';
import * as React from 'react';
Expand Down Expand Up @@ -58,16 +55,12 @@ class Profiler extends React.Component<ProfilerProps> {
return;
}

const activeTransaction = getActiveTransaction();
if (activeTransaction) {
// eslint-disable-next-line deprecation/deprecation
this._mountSpan = activeTransaction.startChild({
description: `<${name}>`,
op: REACT_MOUNT_OP,
origin: 'auto.ui.react.profiler',
data: { 'ui.component_name': name },
});
}
this._mountSpan = startInactiveSpan({
name: `<${name}>`,
op: REACT_MOUNT_OP,
origin: 'auto.ui.react.profiler',
attributes: { 'ui.component_name': name },
});
}

// If a component mounted, we can finish the mount activity.
Expand All @@ -87,16 +80,17 @@ class Profiler extends React.Component<ProfilerProps> {
const changedProps = Object.keys(updateProps).filter(k => updateProps[k] !== this.props.updateProps[k]);
if (changedProps.length > 0) {
const now = timestampInSeconds();
// eslint-disable-next-line deprecation/deprecation
this._updateSpan = this._mountSpan.startChild({
data: {
changedProps,
'ui.component_name': this.props.name,
},
description: `<${this.props.name}>`,
op: REACT_UPDATE_OP,
origin: 'auto.ui.react.profiler',
startTimestamp: now,
this._updateSpan = withActiveSpan(this._mountSpan, () => {
return startInactiveSpan({
name: `<${this.props.name}>`,
op: REACT_UPDATE_OP,
origin: 'auto.ui.react.profiler',
startTimestamp: now,
attributes: {
'ui.component_name': this.props.name,
'ui.react.changed_props': changedProps,
},
});
});
}
}
Expand All @@ -114,19 +108,24 @@ class Profiler extends React.Component<ProfilerProps> {
// If a component is unmounted, we can say it is no longer on the screen.
// This means we can finish the span representing the component render.
public componentWillUnmount(): void {
const endTimestamp = timestampInSeconds();
const { name, includeRender = true } = this.props;

if (this._mountSpan && includeRender) {
// If we were able to obtain the spanId of the mount activity, we should set the
// next activity as a child to the component mount activity.
// eslint-disable-next-line deprecation/deprecation
this._mountSpan.startChild({
description: `<${name}>`,
endTimestamp: timestampInSeconds(),
op: REACT_RENDER_OP,
origin: 'auto.ui.react.profiler',
startTimestamp: spanToJSON(this._mountSpan).timestamp,
data: { 'ui.component_name': name },
const startTimestamp = spanToJSON(this._mountSpan).timestamp;
withActiveSpan(this._mountSpan, () => {
const renderSpan = startInactiveSpan({
name: `<${name}>`,
op: REACT_RENDER_OP,
origin: 'auto.ui.react.profiler',
startTimestamp,
attributes: { 'ui.component_name': name },
});
if (renderSpan) {
// Have to cast to Span because the type of _mountSpan is Span | undefined
// and not getting narrowed properly
renderSpan.end(endTimestamp);
}
});
}
}
Expand All @@ -144,6 +143,7 @@ class Profiler extends React.Component<ProfilerProps> {
* @param WrappedComponent component that is wrapped by Profiler
* @param options the {@link ProfilerProps} you can pass into the Profiler
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function withProfiler<P extends Record<string, any>>(
WrappedComponent: React.ComponentType<P>,
// We do not want to have `updateProps` given in options, it is instead filled through the HOC.
Expand Down Expand Up @@ -185,18 +185,12 @@ function useProfiler(
return undefined;
}

const activeTransaction = getActiveTransaction();
if (activeTransaction) {
// eslint-disable-next-line deprecation/deprecation
return activeTransaction.startChild({
description: `<${name}>`,
op: REACT_MOUNT_OP,
origin: 'auto.ui.react.profiler',
data: { 'ui.component_name': name },
});
}

return undefined;
return startInactiveSpan({
name: `<${name}>`,
op: REACT_MOUNT_OP,
origin: 'auto.ui.react.profiler',
attributes: { 'ui.component_name': name },
});
});

React.useEffect(() => {
Expand All @@ -206,15 +200,21 @@ function useProfiler(

return (): void => {
if (mountSpan && options.hasRenderSpan) {
// eslint-disable-next-line deprecation/deprecation
mountSpan.startChild({
description: `<${name}>`,
endTimestamp: timestampInSeconds(),
const startTimestamp = spanToJSON(mountSpan).timestamp;
const endTimestamp = timestampInSeconds();

const renderSpan = startInactiveSpan({
name: `<${name}>`,
op: REACT_RENDER_OP,
origin: 'auto.ui.react.profiler',
startTimestamp: spanToJSON(mountSpan).timestamp,
data: { 'ui.component_name': name },
startTimestamp,
attributes: { 'ui.component_name': name },
});
if (renderSpan) {
// Have to cast to Span because the type of _mountSpan is Span | undefined
// and not getting narrowed properly
renderSpan.end(endTimestamp);
}
}
};
// We only want this to run once.
Expand All @@ -223,18 +223,3 @@ function useProfiler(
}

export { withProfiler, Profiler, useProfiler };

/** Grabs active transaction off scope */
export function getActiveTransaction<T extends Transaction>(
// eslint-disable-next-line deprecation/deprecation
hub: Hub = getCurrentHub(),
): T | undefined {
if (hub) {
// eslint-disable-next-line deprecation/deprecation
const scope = hub.getScope();
// eslint-disable-next-line deprecation/deprecation
return scope.getTransaction() as T | undefined;
}

return undefined;
}
Loading