Skip to content

Commit

Permalink
v8: support gc profile
Browse files Browse the repository at this point in the history
  • Loading branch information
theanarkh committed Jan 19, 2023
1 parent 5d50b84 commit 4756504
Show file tree
Hide file tree
Showing 8 changed files with 418 additions and 2 deletions.
76 changes: 76 additions & 0 deletions doc/api/v8.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,82 @@ The API is a no-op if `--heapsnapshot-near-heap-limit` is already set from the
command line or the API is called more than once. `limit` must be a positive
integer. See [`--heapsnapshot-near-heap-limit`][] for more information.

## `v8.collectGCProfile(options)`

<!-- YAML
added: REPLACEME
-->

* `options` {Object}
* `duration` {number} how long you want to collect the gc data.

* Returns: {Promise}

This API collects gc data over a period of time and return a object when
the `Promise` resolve. The content is as follows.

```json
{
"version": 1,
"startTime": 1674059033862,
"statistics": [
{
"gcType": "Scavenge",
"beforeGC": {
"heapStatistics": {
"totalHeapSize": 5005312,
"totalHeapSizeExecutable": 524288,
"totalPhysicalSize": 5226496,
"totalAvailableSize": 4341325216,
"totalGlobalHandlesSize": 8192,
"usedGlobalHandlesSize": 2112,
"usedHeapSize": 4883840,
"heapSizeLimit": 4345298944,
"mallocedMemory": 254128,
"externalMemory": 225138,
"peakMallocedMemory": 181760
},
"heapSpaceStatistics": [
{
"spaceName": "read_only_space",
"spaceSize": 0,
"spaceUsedSize": 0,
"spaceAvailableSize": 0,
"physicalSpaceSize": 0
}
]
},
"cost": 1574.14,
"afterGC": {
"heapStatistics": {
"totalHeapSize": 6053888,
"totalHeapSizeExecutable": 524288,
"totalPhysicalSize": 5500928,
"totalAvailableSize": 4341101384,
"totalGlobalHandlesSize": 8192,
"usedGlobalHandlesSize": 2112,
"usedHeapSize": 4059096,
"heapSizeLimit": 4345298944,
"mallocedMemory": 254128,
"externalMemory": 225138,
"peakMallocedMemory": 181760
},
"heapSpaceStatistics": [
{
"spaceName": "read_only_space",
"spaceSize": 0,
"spaceUsedSize": 0,
"spaceAvailableSize": 0,
"physicalSpaceSize": 0
}
]
}
}
],
"endtTime": 1674059036865
}
```

## Serialization API

The serialization API provides means of serializing JavaScript values in a way
Expand Down
24 changes: 22 additions & 2 deletions lib/v8.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const {
} = primordials;

