Skip to content

Commit ba37b75

Browse files
committed
src: add AddCodeEventHook API for code events
Introduces a new API to register code event hooks in nsolid. This includes new handler classes, queueing, and infrastructure to allow extensible code event observation and callbacks.
1 parent 455efa3 commit ba37b75

File tree

10 files changed

+577
-1
lines changed

10 files changed

+577
-1
lines changed

node.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@
514514
'src/nsolid.cc',
515515
'src/nsolid/continuous_profiler.cc',
516516
'src/nsolid/nsolid_api.cc',
517+
'src/nsolid/nsolid_code_event_handler.cc',
517518
'src/nsolid/nsolid_trace.cc',
518519
'src/nsolid/nsolid_cpu_profiler.cc',
519520
'src/nsolid/nsolid_heap_snapshot.cc',

src/nsolid.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,42 @@ int Snapshot::get_snapshot_(SharedEnvInst envinst,
597597
GetHeapSnapshot(envinst, redacted, data, proxy, deleter);
598598
}
599599

600+
class CodeEventHook::Impl {
601+
public:
602+
Impl() = default;
603+
~Impl() {
604+
// Remove the hook from the TSList in EnvList
605+
EnvList::Inst()->RemoveCodeEventHook(hook_);
606+
}
607+
608+
private:
609+
friend class CodeEventHook;
610+
void Setup(internal::code_event_hook_proxy_sig cb,
611+
void(*deleter)(void*),
612+
void* data) {
613+
// Add hook to the TSList in EnvList
614+
hook_ = EnvList::Inst()->AddCodeEventHook(data, cb, deleter);
615+
}
616+
617+
TSList<EnvList::CodeEventHookStor>::iterator hook_;
618+
};
619+
620+
CodeEventHook::CodeEventHook(): impl_(std::make_unique<Impl>()) {
621+
}
622+
623+
CodeEventHook::~CodeEventHook() = default;
624+
625+
void CodeEventHook::Dispose() {
626+
// This method transfers ownership to the callee and deletes the object.
627+
// The caller must not use this object after calling Dispose().
628+
delete this;
629+
}
630+
631+
void CodeEventHook::DoSetup(internal::code_event_hook_proxy_sig cb,
632+
internal::deleter_sig deleter,
633+
void* data) {
634+
impl_->Setup(cb, deleter, data);
635+
}
600636

