Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ jobs:
key: ${{ runner.os }}-${{ github.sha }}
- run: yarn install
- name: Unit Tests
run: yarn test --ignore="@sentry/ember"
run: yarn test
- uses: codecov/codecov-action@v1

job_browserstack_test:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
- [ember] feat: Add performance instrumentation for routes (#2784)

## 5.22.3

Expand Down
28 changes: 28 additions & 0 deletions packages/ember/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,34 @@ Aside from configuration passed from this addon into `@sentry/browser` via the `
sentry: ... // See sentry-javascript configuration https://docs.sentry.io/error-reporting/configuration/?platform=javascript
};
```
#### Disabling Performance

`@sentry/ember` captures performance by default, if you would like to disable the automatic performance instrumentation, you can add the following to your `config/environment.js`:

```javascript
ENV['@sentry/ember'] = {
disablePerformance: true, // Will disable automatic instrumentation of performance. Manual instrumentation will still be sent.
sentry: ... // See sentry-javascript configuration https://docs.sentry.io/error-reporting/configuration/?platform=javascript
};
```

### Performance
#### Routes
If you would like to capture `beforeModel`, `model`, `afterModel` and `setupController` times for one of your routes,
you can import `instrumentRoutePerformance` and wrap your route with it.

```javascript
import Route from '@ember/routing/route';
import { instrumentRoutePerformance } from '@sentry/ember';

export default instrumentRoutePerformance(
class MyRoute extends Route {
model() {
//...
}
}
);
```

### Supported Versions

Expand Down
14 changes: 14 additions & 0 deletions packages/ember/addon/config.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declare module 'ember-get-config' {
import { BrowserOptions } from '@sentry/browser';
type EmberSentryConfig = {
sentry: BrowserOptions;
transitionTimeout: number;
ignoreEmberOnErrorWarning: boolean;
disablePerformance: boolean;
disablePostTransitionRender: boolean;
};
const config: {
'@sentry/ember': EmberSentryConfig;
};
export default config;
}
3 changes: 0 additions & 3 deletions packages/ember/addon/declarations.d.ts

This file was deleted.

34 changes: 34 additions & 0 deletions packages/ember/addon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,40 @@ export function InitSentryForEmber(_runtimeConfig: BrowserOptions | undefined) {
});
}

const getCurrentTransaction = () => {
return Sentry.getCurrentHub()
?.getScope()
?.getTransaction();
};

const instrumentFunction = async (op: string, description: string, fn: Function, args: any) => {
const currentTransaction = getCurrentTransaction();
const span = currentTransaction?.startChild({ op, description });
const result = await fn(...args);
span?.finish();
return result;
};

export const instrumentRoutePerformance = (BaseRoute: any) => {
return class InstrumentedRoute extends BaseRoute {
beforeModel(...args: any[]) {
return instrumentFunction('ember.route.beforeModel', (<any>this).fullRouteName, super.beforeModel, args);
}

async model(...args: any[]) {
return instrumentFunction('ember.route.model', (<any>this).fullRouteName, super.model, args);
}

async afterModel(...args: any[]) {
return instrumentFunction('ember.route.afterModel', (<any>this).fullRouteName, super.afterModel, args);
}

async setupController(...args: any[]) {
return instrumentFunction('ember.route.setupController', (<any>this).fullRouteName, super.setupController, args);
}
};
};

