Skip to content

Commit 573c49f

Browse files
authored
feat(ember): Add more render instrumentation to @sentry/ember (#2902)
* feat(ember): Add more render instrumentation to @sentry/ember This PR will fill out the rest of the instrumentation for Ember to provide meaningful spans across most of the transaction. It adds instrumentation for: - initial load (which issues the build tools to inject a small script into head to measure script evaluation, which includes initial render) - the runloop, to capture queues that take a long time, this can primarily be used to capture long render queues when someone is using the new glimmer components as they don't have instrumentation. - components (non-glimmer) render times Additionally it fixes a couple bugs with routes, especially when nesting, such as beforeModel not consistently getting instrumented.
1 parent c113427 commit 573c49f

File tree

27 files changed

+785
-270
lines changed

27 files changed

+785
-270
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
6+
- [ember] feat: Add more render instrumentation (#2902)
67

78
## 5.24.2
89

packages/ember/README.md

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Aside from configuration passed from this addon into `@sentry/browser` via the `
6262
sentry: ... // See sentry-javascript configuration https://docs.sentry.io/error-reporting/configuration/?platform=javascript
6363
};
6464
```
65+
6566
#### Disabling Performance
6667

6768
`@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`:
@@ -82,13 +83,53 @@ you can import `instrumentRoutePerformance` and wrap your route with it.
8283
import Route from '@ember/routing/route';
8384
import { instrumentRoutePerformance } from '@sentry/ember';
8485

85-
export default instrumentRoutePerformance(
86-
class MyRoute extends Route {
87-
model() {
88-
//...
89-
}
86+
class MyRoute extends Route {
87+
model() {
88+
//...
9089
}
91-
);
90+
}
91+
92+
export default instrumentRoutePerformance(MyRoute);
93+
```
94+
95+
#### Runloop
96+
The runloop queue durations are instrumented by default, as long as they are longer than a threshold (by default 5ms).
97+
This helps (via the render queue) capturing the entire render in case component render times aren't fully instrumented,
98+
such as when using glimmer components.
99+
100+
If you would like to change the runloop queue threshold, add the following to your config:
101+
```javascript
102+
ENV['@sentry/ember'] = {
103+
minimumRunloopQueueDuration: 0, // All runloop queue durations will be added as spans.
104+
};
105+
```
106+
107+
#### Components
108+
Non-glimmer component render times will automatically get captured.
109+
110+
If you would like to disable component render being instrumented, add the following to your config:
111+
```javascript
112+
ENV['@sentry/ember'] = {
113+
disableInstrumentComponents: true, // Will disable automatic instrumentation for components.
114+
};
115+
```
116+
117+
Additionally, components whose render time is below a threshold (by default 2ms) will not be included as spans.
118+
If you would like to change this threshold, add the following to your config:
119+
```javascript
120+
ENV['@sentry/ember'] = {
121+
minimumComponentRenderDuration: 0, // All (non-glimmer) component render durations will be added as spans.
122+
};
123+
```
124+
125+
#### Glimmer components
126+
Currently glimmer component render durations can only be captured indirectly via the runloop instrumentation. You can
127+
optionally enable a setting to show component definitions (which will indicate which components are being rendered) be
128+
adding the following to your config:
129+
```javascript
130+
ENV['@sentry/ember'] = {
131+
enableComponentDefinition: true, // All component definitions will be added as spans.
132+
};
92133
```
93134

94135
### Supported Versions

packages/ember/addon/config.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ declare module 'ember-get-config' {
44
sentry: BrowserOptions;
55
transitionTimeout: number;
66
ignoreEmberOnErrorWarning: boolean;
7+
disableInstrumentComponents: boolean;
78
disablePerformance: boolean;
89
disablePostTransitionRender: boolean;
10+
disableRunloopPerformance: boolean;
11+
disableInitialLoadInstrumentation: boolean;
12+
enableComponentDefinitions: boolean;
13+
minimumRunloopQueueDuration: number;
14+
minimumComponentRenderDuration: number;
915
};
1016
const config: {
1117
'@sentry/ember': EmberSentryConfig;

packages/ember/addon/ember.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Ember from 'ember';
2+
declare module 'ember' {
3+
namespace Ember {
4+
export function subscribe(pattern: string, object: {}): any;
5+
}
6+
}

packages/ember/addon/index.ts

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import environmentConfig from 'ember-get-config';
55
import { next } from '@ember/runloop';
66
import { assert, warn, runInDebug } from '@ember/debug';
77
import Ember from 'ember';
8+
import { timestampWithMs } from '@sentry/utils';
89

910
declare module '@ember/debug' {
1011
export function assert(desc: string, test: unknown): void;
@@ -37,38 +38,49 @@ export function InitSentryForEmber(_runtimeConfig: BrowserOptions | undefined) {
3738
});
3839
}
3940

40-
const getCurrentTransaction = () => {
41+
export const getActiveTransaction = () => {
4142
return Sentry.getCurrentHub()
4243
?.getScope()
4344
?.getTransaction();
4445
};
4546

46-
const instrumentFunction = async (op: string, description: string, fn: Function, args: any) => {
47-
const currentTransaction = getCurrentTransaction();
48-
const span = currentTransaction?.startChild({ op, description });
49-
const result = await fn(...args);
50-
span?.finish();
51-
return result;
52-
};
53-
5447
export const instrumentRoutePerformance = (BaseRoute: any) => {
55-
return class InstrumentedRoute extends BaseRoute {
56-
beforeModel(...args: any[]) {
57-
return instrumentFunction('ember.route.beforeModel', (<any>this).fullRouteName, super.beforeModel, args);
58-
}
59-
60-
async model(...args: any[]) {
61-
return instrumentFunction('ember.route.model', (<any>this).fullRouteName, super.model, args);
62-
}
63-
64-
async afterModel(...args: any[]) {
65-
return instrumentFunction('ember.route.afterModel', (<any>this).fullRouteName, super.afterModel, args);
66-
}
48+
const instrumentFunction = async (op: string, description: string, fn: Function, args: any) => {
49+
const startTimestamp = timestampWithMs();
50+
const result = await fn(...args);
6751

68-
async setupController(...args: any[]) {
69-
return instrumentFunction('ember.route.setupController', (<any>this).fullRouteName, super.setupController, args);
52+
const currentTransaction = getActiveTransaction();
53+
if (!currentTransaction) {
54+
return result;
7055
}
56+
currentTransaction.startChild({ op, description, startTimestamp }).finish();
57+
return result;
7158
};
59+
60+
return {
61+
[BaseRoute.name]: class extends BaseRoute {
62+
beforeModel(...args: any[]) {
63+
return instrumentFunction('ember.route.beforeModel', (<any>this).fullRouteName, super.beforeModel, args);
64+
}
65+
66+
async model(...args: any[]) {
67+
return instrumentFunction('ember.route.model', (<any>this).fullRouteName, super.model, args);
68+
}
69+
70+
async afterModel(...args: any[]) {
71+
return instrumentFunction('ember.route.afterModel', (<any>this).fullRouteName, super.afterModel, args);
72+
}
73+
74+
async setupController(...args: any[]) {
75+
return instrumentFunction(
76+
'ember.route.setupController',
77+
(<any>this).fullRouteName,
78+
super.setupController,
79+
args,
80+
);
81+
}
82+
},
83+
}[BaseRoute.name];
7284
};
7385

7486
function createEmberEventProcessor(): void {

0 commit comments

Comments
 (0)