601637
namespace internal {
602638

@@ -637,6 +673,17 @@ void thread_removed_hook_(void* data,
637673
EnvList::Inst()->EnvironmentDeletionHook(data, proxy, deleter);
638674
}
639675

676+
CodeEventHook* add_code_event_hook_(void* data,
677+
code_event_hook_proxy_sig proxy,
678+
deleter_sig deleter) {
679+
CodeEventHook* hook = new (std::nothrow) CodeEventHook();
680+
if (hook == nullptr) {
681+
return nullptr;
682+
}
683+
hook->DoSetup(proxy, deleter, data);
684+
return hook;
685+
}
686+
640687

641688
int queue_callback_(void* data, queue_callback_proxy_sig proxy) {
642689
return EnvList::Inst()->QueueCallback(proxy, data);

src/nsolid.h

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ namespace nsolid {
2727

2828
class EnvInst;
2929
struct LogWriteInfo;
30+
class CodeEventHook;
31+
struct CodeEventInfo;
3032

3133
#define kNSByte "byte"
3234
#define kNSMhz "MHz"
@@ -285,6 +287,7 @@ using on_log_write_hook_proxy_sig = void(*)(SharedEnvInst, LogWriteInfo, void*);
285287
using at_exit_hook_proxy_sig = void(*)(bool, bool, void*);
286288
using thread_added_hook_proxy_sig = void(*)(SharedEnvInst, void*);
287289
using thread_removed_hook_proxy_sig = thread_added_hook_proxy_sig;
290+
using code_event_hook_proxy_sig = void(*)(SharedEnvInst, CodeEventInfo, void*);
288291
using deleter_sig = void(*)(void*);
289292
using user_data = std::unique_ptr<void, deleter_sig>;
290293

@@ -314,6 +317,8 @@ void thread_added_hook_proxy_(SharedEnvInst, void* data);
314317
template <typename G>
315318
void thread_removed_hook_proxy_(SharedEnvInst, void* data);
316319
template <typename G>
320+
void code_event_hook_proxy_(SharedEnvInst, CodeEventInfo, void*);
321+
template <typename G>
317322
void delete_proxy_(void* g);
318323

319324

@@ -348,7 +353,9 @@ NODE_EXTERN void thread_added_hook_(void*,
348353
NODE_EXTERN void thread_removed_hook_(void*,
349354
thread_removed_hook_proxy_sig,
350355
deleter_sig);
351-
356+
NODE_EXTERN CodeEventHook* add_code_event_hook_(void*,
357+
code_event_hook_proxy_sig,
358+
deleter_sig);
352359
} // namespace internal
353360

354361
/** @endcond */
@@ -647,6 +654,52 @@ NODE_EXTERN int ThreadAddedHook(Cb&& cb, Data&&... data);
647654
template <typename Cb, typename... Data>
648655
NODE_EXTERN int ThreadRemovedHook(Cb&& cb, Data&&... data);
649656

657+
template <typename Cb, typename... Data>
658+
NODE_EXTERN CodeEventHook* AddCodeEventHook(Cb&& cb, Data&&... data);
659+
660+
struct CodeEventInfo {
661+
uint64_t thread_id;
662+
uint64_t timestamp;
663+
v8::CodeEventType type;
664+
uintptr_t code_start;
665+
uintptr_t new_code_start;
666+
size_t code_len;
667+
std::string fn_name;
668+
std::string script_name;
669+
int script_line;
670+
int script_column;
671+
std::string comment;
672+
};
673+
674+
/**
675+
* @brief Opaque handle for a code event hook registered with AddCodeEventHook.
676+
*
677+
* Only Dispose() should be used to remove the hook and release resources.
678+
*/
679+
class NODE_EXTERN CodeEventHook {
680+
public:
681+
/**
682+
* @brief Dispose and remove the registered code event hook.
683+
*
684+
* After calling Dispose(), this object must not be used again.
685+
*/
686+
void Dispose();
687+
688+
private:
689+
CodeEventHook();
690+
~CodeEventHook();
691+
friend CodeEventHook* internal::add_code_event_hook_(
692+
void*, internal::code_event_hook_proxy_sig, internal::deleter_sig);
693+
CodeEventHook(const CodeEventHook&) = delete;
694+
CodeEventHook& operator=(const CodeEventHook&) = delete;
695+
696+
void DoSetup(internal::code_event_hook_proxy_sig cb,
697+
internal::deleter_sig deleter,
698+
void* data);
699+
700+
class Impl;
701+
std::unique_ptr<Impl> impl_;
702+
};
650703

651704
/**
652705
* @brief Defines the types of metrics supported
@@ -1776,6 +1829,38 @@ int ThreadRemovedHook(Cb&& cb, Data&&... data) {
17761829
}
17771830

17781831

1832+
/**
1833+
* @brief Register a hook (function) to be called on code events.
1834+
*
1835+
* @tparam Cb Callback type. The callback will be invoked with (...Data) arguments.
1836+
* @tparam Data Variable argument types to be propagated to the callback.
1837+
* @param cb Hook function with signature: cb(...Data)
1838+
* @param data Variable number of arguments to be propagated to the callback.
1839+
* @return Pointer to CodeEventHook if successful, nullptr otherwise.
1840+
*/
1841+
template <typename Cb, typename... Data>
1842+
inline CodeEventHook* AddCodeEventHook(Cb&& cb, Data&&... data) {
1843+
// NOLINTNEXTLINE(build/namespaces)
1844+
using namespace std::placeholders;
1845+
using UserData = decltype(std::bind(
1846+
std::forward<Cb>(cb), _1, _2, std::forward<Data>(data)...));
1847+
1848+
// _1 - SharedEnvInst
1849+
// _2 - CodeEventInfo
1850+
UserData* user_data = new (std::nothrow) UserData(std::bind(
1851+
std::forward<Cb>(cb), _1, _2, std::forward<Data>(data)...));
1852+
if (user_data == nullptr) {
1853+
return nullptr;
1854+
}
1855+
1856+
return internal::add_code_event_hook_(
1857+
user_data,
1858+
internal::code_event_hook_proxy_<UserData>,
1859+
internal::delete_proxy_<UserData>);
1860+
}
1861+
1862+
1863+
17791864
namespace internal {
17801865

17811866
template <typename G>
@@ -1842,6 +1927,13 @@ void thread_removed_hook_proxy_(SharedEnvInst envinst, void* data) {
18421927
(*static_cast<G*>(data))(envinst);
18431928
}
18441929

1930+
template <typename G>
1931+
void code_event_hook_proxy_(SharedEnvInst envinst,
1932+
CodeEventInfo info,
1933+
void* data) {
1934+
(*static_cast<G*>(data))(envinst, std::move(info));
1935+
}
1936+
18451937
template <typename G>
18461938
void delete_proxy_(void* g) {
18471939
delete static_cast<G*>(g);

src/nsolid/nsolid_api.cc

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "nsolid_api.h"
2+
#include "nsolid/nsolid_code_event_handler.h"
23
#include "nsolid/nsolid_heap_snapshot.h"
34
#include "nsolid_bindings.h"
45
#include "node_buffer.h"
@@ -840,6 +841,19 @@ void EnvInst::StoreSourceCode(int script_id,
840841
}
841842

842843

844+
void EnvInst::setup_code_event_handler() {
845+
if (!code_event_handler_) {
846+
code_event_handler_ =
847+
std::make_unique<NSolidCodeEventHandler>(isolate_, thread_id_);
848+
code_event_handler_->Enable();
849+
}
850+
}
851+
852+
void EnvInst::disable_code_event_handler() {
853+
code_event_handler_.reset();
854+
}
855+
856+
843857
EnvList::EnvList(): info_(nlohmann::json()) {
844858
int er;
845859
// Create event loop and new thread to run EnvList commands.
@@ -869,6 +883,13 @@ EnvList::EnvList(): info_(nlohmann::json()) {
869883
er = thread_.create(env_list_routine_, this);
870884
CHECK_EQ(er, 0);
871885
continuous_profiler_ = std::make_shared<ContinuousProfiler>(&thread_loop_);
886+
on_code_event_q_ =
887+
AsyncTSQueue<CodeEventInfo>::create(
888+
&thread_loop_,
889+
+[](CodeEventInfo&& info, EnvList* envlist) {
890+
envlist->got_code_event(std::move(info));
891+
},
892+
this);
872893
}
873894

874895

@@ -978,6 +999,58 @@ void EnvList::OnUnblockedLoopHook(
978999
{ 0, proxy, nsolid::internal::user_data(data, deleter) });
9791000
}
9801001

1002+
TSList<EnvList::CodeEventHookStor>::iterator EnvList::AddCodeEventHook(
1003+
void* data,
1004+
internal::code_event_hook_proxy_sig proxy,
1005+
internal::deleter_sig deleter) {
1006+
1007+
auto it = code_event_hook_list_.push_back(
1008+
{ proxy, nsolid::internal::user_data(data, deleter) });
1009+
1010+
decltype(env_map_) env_map;
1011+
{
1012+
// Copy the envinst map so we don't need to keep it locked the entire time.
1013+
ns_mutex::scoped_lock lock(map_lock_);
1014+
env_map = env_map_;
1015+
}
1016+
1017+
for (auto& entry : env_map) {
1018+
SharedEnvInst envinst = entry.second;
1019+
int er = RunCommand(envinst,
1020+
CommandType::InterruptOnly,
1021+
setup_code_event_handler);
1022+
if (er) {
1023+
// Nothing to do here, really.
1024+
}
1025+
}
1026+
1027+
return it;
1028+
}
1029+
1030+
void EnvList::RemoveCodeEventHook(
1031+
TSList<EnvList::CodeEventHookStor>::iterator it) {
1032+
size_t size = code_event_hook_list_.erase(it);
1033+
if (size == 0) {
1034+
// No hooks left, disable the code event handler.
1035+
decltype(env_map_) env_map;
1036+
{
1037+
// Copy the envinst map so we don't need to keep it locked the entire
1038+
// time.
1039+
ns_mutex::scoped_lock lock(map_lock_);
1040+
env_map = env_map_;
1041+
}
1042+
1043+
for (auto& entry : env_map) {
1044+
SharedEnvInst envinst = entry.second;
1045+
int er = RunCommand(envinst,
1046+
CommandType::InterruptOnly,
1047+
disable_code_event_handler);
1048+
if (er) {
1049+
// Nothing to do here, really.
1050+
}
1051+
}
1052+
}
1053+
}
9811054

9821055
int EnvList::QueueCallback(q_cb_sig cb, uint64_t timeout, void* data) {
9831056
QCbTimeoutStor* stor = new (std::nothrow) QCbTimeoutStor({
@@ -1041,6 +1114,10 @@ NODE_PERFORMANCE_MILESTONES(V)
10411114
env->isolate()->AddGCEpilogueCallback(EnvInst::v8_gc_epilogue_cb_,
10421115
envinst_sp.get());
10431116

1117+
if (code_event_hook_list_.size() > 0) {
1118+
envinst_sp->setup_code_event_handler();
1119+
}
1120+
10441121
// Run EnvironmentCreationHook callbacks.
10451122
env_creation_list_.for_each([envinst_sp](auto& stor) {
10461123
stor.cb(envinst_sp, stor.data.get());
@@ -1415,6 +1492,18 @@ void EnvList::datapoint_cb_(std::queue<MetricsStream::Datapoint>&& q) {
14151492
});
14161493
}
14171494

1495+
void EnvList::setup_code_event_handler(SharedEnvInst envinst_sp) {
1496+
DCHECK_EQ(envinst_sp->isolate(), v8::Isolate::TryGetCurrent());
1497+
1498+
envinst_sp->setup_code_event_handler();
1499+
}
1500+
1501+
void EnvList::disable_code_event_handler(SharedEnvInst envinst_sp) {
1502+
DCHECK_EQ(envinst_sp->isolate(), v8::Isolate::TryGetCurrent());
1503+
1504+
envinst_sp->disable_code_event_handler();
1505+
}
1506+
14181507

14191508
void EnvInst::send_datapoint(MetricsStream::Type type,
14201509
double value) {
@@ -1617,9 +1706,11 @@ void EnvList::log_written_cb_(ns_async*, EnvList* envlist) {
16171706
// registered users will need to receive their last set of metrics and a
16181707
// notification that it'll be their last.
16191708
void EnvList::removed_env_cb_(ns_async*, EnvList* envlist) {
1709+
envlist->on_code_event_q_.reset();
16201710
if (envlist->continuous_profiler_) {
16211711
envlist->continuous_profiler_.reset();
16221712
}
1713+
16231714
envlist->removed_env_msg_.close();
16241715
envlist->process_callbacks_msg_.close();
16251716
envlist->log_written_msg_.close();
@@ -1885,6 +1976,20 @@ void EnvList::fill_trace_id_q() {
18851976
}
18861977
}
18871978

1979+
void EnvList::got_code_event(CodeEventInfo&& info) {
1980+
auto envinst_sp = EnvInst::GetInst(info.thread_id);
1981+
if (envinst_sp == nullptr) {
1982+
return;
1983+
}
1984+
1985+
code_event_hook_list_.for_each([&info, &envinst_sp](auto& stor, size_t size) {
1986+
if (size == 1) {
1987+
stor.cb(envinst_sp, std::move(info), stor.data.get());
1988+
} else {
1989+
stor.cb(envinst_sp, info, stor.data.get());
1990+
}
1991+
});
1992+
}
18881993

18891994
void EnvInst::custom_command_(SharedEnvInst envinst_sp,
18901995
const std::string req_id) {

0 commit comments

Comments
 (0)