function createEmberEventProcessor(): void {
if (addGlobalEventProcessor) {
addGlobalEventProcessor(event => {
Expand Down
131 changes: 131 additions & 0 deletions packages/ember/addon/instance-initializers/sentry-performance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import ApplicationInstance from '@ember/application/instance';
import Ember from 'ember';
import { scheduleOnce } from '@ember/runloop';
import environmentConfig from 'ember-get-config';
import Sentry from '@sentry/browser';
import { Integration } from '@sentry/types';

export function initialize(appInstance: ApplicationInstance): void {
const config = environmentConfig['@sentry/ember'];
if (config['disablePerformance']) {
return;
}
const performancePromise = instrumentForPerformance(appInstance);
if (Ember.testing) {
(<any>window)._sentryPerformanceLoad = performancePromise;
}
}

function getTransitionInformation(transition: any, router: any) {
const fromRoute = transition?.from?.name;
const toRoute = transition ? transition.to.name : router.currentRouteName;
return {
fromRoute,
toRoute,
};
}

export function _instrumentEmberRouter(
routerService: any,
routerMain: any,
config: typeof environmentConfig['@sentry/ember'],
startTransaction: Function,
startTransactionOnPageLoad?: boolean,
) {
const { disablePostTransitionRender } = config;
const location = routerMain.location;
let activeTransaction: any;
let transitionSpan: any;

const url = location && location.getURL && location.getURL();

if (Ember.testing) {
routerService._sentryInstrumented = true;
}

if (startTransactionOnPageLoad && url) {
const routeInfo = routerService.recognize(url);
activeTransaction = startTransaction({
name: `route:${routeInfo.name}`,
op: 'pageload',
tags: {
url,
toRoute: routeInfo.name,
'routing.instrumentation': '@sentry/ember',
},
});
}

routerService.on('routeWillChange', (transition: any) => {
const { fromRoute, toRoute } = getTransitionInformation(transition, routerService);
activeTransaction = startTransaction({
name: `route:${toRoute}`,
op: 'navigation',
tags: {
fromRoute,
toRoute,
'routing.instrumentation': '@sentry/ember',
},
});
transitionSpan = activeTransaction.startChild({
op: 'ember.transition',
description: `route:${fromRoute} -> route:${toRoute}`,
});
});

routerService.on('routeDidChange', (transition: any) => {
const { toRoute } = getTransitionInformation(transition, routerService);
let renderSpan: any;
if (!transitionSpan || !activeTransaction) {
return;
}
transitionSpan.finish();

if (disablePostTransitionRender) {
activeTransaction.finish();
}

function startRenderSpan() {
renderSpan = activeTransaction.startChild({
op: 'ember.runloop.render',
description: `post-transition render route:${toRoute}`,
});
}

function finishRenderSpan() {
renderSpan.finish();
activeTransaction.finish();
}

scheduleOnce('routerTransitions', null, startRenderSpan);
scheduleOnce('afterRender', null, finishRenderSpan);
});
}

export async function instrumentForPerformance(appInstance: ApplicationInstance) {
const config = environmentConfig['@sentry/ember'];
const sentryConfig = config.sentry;
const tracing = await import('@sentry/tracing');

const idleTimeout = config.transitionTimeout || 5000;

const existingIntegrations = (sentryConfig['integrations'] || []) as Integration[];

sentryConfig['integrations'] = [
...existingIntegrations,
new tracing.Integrations.BrowserTracing({
routingInstrumentation: (startTransaction, startTransactionOnPageLoad) => {
const routerMain = appInstance.lookup('router:main');
const routerService = appInstance.lookup('service:router');
_instrumentEmberRouter(routerService, routerMain, config, startTransaction, startTransactionOnPageLoad);
},
idleTimeout,
}),
];

Sentry.init(sentryConfig); // Call init again to rebind client with new integration list in addition to the defaults
}

export default {
initialize,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, initialize } from '@sentry/ember/instance-initializers/sentry-performance';
7 changes: 6 additions & 1 deletion packages/ember/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
'use strict';

module.exports = {
name: require('./package').name
name: require('./package').name,
options: {
babel: {
plugins: [ require.resolve('ember-auto-import/babel-plugin') ]
}
}
};
2 changes: 2 additions & 0 deletions packages/ember/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"dependencies": {
"@sentry/browser": "5.22.3",
"@sentry/tracing": "5.22.3",
"@sentry/types": "5.22.3",
"@sentry/utils": "5.22.3",
"ember-auto-import": "^1.6.0",
Expand Down Expand Up @@ -69,6 +70,7 @@
"ember-template-lint": "^2.9.1",
"ember-test-selectors": "^4.1.0",
"ember-try": "^1.4.0",
"ember-window-mock": "^0.7.1",
"eslint": "7.6.0",
"eslint-plugin-ember": "^8.6.0",
"eslint-plugin-node": "^11.1.0",
Expand Down
Loading