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(node): Rework ANR to use worker script via an integration #9823

Merged
merged 30 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ed4a316
feat(node): Use worker thread for ANR detection
timfish Dec 9, 2023
14fe3a5
Remove utility
timfish Dec 11, 2023
e5c82dd
PR feedback
timfish Dec 12, 2023
d246581
Merge branch 'develop' into fix/anr-worker-thread
timfish Dec 12, 2023
dce3684
remove outdated comments
timfish Dec 12, 2023
2ca69ca
Merge remote-tracking branch 'origin/fix/anr-worker-thread' into fix/…
timfish Dec 12, 2023
3042553
use getWorkerThreads
timfish Dec 12, 2023
dad68a1
Merge branch 'develop' into fix/anr-worker-thread
timfish Dec 13, 2023
61dd30f
Merge branch 'develop' into fix/anr-worker-thread
timfish Dec 13, 2023
33e1ce0
feat(node): Rework ANR to use worker script via an integration
timfish Dec 13, 2023
97b09bb
Bodge the build and add platform
timfish Dec 13, 2023
b2a1907
also build before test
timfish Dec 13, 2023
8b6289f
Work on build
timfish Dec 13, 2023
0486f12
Merge branch 'develop' into anr-worker-from-base64-script
timfish Dec 13, 2023
91c19b9
Only node 16 😢
timfish Dec 13, 2023
26b4ebe
Merge remote-tracking branch 'origin/anr-worker-from-base64-script' i…
timfish Dec 13, 2023
d098a53
a few worker simplifications
timfish Dec 14, 2023
27ebe02
PR review comments
timfish Dec 17, 2023
624f097
Merge remote-tracking branch 'upstream/develop' into anr-worker-from-…
timfish Dec 18, 2023
94af9ec
Only test node >= 16
timfish Dec 18, 2023
40f25d2
Include and test the old deprecated API
timfish Dec 18, 2023
7ef8050
Merge branch 'develop' into anr-worker-from-base64-script
timfish Dec 18, 2023
0c71dcc
Linting
timfish Dec 18, 2023
930969b
Merge remote-tracking branch 'origin/anr-worker-from-base64-script' i…
timfish Dec 18, 2023
3de4144
Merge remote-tracking branch 'upstream/develop' into anr-worker-from-…
timfish Dec 18, 2023
b2d4b66
Merge remote-tracking branch 'upstream/develop' into anr-worker-from-…
timfish Dec 19, 2023
f0ac67e
remove isAnrChildProcess usage
timfish Dec 19, 2023
41cc61d
Include `parentSpanId` in trace context
timfish Dec 19, 2023
4a7c4fc
Ensure we get app/device/os/culture context with ANR events
timfish Dec 19, 2023
6271c85
Fix test
timfish Dec 19, 2023
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ tmp.js
.eslintcache
**/eslintcache/*

# node worker scripts
packages/node/src/integrations/anr/worker-script.*

# deno
packages/deno/build-types
packages/deno/build-test
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type { ServerRuntimeClientOptions } from './server-runtime-client';
export type { RequestDataIntegrationOptions } from './integrations/requestdata';

export * from './tracing';
export { createEventEnvelope } from './envelope';
export { createEventEnvelope, createSessionEnvelope } from './envelope';
export {
addBreadcrumb,
captureCheckIn,
Expand Down
27 changes: 11 additions & 16 deletions packages/node-integration-tests/suites/anr/basic-session.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,25 @@ const crypto = require('crypto');

const Sentry = require('@sentry/node');

const { transport } = require('./test-transport.js');

// close both processes after 5 seconds
setTimeout(() => {
process.exit();
}, 5000);
}, 10000);

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
debug: true,
transport,
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
});

Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
function longWork() {
for (let i = 0; i < 100; i++) {
const salt = crypto.randomBytes(128).toString('base64');
// eslint-disable-next-line no-unused-vars
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
}
function longWork() {
for (let i = 0; i < 100; i++) {
const salt = crypto.randomBytes(128).toString('base64');
// eslint-disable-next-line no-unused-vars
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
}
}

setTimeout(() => {
longWork();
}, 1000);
});
setTimeout(() => {
longWork();
}, 1000);
27 changes: 11 additions & 16 deletions packages/node-integration-tests/suites/anr/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,26 @@ const crypto = require('crypto');

const Sentry = require('@sentry/node');

const { transport } = require('./test-transport.js');

// close both processes after 5 seconds
setTimeout(() => {
process.exit();
}, 5000);
}, 10000);

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
debug: true,
autoSessionTracking: false,
transport,
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
});

Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
function longWork() {
for (let i = 0; i < 100; i++) {
const salt = crypto.randomBytes(128).toString('base64');
// eslint-disable-next-line no-unused-vars
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
}
function longWork() {
for (let i = 0; i < 100; i++) {
const salt = crypto.randomBytes(128).toString('base64');
// eslint-disable-next-line no-unused-vars
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
timfish marked this conversation as resolved.
Show resolved Hide resolved
}
}

setTimeout(() => {
longWork();
}, 1000);
});
setTimeout(() => {
longWork();
}, 1000);
9 changes: 2 additions & 7 deletions packages/node-integration-tests/suites/anr/basic.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@ import * as crypto from 'crypto';

import * as Sentry from '@sentry/node';

const { transport } = await import('./test-transport.js');

// close both processes after 5 seconds
setTimeout(() => {
process.exit();
}, 5000);
}, 10000);

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
debug: true,
autoSessionTracking: false,
transport,
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
});

await Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 });

function longWork() {
for (let i = 0; i < 100; i++) {
const salt = crypto.randomBytes(128).toString('base64');
Expand Down
27 changes: 11 additions & 16 deletions packages/node-integration-tests/suites/anr/forked.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,26 @@ const crypto = require('crypto');

const Sentry = require('@sentry/node');

const { transport } = require('./test-transport.js');

// close both processes after 5 seconds
setTimeout(() => {
process.exit();
}, 5000);
}, 10000);

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
debug: true,
autoSessionTracking: false,
transport,
integrations: [new Sentry.Integrations.Anr({ captureStackTrace: true, anrThreshold: 200 })],
});

Sentry.enableAnrDetection({ captureStackTrace: true, anrThreshold: 200 }).then(() => {
function longWork() {
for (let i = 0; i < 100; i++) {
const salt = crypto.randomBytes(128).toString('base64');
// eslint-disable-next-line no-unused-vars
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
}
function longWork() {
for (let i = 0; i < 100; i++) {
const salt = crypto.randomBytes(128).toString('base64');
// eslint-disable-next-line no-unused-vars
const hash = crypto.pbkdf2Sync('myPassword', salt, 10000, 512, 'sha512');
}
}

setTimeout(() => {
longWork();
}, 1000);
});
setTimeout(() => {
longWork();
}, 1000);
17 changes: 0 additions & 17 deletions packages/node-integration-tests/suites/anr/test-transport.js

This file was deleted.

49 changes: 26 additions & 23 deletions packages/node-integration-tests/suites/anr/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ function parseJsonLines<T extends unknown[]>(input: string, expected: number): T

describe('should report ANR when event loop blocked', () => {
test('CJS', done => {
// The stack trace is different when node < 12
const testFramesDetails = NODE_VERSION >= 12;
if (NODE_VERSION < 16) {
done();
return;
}

expect.assertions(testFramesDetails ? 7 : 5);
expect.assertions(9);

const testScriptPath = path.resolve(__dirname, 'basic.js');

Expand All @@ -41,17 +43,18 @@ describe('should report ANR when event loop blocked', () => {
expect(event.exception?.values?.[0].value).toEqual('Application Not Responding for at least 200 ms');
expect(event.exception?.values?.[0].stacktrace?.frames?.length).toBeGreaterThan(4);

if (testFramesDetails) {
expect(event.exception?.values?.[0].stacktrace?.frames?.[2].function).toEqual('?');
expect(event.exception?.values?.[0].stacktrace?.frames?.[3].function).toEqual('longWork');
}
expect(event.exception?.values?.[0].stacktrace?.frames?.[2].function).toEqual('?');
expect(event.exception?.values?.[0].stacktrace?.frames?.[3].function).toEqual('longWork');

expect(event.contexts?.trace?.trace_id).toBeDefined();
expect(event.contexts?.trace?.span_id).toBeDefined();

done();
});
});

test('ESM', done => {
if (NODE_VERSION < 14) {
if (NODE_VERSION < 16) {
done();
return;
}
Expand All @@ -66,7 +69,7 @@ describe('should report ANR when event loop blocked', () => {
expect(event.exception?.values?.[0].mechanism).toEqual({ type: 'ANR' });
expect(event.exception?.values?.[0].type).toEqual('ApplicationNotResponding');
expect(event.exception?.values?.[0].value).toEqual('Application Not Responding for at least 200 ms');
expect(event.exception?.values?.[0].stacktrace?.frames?.length).toBeGreaterThan(4);
expect(event.exception?.values?.[0].stacktrace?.frames?.length).toBeGreaterThanOrEqual(4);
expect(event.exception?.values?.[0].stacktrace?.frames?.[2].function).toEqual('?');
expect(event.exception?.values?.[0].stacktrace?.frames?.[3].function).toEqual('longWork');

Expand All @@ -75,10 +78,12 @@ describe('should report ANR when event loop blocked', () => {
});

test('With session', done => {
// The stack trace is different when node < 12
const testFramesDetails = NODE_VERSION >= 12;
if (NODE_VERSION < 16) {
timfish marked this conversation as resolved.
Show resolved Hide resolved
done();
return;
}

expect.assertions(testFramesDetails ? 9 : 7);
expect.assertions(9);

const testScriptPath = path.resolve(__dirname, 'basic-session.js');

Expand All @@ -90,10 +95,8 @@ describe('should report ANR when event loop blocked', () => {
expect(event.exception?.values?.[0].value).toEqual('Application Not Responding for at least 200 ms');
expect(event.exception?.values?.[0].stacktrace?.frames?.length).toBeGreaterThan(4);

if (testFramesDetails) {
expect(event.exception?.values?.[0].stacktrace?.frames?.[2].function).toEqual('?');
expect(event.exception?.values?.[0].stacktrace?.frames?.[3].function).toEqual('longWork');
}
expect(event.exception?.values?.[0].stacktrace?.frames?.[2].function).toEqual('?');
expect(event.exception?.values?.[0].stacktrace?.frames?.[3].function).toEqual('longWork');

expect(session.status).toEqual('abnormal');
expect(session.abnormal_mechanism).toEqual('anr_foreground');
Expand All @@ -103,10 +106,12 @@ describe('should report ANR when event loop blocked', () => {
});

test('from forked process', done => {
// The stack trace is different when node < 12
const testFramesDetails = NODE_VERSION >= 12;
if (NODE_VERSION < 16) {
done();
return;
}

expect.assertions(testFramesDetails ? 7 : 5);
expect.assertions(7);

const testScriptPath = path.resolve(__dirname, 'forker.js');

Expand All @@ -118,10 +123,8 @@ describe('should report ANR when event loop blocked', () => {
expect(event.exception?.values?.[0].value).toEqual('Application Not Responding for at least 200 ms');
expect(event.exception?.values?.[0].stacktrace?.frames?.length).toBeGreaterThan(4);

if (testFramesDetails) {
expect(event.exception?.values?.[0].stacktrace?.frames?.[2].function).toEqual('?');
expect(event.exception?.values?.[0].stacktrace?.frames?.[3].function).toEqual('longWork');
}
expect(event.exception?.values?.[0].stacktrace?.frames?.[2].function).toEqual('?');
expect(event.exception?.values?.[0].stacktrace?.frames?.[3].function).toEqual('longWork');

done();
});
Expand Down
1 change: 1 addition & 0 deletions packages/node/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
env: {
node: true,
},
ignorePatterns: ['src/integrations/anr/worker-script.ts'],
extends: ['../../.eslintrc.js'],
rules: {
'@sentry-internal/sdk/no-optional-chaining': 'off',
Expand Down
8 changes: 5 additions & 3 deletions packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,22 @@
"scripts": {
"build": "run-p build:transpile build:types",
"build:dev": "yarn build",
"build:transpile": "rollup -c rollup.npm.config.js",
"build:types": "run-s build:types:core build:types:downlevel",
"build:transpile": "yarn build:anr-worker && rollup -c rollup.npm.config.js",
"build:types": "run-s build:anr-worker build:types:core build:types:downlevel",
"build:types:core": "tsc -p tsconfig.types.json",
"build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8",
"build:watch": "run-p build:transpile:watch build:types:watch",
"build:dev:watch": "yarn build:watch",
"build:transpile:watch": "rollup -c rollup.npm.config.js --watch",
"build:types:watch": "tsc -p tsconfig.types.json --watch",
"build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build",
"build:anr-worker": "rollup -c rollup.anr-worker.config.js",
"build:anr-worker-stub": "echo 'export const base64WorkerScript=\"\";' > src/integrations/anr/worker-script.ts",
"circularDepCheck": "madge --circular src/index.ts",
"clean": "rimraf build coverage sentry-node-*.tgz",
"fix": "eslint . --format stylish --fix",
"lint": "eslint . --format stylish",
"test": "run-s test:jest test:express test:webpack test:release-health",
"test": "run-s build:anr-worker-stub test:jest test:express test:webpack test:release-health",
"test:express": "node test/manual/express-scope-separation/start.js",
"test:jest": "jest",
"test:release-health": "node test/manual/release-health/runner.js",
Expand Down
23 changes: 23 additions & 0 deletions packages/node/rollup.anr-worker.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { makeBaseBundleConfig } from '../../rollup/index.js';

export default makeBaseBundleConfig({
bundleType: 'node-worker',
entrypoints: ['src/integrations/anr/worker.ts'],
jsVersion: 'es6',
licenseTitle: '@sentry/node',
outputFileBase: () => 'worker-script.ts',
packageSpecificConfig: {
output: {
dir: './src/integrations/anr',
sourcemap: false,
},
plugins: [
{
name: 'output-base64-worker-script',
renderChunk(code) {
return `export const base64WorkerScript = '${Buffer.from(code).toString('base64')}';`;
},
},
],
},
});
Loading