Skip to content

Commit

Permalink
nsolid: add start/stopTrackingHeapObjects APIs
Browse files Browse the repository at this point in the history
This patch implements triggers for `startTrackingHeapObjects`
and `stopTrackingHeapObjects` in the heap profiler.

The implementation is based on what the inspector does:

The `startTrackingHeapObjects` will simply activate that in the
HeapProfiler and; the `stopTrackingHeapObjects` will create a heap
snapshot and deactivate the `TrackingHeapObjects` in the Heap
profiler.

This patch also includes the `trackAllocations` option supported.

PR-URL: #48
Signed-off-by: Juan José Arboleda <soyjuanarbol@gmail.com>
Co-authored-by: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-by: Trevor Norris <trev.norris@gmail.com>
Reviewed-by: Santiago Gimeno <santiago.gimeno@gmail.com>
  • Loading branch information
2 people authored and trevnorris committed Feb 28, 2024
1 parent d86090a commit 4f812b5
Show file tree
Hide file tree
Showing 8 changed files with 464 additions and 2 deletions.
16 changes: 16 additions & 0 deletions src/nsolid.cc
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,22 @@ int CpuProfiler::get_cpu_profile_(SharedEnvInst envinst,
deleter);
}

int Snapshot::start_tracking_heap_objects_(SharedEnvInst envinst,
bool redacted,
bool trackAllocations,
uint64_t duration,
internal::user_data data,
snapshot_proxy_sig proxy) {
return NSolidHeapSnapshot::Inst()->StartTrackingHeapObjects(
envinst, redacted, trackAllocations, duration, std::move(data), proxy);
}

int Snapshot::StopTrackingHeapObjects(SharedEnvInst envinst) {
if (envinst == nullptr)
return UV_ESRCH;

return NSolidHeapSnapshot::Inst()->StopTrackingHeapObjects(envinst);
}

