Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9092e12

Browse files
jasnelltargos
authored andcommittedMar 27, 2019
v8: integrate node-heapdump into core
Adds `v8.writeHeapSnapshot(filename)` with impl adapted from the `node-heapdump` module. Also, adds a v8.getHeapSnapshot() alternative that returns a Readable Stream PR-URL: nodejs#26501 Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com> Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent d0801a1 commit 9092e12

14 files changed

+537
-15
lines changed
 

‎LICENSE

+38-1
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ The externally maintained libraries used by Node.js are:
634634

635635
- OpenSSL, located at deps/openssl, is licensed as follows:
636636
"""
637-
Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved.
637+
Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved.
638638

639639
Redistribution and use in source and binary forms, with or without
640640
modification, are permitted provided that the following conditions
@@ -1445,3 +1445,40 @@ The externally maintained libraries used by Node.js are:
14451445
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
14461446
THE POSSIBILITY OF SUCH DAMAGE.
14471447
"""
1448+
1449+
- node-heapdump, located at src/heap_utils.cc, is licensed as follows:
1450+
"""
1451+
ISC License
1452+
1453+
Copyright (c) 2012, Ben Noordhuis <info@bnoordhuis.nl>
1454+
1455+
Permission to use, copy, modify, and/or distribute this software for any
1456+
purpose with or without fee is hereby granted, provided that the above
1457+
copyright notice and this permission notice appear in all copies.
1458+
1459+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1460+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1461+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1462+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1463+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1464+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1465+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1466+
1467+
=== src/compat.h src/compat-inl.h ===
1468+
1469+
ISC License
1470+
1471+
Copyright (c) 2014, StrongLoop Inc.
1472+
1473+
Permission to use, copy, modify, and/or distribute this software for any
1474+
purpose with or without fee is hereby granted, provided that the above
1475+
copyright notice and this permission notice appear in all copies.
1476+
1477+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1478+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1479+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1480+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1481+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1482+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1483+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1484+
"""

‎doc/api/v8.md

+71
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,24 @@ The value returned is an array of objects containing the following properties:
8787
]
8888
```
8989

90+
## v8.getHeapSnapshot()
91+
<!-- YAML
92+
added: REPLACEME
93+
-->
94+
95+
* Returns: {stream.Readable} A Readable Stream containing the V8 heap snapshot
96+
97+
Generates a snapshot of the current V8 heap and returns a Readable
98+
Stream that may be used to read the JSON serialized representation.
99+
This JSON stream format is intended to be used with tools such as
100+
Chrome DevTools. The JSON schema is undocumented and specific to the
101+
V8 engine, and may change from one version of V8 to the next.
102+
103+
```js
104+
const stream = v8.getHeapSnapshot();
105+
stream.pipe(process.stdout);
106+
```
107+
90108
## v8.getHeapStatistics()
91109
<!-- YAML
92110
added: v1.0.0
@@ -159,6 +177,58 @@ v8.setFlagsFromString('--trace_gc');
159177
setTimeout(() => { v8.setFlagsFromString('--notrace_gc'); }, 60e3);
160178
```
161179

180+
## v8.writeHeapSnapshot([filename])
181+
<!-- YAML
182+
added: REPLACEME
183+
-->
184+
185+
* `filename` {string} The file path where the V8 heap snapshot is to be
186+
saved. If not specified, a file name with the pattern
187+
`'Heap-${yyyymmdd}-${hhmmss}-${pid}-${thread_id}.heapsnapshot'` will be
188+
generated, where `{pid}` will be the PID of the Node.js process,
189+
`{thread_id}` will be `0` when `writeHeapSnapshot()` is called from
190+
the main Node.js thread or the id of a worker thread.
191+
* Returns: {string} The filename where the snapshot was saved.
192+
193+
Generates a snapshot of the current V8 heap and writes it to a JSON
194+
file. This file is intended to be used with tools such as Chrome
195+
DevTools. The JSON schema is undocumented and specific to the V8
196+
engine, and may change from one version of V8 to the next.
197+
198+
A heap snapshot is specific to a single V8 isolate. When using
199+
[Worker Threads][], a heap snapshot generated from the main thread will
200+
not contain any information about the workers, and vice versa.
201+
202+
```js
203+
const { writeHeapSnapshot } = require('v8');
204+
const {
205+
Worker,
206+
isMainThread,
207+
parentPort
208+
} = require('worker_threads');
209+
210+
if (isMainThread) {
211+
const worker = new Worker(__filename);
212+
213+
worker.once('message', (filename) => {
214+
console.log(`worker heapdump: ${filename}`);
215+
// Now get a heapdump for the main thread.
216+
console.log(`main thread heapdump: ${writeHeapSnapshot()}`);
217+
});
218+
219+
// Tell the worker to create a heapdump.
220+
worker.postMessage('heapdump');
221+
} else {
222+
parentPort.once('message', (message) => {
223+
if (message === 'heapdump') {
224+
// Generate a heapdump for the worker
225+
// and return the filename to the parent.
226+
parentPort.postMessage(writeHeapSnapshot());
227+
}
228+
});
229+
}
230+
```
231+
162232
## Serialization API
163233

164234
> Stability: 1 - Experimental
@@ -417,4 +487,5 @@ A subclass of [`Deserializer`][] corresponding to the format written by
417487
[`vm.Script`]: vm.html#vm_constructor_new_vm_script_code_options
418488
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
419489
[V8]: https://developers.google.com/v8/
490+
[Worker Threads]: worker_threads.html
420491
[here]: https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md

‎lib/internal/test/heap.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ process.emitWarning(
44
'These APIs are for internal testing only. Do not use them.',
55
'internal/test/heap');
66

7-
const { createHeapDump, buildEmbedderGraph } = internalBinding('heap_utils');
7+
const {
8+
createHeapSnapshot,
9+
buildEmbedderGraph
10+
} = internalBinding('heap_utils');
811
const assert = require('internal/assert');
912

1013
// This is not suitable for production code. It creates a full V8 heap dump,
1114
// parses it as JSON, and then creates complex objects from it, leading
1215
// to significantly increased memory usage.
13-
function createJSHeapDump() {
14-
const dump = createHeapDump();
16+
function createJSHeapSnapshot() {
17+
const dump = createHeapSnapshot();
1518
const meta = dump.snapshot.meta;
1619

1720
const nodes =
@@ -81,6 +84,6 @@ function readHeapInfo(raw, fields, types, strings) {
8184
}
8285

8386
module.exports = {
84-
createJSHeapDump,
87+
createJSHeapSnapshot,
8588
buildEmbedderGraph
8689
};

‎lib/v8.js

+59-1
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,65 @@ const {
2020
Serializer: _Serializer,
2121
Deserializer: _Deserializer
2222
} = internalBinding('serdes');
23+
const assert = require('internal/assert');
2324
const { copy } = internalBinding('buffer');
2425
const { objectToString } = require('internal/util');
2526
const { FastBuffer } = require('internal/buffer');
27+
const { toPathIfFileURL } = require('internal/url');
28+
const { validatePath } = require('internal/fs/utils');
29+
const { toNamespacedPath } = require('path');
30+
const {
31+
createHeapSnapshotStream,
32+
triggerHeapSnapshot
33+
} = internalBinding('heap_utils');
34+
const { Readable } = require('stream');
35+
const { owner_symbol } = require('internal/async_hooks').symbols;
36+
const {
37+
kUpdateTimer,
38+
onStreamRead,
39+
} = require('internal/stream_base_commons');
40+
const kHandle = Symbol('kHandle');
41+
42+
43+
function writeHeapSnapshot(filename) {
44+
if (filename !== undefined) {
45+
filename = toPathIfFileURL(filename);
46+
validatePath(filename);
47+
filename = toNamespacedPath(filename);
48+
}
49+
return triggerHeapSnapshot(filename);
50+
}
51+
52+
class HeapSnapshotStream extends Readable {
53+
constructor(handle) {
54+
super({ autoDestroy: true });
55+
this[kHandle] = handle;
56+
handle[owner_symbol] = this;
57+
handle.onread = onStreamRead;
58+
}
59+
60+
_read() {
61+
if (this[kHandle])
62+
this[kHandle].readStart();
63+
}
64+
65+
_destroy() {
66+
// Release the references on the handle so that
67+
// it can be garbage collected.
68+
this[kHandle][owner_symbol] = undefined;
69+
this[kHandle] = undefined;
70+
}
71+
72+
[kUpdateTimer]() {
73+
// Does nothing
74+
}
75+
}
76+
77+
function getHeapSnapshot() {
78+
const handle = createHeapSnapshotStream();
79+
assert(handle);
80+
return new HeapSnapshotStream(handle);
81+
}
2682

2783
// Calling exposed c++ functions directly throws exception as it expected to be
2884
// called with new operator and caused an assert to fire.
@@ -210,6 +266,7 @@ function deserialize(buffer) {
210266

211267
module.exports = {
212268
cachedDataVersionTag,
269+
getHeapSnapshot,
213270
getHeapStatistics,
214271
getHeapSpaceStatistics,
215272
setFlagsFromString,
@@ -218,5 +275,6 @@ module.exports = {
218275
DefaultSerializer,
219276
DefaultDeserializer,
220277
deserialize,
221-
serialize
278+
serialize,
279+
writeHeapSnapshot
222280
};

‎src/async_wrap.h

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ namespace node {
4141
V(FSREQPROMISE) \
4242
V(GETADDRINFOREQWRAP) \
4343
V(GETNAMEINFOREQWRAP) \
44+
V(HEAPSNAPSHOT) \
4445
V(HTTP2SESSION) \
4546
V(HTTP2STREAM) \
4647
V(HTTP2PING) \

‎src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
379379
V(script_data_constructor_function, v8::Function) \
380380
V(secure_context_constructor_template, v8::FunctionTemplate) \
381381
V(shutdown_wrap_template, v8::ObjectTemplate) \
382+
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
382383
V(tcp_constructor_template, v8::FunctionTemplate) \
383384
V(tick_callback_function, v8::Function) \
384385
V(timers_callback_function, v8::Function) \

‎src/heap_utils.cc

+202-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#include "env-inl.h"
2+
#include "stream_base-inl.h"
23

34
using v8::Array;
45
using v8::Boolean;
56
using v8::Context;
67
using v8::EmbedderGraph;
78
using v8::EscapableHandleScope;
89
using v8::FunctionCallbackInfo;
10+
using v8::FunctionTemplate;
911
using v8::HandleScope;
1012
using v8::HeapSnapshot;
1113
using v8::Isolate;
@@ -14,6 +16,7 @@ using v8::Local;
1416
using v8::MaybeLocal;
1517
using v8::Number;
1618
using v8::Object;
19+
using v8::ObjectTemplate;
1720
using v8::String;
1821
using v8::Value;
1922

@@ -231,27 +234,220 @@ class BufferOutputStream : public v8::OutputStream {
231234
std::unique_ptr<JSString> buffer_;
232235
};
233236

234-
void CreateHeapDump(const FunctionCallbackInfo<Value>& args) {
237+
namespace {
238+
class FileOutputStream : public v8::OutputStream {
239+
public:
240+
explicit FileOutputStream(FILE* stream) : stream_(stream) {}
241+
242+
int GetChunkSize() override {
243+
return 65536; // big chunks == faster
244+
}
245+
246+
void EndOfStream() override {}
247+
248+
WriteResult WriteAsciiChunk(char* data, int size) override {
249+
const size_t len = static_cast<size_t>(size);
250+
size_t off = 0;
251+
252+
while (off < len && !feof(stream_) && !ferror(stream_))
253+
off += fwrite(data + off, 1, len - off, stream_);
254+
255+
return off == len ? kContinue : kAbort;
256+
}
257+
258+
private:
259+
FILE* stream_;
260+
};
261+
262+
class HeapSnapshotStream : public AsyncWrap,
263+
public StreamBase,
264+
public v8::OutputStream {
265+
public:
266+
HeapSnapshotStream(
267+
Environment* env,
268+
const HeapSnapshot* snapshot,
269+
v8::Local<v8::Object> obj) :
270+
AsyncWrap(env, obj, AsyncWrap::PROVIDER_HEAPSNAPSHOT),
271+
StreamBase(env),
272+
snapshot_(snapshot) {
273+
MakeWeak();
274+
StreamBase::AttachToObject(GetObject());
275+
}
276+
277+
~HeapSnapshotStream() override {
278+
Cleanup();
279+
}
280+
281+
int GetChunkSize() override {
282+
return 65536; // big chunks == faster
283+
}
284+
285+
void EndOfStream() override {
286+
EmitRead(UV_EOF);
287+
Cleanup();
288+
}
289+
290+
WriteResult WriteAsciiChunk(char* data, int size) override {
291+
int len = size;
292+
while (len != 0) {
293+
uv_buf_t buf = EmitAlloc(size);
294+
ssize_t avail = len;
295+
if (static_cast<ssize_t>(buf.len) < avail)
296+
avail = buf.len;
297+
memcpy(buf.base, data, avail);
298+
data += avail;
299+
len -= avail;
300+
EmitRead(size, buf);
301+
}
302+
return kContinue;
303+
}
304+
305+
int ReadStart() override {
306+
CHECK_NE(snapshot_, nullptr);
307+
snapshot_->Serialize(this, HeapSnapshot::kJSON);
308+
return 0;
309+
}
310+
311+
int ReadStop() override {
312+
return 0;
313+
}
314+
315+
int DoShutdown(ShutdownWrap* req_wrap) override {
316+
UNREACHABLE();
317+
return 0;
318+
}
319+
320+
int DoWrite(WriteWrap* w,
321+
uv_buf_t* bufs,
322+
size_t count,
323+
uv_stream_t* send_handle) override {
324+
UNREACHABLE();
325+
return 0;
326+
}
327+
328+
bool IsAlive() override { return snapshot_ != nullptr; }
329+
bool IsClosing() override { return snapshot_ == nullptr; }
330+
AsyncWrap* GetAsyncWrap() override { return this; }
331+
332+
void MemoryInfo(MemoryTracker* tracker) const override {
333+
if (snapshot_ != nullptr) {
334+
tracker->TrackFieldWithSize(
335+
"snapshot", sizeof(*snapshot_), "HeapSnapshot");
336+
}
337+
}
338+
339+
SET_MEMORY_INFO_NAME(HeapSnapshotStream)
340+
SET_SELF_SIZE(HeapSnapshotStream)
341+
342+
private:
343+
void Cleanup() {
344+
if (snapshot_ != nullptr) {
345+
const_cast<HeapSnapshot*>(snapshot_)->Delete();
346+
snapshot_ = nullptr;
347+
}
348+
}
349+
350+
351+
const HeapSnapshot* snapshot_;
352+
};
353+
354+
inline void TakeSnapshot(Isolate* isolate, v8::OutputStream* out) {
355+
const HeapSnapshot* const snapshot =
356+
isolate->GetHeapProfiler()->TakeHeapSnapshot();
357+
snapshot->Serialize(out, HeapSnapshot::kJSON);
358+
const_cast<HeapSnapshot*>(snapshot)->Delete();
359+
}
360+
361+
inline bool WriteSnapshot(Isolate* isolate, const char* filename) {
362+
FILE* fp = fopen(filename, "w");
363+
if (fp == nullptr)
364+
return false;
365+
FileOutputStream stream(fp);
366+
TakeSnapshot(isolate, &stream);
367+
fclose(fp);
368+
return true;
369+
}
370+
371+
} // namespace
372+
373+
void CreateHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
235374
Isolate* isolate = args.GetIsolate();
236-
const HeapSnapshot* snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot();
237375
BufferOutputStream out;
238-
snapshot->Serialize(&out, HeapSnapshot::kJSON);
239-
const_cast<HeapSnapshot*>(snapshot)->Delete();
376+
TakeSnapshot(isolate, &out);
240377
Local<Value> ret;
241378
if (JSON::Parse(isolate->GetCurrentContext(),
242379
out.ToString(isolate)).ToLocal(&ret)) {
243380
args.GetReturnValue().Set(ret);
244381
}
245382
}
246383

384+
void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) {
385+
Environment* env = Environment::GetCurrent(args);
386+
HandleScope scope(env->isolate());
387+
const HeapSnapshot* const snapshot =
388+
env->isolate()->GetHeapProfiler()->TakeHeapSnapshot();
389+
CHECK_NOT_NULL(snapshot);
390+
Local<Object> obj;
391+
if (!env->streambaseoutputstream_constructor_template()
392+
->NewInstance(env->context())
393+
.ToLocal(&obj)) {
394+
return;
395+
}
396+
HeapSnapshotStream* out = new HeapSnapshotStream(env, snapshot, obj);
397+
args.GetReturnValue().Set(out->object());
398+
}
399+
400+
void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
401+
Environment* env = Environment::GetCurrent(args);
402+
Isolate* isolate = args.GetIsolate();
403+
404+
Local<Value> filename_v = args[0];
405+
406+
if (filename_v->IsUndefined()) {
407+
DiagnosticFilename name(env, "Heap", "heapsnapshot");
408+
if (!WriteSnapshot(isolate, *name))
409+
return;
410+
if (String::NewFromUtf8(isolate, *name, v8::NewStringType::kNormal)
411+
.ToLocal(&filename_v)) {
412+
args.GetReturnValue().Set(filename_v);
413+
}
414+
return;
415+
}
416+
417+
BufferValue path(isolate, filename_v);
418+
CHECK_NOT_NULL(*path);
419+
if (!WriteSnapshot(isolate, *path))
420+
return;
421+
return args.GetReturnValue().Set(filename_v);
422+
}
423+
247424
void Initialize(Local<Object> target,
248425
Local<Value> unused,
249426
Local<Context> context,
250427
void* priv) {
251428
Environment* env = Environment::GetCurrent(context);
252429

253-
env->SetMethodNoSideEffect(target, "buildEmbedderGraph", BuildEmbedderGraph);
254-
env->SetMethodNoSideEffect(target, "createHeapDump", CreateHeapDump);
430+
env->SetMethodNoSideEffect(target,
431+
"buildEmbedderGraph",
432+
BuildEmbedderGraph);
433+
env->SetMethodNoSideEffect(target,
434+
"createHeapSnapshot",
435+
CreateHeapSnapshot);
436+
env->SetMethodNoSideEffect(target,
437+
"triggerHeapSnapshot",
438+
TriggerHeapSnapshot);
439+
env->SetMethodNoSideEffect(target,
440+
"createHeapSnapshotStream",
441+
CreateHeapSnapshotStream);
442+
443+
// Create FunctionTemplate for HeapSnapshotStream
444+
Local<FunctionTemplate> os = FunctionTemplate::New(env->isolate());
445+
os->Inherit(AsyncWrap::GetConstructorTemplate(env));
446+
Local<ObjectTemplate> ost = os->InstanceTemplate();
447+
ost->SetInternalFieldCount(StreamBase::kStreamBaseField + 1);
448+
os->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HeapSnapshotStream"));
449+
StreamBase::AddMethods(env, os);
450+
env->set_streambaseoutputstream_constructor_template(ost);
255451
}
256452

257453
} // namespace heap

‎src/node_internals.h

+35
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,41 @@ v8::MaybeLocal<v8::Value> StartExecution(Environment* env,
297297
namespace coverage {
298298
bool StartCoverageCollection(Environment* env);
299299
}
300+
301+
#ifdef _WIN32
302+
typedef SYSTEMTIME TIME_TYPE;
303+
#else // UNIX, OSX
304+
typedef struct tm TIME_TYPE;
305+
#endif
306+
307+
class DiagnosticFilename {
308+
public:
309+
static void LocalTime(TIME_TYPE* tm_struct);
310+
311+
DiagnosticFilename(Environment* env,
312+
const char* prefix,
313+
const char* ext,
314+
int seq = -1) :
315+
filename_(MakeFilename(env->thread_id(), prefix, ext, seq)) {}
316+
317+
DiagnosticFilename(uint64_t thread_id,
318+
const char* prefix,
319+
const char* ext,
320+
int seq = -1) :
321+
filename_(MakeFilename(thread_id, prefix, ext, seq)) {}
322+
323+
const char* operator*() const { return filename_.c_str(); }
324+
325+
private:
326+
static std::string MakeFilename(
327+
uint64_t thread_id,
328+
const char* prefix,
329+
const char* ext,
330+
int seq = -1);
331+
332+
std::string filename_;
333+
};
334+
300335
} // namespace node
301336

302337
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

‎src/util.cc

+65
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@
2727
#include "string_bytes.h"
2828
#include "uv.h"
2929

30+
#ifdef _WIN32
31+
#include <time.h>
32+
#else
33+
#include <sys/time.h>
34+
#include <sys/types.h>
35+
#endif
36+
3037
#include <cstdio>
38+
#include <iomanip>
3139
#include <sstream>
3240

3341
namespace node {
@@ -144,4 +152,61 @@ void ThrowErrStringTooLong(Isolate* isolate) {
144152
isolate->ThrowException(ERR_STRING_TOO_LONG(isolate));
145153
}
146154

155+
void DiagnosticFilename::LocalTime(TIME_TYPE* tm_struct) {
156+
#ifdef _WIN32
157+
GetLocalTime(tm_struct);
158+
#else // UNIX, OSX
159+
struct timeval time_val;
160+
gettimeofday(&time_val, nullptr);
161+
localtime_r(&time_val.tv_sec, tm_struct);
162+
#endif
163+
}
164+
165+
// Defined in node_internals.h
166+
std::string DiagnosticFilename::MakeFilename(
167+
uint64_t thread_id,
168+
const char* prefix,
169+
const char* ext,
170+
int seq) {
171+
std::ostringstream oss;
172+
TIME_TYPE tm_struct;
173+
LocalTime(&tm_struct);
174+
oss << prefix;
175+
#ifdef _WIN32
176+
oss << "." << std::setfill('0') << std::setw(4) << tm_struct.wYear;
177+
oss << std::setfill('0') << std::setw(2) << tm_struct.wMonth;
178+
oss << std::setfill('0') << std::setw(2) << tm_struct.wDay;
179+
oss << "." << std::setfill('0') << std::setw(2) << tm_struct.wHour;
180+
oss << std::setfill('0') << std::setw(2) << tm_struct.wMinute;
181+
oss << std::setfill('0') << std::setw(2) << tm_struct.wSecond;
182+
#else // UNIX, OSX
183+
oss << "."
184+
<< std::setfill('0')
185+
<< std::setw(4)
186+
<< tm_struct.tm_year + 1900;
187+
oss << std::setfill('0')
188+
<< std::setw(2)
189+
<< tm_struct.tm_mon + 1;
190+
oss << std::setfill('0')
191+
<< std::setw(2)
192+
<< tm_struct.tm_mday;
193+
oss << "."
194+
<< std::setfill('0')
195+
<< std::setw(2)
196+
<< tm_struct.tm_hour;
197+
oss << std::setfill('0')
198+
<< std::setw(2)
199+
<< tm_struct.tm_min;
200+
oss << std::setfill('0')
201+
<< std::setw(2)
202+
<< tm_struct.tm_sec;
203+
#endif
204+
oss << "." << uv_os_getpid();
205+
oss << "." << thread_id;
206+
if (seq >= 0)
207+
oss << "." << std::setfill('0') << std::setw(3) << ++seq;
208+
oss << "." << ext;
209+
return oss.str();
210+
}
211+
147212
} // namespace node

‎test/common/heap.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ try {
1010
console.log('using `test/common/heap.js` requires `--expose-internals`');
1111
throw e;
1212
}
13-
const { createJSHeapDump, buildEmbedderGraph } = internalTestHeap;
13+
const { createJSHeapSnapshot, buildEmbedderGraph } = internalTestHeap;
1414

1515
function inspectNode(snapshot) {
1616
return util.inspect(snapshot, { depth: 4 });
@@ -33,7 +33,7 @@ function isEdge(edge, { node_name, edge_name }) {
3333

3434
class State {
3535
constructor() {
36-
this.snapshot = createJSHeapDump();
36+
this.snapshot = createJSHeapSnapshot();
3737
this.embedderGraph = buildEmbedderGraph();
3838
}
3939

‎test/parallel/test-bootstrap-modules.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const assert = require('assert');
1010

1111
const isMainThread = common.isMainThread;
1212
const kCoverageModuleCount = process.env.NODE_V8_COVERAGE ? 1 : 0;
13-
const kMaxModuleCount = (isMainThread ? 65 : 87) + kCoverageModuleCount;
13+
const kMaxModuleCount = (isMainThread ? 65 : 88) + kCoverageModuleCount;
1414

1515
assert(list.length <= kMaxModuleCount,
1616
`Total length: ${list.length}\n` + list.join('\n')

‎test/sequential/test-async-wrap-getasyncid.js

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const common = require('../common');
55
const { internalBinding } = require('internal/test/binding');
66
const assert = require('assert');
77
const fs = require('fs');
8+
const v8 = require('v8');
89
const fsPromises = fs.promises;
910
const net = require('net');
1011
const providers = Object.assign({}, internalBinding('async_wrap').Providers);
@@ -294,3 +295,8 @@ if (process.features.inspector && common.isMainThread) {
294295
testInitialized(handle, 'Connection');
295296
handle.disconnect();
296297
}
298+
299+
// PROVIDER_HEAPDUMP
300+
{
301+
v8.getHeapSnapshot().destroy();
302+
}

‎test/sequential/test-heapdump.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
if (!common.isMainThread)
6+
common.skip('process.chdir is not available in Workers');
7+
8+
const { writeHeapSnapshot, getHeapSnapshot } = require('v8');
9+
const assert = require('assert');
10+
const fs = require('fs');
11+
const tmpdir = require('../common/tmpdir');
12+
13+
tmpdir.refresh();
14+
process.chdir(tmpdir.path);
15+
16+
{
17+
writeHeapSnapshot('my.heapdump');
18+
fs.accessSync('my.heapdump');
19+
}
20+
21+
{
22+
const heapdump = writeHeapSnapshot();
23+
assert.strictEqual(typeof heapdump, 'string');
24+
fs.accessSync(heapdump);
25+
}
26+
27+
[1, true, {}, [], null, Infinity, NaN].forEach((i) => {
28+
common.expectsError(() => writeHeapSnapshot(i), {
29+
code: 'ERR_INVALID_ARG_TYPE',
30+
type: TypeError,
31+
message: 'The "path" argument must be one of type string, Buffer, or URL.' +
32+
` Received type ${typeof i}`
33+
});
34+
});
35+
36+
{
37+
let data = '';
38+
const snapshot = getHeapSnapshot();
39+
snapshot.setEncoding('utf-8');
40+
snapshot.on('data', common.mustCallAtLeast((chunk) => {
41+
data += chunk.toString();
42+
}));
43+
snapshot.on('end', common.mustCall(() => {
44+
JSON.parse(data);
45+
}));
46+
}

‎tools/license-builder.sh

+3
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,7 @@ addlicense "brotli" "deps/brotli" "$(cat ${rootdir}/deps/brotli/LICENSE)"
102102

103103
addlicense "HdrHistogram" "deps/histogram" "$(cat ${rootdir}/deps/histogram/LICENSE.txt)"
104104

105+
addlicense "node-heapdump" "src/heap_utils.cc" \
106+
"$(curl -sL https://raw.githubusercontent.com/bnoordhuis/node-heapdump/0ca52441e46241ffbea56a389e2856ec01c48c97/LICENSE)"
107+
105108
mv $tmplicense $licensefile

0 commit comments

Comments
 (0)
Please sign in to comment.