-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(node): Add eventLoopBlockIntegration
#16709
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
Open
timfish
wants to merge
25
commits into
develop
Choose a base branch
from
timfish/feat/new-anr
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
455d77a
new package
timfish b5b0027
Merge branch 'develop' into timfish/feat/new-anr
timfish 59b6bff
Mostly working
timfish b49f3f5
Lint 🤦🏻♂️
timfish 7a306e4
Merge branch 'develop' into timfish/feat/new-anr
timfish aa69600
Update Sentry deps
timfish 156f67b
Better thread names
timfish 7dfdd7a
Merge branch 'develop' into timfish/feat/new-anr
timfish 327fa55
yarn.lock changes
timfish 6fa63af
Add to verdaccio config
timfish 0745a20
Fix tests
timfish 3f83db2
rename package export
timfish c4a718c
Fix flakey worker thread test
timfish a487e50
Merge remote-tracking branch 'upstream/develop' into timfish/feat/new…
timfish ad4693a
Apply suggestions from code review
timfish 8443d33
PR review
timfish 1ac4549
Merge branch 'timfish/feat/new-anr' of github.com:getsentry/sentry-ja…
timfish 66e6aa5
Add rate limits
timfish 487e667
PR review
timfish 3dfd01b
Merge branch 'develop' into timfish/feat/new-anr
timfish 7fb37dc
Remove `disableBlockedDetectionForCallback` for now
timfish 5289d11
Merge branch 'timfish/feat/new-anr' of github.com:getsentry/sentry-ja…
timfish a346e4c
Merge remote-tracking branch 'upstream/develop' into timfish/feat/new…
timfish c3b9780
remove unused tests
timfish 47edf1c
Merge branch 'develop' into timfish/feat/new-anr
timfish File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
dev-packages/node-integration-tests/suites/thread-blocked-native/app-path.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import * as Sentry from '@sentry/node'; | ||
import { eventLoopBlockIntegration } from '@sentry/node-native'; | ||
import * as path from 'path'; | ||
import * as url from 'url'; | ||
import { longWork } from './long-work.js'; | ||
|
||
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; | ||
|
||
const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); | ||
|
||
setTimeout(() => { | ||
process.exit(); | ||
}, 10000); | ||
|
||
Sentry.init({ | ||
dsn: process.env.SENTRY_DSN, | ||
release: '1.0', | ||
integrations: [eventLoopBlockIntegration({ appRootPath: __dirname })], | ||
}); | ||
|
||
setTimeout(() => { | ||
longWork(); | ||
}, 1000); |
23 changes: 23 additions & 0 deletions
23
dev-packages/node-integration-tests/suites/thread-blocked-native/basic-multiple.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import * as Sentry from '@sentry/node'; | ||
import { eventLoopBlockIntegration } from '@sentry/node-native'; | ||
import { longWork } from './long-work.js'; | ||
|
||
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; | ||
|
||
setTimeout(() => { | ||
process.exit(); | ||
}, 10000); | ||
|
||
Sentry.init({ | ||
dsn: process.env.SENTRY_DSN, | ||
release: '1.0', | ||
integrations: [eventLoopBlockIntegration({ maxEventsPerHour: 2 })], | ||
}); | ||
|
||
setTimeout(() => { | ||
longWork(); | ||
}, 1000); | ||
|
||
setTimeout(() => { | ||
longWork(); | ||
}, 4000); |
24 changes: 24 additions & 0 deletions
24
dev-packages/node-integration-tests/suites/thread-blocked-native/basic.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
const Sentry = require('@sentry/node'); | ||
const { eventLoopBlockIntegration } = require('@sentry/node-native'); | ||
const { longWork } = require('./long-work.js'); | ||
|
||
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; | ||
|
||
setTimeout(() => { | ||
process.exit(); | ||
}, 10000); | ||
|
||
Sentry.init({ | ||
dsn: process.env.SENTRY_DSN, | ||
release: '1.0', | ||
integrations: [eventLoopBlockIntegration()], | ||
}); | ||
|
||
setTimeout(() => { | ||
longWork(); | ||
}, 2000); | ||
|
||
// Ensure we only send one event even with multiple blocking events | ||
setTimeout(() => { | ||
longWork(); | ||
}, 5000); |
24 changes: 24 additions & 0 deletions
24
dev-packages/node-integration-tests/suites/thread-blocked-native/basic.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import * as Sentry from '@sentry/node'; | ||
import { eventLoopBlockIntegration } from '@sentry/node-native'; | ||
import { longWork } from './long-work.js'; | ||
|
||
global._sentryDebugIds = { [new Error().stack]: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa' }; | ||
|
||
setTimeout(() => { | ||
process.exit(); | ||
}, 12000); | ||
|
||
Sentry.init({ | ||
dsn: process.env.SENTRY_DSN, | ||
release: '1.0', | ||
integrations: [eventLoopBlockIntegration()], | ||
}); | ||
|
||
setTimeout(() => { | ||
longWork(); | ||
}, 2000); | ||
|
||
// Ensure we only send one event even with multiple blocking events | ||
setTimeout(() => { | ||
longWork(); | ||
}, 5000); |
27 changes: 27 additions & 0 deletions
27
dev-packages/node-integration-tests/suites/thread-blocked-native/indefinite.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import * as Sentry from '@sentry/node'; | ||
import { eventLoopBlockIntegration } from '@sentry/node-native'; | ||
import * as assert from 'assert'; | ||
import * as crypto from 'crypto'; | ||
|
||
setTimeout(() => { | ||
process.exit(); | ||
}, 10000); | ||
|
||
Sentry.init({ | ||
dsn: process.env.SENTRY_DSN, | ||
release: '1.0', | ||
integrations: [eventLoopBlockIntegration()], | ||
}); | ||
|
||
function longWork() { | ||
// This loop will run almost indefinitely | ||
for (let i = 0; i < 2000000000; i++) { | ||
const salt = crypto.randomBytes(128).toString('base64'); | ||
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512'); | ||
assert.ok(hash); | ||
} | ||
} | ||
|
||
setTimeout(() => { | ||
longWork(); | ||
}, 1000); |
9 changes: 9 additions & 0 deletions
9
dev-packages/node-integration-tests/suites/thread-blocked-native/instrument.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import * as Sentry from '@sentry/node'; | ||
import { eventLoopBlockIntegration } from '@sentry/node-native'; | ||
|
||
Sentry.init({ | ||
debug: true, | ||
dsn: process.env.SENTRY_DSN, | ||
release: '1.0', | ||
integrations: [eventLoopBlockIntegration()], | ||
}); |
12 changes: 12 additions & 0 deletions
12
dev-packages/node-integration-tests/suites/thread-blocked-native/long-work.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
const crypto = require('crypto'); | ||
const assert = require('assert'); | ||
|
||
function longWork() { | ||
for (let i = 0; i < 200; i++) { | ||
const salt = crypto.randomBytes(128).toString('base64'); | ||
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512'); | ||
assert.ok(hash); | ||
} | ||
} | ||
|
||
exports.longWork = longWork; |
19 changes: 19 additions & 0 deletions
19
dev-packages/node-integration-tests/suites/thread-blocked-native/should-exit-forced.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
const Sentry = require('@sentry/node'); | ||
const { eventLoopBlockIntegration } = require('@sentry/node-native'); | ||
|
||
function configureSentry() { | ||
Sentry.init({ | ||
dsn: 'https://public@dsn.ingest.sentry.io/1337', | ||
release: '1.0', | ||
debug: true, | ||
integrations: [eventLoopBlockIntegration()], | ||
}); | ||
} | ||
|
||
async function main() { | ||
configureSentry(); | ||
await new Promise(resolve => setTimeout(resolve, 1000)); | ||
process.exit(0); | ||
} | ||
|
||
main(); |
18 changes: 18 additions & 0 deletions
18
dev-packages/node-integration-tests/suites/thread-blocked-native/should-exit.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
const Sentry = require('@sentry/node'); | ||
const { eventLoopBlockIntegration } = require('@sentry/node-native'); | ||
|
||
function configureSentry() { | ||
Sentry.init({ | ||
dsn: 'https://public@dsn.ingest.sentry.io/1337', | ||
release: '1.0', | ||
debug: true, | ||
integrations: [eventLoopBlockIntegration()], | ||
}); | ||
} | ||
|
||
async function main() { | ||
configureSentry(); | ||
await new Promise(resolve => setTimeout(resolve, 1000)); | ||
} | ||
|
||
main(); |
200 changes: 200 additions & 0 deletions
200
dev-packages/node-integration-tests/suites/thread-blocked-native/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import { join } from 'node:path'; | ||
import type { Event } from '@sentry/core'; | ||
import { afterAll, describe, expect, test } from 'vitest'; | ||
import { cleanupChildProcesses, createRunner } from '../../utils/runner'; | ||
|
||
function EXCEPTION(thread_id = '0') { | ||
return { | ||
values: [ | ||
{ | ||
type: 'EventLoopBlocked', | ||
value: 'Event Loop Blocked for at least 1000 ms', | ||
mechanism: { type: 'ANR' }, | ||
thread_id, | ||
stacktrace: { | ||
frames: expect.arrayContaining([ | ||
expect.objectContaining({ | ||
colno: expect.any(Number), | ||
lineno: expect.any(Number), | ||
filename: expect.any(String), | ||
function: '?', | ||
in_app: true, | ||
}), | ||
expect.objectContaining({ | ||
colno: expect.any(Number), | ||
lineno: expect.any(Number), | ||
filename: expect.any(String), | ||
function: 'longWork', | ||
in_app: true, | ||
}), | ||
]), | ||
}, | ||
}, | ||
], | ||
}; | ||
} | ||
|
||
const ANR_EVENT = { | ||
// Ensure we have context | ||
contexts: { | ||
device: { | ||
arch: expect.any(String), | ||
}, | ||
app: { | ||
app_start_time: expect.any(String), | ||
}, | ||
os: { | ||
name: expect.any(String), | ||
}, | ||
culture: { | ||
timezone: expect.any(String), | ||
}, | ||
}, | ||
threads: { | ||
values: [ | ||
{ | ||
id: '0', | ||
name: 'main', | ||
crashed: true, | ||
current: true, | ||
main: true, | ||
}, | ||
], | ||
}, | ||
// and an exception that is our ANR | ||
exception: EXCEPTION(), | ||
}; | ||
|
||
function ANR_EVENT_WITH_DEBUG_META(file: string): Event { | ||
return { | ||
...ANR_EVENT, | ||
debug_meta: { | ||
images: [ | ||
{ | ||
type: 'sourcemap', | ||
debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa', | ||
code_file: expect.stringContaining(file), | ||
}, | ||
], | ||
}, | ||
}; | ||
} | ||
|
||
describe('Thread Blocked Native', { timeout: 30_000 }, () => { | ||
afterAll(() => { | ||
cleanupChildProcesses(); | ||
}); | ||
|
||
test('CJS', async () => { | ||
await createRunner(__dirname, 'basic.js') | ||
.withMockSentryServer() | ||
.expect({ event: ANR_EVENT_WITH_DEBUG_META('basic') }) | ||
.start() | ||
.completed(); | ||
}); | ||
|
||
test('ESM', async () => { | ||
await createRunner(__dirname, 'basic.mjs') | ||
.withMockSentryServer() | ||
.expect({ event: ANR_EVENT_WITH_DEBUG_META('basic') }) | ||
.start() | ||
.completed(); | ||
}); | ||
|
||
test('Custom appRootPath', async () => { | ||
const ANR_EVENT_WITH_SPECIFIC_DEBUG_META: Event = { | ||
...ANR_EVENT, | ||
debug_meta: { | ||
images: [ | ||
{ | ||
type: 'sourcemap', | ||
debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa', | ||
code_file: 'app:///app-path.mjs', | ||
}, | ||
], | ||
}, | ||
}; | ||
|
||
await createRunner(__dirname, 'app-path.mjs') | ||
.withMockSentryServer() | ||
.expect({ event: ANR_EVENT_WITH_SPECIFIC_DEBUG_META }) | ||
.start() | ||
.completed(); | ||
}); | ||
|
||
test('multiple events via maxEventsPerHour', async () => { | ||
await createRunner(__dirname, 'basic-multiple.mjs') | ||
.withMockSentryServer() | ||
.expect({ event: ANR_EVENT_WITH_DEBUG_META('basic-multiple') }) | ||
.expect({ event: ANR_EVENT_WITH_DEBUG_META('basic-multiple') }) | ||
.start() | ||
.completed(); | ||
}); | ||
|
||
test('blocked indefinitely', async () => { | ||
await createRunner(__dirname, 'indefinite.mjs') | ||
.withMockSentryServer() | ||
.expect({ event: ANR_EVENT }) | ||
.start() | ||
.completed(); | ||
}); | ||
|
||
test('should exit', async () => { | ||
const runner = createRunner(__dirname, 'should-exit.js').start(); | ||
|
||
await new Promise(resolve => setTimeout(resolve, 5_000)); | ||
|
||
expect(runner.childHasExited()).toBe(true); | ||
}); | ||
|
||
test('should exit forced', async () => { | ||
const runner = createRunner(__dirname, 'should-exit-forced.js').start(); | ||
|
||
await new Promise(resolve => setTimeout(resolve, 5_000)); | ||
|
||
expect(runner.childHasExited()).toBe(true); | ||
}); | ||
|
||
test('worker thread', async () => { | ||
const instrument = join(__dirname, 'instrument.mjs'); | ||
await createRunner(__dirname, 'worker-main.mjs') | ||
.withMockSentryServer() | ||
.withFlags('--import', instrument) | ||
.expect({ | ||
event: event => { | ||
const crashedThread = event.threads?.values?.find(thread => thread.crashed)?.id as string; | ||
expect(crashedThread).toBeDefined(); | ||
|
||
expect(event).toMatchObject({ | ||
...ANR_EVENT, | ||
exception: { | ||
...EXCEPTION(crashedThread), | ||
}, | ||
threads: { | ||
values: [ | ||
{ | ||
id: '0', | ||
name: 'main', | ||
crashed: false, | ||
current: true, | ||
main: true, | ||
stacktrace: { | ||
frames: expect.any(Array), | ||
}, | ||
}, | ||
{ | ||
id: crashedThread, | ||
name: `worker-${crashedThread}`, | ||
crashed: true, | ||
current: true, | ||
main: false, | ||
}, | ||
], | ||
}, | ||
}); | ||
}, | ||
}) | ||
.start() | ||
.completed(); | ||
}); | ||
}); |
5 changes: 5 additions & 0 deletions
5
dev-packages/node-integration-tests/suites/thread-blocked-native/worker-block.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { longWork } from './long-work.js'; | ||
|
||
setTimeout(() => { | ||
longWork(); | ||
}, 2000); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.