const { Buffer } = require('buffer');
const { validateString, validateUint32 } = require('internal/validators');
const { validateString, validateUint32, validateObject, validateNumber } = require('internal/validators');
const {
Serializer,
Deserializer
Expand Down Expand Up @@ -63,7 +63,8 @@ const {
} = require('internal/heap_utils');
const promiseHooks = require('internal/promise_hooks');
const { getOptionValue } = require('internal/options');

const { setUnrefTimeout } = require('internal/timers');
const { Promise, JSONParse } = primordials;
/**
* Generates a snapshot of the current V8 heap
* and writes it to a JSON file.
Expand Down Expand Up @@ -397,6 +398,24 @@ function deserialize(buffer) {
return der.readValue();
}

/**
* @param {{
* duration: number,
* }} [options]
*/
function collectGCProfile(options) {
validateObject(options, 'options');
validateNumber(options.duration, 'options.duration', 1);
return new Promise((resolve) => {
const profiler = new binding.GCProfiler();
profiler.start();
setUnrefTimeout(() => {
const data = profiler.stop();
resolve(JSONParse(data));
}, options.duration);
});
}

module.exports = {
cachedDataVersionTag,
getHeapSnapshot,
Expand All @@ -416,4 +435,5 @@ module.exports = {
promiseHooks,
startupSnapshot,
setHeapSnapshotNearHeapLimit,
collectGCProfile,
};
182 changes: 182 additions & 0 deletions src/node_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace v8_utils {
using v8::Array;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::HeapCodeStatistics;
using v8::HeapSpaceStatistics;
Expand Down Expand Up @@ -210,6 +211,177 @@ void SetFlagsFromString(const FunctionCallbackInfo<Value>& args) {
V8::SetFlagsFromString(*flags, static_cast<size_t>(flags.length()));
}

static const char* GetGCTypeName(v8::GCType gc_type) {
switch (gc_type) {
case v8::GCType::kGCTypeScavenge:
return "Scavenge";
case v8::GCType::kGCTypeMarkSweepCompact:
return "MarkSweepCompact";
case v8::GCType::kGCTypeIncrementalMarking:
return "IncrementalMarking";
case v8::GCType::kGCTypeProcessWeakCallbacks:
return "ProcessWeakCallbacks";
default:
return "UnKnown";
}
}

static void SetHeapStatistics(JSONWriter* writer, Isolate* isolate) {
HeapStatistics heap_statistics;
isolate->GetHeapStatistics(&heap_statistics);
writer->json_objectstart("heapStatistics");
writer->json_keyvalue("totalHeapSize", heap_statistics.total_heap_size());
writer->json_keyvalue("totalHeapSizeExecutable",
heap_statistics.total_heap_size_executable());
writer->json_keyvalue("totalPhysicalSize",
heap_statistics.total_physical_size());
writer->json_keyvalue("totalAvailableSize",
heap_statistics.total_available_size());
writer->json_keyvalue("totalGlobalHandlesSize",
heap_statistics.total_global_handles_size());
writer->json_keyvalue("usedGlobalHandlesSize",
heap_statistics.used_global_handles_size());
writer->json_keyvalue("usedHeapSize", heap_statistics.used_heap_size());
writer->json_keyvalue("heapSizeLimit", heap_statistics.heap_size_limit());
writer->json_keyvalue("mallocedMemory", heap_statistics.malloced_memory());
writer->json_keyvalue("externalMemory", heap_statistics.external_memory());
writer->json_keyvalue("peakMallocedMemory",
heap_statistics.peak_malloced_memory());
writer->json_objectend();

int space_count = isolate->NumberOfHeapSpaces();
writer->json_arraystart("heapSpaceStatistics");
for (int i = 0; i < space_count; i++) {
HeapSpaceStatistics heap_space_statistics;
isolate->GetHeapSpaceStatistics(&heap_space_statistics, i);
writer->json_start();
writer->json_keyvalue("spaceName", heap_space_statistics.space_name());
writer->json_keyvalue("spaceSize", heap_space_statistics.space_size());
writer->json_keyvalue("spaceUsedSize",
heap_space_statistics.space_used_size());
writer->json_keyvalue("spaceAvailableSize",
heap_space_statistics.space_available_size());
writer->json_keyvalue("physicalSpaceSize",
heap_space_statistics.physical_space_size());
writer->json_end();
}
writer->json_arrayend();
}

static void BeforeGCCallback(Isolate* isolate,
v8::GCType gc_type,
v8::GCCallbackFlags flags,
void* data) {
GCProfiler* profiler = static_cast<GCProfiler*>(data);
if (profiler->current_gc_type != 0) {
return;
}
JSONWriter* writer = profiler->writer();
writer->json_start();
writer->json_keyvalue("gcType", GetGCTypeName(gc_type));
writer->json_objectstart("beforeGC");
SetHeapStatistics(writer, isolate);
writer->json_objectend();
profiler->current_gc_type = gc_type;
profiler->start_time = uv_hrtime();
}

static void AfterGCCallback(Isolate* isolate,
v8::GCType gc_type,
v8::GCCallbackFlags flags,
void* data) {
GCProfiler* profiler = static_cast<GCProfiler*>(data);
if (profiler->current_gc_type != gc_type) {
return;
}
JSONWriter* writer = profiler->writer();
profiler->current_gc_type = 0;
writer->json_keyvalue("cost", (uv_hrtime() - profiler->start_time) / 1e3);
profiler->start_time = 0;
writer->json_objectstart("afterGC");
SetHeapStatistics(writer, isolate);
writer->json_objectend();
writer->json_end();
}

GCProfiler::GCProfiler(Environment* env, Local<Object> object)
: BaseObject(env, object), writer_(out_stream_, false) {
MakeWeak();
}

// This function will be called when
// 1. StartGCProfile and StopGCProfile are called and
// JS land do not keep the object any more.
// 2. StartGCProfile is called then the env exits before
// StopGCProfile is called.
GCProfiler::~GCProfiler() {
if (state != GCProfiler::GCProfilerState::kInitialized) {
env()->isolate()->RemoveGCPrologueCallback(BeforeGCCallback, this);
env()->isolate()->RemoveGCEpilogueCallback(AfterGCCallback, this);
}
}

JSONWriter* GCProfiler::writer() {
return &writer_;
}

std::ostringstream* GCProfiler::out_stream() {
return &out_stream_;
}

void GCProfiler::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
Environment* env = Environment::GetCurrent(args);
new GCProfiler(env, args.This());
}

void GCProfiler::Start(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
GCProfiler* profiler;
ASSIGN_OR_RETURN_UNWRAP(&profiler, args.Holder());
if (profiler->state != GCProfiler::GCProfilerState::kInitialized) {
return;
}
profiler->writer()->json_start();
profiler->writer()->json_keyvalue("version", 1);

uv_timeval64_t ts;
if (uv_gettimeofday(&ts) == 0) {
profiler->writer()->json_keyvalue("startTime",
ts.tv_sec * 1000 + ts.tv_usec / 1000);
} else {
profiler->writer()->json_keyvalue("startTime", 0);
}
profiler->writer()->json_arraystart("statistics");
env->isolate()->AddGCPrologueCallback(BeforeGCCallback,
static_cast<void*>(profiler));
env->isolate()->AddGCEpilogueCallback(AfterGCCallback,
static_cast<void*>(profiler));
profiler->state = GCProfiler::GCProfilerState::kStarted;
}

void GCProfiler::Stop(const FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
GCProfiler* profiler;
ASSIGN_OR_RETURN_UNWRAP(&profiler, args.Holder());
if (profiler->state != GCProfiler::GCProfilerState::kStarted) {
return;
}
profiler->writer()->json_arrayend();
uv_timeval64_t ts;
if (uv_gettimeofday(&ts) == 0) {
profiler->writer()->json_keyvalue("endtTime",
ts.tv_sec * 1000 + ts.tv_usec / 1000);
} else {
profiler->writer()->json_keyvalue("endtTime", 0);
}
profiler->writer()->json_end();
profiler->state = GCProfiler::GCProfilerState::kStopped;
args.GetReturnValue().Set(
String::NewFromUtf8(env->isolate(), profiler->out_stream()->str().c_str())
.ToLocalChecked());
}

void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
Expand Down Expand Up @@ -272,6 +444,14 @@ void Initialize(Local<Object> target,

// Export symbols used by v8.setFlagsFromString()
SetMethod(context, target, "setFlagsFromString", SetFlagsFromString);

// GCProfiler
Local<FunctionTemplate> t =
NewFunctionTemplate(env->isolate(), GCProfiler::New);
t->InstanceTemplate()->SetInternalFieldCount(BaseObject::kInternalFieldCount);
SetProtoMethod(env->isolate(), t, "start", GCProfiler::Start);
SetProtoMethod(env->isolate(), t, "stop", GCProfiler::Stop);
SetConstructorFunction(context, target, "GCProfiler", t);
}

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
Expand All @@ -281,6 +461,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(UpdateHeapSpaceStatisticsBuffer);
registry->Register(SetFlagsFromString);
registry->Register(SetHeapSnapshotNearHeapLimit);
registry->Register(GCProfiler::Start);
registry->Register(GCProfiler::Stop);
}

} // namespace v8_utils
Expand Down
28 changes: 28 additions & 0 deletions src/node_v8.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <sstream>
#include "aliased_buffer.h"
#include "base_object.h"
#include "json_utils.h"
#include "node_snapshotable.h"
#include "util.h"
#include "v8.h"
Expand Down Expand Up @@ -34,6 +36,32 @@ class BindingData : public SnapshotableObject {
SET_MEMORY_INFO_NAME(BindingData)
};

class GCProfiler : public BaseObject {
public:
enum class GCProfilerState { kInitialized, kStarted, kStopped };
GCProfiler(Environment* env, v8::Local<v8::Object> object);
inline ~GCProfiler() override;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Stop(const v8::FunctionCallbackInfo<v8::Value>& args);

JSONWriter* writer();

std::ostringstream* out_stream();

SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(GCProfiler)
SET_SELF_SIZE(GCProfiler)

u_int64_t start_time = 0;
u_int8_t current_gc_type = 0;
GCProfilerState state = GCProfilerState::kInitialized;

private:
std::ostringstream out_stream_;
JSONWriter writer_;
};

} // namespace v8_utils

} // namespace node
Expand Down
Loading

0 comments on commit 4756504

Please sign in to comment.