Skip to content
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

feat(profiling): js self profiling #7273

Merged
merged 28 commits into from Mar 2, 2023
Merged

Conversation

JonasBa
Copy link
Member

@JonasBa JonasBa commented Feb 23, 2023

This pr adds support for JS self profiling to @sentry/browser package. This is a POC stage and we want to test it on our sentry dashboard - the current approach is to take the js profiler output and convert it to our sample format. Due to overhead reasons I do not think this will be a good long term solution, but at least for now, it allows us to visualize the profiles and test the feasibility of the API without a significant investment.

To support automated browser instrumentation (route navigation), we had to make a small change to @sentry/tracing to support. Tldr, browser tracing uses idle transactions as opposed to regular transactions and our hubExtension patch was never being called. Thanks @k-fish for the help there.

I will be adding some more tests to cover the profile conversion logic -> done

@github-actions
Copy link
Contributor

github-actions bot commented Feb 23, 2023

size-limit report 📦

Path Size
@sentry/browser - ES5 CDN Bundle (gzipped + minified) 20.11 KB (-0.02% 🔽)
@sentry/browser - ES5 CDN Bundle (minified) 62.49 KB (0%)
@sentry/browser - ES6 CDN Bundle (gzipped + minified) 18.74 KB (-0.02% 🔽)
@sentry/browser - ES6 CDN Bundle (minified) 55.5 KB (0%)
@sentry/browser - Webpack (gzipped + minified) 20.48 KB (-0.01% 🔽)
@sentry/browser - Webpack (minified) 66.94 KB (0%)
@sentry/react - Webpack (gzipped + minified) 20.51 KB (-0.01% 🔽)
@sentry/nextjs Client - Webpack (gzipped + minified) 48.08 KB (+0.05% 🔺)
@sentry/browser + @sentry/tracing - ES5 CDN Bundle (gzipped + minified) 27.06 KB (+0.08% 🔺)
@sentry/browser + @sentry/tracing - ES6 CDN Bundle (gzipped + minified) 25.31 KB (+0.11% 🔺)
@sentry/replay ES6 CDN Bundle (gzipped + minified) 42.86 KB (-0.01% 🔽)
@sentry/replay - Webpack (gzipped + minified) 36.93 KB (0%)
@sentry/browser + @sentry/tracing + @sentry/replay - ES6 CDN Bundle (gzipped + minified) 60.46 KB (+0.04% 🔺)
@sentry/browser + @sentry/replay - ES6 CDN Bundle (gzipped + minified) 53.99 KB (-0.01% 🔽)

@github-actions
Copy link
Contributor

github-actions bot commented Feb 24, 2023

Replay SDK metrics 🚀

    Plain +Sentry +Replay
Revision Value Value Diff Ratio Value Diff Ratio
LCP This PR 4b95c04 65.31 ms 95.62 ms +30.31 ms +46.40 % 127.81 ms +62.50 ms +95.69 %
Previous 4b95c04 74.67 ms 100.14 ms +25.47 ms +34.11 % 132.23 ms +57.56 ms +77.09 %
CLS This PR 4b95c04 0.06 ms 0.06 ms +0.00 ms +0.05 % 0.06 ms +0.00 ms +0.13 %
Previous 4b95c04 0.06 ms 0.06 ms -0.00 ms -0.79 % 0.06 ms -0.00 ms -0.47 %
CPU This PR 4b95c04 13.21 % 13.60 % +0.38 pp +2.91 % 18.64 % +5.43 pp +41.07 %
Previous 4b95c04 12.32 % 12.48 % +0.16 pp +1.28 % 20.27 % +7.94 pp +64.46 %
JS heap avg This PR 4b95c04 1.94 MB 2 MB +61.95 kB +3.19 % 2.87 MB +931.53 kB +47.96 %
Previous 4b95c04 1.94 MB 1.99 MB +43.74 kB +2.25 % 2.86 MB +920.88 kB +47.38 %
JS heap max This PR 4b95c04 2.3 MB 2.56 MB +257.23 kB +11.17 % 3.34 MB +1.04 MB +45.11 %
Previous 4b95c04 2.3 MB 2.55 MB +249.86 kB +10.85 % 3.35 MB +1.05 MB +45.58 %
netTx This PR 4b95c04 0 B 0 B 0 B n/a 2.22 kB +2.22 kB n/a
Previous 4b95c04 0 B 0 B 0 B n/a 2.21 kB +2.21 kB n/a
netRx This PR 4b95c04 0 B 0 B 0 B n/a 41 B +41 B n/a
Previous 4b95c04 0 B 0 B 0 B n/a 41 B +41 B n/a
netCount This PR 4b95c04 0 0 0 n/a 1 +1 n/a
Previous 4b95c04 0 0 0 n/a 1 +1 n/a
netTime This PR 4b95c04 0.00 ms 0.00 ms 0.00 ms n/a 64.22 ms +64.22 ms n/a
Previous 4b95c04 0.00 ms 0.00 ms 0.00 ms n/a 90.32 ms +90.32 ms n/a

