diff --git a/jest.config.js b/jest.config.js index d7079094..836a69a9 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,7 +3,7 @@ module.exports = { coverageReporters: ['html', 'lcov'], coverageThreshold: { global: { - branches: 87.29, + branches: 87.25, functions: 96.25, lines: 90.85, statements: -249, diff --git a/package-lock.json b/package-lock.json index 2adf5272..202cd841 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "devDependencies": { "@azure/cosmos": "^2.1.7", "@bugsnag/js": "^6.3.2", + "@datadog/pprof": "^5.2.0", "@ffmpeg-installer/ffmpeg": "^1.1.0", "@google-cloud/bigquery": "^4.1.4", "@google-cloud/firestore": "^7.6.0", @@ -991,6 +992,43 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@datadog/pprof": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@datadog/pprof/-/pprof-5.2.0.tgz", + "integrity": "sha512-pSwLARpNLAIV1JttxXOBRKTn/NQYXDy1PJaV458YFDdAYxnBqpsYTat3/nX+8V5GoN4SfdHDci3zqXM+Ym66gQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "delay": "^5.0.0", + "node-gyp-build": "<4.0", + "p-limit": "^3.1.0", + "pprof-format": "^2.1.0", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@datadog/pprof/node_modules/node-gyp-build": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.9.0.tgz", + "integrity": "sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==", + "dev": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/@datadog/pprof/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/@emnapi/runtime": { "version": "0.44.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz", @@ -10017,6 +10055,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -23596,6 +23646,12 @@ "node": ">=0.10.0" } }, + "node_modules/pprof-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pprof-format/-/pprof-format-2.1.0.tgz", + "integrity": "sha512-0+G5bHH0RNr8E5hoZo/zJYsL92MhkZjwrHp3O2IxmY8RJL9ooKeuZ8Tm0ZNBw5sGZ9TiM71sthTjWoR2Vf5/xw==", + "dev": true + }, "node_modules/prepare-response": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/prepare-response/-/prepare-response-2.1.1.tgz", diff --git a/package.json b/package.json index d3cc9433..e9292109 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "devDependencies": { "@azure/cosmos": "^2.1.7", "@bugsnag/js": "^6.3.2", + "@datadog/pprof": "^5.2.0", "@ffmpeg-installer/ffmpeg": "^1.1.0", "@google-cloud/bigquery": "^4.1.4", "@google-cloud/firestore": "^7.6.0", diff --git a/src/analyze.ts b/src/analyze.ts index 4af46c71..d6a8a821 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -754,46 +754,38 @@ export default async function analyze( break; case NODE_GYP_BUILD: // handle case: require('node-gyp-build')(__dirname) - const withDirname = - node.arguments.length === 1 && - node.arguments[0].type === 'Identifier' && - node.arguments[0].name === '__dirname'; - - // handle case: require('node-gyp-build')(path.join(__dirname, '..')) - const withPathJoinDirname = - node.arguments.length === 1 && - node.arguments[0].callee?.object?.name === 'path' && - node.arguments[0].callee?.property?.name === 'join' && - node.arguments[0].arguments.length === 2 && - node.arguments[0].arguments[0].type === 'Identifier' && - node.arguments[0].arguments[0].name === '__dirname' && - node.arguments[0].arguments[1].type === 'Literal'; - - if ( - knownBindings.__dirname.shadowDepth === 0 && - (withDirname || withPathJoinDirname) - ) { - const pathJoinedDir = withPathJoinDirname - ? path.join(dir, node.arguments[0].arguments[1].value) - : dir; + // handle case: require('node-gyp-build')(join(__dirname, '')) + // also case: require('@aminya/node-gyp-build')(__dirname) - let resolved: string | undefined; - try { - // the pkg could be 'node-gyp-build' or '@aminya/node-gyp-build' - const pkgName = node.callee.arguments[0].value; - // use installed version of node-gyp-build since resolving - // binaries can differ among versions - const nodeGypBuildPath = resolveFrom(pathJoinedDir, pkgName); - resolved = require(nodeGypBuildPath).path(pathJoinedDir); - } catch (e) { + if (node.arguments.length) { + const arg = await computePureStaticValue( + node.arguments[0], + false, + ); + if (arg && 'value' in arg && arg.value) { + const pathJoinedDir = arg.value; + let resolved: string | undefined; try { - resolved = nodeGypBuild.path(pathJoinedDir); - } catch (e) {} - } - if (resolved) { - staticChildValue = { value: resolved }; - staticChildNode = node; - await emitStaticChildAsset(); + // the pkg could be 'node-gyp-build' or '@aminya/node-gyp-build' + const pkgName = + node?.callee?.arguments?.[0]?.value || 'node-gyp-build'; + // use installed version of node-gyp-build since resolving + // binaries can differ among versions + const nodeGypBuildPath = resolveFrom( + pathJoinedDir, + pkgName, + ); + resolved = require(nodeGypBuildPath).path(pathJoinedDir); + } catch (e) { + try { + resolved = nodeGypBuild.path(pathJoinedDir); + } catch (e) {} + } + if (resolved) { + staticChildValue = { value: resolved }; + staticChildNode = node; + await emitStaticChildAsset(); + } } } break; diff --git a/test/integration/datadog-pprof.js b/test/integration/datadog-pprof.js new file mode 100644 index 00000000..0e859876 --- /dev/null +++ b/test/integration/datadog-pprof.js @@ -0,0 +1 @@ +const dd = require('@datadog/pprof'); diff --git a/test/unit.test.js b/test/unit.test.js index c2f34426..d54a50fd 100644 --- a/test/unit.test.js +++ b/test/unit.test.js @@ -10,7 +10,14 @@ const readFile = gracefulFS.promises.readFile; global._unit = true; +const nodeGypTests = [ + 'datadog-pprof-node-gyp', + 'microtime-node-gyp', + 'zeromq-node-gyp', +]; + const skipOnWindows = [ + 'datadog-pprof-node-gyp', 'yarn-workspaces', 'yarn-workspaces-base-root', 'yarn-workspace-esm', @@ -234,7 +241,7 @@ for (const { testName, isRoot } of unitTests) { } let sortedFileList = [...fileList].sort(); - if (testName === 'microtime-node-gyp' || testName === 'zeromq-node-gyp') { + if (nodeGypTests.includes(testName)) { let foundMatchingBinary = false; sortedFileList = sortedFileList.filter((file) => { if (file.includes('prebuilds') && file.endsWith('.node')) { diff --git a/test/unit/datadog-pprof-node-gyp/input.js b/test/unit/datadog-pprof-node-gyp/input.js new file mode 100644 index 00000000..0e859876 --- /dev/null +++ b/test/unit/datadog-pprof-node-gyp/input.js @@ -0,0 +1 @@ +const dd = require('@datadog/pprof'); diff --git a/test/unit/datadog-pprof-node-gyp/output.js b/test/unit/datadog-pprof-node-gyp/output.js new file mode 100644 index 00000000..1ef3c855 --- /dev/null +++ b/test/unit/datadog-pprof-node-gyp/output.js @@ -0,0 +1,38 @@ +[ + "node_modules/@datadog/pprof/node_modules/node-gyp-build/index.js", + "node_modules/@datadog/pprof/node_modules/node-gyp-build/package.json", + "node_modules/@datadog/pprof/node_modules/source-map/lib/array-set.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/base64-vlq.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/base64.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/binary-search.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/mapping-list.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/mappings.wasm", + "node_modules/@datadog/pprof/node_modules/source-map/lib/read-wasm.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/source-map-consumer.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/source-map-generator.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/source-node.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/util.js", + "node_modules/@datadog/pprof/node_modules/source-map/lib/wasm.js", + "node_modules/@datadog/pprof/node_modules/source-map/package.json", + "node_modules/@datadog/pprof/node_modules/source-map/source-map.js", + "node_modules/@datadog/pprof/out/src/heap-profiler-bindings.js", + "node_modules/@datadog/pprof/out/src/heap-profiler.js", + "node_modules/@datadog/pprof/out/src/index.js", + "node_modules/@datadog/pprof/out/src/logger.js", + "node_modules/@datadog/pprof/out/src/profile-encoder.js", + "node_modules/@datadog/pprof/out/src/profile-serializer.js", + "node_modules/@datadog/pprof/out/src/sourcemapper/sourcemapper.js", + "node_modules/@datadog/pprof/out/src/time-profiler-bindings.js", + "node_modules/@datadog/pprof/out/src/time-profiler.js", + "node_modules/@datadog/pprof/package.json", + "node_modules/delay/index.js", + "node_modules/delay/package.json", + "node_modules/p-limit/index.js", + "node_modules/p-limit/package.json", + "node_modules/pprof-format/dist/index.js", + "node_modules/pprof-format/package.json", + "node_modules/yocto-queue/index.js", + "node_modules/yocto-queue/package.json", + "package.json", + "test/unit/datadog-pprof-node-gyp/input.js" +] \ No newline at end of file