diff --git a/.gitignore b/.gitignore index ef7d1791..0afadb1e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ coverage/ lib/ +binaries/ node_modules/ memory_db diff --git a/binaries/sentry_cpu_profiler-v93-darwin-arm64.node b/binaries/sentry_cpu_profiler-v93-darwin-arm64.node deleted file mode 100755 index 8d0b7213..00000000 Binary files a/binaries/sentry_cpu_profiler-v93-darwin-arm64.node and /dev/null differ diff --git a/package-lock.json b/package-lock.json index 6452f250..3823d50f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,8 @@ "packages": { "": { "name": "@sentry/profiling-node", - "version": "0.0.7", + "version": "0.0.8", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@sentry/hub": "^7.16.0", @@ -22,6 +23,7 @@ "@types/express": "^4.17.14", "@types/jest": "^29.0.0", "@types/node": "^18.0.2", + "@types/node-abi": "^3.0.0", "@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/parser": "^5.36.2", "autocannon": "^7.9.0", @@ -1997,6 +1999,12 @@ "integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==", "dev": true }, + "node_modules/@types/node-abi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/node-abi/-/node-abi-3.0.0.tgz", + "integrity": "sha512-lhjcQxXaKhbP3SpIjJONnx4cy6cUW2bdCSwPJISuznG3S889TUPQZsYswxYhS4vg8eJDIG5/6pg533HorQI0rw==", + "dev": true + }, "node_modules/@types/prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", @@ -10055,6 +10063,12 @@ "integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==", "dev": true }, + "@types/node-abi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/node-abi/-/node-abi-3.0.0.tgz", + "integrity": "sha512-lhjcQxXaKhbP3SpIjJONnx4cy6cUW2bdCSwPJISuznG3S889TUPQZsYswxYhS4vg8eJDIG5/6pg533HorQI0rw==", + "dev": true + }, "@types/prettier": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz", diff --git a/package.json b/package.json index 205ec1bd..8eb994de 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,13 @@ "README.md", "package.json", "package-lock.json", - "binaries" + "binaries", + "scripts/binaries.js", + "scripts/check-build.js", + "scripts/copu-target.js" ], "scripts": { + "postinstall": "node scripts/check-build.js", "clean": "rm -rf ./lib && rm -rf build", "lint": "eslint ./src --ext .ts", "build": "npm run build:bindings && npm run build:lib", @@ -62,6 +66,7 @@ "@types/express": "^4.17.14", "@types/jest": "^29.0.0", "@types/node": "^18.0.2", + "@types/node-abi": "^3.0.0", "@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/parser": "^5.36.2", "autocannon": "^7.9.0", diff --git a/scripts/binaries.js b/scripts/binaries.js new file mode 100644 index 00000000..faa989a3 --- /dev/null +++ b/scripts/binaries.js @@ -0,0 +1,14 @@ +/* eslint-env node */ +const os = require('os'); +const path = require('path'); +const abi = require('node-abi'); + +function getModuleName() { + return `sentry_cpu_profiler-v${abi.getAbi(process.versions.node, 'node')}-${os.platform()}-${os.arch()}.node`; +} + +const source = path.join(__dirname, '..', 'build', 'Release', 'sentry_cpu_profiler.node'); +const target = path.join(__dirname, '..', 'binaries', getModuleName()); + +module.exports.target = target; +module.exports.source = source; diff --git a/scripts/check-build.js b/scripts/check-build.js new file mode 100644 index 00000000..ec319e2d --- /dev/null +++ b/scripts/check-build.js @@ -0,0 +1,44 @@ +/* eslint-env node */ +const cp = require('child_process'); +const os = require('os'); + +const { target } = require('./binaries'); + +function recompileFromSource() { + try { + console.log('@sentry/profiling-node: Precompiled binary not found, compiling from source...'); + cp.execSync(`npm run build:configure --arch=${os.arch()}`); + cp.execSync(`npm run build:bindings`); + cp.execSync('node scripts/copy-target.js'); + return true; + } catch (e) { + console.error( + '@sentry/profiling-node: Failed to build from source, please report this a bug at https://github.com/getsentry/profiling-node/issues/new?assignees=&labels=Type%3A+Bug&template=bug.yml' + ); + return false; + } +} + +try { + require(target); + console.log('@sentry/profiling-node: Precompiled binary found, skipping build from source.'); +} catch (e) { + // Check for node version missmatch + if (/was compiled against a different Node.js/.test(e.message)) { + const success = recompileFromSource(); + if (success) { + process.exit(0); + } + } + // Not sure if this could even happen, but just in case it somehow does, + // we can provide a better experience than just crashing with cannot find module message. + if (/Cannot find module/.test(e.message)) { + const success = recompileFromSource(); + if (success) { + process.exit(0); + } + } + + // re-throw so we dont end up swallowing errors + throw e; +} diff --git a/src/cpu_profiler.ts b/src/cpu_profiler.ts index 98a3f050..4ebb03f9 100644 --- a/src/cpu_profiler.ts +++ b/src/cpu_profiler.ts @@ -1,7 +1,13 @@ -// @ts-expect-error this screams because it cannot resolve the module? -import profiler from './../build/Release/sentry_cpu_profiler.node'; +import os from 'os'; +import path from 'path'; +import abi from 'node-abi'; import { threadId } from 'worker_threads'; +export function importCppBindingsModule(): PrivateV8CpuProfilerBindings { + const name = `sentry_cpu_profiler-v${abi.getAbi(process.versions.node, 'node')}-${os.platform()}-${os.arch()}.node`; + return require(path.join(__dirname, '..', 'binaries', name)); +} + interface Sample { stack_id: number; thread_id: string; @@ -44,7 +50,7 @@ interface V8CpuProfilerBindings { stopProfiling(name: string): RawThreadCpuProfile | null; } -const privateBindings: PrivateV8CpuProfilerBindings = profiler; +const privateBindings: PrivateV8CpuProfilerBindings = importCppBindingsModule(); const CpuProfilerBindings: V8CpuProfilerBindings = { startProfiling(name: string) { return privateBindings.startProfiling(name); diff --git a/src/hubextensions.hub.test.ts b/src/hubextensions.hub.test.ts index 0ac7200f..df78d533 100644 --- a/src/hubextensions.hub.test.ts +++ b/src/hubextensions.hub.test.ts @@ -1,6 +1,7 @@ import * as Sentry from '@sentry/node'; import '@sentry/tracing'; // this has a addExtensionMethods side effect import { ProfilingIntegration } from './index'; // this has a addExtensionMethods side effect +import { importCppBindingsModule } from './cpu_profiler'; Sentry.init({ dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', @@ -11,8 +12,7 @@ Sentry.init({ integrations: [new ProfilingIntegration()] }); -// @ts-expect-error file extension errors -import profiler from './../build/Release/sentry_cpu_profiler'; +const profiler = importCppBindingsModule(); describe('hubextensions', () => { beforeEach(() => { diff --git a/src/hubextensions.test.ts b/src/hubextensions.test.ts index 9e27660e..c116a857 100644 --- a/src/hubextensions.test.ts +++ b/src/hubextensions.test.ts @@ -1,9 +1,9 @@ import type { BaseTransportOptions, ClientOptions, Hub, Transaction, TransactionMetadata } from '@sentry/types'; import { __PRIVATE__wrapStartTransactionWithProfiling } from './hubextensions'; +import { importCppBindingsModule } from './cpu_profiler'; -// @ts-expect-error file extension errors -import profiler from './../build/Release/sentry_cpu_profiler'; +const profiler = importCppBindingsModule(); function makeTransactionMock(): Transaction { return {