Previous results on branch: develop

RevisionLCPCLSCPUJS heap avgJS heap maxnetTxnetRxnetCountnetTime
4b95c04+57.56 ms-0.00 ms+7.94 pp+920.88 kB+1.05 MB+2.21 kB+41 B+1+90.32 ms
e60cd02+56.25 ms-0.00 ms+6.32 pp+927.44 kB+1.06 MB+2.21 kB+41 B+1+117.55 ms
e25c067+48.34 ms+0.00 ms+5.59 pp+926.37 kB+1.05 MB+2.22 kB+41 B+1+65.23 ms
b1b249b+43.88 ms+0.00 ms+4.80 pp+937.99 kB+1.05 MB+2.22 kB+41 B+1+111.56 ms
12e34d4+28.57 ms+0.00 ms+5.77 pp+930.12 kB+1.04 MB+2.26 kB+41 B+1+109.67 ms
c46c56c+65.45 ms-0.00 ms+5.38 pp+930.26 kB+1.07 MB+2.21 kB+41 B+1+91.29 ms
7f4c4ec+56.64 ms-0.00 ms+5.57 pp+927.42 kB+1.06 MB+2.21 kB+41 B+1+110.83 ms
00d2360+55.18 ms+0.00 ms+2.23 pp+934.14 kB+1.05 MB+2.22 kB+41 B+1+71.65 ms

*) pp - percentage points - an absolute difference between two percentages.
Last updated: Mon, 27 Feb 2023 00:20:25 GMT

@JonasBa JonasBa marked this pull request as ready for review February 27, 2023 16:55
@JonasBa JonasBa requested review from Lms24, AbhiPrasad, lforst and a team February 27, 2023 17:12
@JonasBa
Copy link
Member Author

JonasBa commented Feb 28, 2023

Are those replay metrics accurate? I did not make any changes to replay and unless profiling is installed and enabled, the metrics should not be impacted 🤔

@lforst
Copy link
Member

lforst commented Feb 28, 2023

Are those replay metrics accurate? I did not make any changes to replay and unless profiling is installed and enabled, the metrics should not be impacted 🤔

@JonasBa honestly I kinda ignore them but from what I've seen they're pretty fluctuating.

@JonasBa
Copy link
Member Author

JonasBa commented Feb 28, 2023

Since this is a large PR to review, some comments on how it works:

TLDR:

Sentry.startTransaction -> create transaction -> start profiler
transaction.finish -> finish transaction -> profiling integration inserts it to cache and forwards it -> txn is sent (this chain is synchronous)
                              ------> async profiler.stop -> attempt to pull processed txn event from profiling integration cache -> construct envelope -> profile is sent

Longer explanation:
Profiling package works by patching the startTransaction call on the main carrier (same as profiling-node) - by doing so, we can hook ourselves into the transaction lifecycle. That said, browser profiling packages differ slightly from node as calls to profiler.stop are async - this presents a problem because calling transaction.finish will immediately process the transaction event and send it to sentry, meaning by the time our profiler.stop method has resolved, the transaction had already been processed and our reference is "stale" and attaching a profile to the transaction metadata then is too late as the transaction will not be processed again (unless we were to send it twice)

