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:
|
||||
"""
|
||||
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
|
||||
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
|
||||
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()
|
||||
<!-- YAML
|
||||
added: v1.0.0
|
||||
@ -159,6 +177,58 @@ v8.setFlagsFromString('--trace_gc');
|
||||
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
|
||||
|
||||
> 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
|
||||
[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/
|
||||
[Worker Threads]: worker_threads.html
|
||||
[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.',
|
||||
'internal/test/heap');
|
||||
|
||||
const { createHeapDump, buildEmbedderGraph } = internalBinding('heap_utils');
|
||||
const {
|
||||
createHeapSnapshot,
|
||||
buildEmbedderGraph
|
||||
} = internalBinding('heap_utils');
|
||||
const assert = require('internal/assert');
|
||||
|
||||
// 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
|
||||
// to significantly increased memory usage.
|
||||
function createJSHeapDump() {
|
||||
const dump = createHeapDump();
|
||||
function createJSHeapSnapshot() {
|
||||
const dump = createHeapSnapshot();
|
||||
const meta = dump.snapshot.meta;
|
||||
|
||||
const nodes =
|
||||
@ -81,6 +84,6 @@ function readHeapInfo(raw, fields, types, strings) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createJSHeapDump,
|
||||
createJSHeapSnapshot,
|
||||
buildEmbedderGraph
|
||||
};
|
||||
|
60
lib/v8.js
60
lib/v8.js
@ -20,9 +20,65 @@ const {
|
||||
Serializer: _Serializer,
|
||||
Deserializer: _Deserializer
|
||||
} = internalBinding('serdes');
|
||||
const assert = require('internal/assert');
|
||||
const { copy } = internalBinding('buffer');
|
||||
const { objectToString } = require('internal/util');
|
||||
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
|
||||
// called with new operator and caused an assert to fire.
|
||||
@ -210,6 +266,7 @@ function deserialize(buffer) {
|
||||
|
||||
module.exports = {
|
||||
cachedDataVersionTag,
|
||||
getHeapSnapshot,
|
||||
getHeapStatistics,
|
||||
getHeapSpaceStatistics,
|
||||
setFlagsFromString,
|
||||
@ -218,5 +275,6 @@ module.exports = {
|
||||
DefaultSerializer,
|
||||
DefaultDeserializer,
|
||||
deserialize,
|
||||
serialize
|
||||
serialize,
|
||||
writeHeapSnapshot
|
||||
};
|
||||
|
@ -41,6 +41,7 @@ namespace node {
|
||||
V(FSREQPROMISE) \
|
||||
V(GETADDRINFOREQWRAP) \
|
||||
V(GETNAMEINFOREQWRAP) \
|
||||
V(HEAPSNAPSHOT) \
|
||||
V(HTTP2SESSION) \
|
||||
V(HTTP2STREAM) \
|
||||
V(HTTP2PING) \
|
||||
|
@ -383,6 +383,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
|
||||
V(script_data_constructor_function, v8::Function) \
|
||||
V(secure_context_constructor_template, v8::FunctionTemplate) \
|
||||
V(shutdown_wrap_template, v8::ObjectTemplate) \
|
||||
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
|
||||
V(tcp_constructor_template, v8::FunctionTemplate) \
|
||||
V(tick_callback_function, v8::Function) \
|
||||
V(timers_callback_function, v8::Function) \
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "env-inl.h"
|
||||
#include "stream_base-inl.h"
|
||||
|
||||
using v8::Array;
|
||||
using v8::Boolean;
|
||||
@ -6,6 +7,7 @@ using v8::Context;
|
||||
using v8::EmbedderGraph;
|
||||
using v8::EscapableHandleScope;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::FunctionTemplate;
|
||||
using v8::HandleScope;
|
||||
using v8::HeapSnapshot;
|
||||
using v8::Isolate;
|
||||
@ -14,6 +16,7 @@ using v8::Local;
|
||||
using v8::MaybeLocal;
|
||||
using v8::Number;
|
||||
using v8::Object;
|
||||
using v8::ObjectTemplate;
|
||||
using v8::String;
|
||||
using v8::Value;
|
||||
|
||||
@ -231,12 +234,146 @@ class BufferOutputStream : public v8::OutputStream {
|
||||
std::unique_ptr<JSString> buffer_;
|
||||
};
|
||||
|
||||
void CreateHeapDump(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
const HeapSnapshot* snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot();
|
||||
BufferOutputStream out;
|
||||
snapshot->Serialize(&out, HeapSnapshot::kJSON);
|
||||
namespace {
|
||||
class FileOutputStream : public v8::OutputStream {
|
||||
public:
|
||||
explicit FileOutputStream(FILE* stream) : stream_(stream) {}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
if (JSON::Parse(isolate->GetCurrentContext(),
|
||||
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,
|
||||
Local<Value> unused,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
|
||||
env->SetMethodNoSideEffect(target, "buildEmbedderGraph", BuildEmbedderGraph);
|
||||
env->SetMethodNoSideEffect(target, "createHeapDump", CreateHeapDump);
|
||||
env->SetMethodNoSideEffect(target,
|
||||
"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
|
||||
|
@ -299,6 +299,41 @@ v8::MaybeLocal<v8::Value> StartExecution(Environment* env,
|
||||
namespace profiler {
|
||||
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
|
||||
|
||||
#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 "uv.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <time.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace node {
|
||||
@ -144,4 +152,61 @@ void ThrowErrStringTooLong(Isolate* 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
|
||||
|
@ -10,7 +10,7 @@ try {
|
||||
console.log('using `test/common/heap.js` requires `--expose-internals`');
|
||||
throw e;
|
||||
}
|
||||
const { createJSHeapDump, buildEmbedderGraph } = internalTestHeap;
|
||||
const { createJSHeapSnapshot, buildEmbedderGraph } = internalTestHeap;
|
||||
|
||||
function inspectNode(snapshot) {
|
||||
return util.inspect(snapshot, { depth: 4 });
|
||||
@ -33,7 +33,7 @@ function isEdge(edge, { node_name, edge_name }) {
|
||||
|
||||
class State {
|
||||
constructor() {
|
||||
this.snapshot = createJSHeapDump();
|
||||
this.snapshot = createJSHeapSnapshot();
|
||||
this.embedderGraph = buildEmbedderGraph();
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ const assert = require('assert');
|
||||
|
||||
const isMainThread = common.isMainThread;
|
||||
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,
|
||||
`Total length: ${list.length}\n` + list.join('\n')
|
||||
|
@ -5,6 +5,7 @@ const common = require('../common');
|
||||
const { internalBinding } = require('internal/test/binding');
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const v8 = require('v8');
|
||||
const fsPromises = fs.promises;
|
||||
const net = require('net');
|
||||
const providers = Object.assign({}, internalBinding('async_wrap').Providers);
|
||||
@ -294,3 +295,8 @@ if (process.features.inspector && common.isMainThread) {
|
||||
testInitialized(handle, 'Connection');
|
||||
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 "node-heapdump" "src/heap_utils.cc" \
|
||||
"$(curl -sL https://raw.githubusercontent.com/bnoordhuis/node-heapdump/0ca52441e46241ffbea56a389e2856ec01c48c97/LICENSE)"
|
||||
|
||||
mv $tmplicense $licensefile
|
||||
|
Loading…
x
Reference in New Issue
Block a user