int Snapshot::get_snapshot_(SharedEnvInst envinst,
bool redacted,
Expand Down
74 changes: 74 additions & 0 deletions src/nsolid.h
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,7 @@ class NODE_EXTERN Snapshot {
* concurrent snapshot per thread can be taken.
*
* @param envinst SharedEnvInst of thread to take the snapshot from.
* @param redacted to get redacted strings in the heap snapshot.
* @param cb callback function with the following signature:
* `cb(int status, std::string snapshot, ...Data)`. It will be called from the
* NSolid thread.
Expand All @@ -967,7 +968,50 @@ class NODE_EXTERN Snapshot {
Cb&& cb,
Data&&... data);

/**
* @brief activates the heap profiler tracking objects from a specific JS
* thread.
*
* @param envinst SharedEnvInst of thread to take the snapshot from.
* @param redacted to get redacted strings in the heap snapshot.
* @param trackAllLocations record stack traces of allocations, set this as
* as true will add a significant overhead.
* @param duration duration in milliseconds of the heap profiler after which
* the heap snapshot will be returned in the callback.
* @param data variable number of arguments to be propagated to the callback.
* @param cb callback function with the following signature:
* `cb(int status, std::string snapshot, ...Data)`. It will be called from the
* NSolid thread.
* @param data variable number of arguments to be propagated to the callback.
* @return NSOLID_E_SUCCESS in case of success or a different NSOLID_E_
* error value otherwise.
*/
template <typename Cb, typename... Data>
static int StartTrackingHeapObjects(SharedEnvInst envinst,
bool redacted,
bool trackAllocations,
uint64_t duration,
Cb&& cb,
Data&&... data);

/**
* @brief same as TakeSnapshot but stops tracking heap objects in the heap
* profiler.
*
* @param envinst SharedEnvInst of thread to take the snapshot from.
* @return NSOLID_E_SUCCESS in case of success or a different NSOLID_E_
* error value otherwise.
*/
static int StopTrackingHeapObjects(SharedEnvInst envinst);

private:
static int start_tracking_heap_objects_(SharedEnvInst envinst,
bool redacted,
bool trackAllocations,
uint64_t duration,
internal::user_data data,
snapshot_proxy_sig proxy);

static int get_snapshot_(SharedEnvInst envinst,
bool redacted,
void* data,
Expand Down Expand Up @@ -1133,6 +1177,36 @@ void CpuProfiler::cpu_profiler_proxy_(int status,
(*static_cast<G*>(data))(status, json);
}

template <typename Cb, typename... Data>
int Snapshot::StartTrackingHeapObjects(SharedEnvInst envinst,
bool redacted,
bool trackAllocations,
uint64_t duration,
Cb&& cb,
Data&&... data) {
if (envinst == nullptr) {
return UV_ESRCH;
}

// NOLINTNEXTLINE(build/namespaces)
using namespace std::placeholders;
using UserData = decltype(std::bind(
std::forward<Cb>(cb), _1, _2, std::forward<Data>(data)...));

auto user_data = internal::user_data(new (std::nothrow) UserData(
std::bind(std::forward<Cb>(cb), _1, _2, std::forward<Data>(data)...)),
internal::delete_proxy_<UserData>);
if (user_data == nullptr) {
return UV_ENOMEM;
}

return start_tracking_heap_objects_(envinst,
redacted,
trackAllocations,
duration,
std::move(user_data),
snapshot_proxy_<UserData>);
}

template <typename Cb, typename... Data>
int Snapshot::TakeSnapshot(SharedEnvInst envinst,
Expand Down
2 changes: 2 additions & 0 deletions src/nsolid/nsolid_api.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "nsolid_api.h"
#include "nsolid/nsolid_heap_snapshot.h"
#include "nsolid_bindings.h"
#include "node_buffer.h"
#include "nsolid_cpu_profiler.h"
Expand Down Expand Up @@ -966,6 +967,7 @@ void EnvList::RemoveEnv(Environment* env) {
// End any pending CPU profiles. This has to be done before removing the
// EnvList from env_map_ so the checks in StopProfilingSync() pass.
NSolidCpuProfiler::Inst()->StopProfilingSync(envinst_sp);
NSolidHeapSnapshot::Inst()->StopTrackingHeapObjects(envinst_sp);

// Remove the GC prologue and epilogue callbacks just to be safe.
envinst_sp->env()->isolate()->RemoveGCPrologueCallback(
Expand Down
173 changes: 171 additions & 2 deletions src/nsolid/nsolid_heap_snapshot.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,35 @@ NSolidHeapSnapshot::NSolidHeapSnapshot() {
ASSERT_EQ(0, in_progress_heap_snapshots_.init(true));
}

int NSolidHeapSnapshot::StartTrackingHeapObjects(
SharedEnvInst envinst,
bool redacted,
bool trackAllocations,
uint64_t duration,
internal::user_data data,
Snapshot::snapshot_proxy_sig proxy) {
uint64_t thread_id = envinst->thread_id();
nsuv::ns_mutex::scoped_lock lock(&in_progress_heap_snapshots_);
// We can not trigger this command if there is already a snapshot in progress
auto it = threads_running_snapshots_.emplace(
thread_id,
HeapSnapshotStor{ redacted, true, proxy, std::move(data) });
if (it.second == false) {
return UV_EEXIST;
}
int status = RunCommand(envinst,
CommandType::Interrupt,
start_tracking_heapobjects,
trackAllocations,
duration,
this);
if (status != 0) {
// Now we are tracking heap objects in this thread
threads_running_snapshots_.erase(it.first);
}
return status;
}

int NSolidHeapSnapshot::GetHeapSnapshot(SharedEnvInst envinst,
bool redacted,
void* data,
Expand All @@ -31,13 +60,147 @@ int NSolidHeapSnapshot::GetHeapSnapshot(SharedEnvInst envinst,

if (status == 0) {
threads_running_snapshots_.emplace(
thread_id,
HeapSnapshotStor{ redacted, proxy, internal::user_data(data, deleter) });
thread_id,
HeapSnapshotStor{
redacted, false, proxy, internal::user_data(data, deleter)});
}

return status;
}

int NSolidHeapSnapshot::StopTrackingHeapObjects(SharedEnvInst envinst) {
uint64_t thread_id = envinst->thread_id();
nsuv::ns_mutex::scoped_lock lock(&in_progress_heap_snapshots_);
auto it = threads_running_snapshots_.find(thread_id);
// Make sure there is a snapshot running
if (it == threads_running_snapshots_.end())
return UV_ENOENT;

int er = RunCommand(envinst,
CommandType::Interrupt,
stop_tracking_heap_objects,
this);
return er;
}

void NSolidHeapSnapshot::stop_tracking_heap_objects(
SharedEnvInst envinst,
NSolidHeapSnapshot* snapshotter) {
uint64_t thread_id = envinst->thread_id();
nsuv::ns_mutex::scoped_lock lock(&snapshotter->in_progress_heap_snapshots_);
auto it = snapshotter->threads_running_snapshots_.find(thread_id);
if (it == snapshotter->threads_running_snapshots_.end()) {
// This might happen if snapshot_cb is called before RemoveEnv. Just return;
return;
}

HeapSnapshotStor& stor = it->second;
// If this condition is reached. This was called by EnvList::RemoveEnv.
// It wants to stop any pending snapshot w/ tracking heap object.
if (!stor.is_tracking_heapobjects_) {
// If no pending trackers, just do nothing
// There are not peding snapshots with trackers
return;
}

v8::Isolate* isolate = envinst->isolate();

v8::HeapProfiler* profiler = isolate->GetHeapProfiler();
ASSERT_NOT_NULL(profiler);

v8::HandleScope scope(isolate);

const v8::HeapSnapshot* snapshot = profiler->TakeHeapSnapshot();
if (snapshot == nullptr) {
stor.cb(heap_profiler::HEAP_SNAPSHOT_FAILURE,
std::string(),
stor.data.get());
} else {
std::string snapshot_str;
DataOutputStream<uint64_t, v8::HeapSnapshot> stream(&snapshot_str,
snapshot,
&thread_id);

if (stor.redacted) {
const v8::RedactedHeapSnapshot snapshot_redact(snapshot);
snapshot_redact.Serialize(&stream);
} else {
snapshot->Serialize(&stream);
}

ASSERT_EQ(stor.is_tracking_heapobjects_, true);
profiler->StopTrackingHeapObjects();
stor.is_tracking_heapobjects_ = false;


// At this point, the snapshot is fully serialized
stor.cb(0, snapshot_str.c_str(), stor.data.get());
// Tell ZMQ that the snapshot is done
stor.cb(0, std::string(), stor.data.get());

// Don't leak the snapshot string
snapshot_str.clear();

// Work around a deficiency in the API. The HeapSnapshot object is const
// but we cannot call HeapProfiler::DeleteAllHeapSnapshots() because that
// invalidates _all_ snapshots, including those created by other tools.
const_cast<v8::HeapSnapshot*>(snapshot)->Delete();
}
// Delete the snapshot from the map
snapshotter->threads_running_snapshots_.erase(it);
}

void NSolidHeapSnapshot::start_tracking_heapobjects(
SharedEnvInst envinst,
bool trackAllocations,
uint64_t duration,
NSolidHeapSnapshot* snapshotter) {
uint64_t thread_id = envinst->thread_id();

nsuv::ns_mutex::scoped_lock lock(&snapshotter->in_progress_heap_snapshots_);
auto it = snapshotter->threads_running_snapshots_.find(thread_id);
ASSERT(it != snapshotter->threads_running_snapshots_.end());

HeapSnapshotStor& stor = it->second;
ASSERT_EQ(stor.is_tracking_heapobjects_, true);

envinst->isolate()->GetHeapProfiler()->StartTrackingHeapObjects(
trackAllocations);
if (duration > 0) {
// Schedule a timer to take the snapshot
int er = QueueCallback(duration, take_snapshot_timer, envinst, snapshotter);

if (er) {
// In case the the thread is already gone, the cpu profile will be stopped
// on RemoveEnv, so do nothing here.
}
}
}

void NSolidHeapSnapshot::take_snapshot_timer(SharedEnvInst envinst,
NSolidHeapSnapshot* snapshotter) {
uint64_t thread_id = envinst->thread_id();
nsuv::ns_mutex::scoped_lock lock(&snapshotter->in_progress_heap_snapshots_);
auto it = snapshotter->threads_running_snapshots_.find(thread_id);
// The snapshot was stopped before the timer was triggered
if (it == snapshotter->threads_running_snapshots_.end())
return;

HeapSnapshotStor& stor = it->second;
ASSERT_EQ(stor.is_tracking_heapobjects_, true);

// Give control back to the V8 thread
int er = RunCommand(envinst,
CommandType::Interrupt,
take_snapshot,
snapshotter);
// NO BUENO???
if (er) {
// In case the the thread is already gone, the cpu profile will be stopped
// on RemoveEnv, so do nothing here.
}
}

void NSolidHeapSnapshot::take_snapshot(SharedEnvInst envinst,
NSolidHeapSnapshot* snapshotter) {
v8::Isolate* isolate = envinst->isolate();
Expand Down Expand Up @@ -75,6 +238,12 @@ void NSolidHeapSnapshot::take_snapshot(SharedEnvInst envinst,
snapshot->Serialize(&stream);
}

// A snapshot requested via `StopTrackingHeapObjects` or timer
if (stor.is_tracking_heapobjects_) {
profiler->StopTrackingHeapObjects();
stor.is_tracking_heapobjects_ = false;
}

// Work around a deficiency in the API. The HeapSnapshot object is const
// but we cannot call HeapProfiler::DeleteAllHeapSnapshots() because that
// invalidates _all_ snapshots, including those created by other tools.
Expand Down
21 changes: 21 additions & 0 deletions src/nsolid/nsolid_heap_snapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class NSolidHeapSnapshot {
public:
struct HeapSnapshotStor {
bool redacted;
bool is_tracking_heapobjects_;
Snapshot::snapshot_proxy_sig cb;
internal::user_data data;
};
Expand All @@ -36,11 +37,31 @@ class NSolidHeapSnapshot {
Snapshot::snapshot_proxy_sig proxy,
internal::deleter_sig deleter);

int StartTrackingHeapObjects(SharedEnvInst envinst,
bool redacted,
bool trackAllocations,
uint64_t duration,
internal::user_data data,
Snapshot::snapshot_proxy_sig proxy);

int StopTrackingHeapObjects(SharedEnvInst envinst);

private:
NSolidHeapSnapshot();

static void start_tracking_heapobjects(SharedEnvInst envinst,
bool trackAllocations,
uint64_t duration,
NSolidHeapSnapshot*);

static void stop_tracking_heap_objects(SharedEnvInst envinst_sp,
NSolidHeapSnapshot*);

static void take_snapshot(SharedEnvInst envinst_sp, NSolidHeapSnapshot*);

static void take_snapshot_timer(SharedEnvInst envinst_sp,
NSolidHeapSnapshot*);

static void snapshot_cb(uint64_t thread_id,
int status,
const std::string& snapshot);
Expand Down
Loading

0 comments on commit 4f812b5

Please sign in to comment.