A naive solution would be to await profiler.stop and block transaction.finish which would delay transaction.finish as well as block the event loop (from some testing, that can be in the range of hundreds of ms) which makes it prohibitive.

The second solution and the one I opted for was to introduce an event cache in our profiling integration which stores events that are carrying the profile context - this removes the need for awaiting profiler.stop at the expense of possibly dropping profiles (if the corresponding transaction event cannot be pulled from the cache, we cannot construct a profiling envelope). We would be dropping profiles in the event that max active transactions exceeds N (where N is the cache size = 20 = magic number. I'm open to increasing/decreasing that number, but my senses tell me that it is probably unlikely that more than 20 transactions would be active in the browser).

Supporting browser tracing
Til, but route tracing does not call startTransactions and instead uses idleTransactions which is why we expose the change in tracing which wraps the calls to startTransaction.

@@ -0,0 +1,254 @@
import { WINDOW } from '@sentry/browser';
Copy link
Member Author

Choose a reason for hiding this comment

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

exported code here is responsible for providing both the implementation of the patched startTransaction functionality (wrapTransactionWithProfiling) and the code that performs the patching addExtensionMethods

@@ -0,0 +1,10 @@
import { addExtensionMethods, wrapTransactionWithProfiling } from './hubextensions';
Copy link
Member Author

Choose a reason for hiding this comment

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

reexports the integration and patches the carrier

* rely on being able to pull the event from the cache when we need to construct the envelope. This makes the
* integration less reliable as we might be dropping profiles when the cache is full.
*/
export class BrowserProfilingIntegration implements Integration {
Copy link
Member Author

Choose a reason for hiding this comment

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

Listens for processed events with profile context and stores them in the cache

@@ -0,0 +1,400 @@
import { WINDOW } from '@sentry/browser';
import type {
Copy link
Member Author

Choose a reason for hiding this comment

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

All of this is mostly about constructing the profile envelope and converting from JS sampled format to the one we expect in Sentry

@lforst lforst self-assigned this Feb 28, 2023
packages/profiling-browser/package.json Outdated Show resolved Hide resolved
packages/profiling-browser/src/hubextensions.ts Outdated Show resolved Hide resolved
packages/profiling-browser/src/hubextensions.ts Outdated Show resolved Hide resolved
packages/profiling-browser/src/hubextensions.ts Outdated Show resolved Hide resolved
packages/profiling-browser/src/hubextensions.ts Outdated Show resolved Hide resolved
packages/profiling-browser/src/utils.ts Outdated Show resolved Hide resolved
packages/profiling-browser/src/utils.ts Outdated Show resolved Hide resolved
packages/profiling-browser/test/integration.test.ts Outdated Show resolved Hide resolved
packages/profiling-browser/test/utils.test.ts Outdated Show resolved Hide resolved
packages/profiling-browser/package.json Outdated Show resolved Hide resolved
@JonasBa JonasBa force-pushed the jb/boilerplate/js-self-profiling branch 3 times, most recently from f292041 to 42a9639 Compare March 1, 2023 17:07
@JonasBa JonasBa force-pushed the jb/boilerplate/js-self-profiling branch from 42a9639 to d62e3aa Compare March 1, 2023 17:07
@JonasBa
Copy link
Member Author

JonasBa commented Mar 2, 2023

@lforst profiling logic has been moved under the browser package so this is now ready for another review :)

Copy link
Member

@lforst lforst left a comment

Choose a reason for hiding this comment

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

Super clean PR 👌 Just some minor stuff.

packages/browser/src/profiling/integration.ts Outdated Show resolved Hide resolved
JonasBa and others added 2 commits March 2, 2023 11:16
Co-authored-by: Luca Forstner <luca.forstner@sentry.io>
Co-authored-by: Luca Forstner <luca.forstner@sentry.io>
@JonasBa JonasBa merged commit 45158f4 into develop Mar 2, 2023
@JonasBa JonasBa deleted the jb/boilerplate/js-self-profiling branch March 2, 2023 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants