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: https://github.com/nodejs/node/pull/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>
This commit is contained in:
parent
024842f8f4
commit
5f38797ea5
39
LICENSE
39
LICENSE
@ -634,7 +634,7 @@ The externally maintained libraries used by Node.js are:
|
|||||||
|
|
||||||
- OpenSSL, located at deps/openssl, is licensed as follows:
|
- OpenSSL, located at deps/openssl, is licensed as follows:
|
||||||
"""
|
"""
|
||||||
Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved.
|
Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions
|
modification, are permitted provided that the following conditions
|
||||||
@ -1445,3 +1445,40 @@ The externally maintained libraries used by Node.js are:
|
|||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||||
THE POSSIBILITY OF SUCH DAMAGE.
|
THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
- node-heapdump, located at src/heap_utils.cc, is licensed as follows:
|
||||||
|
"""
|
||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2012, Ben Noordhuis <info@bnoordhuis.nl>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
=== src/compat.h src/compat-inl.h ===
|
||||||
|
|
||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2014, StrongLoop Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
"""
|
||||||
|
@ -87,6 +87,24 @@ The value returned is an array of objects containing the following properties:
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## v8.getHeapSnapshot()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* Returns: {stream.Readable} A Readable Stream containing the V8 heap snapshot
|
||||||
|
|
||||||
|
Generates a snapshot of the current V8 heap and returns a Readable
|
||||||
|
Stream that may be used to read the JSON serialized representation.
|
||||||
|
This JSON stream format is intended to be used with tools such as
|
||||||
|
Chrome DevTools. The JSON schema is undocumented and specific to the
|
||||||
|
V8 engine, and may change from one version of V8 to the next.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const stream = v8.getHeapSnapshot();
|
||||||
|
stream.pipe(process.stdout);
|
||||||
|
```
|
||||||
|
|
||||||
## v8.getHeapStatistics()
|
## v8.getHeapStatistics()
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v1.0.0
|
added: v1.0.0
|
||||||
@ -159,6 +177,58 @@ v8.setFlagsFromString('--trace_gc');
|
|||||||
setTimeout(() => { v8.setFlagsFromString('--notrace_gc'); }, 60e3);
|
setTimeout(() => { v8.setFlagsFromString('--notrace_gc'); }, 60e3);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## v8.writeHeapSnapshot([filename])
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
* `filename` {string} The file path where the V8 heap snapshot is to be
|
||||||
|
saved. If not specified, a file name with the pattern
|
||||||
|
`'Heap-${yyyymmdd}-${hhmmss}-${pid}-${thread_id}.heapsnapshot'` will be
|
||||||
|
generated, where `{pid}` will be the PID of the Node.js process,
|
||||||
|
`{thread_id}` will be `0` when `writeHeapSnapshot()` is called from
|
||||||
|
the main Node.js thread or the id of a worker thread.
|
||||||
|
* Returns: {string} The filename where the snapshot was saved.
|
||||||
|
|
||||||
|
Generates a snapshot of the current V8 heap and writes it to a JSON
|
||||||
|
file. This file is intended to be used with tools such as Chrome
|
||||||
|
DevTools. The JSON schema is undocumented and specific to the V8
|
||||||
|
engine, and may change from one version of V8 to the next.
|
||||||
|
|
||||||
|
A heap snapshot is specific to a single V8 isolate. When using
|
||||||
|
[Worker Threads][], a heap snapshot generated from the main thread will
|
||||||
|
not contain any information about the workers, and vice versa.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { writeHeapSnapshot } = require('v8');
|
||||||
|
const {
|
||||||
|
Worker,
|
||||||
|
isMainThread,
|
||||||
|
parentPort
|
||||||
|
} = require('worker_threads');
|
||||||
|
|
||||||
|
if (isMainThread) {
|
||||||
|
const worker = new Worker(__filename);
|
||||||
|
|
||||||
|
worker.once('message', (filename) => {
|
||||||
|
console.log(`worker heapdump: ${filename}`);
|
||||||
|
// Now get a heapdump for the main thread.
|
||||||
|
console.log(`main thread heapdump: ${writeHeapSnapshot()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tell the worker to create a heapdump.
|
||||||
|
worker.postMessage('heapdump');
|
||||||
|
} else {
|
||||||
|
parentPort.once('message', (message) => {
|
||||||
|
if (message === 'heapdump') {
|
||||||
|
// Generate a heapdump for the worker
|
||||||
|
// and return the filename to the parent.
|
||||||
|
parentPort.postMessage(writeHeapSnapshot());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Serialization API
|
## Serialization API
|
||||||
|
|
||||||
> Stability: 1 - Experimental
|
> Stability: 1 - Experimental
|
||||||
@ -417,4 +487,5 @@ A subclass of [`Deserializer`][] corresponding to the format written by
|
|||||||
[`vm.Script`]: vm.html#vm_constructor_new_vm_script_code_options
|
[`vm.Script`]: vm.html#vm_constructor_new_vm_script_code_options
|
||||||
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
||||||
[V8]: https://developers.google.com/v8/
|
[V8]: https://developers.google.com/v8/
|
||||||
|
[Worker Threads]: worker_threads.html
|
||||||
[here]: https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md
|
[here]: https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md
|
||||||
|
@ -4,14 +4,17 @@ process.emitWarning(
|
|||||||
'These APIs are for internal testing only. Do not use them.',
|
'These APIs are for internal testing only. Do not use them.',
|
||||||
'internal/test/heap');
|
'internal/test/heap');
|
||||||
|
|
||||||
const { createHeapDump, buildEmbedderGraph } = internalBinding('heap_utils');
|
const {
|
||||||
|
createHeapSnapshot,
|
||||||
|
buildEmbedderGraph
|
||||||
|
} = internalBinding('heap_utils');
|
||||||
const assert = require('internal/assert');
|
const assert = require('internal/assert');
|
||||||
|
|
||||||
// This is not suitable for production code. It creates a full V8 heap dump,
|
// This is not suitable for production code. It creates a full V8 heap dump,
|
||||||
// parses it as JSON, and then creates complex objects from it, leading
|
// parses it as JSON, and then creates complex objects from it, leading
|
||||||
// to significantly increased memory usage.
|
// to significantly increased memory usage.
|
||||||
function createJSHeapDump() {
|
function createJSHeapSnapshot() {
|
||||||
const dump = createHeapDump();
|
const dump = createHeapSnapshot();
|
||||||
const meta = dump.snapshot.meta;
|
const meta = dump.snapshot.meta;
|
||||||
|
|
||||||
const nodes =
|
const nodes =
|
||||||
@ -81,6 +84,6 @@ function readHeapInfo(raw, fields, types, strings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createJSHeapDump,
|
createJSHeapSnapshot,
|
||||||
buildEmbedderGraph
|
buildEmbedderGraph
|
||||||
};
|
};
|
||||||
|
60
lib/v8.js
60
lib/v8.js
@ -20,9 +20,65 @@ const {
|
|||||||
Serializer: _Serializer,
|
Serializer: _Serializer,
|
||||||
Deserializer: _Deserializer
|
Deserializer: _Deserializer
|
||||||
} = internalBinding('serdes');
|
} = internalBinding('serdes');
|
||||||
|
const assert = require('internal/assert');
|
||||||
const { copy } = internalBinding('buffer');
|
const { copy } = internalBinding('buffer');
|
||||||
const { objectToString } = require('internal/util');
|
const { objectToString } = require('internal/util');
|
||||||
const { FastBuffer } = require('internal/buffer');
|
const { FastBuffer } = require('internal/buffer');
|
||||||
|
const { toPathIfFileURL } = require('internal/url');
|
||||||
|
const { validatePath } = require('internal/fs/utils');
|
||||||
|
const { toNamespacedPath } = require('path');
|
||||||
|
const {
|
||||||
|
createHeapSnapshotStream,
|
||||||
|
triggerHeapSnapshot
|
||||||
|
} = internalBinding('heap_utils');
|
||||||
|
const { Readable } = require('stream');
|
||||||
|
const { owner_symbol } = require('internal/async_hooks').symbols;
|
||||||
|
const {
|
||||||
|
kUpdateTimer,
|
||||||
|
onStreamRead,
|
||||||
|
} = require('internal/stream_base_commons');
|
||||||
|
const kHandle = Symbol('kHandle');
|
||||||
|
|
||||||
|
|
||||||
|
function writeHeapSnapshot(filename) {
|
||||||
|
if (filename !== undefined) {
|
||||||
|
filename = toPathIfFileURL(filename);
|
||||||
|
validatePath(filename);
|
||||||
|
filename = toNamespacedPath(filename);
|
||||||
|
}
|
||||||
|
return triggerHeapSnapshot(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
class HeapSnapshotStream extends Readable {
|
||||||
|
constructor(handle) {
|
||||||
|
super({ autoDestroy: true });
|
||||||
|
this[kHandle] = handle;
|
||||||
|
handle[owner_symbol] = this;
|
||||||
|
handle.onread = onStreamRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
_read() {
|
||||||
|
if (this[kHandle])
|
||||||
|
this[kHandle].readStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
_destroy() {
|
||||||
|
// Release the references on the handle so that
|
||||||
|
// it can be garbage collected.
|
||||||
|
this[kHandle][owner_symbol] = undefined;
|
||||||
|
this[kHandle] = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
[kUpdateTimer]() {
|
||||||
|
// Does nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeapSnapshot() {
|
||||||
|
const handle = createHeapSnapshotStream();
|
||||||
|
assert(handle);
|
||||||
|
return new HeapSnapshotStream(handle);
|
||||||
|
}
|
||||||
|
|
||||||
// Calling exposed c++ functions directly throws exception as it expected to be
|
// Calling exposed c++ functions directly throws exception as it expected to be
|
||||||
// called with new operator and caused an assert to fire.
|
// called with new operator and caused an assert to fire.
|
||||||
@ -210,6 +266,7 @@ function deserialize(buffer) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
cachedDataVersionTag,
|
cachedDataVersionTag,
|
||||||
|
getHeapSnapshot,
|
||||||
getHeapStatistics,
|
getHeapStatistics,
|
||||||
getHeapSpaceStatistics,
|
getHeapSpaceStatistics,
|
||||||
setFlagsFromString,
|
setFlagsFromString,
|
||||||
@ -218,5 +275,6 @@ module.exports = {
|
|||||||
DefaultSerializer,
|
DefaultSerializer,
|
||||||
DefaultDeserializer,
|
DefaultDeserializer,
|
||||||
deserialize,
|
deserialize,
|
||||||
serialize
|
serialize,
|
||||||
|
writeHeapSnapshot
|
||||||
};
|
};
|
||||||
|
@ -41,6 +41,7 @@ namespace node {
|
|||||||
V(FSREQPROMISE) \
|
V(FSREQPROMISE) \
|
||||||
V(GETADDRINFOREQWRAP) \
|
V(GETADDRINFOREQWRAP) \
|
||||||
V(GETNAMEINFOREQWRAP) \
|
V(GETNAMEINFOREQWRAP) \
|
||||||
|
V(HEAPSNAPSHOT) \
|
||||||
V(HTTP2SESSION) \
|
V(HTTP2SESSION) \
|
||||||
V(HTTP2STREAM) \
|
V(HTTP2STREAM) \
|
||||||
V(HTTP2PING) \
|
V(HTTP2PING) \
|
||||||
|
@ -383,6 +383,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
|
|||||||
V(script_data_constructor_function, v8::Function) \
|
V(script_data_constructor_function, v8::Function) \
|
||||||
V(secure_context_constructor_template, v8::FunctionTemplate) \
|
V(secure_context_constructor_template, v8::FunctionTemplate) \
|
||||||
V(shutdown_wrap_template, v8::ObjectTemplate) \
|
V(shutdown_wrap_template, v8::ObjectTemplate) \
|
||||||
|
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
|
||||||
V(tcp_constructor_template, v8::FunctionTemplate) \
|
V(tcp_constructor_template, v8::FunctionTemplate) \
|
||||||
V(tick_callback_function, v8::Function) \
|
V(tick_callback_function, v8::Function) \
|
||||||
V(timers_callback_function, v8::Function) \
|
V(timers_callback_function, v8::Function) \
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "env-inl.h"
|
#include "env-inl.h"
|
||||||
|
#include "stream_base-inl.h"
|
||||||
|
|
||||||
using v8::Array;
|
using v8::Array;
|
||||||
using v8::Boolean;
|
using v8::Boolean;
|
||||||
@ -6,6 +7,7 @@ using v8::Context;
|
|||||||
using v8::EmbedderGraph;
|
using v8::EmbedderGraph;
|
||||||
using v8::EscapableHandleScope;
|
using v8::EscapableHandleScope;
|
||||||
using v8::FunctionCallbackInfo;
|
using v8::FunctionCallbackInfo;
|
||||||
|
using v8::FunctionTemplate;
|
||||||
using v8::HandleScope;
|
using v8::HandleScope;
|
||||||
using v8::HeapSnapshot;
|
using v8::HeapSnapshot;
|
||||||
using v8::Isolate;
|
using v8::Isolate;
|
||||||
@ -14,6 +16,7 @@ using v8::Local;
|
|||||||
using v8::MaybeLocal;
|
using v8::MaybeLocal;
|
||||||
using v8::Number;
|
using v8::Number;
|
||||||
using v8::Object;
|
using v8::Object;
|
||||||
|
using v8::ObjectTemplate;
|
||||||
using v8::String;
|
using v8::String;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
@ -231,12 +234,146 @@ class BufferOutputStream : public v8::OutputStream {
|
|||||||
std::unique_ptr<JSString> buffer_;
|
std::unique_ptr<JSString> buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
void CreateHeapDump(const FunctionCallbackInfo<Value>& args) {
|
namespace {
|
||||||
Isolate* isolate = args.GetIsolate();
|
class FileOutputStream : public v8::OutputStream {
|
||||||
const HeapSnapshot* snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot();
|
public:
|
||||||
BufferOutputStream out;
|
explicit FileOutputStream(FILE* stream) : stream_(stream) {}
|
||||||
snapshot->Serialize(&out, HeapSnapshot::kJSON);
|
|
||||||
|
int GetChunkSize() override {
|
||||||
|
return 65536; // big chunks == faster
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndOfStream() override {}
|
||||||
|
|
||||||
|
WriteResult WriteAsciiChunk(char* data, int size) override {
|
||||||
|
const size_t len = static_cast<size_t>(size);
|
||||||
|
size_t off = 0;
|
||||||
|
|
||||||
|
while (off < len && !feof(stream_) && !ferror(stream_))
|
||||||
|
off += fwrite(data + off, 1, len - off, stream_);
|
||||||
|
|
||||||
|
return off == len ? kContinue : kAbort;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FILE* stream_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeapSnapshotStream : public AsyncWrap,
|
||||||
|
public StreamBase,
|
||||||
|
public v8::OutputStream {
|
||||||
|
public:
|
||||||
|
HeapSnapshotStream(
|
||||||
|
Environment* env,
|
||||||
|
const HeapSnapshot* snapshot,
|
||||||
|
v8::Local<v8::Object> obj) :
|
||||||
|
AsyncWrap(env, obj, AsyncWrap::PROVIDER_HEAPSNAPSHOT),
|
||||||
|
StreamBase(env),
|
||||||
|
snapshot_(snapshot) {
|
||||||
|
MakeWeak();
|
||||||
|
StreamBase::AttachToObject(GetObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
~HeapSnapshotStream() override {
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetChunkSize() override {
|
||||||
|
return 65536; // big chunks == faster
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndOfStream() override {
|
||||||
|
EmitRead(UV_EOF);
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteResult WriteAsciiChunk(char* data, int size) override {
|
||||||
|
int len = size;
|
||||||
|
while (len != 0) {
|
||||||
|
uv_buf_t buf = EmitAlloc(size);
|
||||||
|
ssize_t avail = len;
|
||||||
|
if (static_cast<ssize_t>(buf.len) < avail)
|
||||||
|
avail = buf.len;
|
||||||
|
memcpy(buf.base, data, avail);
|
||||||
|
data += avail;
|
||||||
|
len -= avail;
|
||||||
|
EmitRead(size, buf);
|
||||||
|
}
|
||||||
|
return kContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadStart() override {
|
||||||
|
CHECK_NE(snapshot_, nullptr);
|
||||||
|
snapshot_->Serialize(this, HeapSnapshot::kJSON);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadStop() override {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DoShutdown(ShutdownWrap* req_wrap) override {
|
||||||
|
UNREACHABLE();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DoWrite(WriteWrap* w,
|
||||||
|
uv_buf_t* bufs,
|
||||||
|
size_t count,
|
||||||
|
uv_stream_t* send_handle) override {
|
||||||
|
UNREACHABLE();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAlive() override { return snapshot_ != nullptr; }
|
||||||
|
bool IsClosing() override { return snapshot_ == nullptr; }
|
||||||
|
AsyncWrap* GetAsyncWrap() override { return this; }
|
||||||
|
|
||||||
|
void MemoryInfo(MemoryTracker* tracker) const override {
|
||||||
|
if (snapshot_ != nullptr) {
|
||||||
|
tracker->TrackFieldWithSize(
|
||||||
|
"snapshot", sizeof(*snapshot_), "HeapSnapshot");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SET_MEMORY_INFO_NAME(HeapSnapshotStream)
|
||||||
|
SET_SELF_SIZE(HeapSnapshotStream)
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Cleanup() {
|
||||||
|
if (snapshot_ != nullptr) {
|
||||||
|
const_cast<HeapSnapshot*>(snapshot_)->Delete();
|
||||||
|
snapshot_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const HeapSnapshot* snapshot_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void TakeSnapshot(Isolate* isolate, v8::OutputStream* out) {
|
||||||
|
const HeapSnapshot* const snapshot =
|
||||||
|
isolate->GetHeapProfiler()->TakeHeapSnapshot();
|
||||||
|
snapshot->Serialize(out, HeapSnapshot::kJSON);
|
||||||
const_cast<HeapSnapshot*>(snapshot)->Delete();
|
const_cast<HeapSnapshot*>(snapshot)->Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool WriteSnapshot(Isolate* isolate, const char* filename) {
|
||||||
|
FILE* fp = fopen(filename, "w");
|
||||||
|
if (fp == nullptr)
|
||||||
|
return false;
|
||||||
|
FileOutputStream stream(fp);
|
||||||
|
TakeSnapshot(isolate, &stream);
|
||||||
|
fclose(fp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void CreateHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
BufferOutputStream out;
|
||||||
|
TakeSnapshot(isolate, &out);
|
||||||
Local<Value> ret;
|
Local<Value> ret;
|
||||||
if (JSON::Parse(isolate->GetCurrentContext(),
|
if (JSON::Parse(isolate->GetCurrentContext(),
|
||||||
out.ToString(isolate)).ToLocal(&ret)) {
|
out.ToString(isolate)).ToLocal(&ret)) {
|
||||||
@ -244,14 +381,73 @@ void CreateHeapDump(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
HandleScope scope(env->isolate());
|
||||||
|
const HeapSnapshot* const snapshot =
|
||||||
|
env->isolate()->GetHeapProfiler()->TakeHeapSnapshot();
|
||||||
|
CHECK_NOT_NULL(snapshot);
|
||||||
|
Local<Object> obj;
|
||||||
|
if (!env->streambaseoutputstream_constructor_template()
|
||||||
|
->NewInstance(env->context())
|
||||||
|
.ToLocal(&obj)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HeapSnapshotStream* out = new HeapSnapshotStream(env, snapshot, obj);
|
||||||
|
args.GetReturnValue().Set(out->object());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
|
||||||
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
Isolate* isolate = args.GetIsolate();
|
||||||
|
|
||||||
|
Local<Value> filename_v = args[0];
|
||||||
|
|
||||||
|
if (filename_v->IsUndefined()) {
|
||||||
|
DiagnosticFilename name(env, "Heap", "heapsnapshot");
|
||||||
|
if (!WriteSnapshot(isolate, *name))
|
||||||
|
return;
|
||||||
|
if (String::NewFromUtf8(isolate, *name, v8::NewStringType::kNormal)
|
||||||
|
.ToLocal(&filename_v)) {
|
||||||
|
args.GetReturnValue().Set(filename_v);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferValue path(isolate, filename_v);
|
||||||
|
CHECK_NOT_NULL(*path);
|
||||||
|
if (!WriteSnapshot(isolate, *path))
|
||||||
|
return;
|
||||||
|
return args.GetReturnValue().Set(filename_v);
|
||||||
|
}
|
||||||
|
|
||||||
void Initialize(Local<Object> target,
|
void Initialize(Local<Object> target,
|
||||||
Local<Value> unused,
|
Local<Value> unused,
|
||||||
Local<Context> context,
|
Local<Context> context,
|
||||||
void* priv) {
|
void* priv) {
|
||||||
Environment* env = Environment::GetCurrent(context);
|
Environment* env = Environment::GetCurrent(context);
|
||||||
|
|
||||||
env->SetMethodNoSideEffect(target, "buildEmbedderGraph", BuildEmbedderGraph);
|
env->SetMethodNoSideEffect(target,
|
||||||
env->SetMethodNoSideEffect(target, "createHeapDump", CreateHeapDump);
|
"buildEmbedderGraph",
|
||||||
|
BuildEmbedderGraph);
|
||||||
|
env->SetMethodNoSideEffect(target,
|
||||||
|
"createHeapSnapshot",
|
||||||
|
CreateHeapSnapshot);
|
||||||
|
env->SetMethodNoSideEffect(target,
|
||||||
|
"triggerHeapSnapshot",
|
||||||
|
TriggerHeapSnapshot);
|
||||||
|
env->SetMethodNoSideEffect(target,
|
||||||
|
"createHeapSnapshotStream",
|
||||||
|
CreateHeapSnapshotStream);
|
||||||
|
|
||||||
|
// Create FunctionTemplate for HeapSnapshotStream
|
||||||
|
Local<FunctionTemplate> os = FunctionTemplate::New(env->isolate());
|
||||||
|
os->Inherit(AsyncWrap::GetConstructorTemplate(env));
|
||||||
|
Local<ObjectTemplate> ost = os->InstanceTemplate();
|
||||||
|
ost->SetInternalFieldCount(StreamBase::kStreamBaseField + 1);
|
||||||
|
os->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "HeapSnapshotStream"));
|
||||||
|
StreamBase::AddMethods(env, os);
|
||||||
|
env->set_streambaseoutputstream_constructor_template(ost);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace heap
|
} // namespace heap
|
||||||
|
@ -299,6 +299,41 @@ v8::MaybeLocal<v8::Value> StartExecution(Environment* env,
|
|||||||
namespace profiler {
|
namespace profiler {
|
||||||
void StartCoverageCollection(Environment* env);
|
void StartCoverageCollection(Environment* env);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
typedef SYSTEMTIME TIME_TYPE;
|
||||||
|
#else // UNIX, OSX
|
||||||
|
typedef struct tm TIME_TYPE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class DiagnosticFilename {
|
||||||
|
public:
|
||||||
|
static void LocalTime(TIME_TYPE* tm_struct);
|
||||||
|
|
||||||
|
DiagnosticFilename(Environment* env,
|
||||||
|
const char* prefix,
|
||||||
|
const char* ext,
|
||||||
|
int seq = -1) :
|
||||||
|
filename_(MakeFilename(env->thread_id(), prefix, ext, seq)) {}
|
||||||
|
|
||||||
|
DiagnosticFilename(uint64_t thread_id,
|
||||||
|
const char* prefix,
|
||||||
|
const char* ext,
|
||||||
|
int seq = -1) :
|
||||||
|
filename_(MakeFilename(thread_id, prefix, ext, seq)) {}
|
||||||
|
|
||||||
|
const char* operator*() const { return filename_.c_str(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string MakeFilename(
|
||||||
|
uint64_t thread_id,
|
||||||
|
const char* prefix,
|
||||||
|
const char* ext,
|
||||||
|
int seq = -1);
|
||||||
|
|
||||||
|
std::string filename_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||||
|
65
src/util.cc
65
src/util.cc
@ -27,7 +27,15 @@
|
|||||||
#include "string_bytes.h"
|
#include "string_bytes.h"
|
||||||
#include "uv.h"
|
#include "uv.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <time.h>
|
||||||
|
#else
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace node {
|
namespace node {
|
||||||
@ -144,4 +152,61 @@ void ThrowErrStringTooLong(Isolate* isolate) {
|
|||||||
isolate->ThrowException(ERR_STRING_TOO_LONG(isolate));
|
isolate->ThrowException(ERR_STRING_TOO_LONG(isolate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DiagnosticFilename::LocalTime(TIME_TYPE* tm_struct) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
GetLocalTime(tm_struct);
|
||||||
|
#else // UNIX, OSX
|
||||||
|
struct timeval time_val;
|
||||||
|
gettimeofday(&time_val, nullptr);
|
||||||
|
localtime_r(&time_val.tv_sec, tm_struct);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defined in node_internals.h
|
||||||
|
std::string DiagnosticFilename::MakeFilename(
|
||||||
|
uint64_t thread_id,
|
||||||
|
const char* prefix,
|
||||||
|
const char* ext,
|
||||||
|
int seq) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
TIME_TYPE tm_struct;
|
||||||
|
LocalTime(&tm_struct);
|
||||||
|
oss << prefix;
|
||||||
|
#ifdef _WIN32
|
||||||
|
oss << "." << std::setfill('0') << std::setw(4) << tm_struct.wYear;
|
||||||
|
oss << std::setfill('0') << std::setw(2) << tm_struct.wMonth;
|
||||||
|
oss << std::setfill('0') << std::setw(2) << tm_struct.wDay;
|
||||||
|
oss << "." << std::setfill('0') << std::setw(2) << tm_struct.wHour;
|
||||||
|
oss << std::setfill('0') << std::setw(2) << tm_struct.wMinute;
|
||||||
|
oss << std::setfill('0') << std::setw(2) << tm_struct.wSecond;
|
||||||
|
#else // UNIX, OSX
|
||||||
|
oss << "."
|
||||||
|
<< std::setfill('0')
|
||||||
|
<< std::setw(4)
|
||||||
|
<< tm_struct.tm_year + 1900;
|
||||||
|
oss << std::setfill('0')
|
||||||
|
<< std::setw(2)
|
||||||
|
<< tm_struct.tm_mon + 1;
|
||||||
|
oss << std::setfill('0')
|
||||||
|
<< std::setw(2)
|
||||||
|
<< tm_struct.tm_mday;
|
||||||
|
oss << "."
|
||||||
|
<< std::setfill('0')
|
||||||
|
<< std::setw(2)
|
||||||
|
<< tm_struct.tm_hour;
|
||||||
|
oss << std::setfill('0')
|
||||||
|
<< std::setw(2)
|
||||||
|
<< tm_struct.tm_min;
|
||||||
|
oss << std::setfill('0')
|
||||||
|
<< std::setw(2)
|
||||||
|
<< tm_struct.tm_sec;
|
||||||
|
#endif
|
||||||
|
oss << "." << uv_os_getpid();
|
||||||
|
oss << "." << thread_id;
|
||||||
|
if (seq >= 0)
|
||||||
|
oss << "." << std::setfill('0') << std::setw(3) << ++seq;
|
||||||
|
oss << "." << ext;
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
@ -10,7 +10,7 @@ try {
|
|||||||
console.log('using `test/common/heap.js` requires `--expose-internals`');
|
console.log('using `test/common/heap.js` requires `--expose-internals`');
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
const { createJSHeapDump, buildEmbedderGraph } = internalTestHeap;
|
const { createJSHeapSnapshot, buildEmbedderGraph } = internalTestHeap;
|
||||||
|
|
||||||
function inspectNode(snapshot) {
|
function inspectNode(snapshot) {
|
||||||
return util.inspect(snapshot, { depth: 4 });
|
return util.inspect(snapshot, { depth: 4 });
|
||||||
@ -33,7 +33,7 @@ function isEdge(edge, { node_name, edge_name }) {
|
|||||||
|
|
||||||
class State {
|
class State {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.snapshot = createJSHeapDump();
|
this.snapshot = createJSHeapSnapshot();
|
||||||
this.embedderGraph = buildEmbedderGraph();
|
this.embedderGraph = buildEmbedderGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ const assert = require('assert');
|
|||||||
|
|
||||||
const isMainThread = common.isMainThread;
|
const isMainThread = common.isMainThread;
|
||||||
const kCoverageModuleCount = process.env.NODE_V8_COVERAGE ? 1 : 0;
|
const kCoverageModuleCount = process.env.NODE_V8_COVERAGE ? 1 : 0;
|
||||||
const kMaxModuleCount = (isMainThread ? 65 : 87) + kCoverageModuleCount;
|
const kMaxModuleCount = (isMainThread ? 65 : 88) + kCoverageModuleCount;
|
||||||
|
|
||||||
assert(list.length <= kMaxModuleCount,
|
assert(list.length <= kMaxModuleCount,
|
||||||
`Total length: ${list.length}\n` + list.join('\n')
|
`Total length: ${list.length}\n` + list.join('\n')
|
||||||
|
@ -5,6 +5,7 @@ const common = require('../common');
|
|||||||
const { internalBinding } = require('internal/test/binding');
|
const { internalBinding } = require('internal/test/binding');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const v8 = require('v8');
|
||||||
const fsPromises = fs.promises;
|
const fsPromises = fs.promises;
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
const providers = Object.assign({}, internalBinding('async_wrap').Providers);
|
const providers = Object.assign({}, internalBinding('async_wrap').Providers);
|
||||||
@ -294,3 +295,8 @@ if (process.features.inspector && common.isMainThread) {
|
|||||||
testInitialized(handle, 'Connection');
|
testInitialized(handle, 'Connection');
|
||||||
handle.disconnect();
|
handle.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PROVIDER_HEAPDUMP
|
||||||
|
{
|
||||||
|
v8.getHeapSnapshot().destroy();
|
||||||
|
}
|
||||||
|
46
test/sequential/test-heapdump.js
Normal file
46
test/sequential/test-heapdump.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
if (!common.isMainThread)
|
||||||
|
common.skip('process.chdir is not available in Workers');
|
||||||
|
|
||||||
|
const { writeHeapSnapshot, getHeapSnapshot } = require('v8');
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const tmpdir = require('../common/tmpdir');
|
||||||
|
|
||||||
|
tmpdir.refresh();
|
||||||
|
process.chdir(tmpdir.path);
|
||||||
|
|
||||||
|
{
|
||||||
|
writeHeapSnapshot('my.heapdump');
|
||||||
|
fs.accessSync('my.heapdump');
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const heapdump = writeHeapSnapshot();
|
||||||
|
assert.strictEqual(typeof heapdump, 'string');
|
||||||
|
fs.accessSync(heapdump);
|
||||||
|
}
|
||||||
|
|
||||||
|
[1, true, {}, [], null, Infinity, NaN].forEach((i) => {
|
||||||
|
common.expectsError(() => writeHeapSnapshot(i), {
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
type: TypeError,
|
||||||
|
message: 'The "path" argument must be one of type string, Buffer, or URL.' +
|
||||||
|
` Received type ${typeof i}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
let data = '';
|
||||||
|
const snapshot = getHeapSnapshot();
|
||||||
|
snapshot.setEncoding('utf-8');
|
||||||
|
snapshot.on('data', common.mustCallAtLeast((chunk) => {
|
||||||
|
data += chunk.toString();
|
||||||
|
}));
|
||||||
|
snapshot.on('end', common.mustCall(() => {
|
||||||
|
JSON.parse(data);
|
||||||
|
}));
|
||||||
|
}
|
@ -102,4 +102,7 @@ addlicense "brotli" "deps/brotli" "$(cat ${rootdir}/deps/brotli/LICENSE)"
|
|||||||
|
|
||||||
addlicense "HdrHistogram" "deps/histogram" "$(cat ${rootdir}/deps/histogram/LICENSE.txt)"
|
addlicense "HdrHistogram" "deps/histogram" "$(cat ${rootdir}/deps/histogram/LICENSE.txt)"
|
||||||
|
|
||||||
|
addlicense "node-heapdump" "src/heap_utils.cc" \
|
||||||
|
"$(curl -sL https://raw.githubusercontent.com/bnoordhuis/node-heapdump/0ca52441e46241ffbea56a389e2856ec01c48c97/LICENSE)"
|
||||||
|
|
||||||
mv $tmplicense $licensefile
|
mv $tmplicense $licensefile
|
||||||
|
Loading…
x
Reference in New Issue
Block a user