diff --git a/test/fixtures/linux-perf.js b/test/fixtures/linux-perf.js new file mode 100644 index 00000000000000..011ef19777bd37 --- /dev/null +++ b/test/fixtures/linux-perf.js @@ -0,0 +1,26 @@ +'use strict'; + +const crypto = require('crypto'); + +// Functions should be complex enough for V8 to run them a few times before +// compiling, but not complex enough to always stay in interpreted mode. They +// should also take some time to run, otherwise Linux perf might miss them +// entirely even when sampling at a high frequency. +function functionOne(i) { + for (let j=i; j > 0; j--) { + crypto.createHash('md5').update(functionTwo(i, j)).digest("hex"); + } +} + +function functionTwo(x, y) { + let data = ((((x * y) + (x / y)) * y) ** (x + 1)).toString(); + if (x % 2 == 0) { + return crypto.createHash('md5').update(data.repeat((x % 100) + 1)).digest("hex"); + } else { + return crypto.createHash('md5').update(data.repeat((y % 100) + 1)).digest("hex"); + } +} + +for (let i = 0; i < 1000; i++) { + functionOne(i); +} diff --git a/test/v8-updates/test-linux-perf.js b/test/v8-updates/test-linux-perf.js new file mode 100644 index 00000000000000..0a7f199e040d58 --- /dev/null +++ b/test/v8-updates/test-linux-perf.js @@ -0,0 +1,81 @@ +'use strict'; + +// This test verifies that JavaScript functions are being correctly sampled by +// Linux perf. The test runs a JavaScript script, sampling the execution with +// Linux perf. It then uses `perf script` to generate a human-readable output, +// and uses regular expressions to find samples of the functions defined in +// `fixtures/linux-perf.js`. + +// NOTE (mmarchini): this test is meant to run only on Linux machines with Linux +// perf installed. It will skip if those criteria are not met. + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const { spawnSync } = require('child_process'); +const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +if (process.config.variables.node_shared) + common.skip("can't test Linux perf with shared libraries yet"); + +const perfArgs = [ + 'record', + '-F500', + '-g', + '--', + process.execPath, + '--perf-basic-prof', + '--interpreted-frames-native-stack', + '--no-turbo-inlining', // Otherwise simple functions might get inlined. + fixtures.path('linux-perf.js'), +]; + +const perfScriptArgs = [ + 'script', +]; + +const options = { + cwd: tmpdir.path, + encoding: 'utf-8', +}; + +if (!common.isLinux) + common.skip('only testing Linux for now'); + +const perf = spawnSync('perf', perfArgs, options); + +if (perf.error && perf.error.errno === 'ENOENT') + common.skip('perf not found on system'); + +if (perf.status !== 0) { + common.skip(`Failed to execute perf: ${perf.stderr}`); +} + +const perfScript = spawnSync('perf', perfScriptArgs, options); + +if (perf.error) + common.skip(`perf script aborted: ${perf.error.errno}`); + +if (perfScript.status !== 0) { + common.skip(`Failed to execute perf script: ${perfScript.stderr}`); +} + +const interpretedFunctionOneRe = /InterpretedFunction:functionOne/; +const compiledFunctionOneRe = /LazyCompile:\*functionOne/; +const interpretedFunctionTwoRe = /InterpretedFunction:functionTwo/; +const compiledFunctionTwoRe = /LazyCompile:\*functionTwo/; + +const output = perfScript.stdout; + +assert.ok(output.match(interpretedFunctionOneRe), + "Couldn't find interpreted functionOne()"); +assert.ok(output.match(compiledFunctionOneRe), + "Couldn't find compiled functionOne()"); +assert.ok(output.match(interpretedFunctionTwoRe), + "Couldn't find interpreted functionTwo()"); +assert.ok(output.match(compiledFunctionTwoRe), + "Couldn't find compiled functionTwo");