Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nsolid: add support for start/stopTrackingHeapObjects #48

Merged
merged 1 commit into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
juanarbol marked this conversation as resolved.
Show resolved Hide resolved
* @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.
santigimeno marked this conversation as resolved.
Show resolved Hide resolved
*
* @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) {
juanarbol marked this conversation as resolved.
Show resolved Hide resolved
// 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)});
santigimeno marked this conversation as resolved.
Show resolved Hide resolved
}

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.
}
juanarbol marked this conversation as resolved.
Show resolved Hide resolved
}
}

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
Loading