Skip to content

Commit

Permalink
feat: Add @sentry/tracing
Browse files Browse the repository at this point in the history
CHANGELOG

Rename tracing to APM

bring back APM

fix: APM -> tracing

feat(tracing): Add @sentry/tracing

delete src

mass rename

mass move test

chore: Change APM -> Tracing

feat(tracing): Add IdleTransaction class

ref: Delete Tracing integration

feat: BrowserTracing integration

some comments about what I've done so far

router stuff

more router changes

more comments and thoughts

refactor heartbeat

beforeFinish

Squash 1

router tracing init

reset active transactions

more idle transaction refactor

Revert "mass move test"

This reverts commit c1a11a1.

Revert "mass rename"

This reverts commit 8173c5d.

Revert "delete src"

This reverts commit 0298cd4.

fix routing typings

sentry-trace meta tag

Add request logic

smaller errors + backgroundtab

performance

fix stuff

fix some errors

get it working

it finally works :)

zero counter

react changes

Vue changes

Fix all errors

delete tracing again again

any typings

any changes

more refactors

Fix performance and router

better
  • Loading branch information
AbhiPrasad committed Jul 3, 2020
1 parent ef59276 commit f989678
Show file tree
Hide file tree
Showing 19 changed files with 1,747 additions and 130 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"packages/minimal",
"packages/node",
"packages/react",
"packages/tracing",
"packages/types",
"packages/typescript",
"packages/utils"
Expand Down
4 changes: 2 additions & 2 deletions packages/hub/src/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,8 @@ export class Hub implements HubInterface {
/**
* @inheritDoc
*/
public startTransaction(context: TransactionContext): Transaction {
return this._callExtensionMethod('startTransaction', context);
public startTransaction(context: TransactionContext, idleTimeout?: number): Transaction {
return this._callExtensionMethod('startTransaction', context, idleTimeout);
}

/**
Expand Down
63 changes: 26 additions & 37 deletions packages/integrations/src/vue.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { EventProcessor, Hub, Integration, IntegrationClass, Span } from '@sentry/types';
import { EventProcessor, Hub, Integration, Scope, Span, Transaction } from '@sentry/types';
import { basename, getGlobalObject, logger, timestampWithMs } from '@sentry/utils';

/**
* Used to extract Tracing integration from the current client,
* without the need to import `Tracing` itself from the @sentry/apm package.
*/
const TRACING_GETTER = ({
id: 'Tracing',
} as any) as IntegrationClass<Integration>;
// tslint:disable-next-line: completed-docs
function getActiveTransaction(hub: Hub & { getScope?(): Scope }): Transaction | undefined {
if (!hub.getScope) {
return undefined;
}

const scope = hub.getScope();
if (scope) {
return scope.getTransaction();
}

return undefined;
}

/** Global Vue object limited to the methods/attributes we require */
interface VueInstance {
Expand Down Expand Up @@ -137,7 +143,6 @@ export class Vue implements Integration {
private readonly _componentsCache: { [key: string]: string } = {};
private _rootSpan?: Span;
private _rootSpanTimer?: ReturnType<typeof setTimeout>;
private _tracingActivity?: number;

/**
* @inheritDoc
Expand Down Expand Up @@ -221,27 +226,20 @@ export class Vue implements Integration {
// On the first handler call (before), it'll be undefined, as `$once` will add it in the future.
// However, on the second call (after), it'll be already in place.
if (this._rootSpan) {
this._finishRootSpan(now, getCurrentHub);
this._finishRootSpan(now);
} else {
vm.$once(`hook:${hook}`, () => {
// Create an activity on the first event call. There'll be no second call, as rootSpan will be in place,
// thus new event handler won't be attached.

// We do this whole dance with `TRACING_GETTER` to prevent `@sentry/apm` from becoming a peerDependency.
// We also need to ask for the `.constructor`, as `pushActivity` and `popActivity` are static, not instance methods.
const tracingIntegration = getCurrentHub().getIntegration(TRACING_GETTER);
if (tracingIntegration) {
// tslint:disable-next-line:no-unsafe-any
this._tracingActivity = (tracingIntegration as any).constructor.pushActivity('Vue Application Render');
// tslint:disable-next-line:no-unsafe-any
const transaction = (tracingIntegration as any).constructor.getTransaction();
if (transaction) {
// tslint:disable-next-line:no-unsafe-any
this._rootSpan = transaction.startChild({
description: 'Application Render',
op: 'Vue',
});
}
const activeTransaction = getActiveTransaction(getCurrentHub());
if (activeTransaction) {
this._rootSpan = activeTransaction.startChild({
description: 'Application Render',
op: 'Vue',
});
}
});
}
Expand All @@ -264,7 +262,7 @@ export class Vue implements Integration {
// However, on the second call (after), it'll be already in place.
if (span) {
span.finish();
this._finishRootSpan(now, getCurrentHub);
this._finishRootSpan(now);
} else {
vm.$once(`hook:${hook}`, () => {
if (this._rootSpan) {
Expand Down Expand Up @@ -306,23 +304,14 @@ export class Vue implements Integration {
};

/** Finish top-level span and activity with a debounce configured using `timeout` option */
private _finishRootSpan(timestamp: number, getCurrentHub: () => Hub): void {
private _finishRootSpan(timestamp: number): void {
if (this._rootSpanTimer) {
clearTimeout(this._rootSpanTimer);
}

this._rootSpanTimer = setTimeout(() => {
if (this._tracingActivity) {
// We do this whole dance with `TRACING_GETTER` to prevent `@sentry/apm` from becoming a peerDependency.
// We also need to ask for the `.constructor`, as `pushActivity` and `popActivity` are static, not instance methods.
const tracingIntegration = getCurrentHub().getIntegration(TRACING_GETTER);
if (tracingIntegration) {
// tslint:disable-next-line:no-unsafe-any
(tracingIntegration as any).constructor.popActivity(this._tracingActivity);
if (this._rootSpan) {
this._rootSpan.finish(timestamp);
}
}
if (this._rootSpan) {
this._rootSpan.finish(timestamp);
}
}, this._options.tracingOptions.timeout);
}
Expand All @@ -333,7 +322,7 @@ export class Vue implements Integration {

this._options.Vue.mixin({
beforeCreate(this: ViewModel): void {
if (getCurrentHub().getIntegration(TRACING_GETTER)) {
if (getActiveTransaction(getCurrentHub())) {
// `this` points to currently rendered component
applyTracingHooks(this, getCurrentHub);
} else {
Expand Down
116 changes: 31 additions & 85 deletions packages/react/src/profiler.tsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,19 @@
import { getCurrentHub } from '@sentry/browser';
import { Integration, IntegrationClass, Span } from '@sentry/types';
import { logger, timestampWithMs } from '@sentry/utils';
import { Span, Transaction } from '@sentry/types';
import { timestampWithMs } 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>;

let globalTracingIntegration: Integration | null = null;
const getTracingIntegration = () => {
if (globalTracingIntegration) {
return globalTracingIntegration;
}

globalTracingIntegration = getCurrentHub().getIntegration(TRACING_GETTER);
return globalTracingIntegration;
};

/**
* Warn if tracing integration not configured. Will only warn once.
*/
function warnAboutTracing(name: string): void {
if (globalTracingIntegration === null) {
logger.warn(
`Unable to profile component ${name} due to invalid Tracing Integration. Please make sure the Tracing integration is setup properly.`,
);
}
}

/**
* pushActivity creates an new react activity.
* Is a no-op if Tracing integration is not valid
* @param name displayName of component that started activity
*/
function pushActivity(name: string, op: string): number | null {
if (globalTracingIntegration === null) {
return null;
function getActiveTransaction(): Transaction | undefined {
const hub = getCurrentHub();
const scope = hub.getScope();
if (scope) {
return scope.getTransaction();
}

// tslint:disable-next-line:no-unsafe-any
return (globalTracingIntegration as any).constructor.pushActivity(name, {
description: `<${name}>`,
op: `react.${op}`,
});
}

/**
* popActivity removes a React activity.
* Is a no-op if Tracing integration is not valid.
* @param activity id of activity that is being popped
*/
function popActivity(activity: number | null): void {
if (activity === null || globalTracingIntegration === null) {
return;
}

// tslint:disable-next-line:no-unsafe-any
(globalTracingIntegration as any).constructor.popActivity(activity);
}

/**
* Obtain a span given an activity id.
* Is a no-op if Tracing integration is not valid.
* @param activity activity id associated with obtained span
*/
function getActivitySpan(activity: number | null): Span | undefined {
if (activity === null || globalTracingIntegration === null) {
return undefined;
}

// tslint:disable-next-line:no-unsafe-any
return (globalTracingIntegration as any).constructor.getActivitySpan(activity) as Span | undefined;
return undefined;
}

export type ProfilerProps = {
Expand All @@ -95,8 +35,6 @@ export type ProfilerProps = {
* spans based on component lifecycles.
*/
class Profiler extends React.Component<ProfilerProps> {
// The activity representing how long it takes to mount a component.
public mountActivity: number | null = null;
// The span of the mount activity
public mountSpan: Span | undefined = undefined;
// The span of the render
Expand All @@ -116,18 +54,21 @@ class Profiler extends React.Component<ProfilerProps> {
return;
}

if (getTracingIntegration()) {
this.mountActivity = pushActivity(name, 'mount');
} else {
warnAboutTracing(name);
const activeTransaction = getActiveTransaction();

if (activeTransaction) {
this.mountSpan = activeTransaction.startChild({
description: `<${name}>`,
op: 'react.mount',
});
}
}

// If a component mounted, we can finish the mount activity.
public componentDidMount(): void {
this.mountSpan = getActivitySpan(this.mountActivity);
popActivity(this.mountActivity);
this.mountActivity = null;
if (this.mountSpan) {
this.mountSpan.finish();
}
}

public componentDidUpdate({ updateProps, includeUpdates = true }: ProfilerProps): void {
Expand Down Expand Up @@ -221,22 +162,27 @@ function useProfiler(
hasRenderSpan: true,
},
): void {
const [mountActivity] = React.useState(() => {
const [mountSpan] = React.useState(() => {
if (options && options.disabled) {
return null;
return undefined;
}

if (getTracingIntegration()) {
return pushActivity(name, 'mount');
const activeTransaction = getActiveTransaction();

if (activeTransaction) {
return activeTransaction.startChild({
description: `<${name}>`,
op: 'react.mount',
});
}

warnAboutTracing(name);
return null;
return undefined;
});

React.useEffect(() => {
const mountSpan = getActivitySpan(mountActivity);
popActivity(mountActivity);
if (mountSpan) {
mountSpan.finish();
}

return () => {
if (mountSpan && options.hasRenderSpan) {
Expand Down
9 changes: 9 additions & 0 deletions packages/tracing/src/hubextensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { getMainCarrier, Hub } from '@sentry/hub';
import { SpanContext, TransactionContext } from '@sentry/types';
import { logger } from '@sentry/utils';

<<<<<<< HEAD
=======
import { IdleTransaction } from './idletransaction';
>>>>>>> feat: Add @sentry/tracing
import { Span } from './span';
import { Transaction } from './transaction';

Expand All @@ -22,8 +26,13 @@ function traceHeaders(this: Hub): { [key: string]: string } {
/**
* {@see Hub.startTransaction}
*/
<<<<<<< HEAD
function startTransaction(this: Hub, context: TransactionContext): Transaction {
const transaction = new Transaction(context, this);
=======
function startTransaction(this: Hub, context: TransactionContext, idleTimeout?: number): Transaction {
const transaction = idleTimeout ? new IdleTransaction(context, this, idleTimeout) : new Transaction(context, this);
>>>>>>> feat: Add @sentry/tracing

const client = this.getClient();
// Roll the dice for sampling transaction, all child spans inherit the sampling decision.
Expand Down
Loading

0 comments on commit f989678

Please sign in to comment.