diff --git a/doc/api/cli.md b/doc/api/cli.md index e76a3296f0670b..3dbb230c87816a 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -245,6 +245,57 @@ new X(); added: v12.0.0 --> +### `--heap-prof` + + +> Stability: 1 - Experimental + +Starts the V8 heap profiler on start up, and writes the heap profile to disk +before exit. + +If `--heap-prof-dir` is not specified, the generated profile will be placed +in the current working directory. + +If `--heap-prof-name` is not specified, the generated profile will be +named `Heap.${yyyymmdd}.${hhmmss}.${pid}.${tid}.${seq}.heapprofile`. + +```console +$ node --heap-prof index.js +$ ls *.heapprofile +Heap.20190409.202950.15293.0.001.heapprofile +``` + +### `--heap-prof-dir` + + +> Stability: 1 - Experimental + +Specify the directory where the heap profiles generated by `--heap-prof` will +be placed. + +### `--heap-prof-interval` + + +> Stability: 1 - Experimental + +Specify the average sampling interval in bytes for the heap profiles generated +by `--heap-prof`. The default is 512 * 1024 bytes. + +### `--heap-prof-name` + + +> Stability: 1 - Experimental + +Specify the file name of the heap profile generated by `--heap-prof`. + Generates a heap snapshot each time the process receives the specified signal. `signal` must be a valid signal name. Disabled by default. diff --git a/doc/node.1 b/doc/node.1 index 13aa94ff383705..7349b635680499 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -139,6 +139,28 @@ Enable experimental frozen intrinsics support. .It Fl -heapsnapshot-signal Ns = Ns Ar signal Generate heap snapshot on specified signal. . +.It Fl -heap-prof +Start the V8 heap profiler on start up, and write the heap profile to disk +before exit. If +.Fl -heap-prof-dir +is not specified, the profile will be written to the current working directory +with a generated file name. +. +.It Fl -heap-prof-dir +The directory where the heap profiles generated by +.Fl -heap-prof +will be placed. +. +.It Fl -heap-prof-interval +The average sampling interval in bytes for the heap profiles generated by +.Fl -heap-prof . +The default is +.Sy 512 * 1024 . +. +.It Fl -heap-prof-name +File name of the V8 heap profile generated with +.Fl -heap-prof +. .It Fl -http-parser Ns = Ns Ar library Chooses an HTTP parser library. Available values are .Sy llhttp diff --git a/src/env-inl.h b/src/env-inl.h index 4765d0db98525a..2239412ccac9cb 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -689,6 +689,41 @@ inline const std::string& Environment::cpu_prof_dir() const { return cpu_prof_dir_; } +inline void Environment::set_heap_profiler_connection( + std::unique_ptr connection) { + CHECK_NULL(heap_profiler_connection_); + std::swap(heap_profiler_connection_, connection); +} + +inline profiler::V8HeapProfilerConnection* +Environment::heap_profiler_connection() { + return heap_profiler_connection_.get(); +} + +inline void Environment::set_heap_prof_name(const std::string& name) { + heap_prof_name_ = name; +} + +inline const std::string& Environment::heap_prof_name() const { + return heap_prof_name_; +} + +inline void Environment::set_heap_prof_dir(const std::string& dir) { + heap_prof_dir_ = dir; +} + +inline const std::string& Environment::heap_prof_dir() const { + return heap_prof_dir_; +} + +inline void Environment::set_heap_prof_interval(uint64_t interval) { + heap_prof_interval_ = interval; +} + +inline uint64_t Environment::heap_prof_interval() const { + return heap_prof_interval_; +} + #endif // HAVE_INSPECTOR inline std::shared_ptr Environment::inspector_host_port() { diff --git a/src/env.h b/src/env.h index 5544ac44db5f8a..0c6dbe3c8f581f 100644 --- a/src/env.h +++ b/src/env.h @@ -73,6 +73,7 @@ class AgentWriterHandle; namespace profiler { class V8CoverageConnection; class V8CpuProfilerConnection; +class V8HeapProfilerConnection; } // namespace profiler #endif // HAVE_INSPECTOR @@ -1151,6 +1152,20 @@ class Environment : public MemoryRetainer { inline void set_cpu_prof_dir(const std::string& dir); inline const std::string& cpu_prof_dir() const; + + void set_heap_profiler_connection( + std::unique_ptr connection); + profiler::V8HeapProfilerConnection* heap_profiler_connection(); + + inline void set_heap_prof_name(const std::string& name); + inline const std::string& heap_prof_name() const; + + inline void set_heap_prof_dir(const std::string& dir); + inline const std::string& heap_prof_dir() const; + + inline void set_heap_prof_interval(uint64_t interval); + inline uint64_t heap_prof_interval() const; + #endif // HAVE_INSPECTOR private: @@ -1190,6 +1205,10 @@ class Environment : public MemoryRetainer { std::string cpu_prof_dir_; std::string cpu_prof_name_; uint64_t cpu_prof_interval_; + std::unique_ptr heap_profiler_connection_; + std::string heap_prof_dir_; + std::string heap_prof_name_; + uint64_t heap_prof_interval_; #endif // HAVE_INSPECTOR std::shared_ptr options_; diff --git a/src/inspector_profiler.cc b/src/inspector_profiler.cc index 2a1559f8f97495..1b20398dba4d3d 100644 --- a/src/inspector_profiler.cc +++ b/src/inspector_profiler.cc @@ -258,6 +258,44 @@ void V8CpuProfilerConnection::End() { DispatchMessage("Profiler.stop"); } +std::string V8HeapProfilerConnection::GetDirectory() const { + return env()->heap_prof_dir(); +} + +std::string V8HeapProfilerConnection::GetFilename() const { + return env()->heap_prof_name(); +} + +MaybeLocal V8HeapProfilerConnection::GetProfile(Local result) { + Local profile_v; + if (!result + ->Get(env()->context(), + FIXED_ONE_BYTE_STRING(env()->isolate(), "profile")) + .ToLocal(&profile_v)) { + fprintf(stderr, "'profile' from heap profile result is undefined\n"); + return MaybeLocal(); + } + if (!profile_v->IsObject()) { + fprintf(stderr, "'profile' from heap profile result is not an Object\n"); + return MaybeLocal(); + } + return profile_v.As(); +} + +void V8HeapProfilerConnection::Start() { + DispatchMessage("HeapProfiler.enable"); + std::string params = R"({ "samplingInterval": )"; + params += std::to_string(env()->heap_prof_interval()); + params += " }"; + DispatchMessage("HeapProfiler.startSampling", params.c_str()); +} + +void V8HeapProfilerConnection::End() { + CHECK_EQ(ending_, false); + ending_ = true; + DispatchMessage("HeapProfiler.stopSampling"); +} + // For now, we only support coverage profiling, but we may add more // in the future. void EndStartedProfilers(Environment* env) { @@ -268,6 +306,12 @@ void EndStartedProfilers(Environment* env) { connection->End(); } + connection = env->heap_profiler_connection(); + if (connection != nullptr && !connection->ending()) { + Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending heap profiling\n"); + connection->End(); + } + connection = env->coverage_connection(); if (connection != nullptr && !connection->ending()) { Debug( @@ -313,6 +357,20 @@ void StartProfilers(Environment* env) { std::make_unique(env)); env->cpu_profiler_connection()->Start(); } + if (env->options()->heap_prof) { + const std::string& dir = env->options()->heap_prof_dir; + env->set_heap_prof_interval(env->options()->heap_prof_interval); + env->set_heap_prof_dir(dir.empty() ? GetCwd() : dir); + if (env->options()->heap_prof_name.empty()) { + DiagnosticFilename filename(env, "Heap", "heapprofile"); + env->set_heap_prof_name(*filename); + } else { + env->set_heap_prof_name(env->options()->heap_prof_name); + } + env->set_heap_profiler_connection( + std::make_unique(env)); + env->heap_profiler_connection()->Start(); + } } static void SetCoverageDirectory(const FunctionCallbackInfo& args) { diff --git a/src/inspector_profiler.h b/src/inspector_profiler.h index 345ef90d4e15c6..e7d45d7de34f35 100644 --- a/src/inspector_profiler.h +++ b/src/inspector_profiler.h @@ -107,6 +107,26 @@ class V8CpuProfilerConnection : public V8ProfilerConnection { bool ending_ = false; }; +class V8HeapProfilerConnection : public V8ProfilerConnection { + public: + explicit V8HeapProfilerConnection(Environment* env) + : V8ProfilerConnection(env) {} + + void Start() override; + void End() override; + + const char* type() const override { return "heap"; } + bool ending() const override { return ending_; } + + std::string GetDirectory() const override; + std::string GetFilename() const override; + v8::MaybeLocal GetProfile(v8::Local result) override; + + private: + std::unique_ptr session_; + bool ending_ = false; +}; + } // namespace profiler } // namespace node diff --git a/src/node_options.cc b/src/node_options.cc index 37aefca1e46450..da7a49231fcb91 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -168,6 +168,19 @@ void EnvironmentOptions::CheckOptions(std::vector* errors) { } } + if (!heap_prof) { + if (!heap_prof_name.empty()) { + errors->push_back("--heap-prof-name must be used with --heap-prof"); + } + if (!heap_prof_dir.empty()) { + errors->push_back("--heap-prof-dir must be used with --heap-prof"); + } + // We can't catch the case where the value passed is the default value, + // then the option just becomes a noop which is fine. + if (heap_prof_interval != kDefaultHeapProfInterval) { + errors->push_back("--heap-prof-interval must be used with --heap-prof"); + } + } debug_options_.CheckOptions(errors); #endif // HAVE_INSPECTOR } @@ -369,6 +382,24 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "Directory where the V8 profiles generated by --cpu-prof will be " "placed. Does not affect --prof.", &EnvironmentOptions::cpu_prof_dir); + AddOption( + "--heap-prof", + "Start the V8 heap profiler on start up, and write the heap profile " + "to disk before exit. If --heap-prof-dir is not specified, write " + "the profile to the current working directory.", + &EnvironmentOptions::heap_prof); + AddOption("--heap-prof-name", + "specified file name of the V8 CPU profile generated with " + "--heap-prof", + &EnvironmentOptions::heap_prof_name); + AddOption("--heap-prof-dir", + "Directory where the V8 heap profiles generated by --heap-prof " + "will be placed.", + &EnvironmentOptions::heap_prof_dir); + AddOption("--heap-prof-interval", + "specified sampling interval in bytes for the V8 heap " + "profile generated with --heap-prof. (default: 512 * 1024)", + &EnvironmentOptions::heap_prof_interval); #endif // HAVE_INSPECTOR AddOption("--redirect-warnings", "write warnings to file instead of stderr", diff --git a/src/node_options.h b/src/node_options.h index e84719b6c7e354..6b4eb89a9698a1 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -115,6 +115,11 @@ class EnvironmentOptions : public Options { uint64_t cpu_prof_interval = kDefaultCpuProfInterval; std::string cpu_prof_name; bool cpu_prof = false; + std::string heap_prof_dir; + std::string heap_prof_name; + static const uint64_t kDefaultHeapProfInterval = 512 * 1024; + uint64_t heap_prof_interval = kDefaultHeapProfInterval; + bool heap_prof = false; #endif // HAVE_INSPECTOR std::string redirect_warnings; bool throw_deprecation = false; diff --git a/test/fixtures/workload/allocation-exit.js b/test/fixtures/workload/allocation-exit.js new file mode 100644 index 00000000000000..dccc61ac94f9fa --- /dev/null +++ b/test/fixtures/workload/allocation-exit.js @@ -0,0 +1,17 @@ +'use strict'; + +const util = require('util'); +const total = parseInt(process.env.TEST_ALLOCATION) || 100; +let count = 0; +let string = ''; +function runAllocation() { + string += util.inspect(process.env); + if (count++ < total) { + setTimeout(runAllocation, 1); + } else { + console.log(string.length); + process.exit(55); + } +} + +setTimeout(runAllocation, 1); diff --git a/test/fixtures/workload/allocation-sigint.js b/test/fixtures/workload/allocation-sigint.js new file mode 100644 index 00000000000000..96ae669016a91b --- /dev/null +++ b/test/fixtures/workload/allocation-sigint.js @@ -0,0 +1,17 @@ +'use strict'; + +const util = require('util'); +const total = parseInt(process.env.TEST_ALLOCATION) || 100; +let count = 0; +let string = ''; +function runAllocation() { + string += util.inspect(process.env); + if (count++ < total) { + setTimeout(runAllocation, 1); + } else { + console.log(string.length); + process.kill(process.pid, "SIGINT"); + } +} + +setTimeout(runAllocation, 1); diff --git a/test/fixtures/workload/allocation-worker-argv.js b/test/fixtures/workload/allocation-worker-argv.js new file mode 100644 index 00000000000000..299eb884e51442 --- /dev/null +++ b/test/fixtures/workload/allocation-worker-argv.js @@ -0,0 +1,11 @@ +'use strict'; + +const { Worker } = require('worker_threads'); +const path = require('path'); +new Worker(path.join(__dirname, 'allocation.js'), { + execArgv: [ + '--heap-prof', + '--heap-prof-interval', + process.HEAP_PROF_INTERVAL || '128', + ] +}); diff --git a/test/fixtures/workload/allocation-worker.js b/test/fixtures/workload/allocation-worker.js new file mode 100644 index 00000000000000..21be6ce91a35a9 --- /dev/null +++ b/test/fixtures/workload/allocation-worker.js @@ -0,0 +1,5 @@ +'use strict'; + +const { Worker } = require('worker_threads'); +const path = require('path'); +new Worker(path.join(__dirname, 'allocation.js')); diff --git a/test/fixtures/workload/allocation.js b/test/fixtures/workload/allocation.js new file mode 100644 index 00000000000000..b9a767f0f5b10e --- /dev/null +++ b/test/fixtures/workload/allocation.js @@ -0,0 +1,16 @@ +'use strict'; + +const util = require('util'); +const total = parseInt(process.env.TEST_ALLOCATION) || 100; +let count = 0; +let string = ''; +function runAllocation() { + string += util.inspect(process.env); + if (count++ < total) { + setTimeout(runAllocation, 1); + } else { + console.log(string.length); + } +} + +setTimeout(runAllocation, 1); diff --git a/test/sequential/test-heap-prof.js b/test/sequential/test-heap-prof.js new file mode 100644 index 00000000000000..cf70fa926091a6 --- /dev/null +++ b/test/sequential/test-heap-prof.js @@ -0,0 +1,375 @@ +'use strict'; + +// This tests that --heap-prof, --heap-prof-dir and --heap-prof-name works. + +const common = require('../common'); + +const fixtures = require('../common/fixtures'); +common.skipIfInspectorDisabled(); + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const { spawnSync } = require('child_process'); + +const tmpdir = require('../common/tmpdir'); + +function getHeapProfiles(dir) { + const list = fs.readdirSync(dir); + return list + .filter((file) => file.endsWith('.heapprofile')) + .map((file) => path.join(dir, file)); +} + +function findFirstFrameInNode(root, func) { + const first = root.children.find( + (child) => child.callFrame.functionName === func + ); + if (first) { + return first; + } + for (const child of root.children) { + const first = findFirstFrameInNode(child, func); + if (first) { + return first; + } + } + return undefined; +} + +function findFirstFrame(file, func) { + const data = fs.readFileSync(file, 'utf8'); + const profile = JSON.parse(data); + const first = findFirstFrameInNode(profile.head, func); + return { frame: first, roots: profile.head.children }; +} + +function verifyFrames(output, file, func) { + const { frame, roots } = findFirstFrame(file, func); + if (!frame) { + // Show native debug output and the profile for debugging. + console.log(output.stderr.toString()); + console.log(roots); + } + assert.notDeepStrictEqual(frame, undefined); +} + +// We need to set --heap-prof-interval to a small enough value to make +// sure we can find our workload in the samples, so we need to set +// TEST_ALLOCATION > kHeapProfInterval. +const kHeapProfInterval = 128; +const TEST_ALLOCATION = kHeapProfInterval * 2; + +const env = { + ...process.env, + TEST_ALLOCATION, + NODE_DEBUG_NATIVE: 'INSPECTOR_PROFILER' +}; + +// Test --heap-prof without --heap-prof-interval. Here we just verify that +// we manage to generate a profile. +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + '--heap-prof', + fixtures.path('workload', 'allocation.js'), + ], { + cwd: tmpdir.path, + env + }); + if (output.status !== 0) { + console.log(output.stderr.toString()); + console.log(output); + } + assert.strictEqual(output.status, 0); + const profiles = getHeapProfiles(tmpdir.path); + assert.strictEqual(profiles.length, 1); +} + +// Outputs heap profile when event loop is drained. +// TODO(joyeecheung): share the fixutres with v8 coverage tests +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + '--heap-prof', + '--heap-prof-interval', + kHeapProfInterval, + fixtures.path('workload', 'allocation.js'), + ], { + cwd: tmpdir.path, + env + }); + if (output.status !== 0) { + console.log(output.stderr.toString()); + console.log(output); + } + assert.strictEqual(output.status, 0); + const profiles = getHeapProfiles(tmpdir.path); + assert.strictEqual(profiles.length, 1); + verifyFrames(output, profiles[0], 'runAllocation'); +} + +// Outputs heap profile when process.exit(55) exits process. +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + '--heap-prof', + '--heap-prof-interval', + kHeapProfInterval, + fixtures.path('workload', 'allocation-exit.js'), + ], { + cwd: tmpdir.path, + env + }); + if (output.status !== 55) { + console.log(output.stderr.toString()); + } + assert.strictEqual(output.status, 55); + const profiles = getHeapProfiles(tmpdir.path); + assert.strictEqual(profiles.length, 1); + verifyFrames(output, profiles[0], 'runAllocation'); +} + +// Outputs heap profile when process.kill(process.pid, "SIGINT"); exits process. +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + '--heap-prof', + '--heap-prof-interval', + kHeapProfInterval, + fixtures.path('workload', 'allocation-sigint.js'), + ], { + cwd: tmpdir.path, + env + }); + if (!common.isWindows) { + if (output.signal !== 'SIGINT') { + console.log(output.stderr.toString()); + } + assert.strictEqual(output.signal, 'SIGINT'); + } + const profiles = getHeapProfiles(tmpdir.path); + assert.strictEqual(profiles.length, 1); + verifyFrames(output, profiles[0], 'runAllocation'); +} + +// Outputs heap profile from worker when execArgv is set. +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + fixtures.path('workload', 'allocation-worker-argv.js'), + ], { + cwd: tmpdir.path, + env: { + ...process.env, + HEAP_PROF_INTERVAL: '128' + } + }); + if (output.status !== 0) { + console.log(output.stderr.toString()); + } + assert.strictEqual(output.status, 0); + const profiles = getHeapProfiles(tmpdir.path); + assert.strictEqual(profiles.length, 1); + verifyFrames(output, profiles[0], 'runAllocation'); +} + +// --heap-prof-name without --heap-prof +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + '--heap-prof-name', + 'test.heapprofile', + fixtures.path('workload', 'allocation.js'), + ], { + cwd: tmpdir.path, + env + }); + const stderr = output.stderr.toString().trim(); + if (output.status !== 9) { + console.log(stderr); + } + assert.strictEqual(output.status, 9); + assert.strictEqual( + stderr, + `${process.execPath}: --heap-prof-name must be used with --heap-prof`); +} + +// --heap-prof-dir without --heap-prof +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + '--heap-prof-dir', + 'prof', + fixtures.path('workload', 'allocation.js'), + ], { + cwd: tmpdir.path, + env + }); + const stderr = output.stderr.toString().trim(); + if (output.status !== 9) { + console.log(stderr); + } + assert.strictEqual(output.status, 9); + assert.strictEqual( + stderr, + `${process.execPath}: --heap-prof-dir must be used with --heap-prof`); +} + +// --heap-prof-interval without --heap-prof +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + '--heap-prof-interval', + kHeapProfInterval, + fixtures.path('workload', 'allocation.js'), + ], { + cwd: tmpdir.path, + env + }); + const stderr = output.stderr.toString().trim(); + if (output.status !== 9) { + console.log(stderr); + } + assert.strictEqual(output.status, 9); + assert.strictEqual( + stderr, + `${process.execPath}: ` + + '--heap-prof-interval must be used with --heap-prof'); +} + +// --heap-prof-name +{ + tmpdir.refresh(); + const file = path.join(tmpdir.path, 'test.heapprofile'); + const output = spawnSync(process.execPath, [ + '--heap-prof', + '--heap-prof-name', + 'test.heapprofile', + '--heap-prof-interval', + kHeapProfInterval, + fixtures.path('workload', 'allocation.js'), + ], { + cwd: tmpdir.path, + env + }); + if (output.status !== 0) { + console.log(output.stderr.toString()); + } + assert.strictEqual(output.status, 0); + const profiles = getHeapProfiles(tmpdir.path); + assert.deepStrictEqual(profiles, [file]); + verifyFrames(output, file, 'runAllocation'); +} + +// relative --heap-prof-dir +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + '--heap-prof', + '--heap-prof-dir', + 'prof', + '--heap-prof-interval', + kHeapProfInterval, + fixtures.path('workload', 'allocation.js'), + ], { + cwd: tmpdir.path, + env + }); + if (output.status !== 0) { + console.log(output.stderr.toString()); + } + assert.strictEqual(output.status, 0); + const dir = path.join(tmpdir.path, 'prof'); + assert(fs.existsSync(dir)); + const profiles = getHeapProfiles(dir); + assert.strictEqual(profiles.length, 1); + verifyFrames(output, profiles[0], 'runAllocation'); +} + +// absolute --heap-prof-dir +{ + tmpdir.refresh(); + const dir = path.join(tmpdir.path, 'prof'); + const output = spawnSync(process.execPath, [ + '--heap-prof', + '--heap-prof-dir', + dir, + '--heap-prof-interval', + kHeapProfInterval, + fixtures.path('workload', 'allocation.js'), + ], { + cwd: tmpdir.path, + env + }); + if (output.status !== 0) { + console.log(output.stderr.toString()); + } + assert.strictEqual(output.status, 0); + assert(fs.existsSync(dir)); + const profiles = getHeapProfiles(dir); + assert.strictEqual(profiles.length, 1); + verifyFrames(output, profiles[0], 'runAllocation'); +} + +// --heap-prof-dir and --heap-prof-name +{ + tmpdir.refresh(); + const dir = path.join(tmpdir.path, 'prof'); + const file = path.join(dir, 'test.heapprofile'); + const output = spawnSync(process.execPath, [ + '--heap-prof', + '--heap-prof-name', + 'test.heapprofile', + '--heap-prof-dir', + dir, + '--heap-prof-interval', + kHeapProfInterval, + fixtures.path('workload', 'allocation.js'), + ], { + cwd: tmpdir.path, + env + }); + if (output.status !== 0) { + console.log(output.stderr.toString()); + } + assert.strictEqual(output.status, 0); + assert(fs.existsSync(dir)); + const profiles = getHeapProfiles(dir); + assert.deepStrictEqual(profiles, [file]); + verifyFrames(output, file, 'runAllocation'); +} + +{ + tmpdir.refresh(); + const output = spawnSync(process.execPath, [ + '--heap-prof-interval', + kHeapProfInterval, + '--heap-prof-dir', + 'prof', + '--heap-prof', + fixtures.path('workload', 'allocation-worker.js'), + ], { + cwd: tmpdir.path, + env + }); + if (output.status !== 0) { + console.log(output.stderr.toString()); + } + assert.strictEqual(output.status, 0); + const dir = path.join(tmpdir.path, 'prof'); + assert(fs.existsSync(dir)); + const profiles = getHeapProfiles(dir); + assert.strictEqual(profiles.length, 2); + const profile1 = findFirstFrame(profiles[0], 'runAllocation'); + const profile2 = findFirstFrame(profiles[1], 'runAllocation'); + if (!profile1.frame && !profile2.frame) { + // Show native debug output and the profile for debugging. + console.log(output.stderr.toString()); + console.log('heap path: ', profiles[0]); + console.log(profile1.roots); + console.log('heap path: ', profiles[1]); + console.log(profile2.roots); + } + assert(profile1.frame || profile2.frame); +}