11#include < node.h>
22#include < mutex>
33#include < future>
4+ #include < chrono>
45
56using namespace v8 ;
67using namespace node ;
8+ using namespace std ::chrono;
79
810static const int kMaxStackFrames = 255 ;
911
12+ // Structure to hold information for each thread/isolate
13+ struct ThreadInfo
14+ {
15+ // Thread name
16+ std::string thread_name;
17+ // Last time this thread was seen in milliseconds since epoch
18+ milliseconds last_seen;
19+ };
20+
1021static std::mutex threads_mutex;
11- static std::unordered_map<v8::Isolate *, int > threads = {};
22+ // Map to hold all registered threads and their information
23+ static std::unordered_map<v8::Isolate *, ThreadInfo> threads = {};
1224
25+ // Function to be called when an isolate's execution is interrupted
1326static void ExecutionInterrupted (Isolate *isolate, void *data)
1427{
1528 auto promise = static_cast <std::promise<Local<Array>> *>(data);
@@ -68,32 +81,38 @@ static void ExecutionInterrupted(Isolate *isolate, void *data)
6881 promise->set_value (frames);
6982}
7083
84+ // Function to capture the stack trace of a single isolate
7185Local<Array> CaptureStackTrace (Isolate *isolate)
7286{
7387 std::promise<Local<Array>> promise;
7488 auto future = promise.get_future ();
7589
90+ // The v8 isolate must be interrupted to capture the stack trace
91+ // Execution resumes automatically after ExecutionInterrupted returns
7692 isolate->RequestInterrupt (ExecutionInterrupted, &promise);
7793 return future.get ();
7894}
7995
96+ // Function to capture stack traces from all registered threads
8097void CaptureStackTraces (const FunctionCallbackInfo<Value> &args)
8198{
8299 auto capture_from_isolate = args.GetIsolate ();
83100
84101 using ThreadResult = std::tuple<std::string, Local<Array>>;
85102 std::vector<std::future<ThreadResult>> futures;
86103
104+ // We collect the futures into a vec so they can be processed in parallel
87105 std::lock_guard<std::mutex> lock (threads_mutex);
88- for (auto [thread_isolate, thread_id ] : threads)
106+ for (auto [thread_isolate, thread_info ] : threads)
89107 {
90108 if (thread_isolate == capture_from_isolate)
91109 continue ;
92- auto thread_name = thread_id == - 1 ? " main " : " worker- " + std::to_string (thread_id) ;
110+ auto thread_name = thread_info. thread_name ;
93111 futures.emplace_back (std::async (std::launch::async, [thread_name](Isolate *isolate) -> ThreadResult
94112 { return std::make_tuple (thread_name, CaptureStackTrace (isolate)); }, thread_isolate));
95113 }
96114
115+ // We wait for all futures to complete and collect their results into a JavaScript object
97116 Local<Object> result = Object::New (capture_from_isolate);
98117 for (auto &future : futures)
99118 {
@@ -105,30 +124,67 @@ void CaptureStackTraces(const FunctionCallbackInfo<Value> &args)
105124 args.GetReturnValue ().Set (result);
106125}
107126
127+ // Cleanup function to remove the thread from the map when the isolate is destroyed
108128void Cleanup (void *arg)
109129{
110130 auto isolate = static_cast <Isolate *>(arg);
111131 std::lock_guard<std::mutex> lock (threads_mutex);
112132 threads.erase (isolate);
113133}
114134
135+ // Function to register a thread and update it's last seen time
115136void RegisterThread (const FunctionCallbackInfo<Value> &args)
116137{
117138 auto isolate = args.GetIsolate ();
118139
119- if (args.Length () != 1 || !args[0 ]->IsNumber ())
140+ if (args.Length () != 1 || !args[0 ]->IsString ())
120141 {
121- isolate->ThrowException (Exception::Error (String::NewFromUtf8 (isolate, " registerThread() requires a single threadId argument" , NewStringType::kInternalized ).ToLocalChecked ()));
142+ isolate->ThrowException (Exception::Error (String::NewFromUtf8 (isolate, " registerThread(name ) requires a single name argument" , NewStringType::kInternalized ).ToLocalChecked ()));
122143 return ;
123144 }
124145
125- auto thread_id = args[0 ].As <Number>()->Value ();
146+ v8::String::Utf8Value utf8 (isolate, args[0 ]);
147+ std::string thread_name (*utf8 ? *utf8 : " " );
148+
149+ {
150+ std::lock_guard<std::mutex> lock (threads_mutex);
151+ auto found = threads.find (isolate);
152+ if (found == threads.end ())
153+ {
154+ threads.emplace (isolate, ThreadInfo{thread_name, milliseconds::zero ()});
155+ // Register a cleanup hook to remove this thread when the isolate is destroyed
156+ node::AddEnvironmentCleanupHook (isolate, Cleanup, isolate);
157+ }
158+ else
159+ {
160+ auto &thread_info = found->second ;
161+ thread_info.thread_name = thread_name;
162+ thread_info.last_seen = duration_cast<milliseconds>(system_clock::now ().time_since_epoch ());
163+ }
164+ }
165+ }
126166
167+ // Function to get the last seen time of all registered threads
168+ void GetThreadLastSeen (const FunctionCallbackInfo<Value> &args)
169+ {
170+ Isolate *isolate = args.GetIsolate ();
171+ Local<Object> result = Object::New (isolate);
172+ milliseconds now = duration_cast<milliseconds>(system_clock::now ().time_since_epoch ());
127173 {
128174 std::lock_guard<std::mutex> lock (threads_mutex);
129- threads.emplace (isolate, thread_id);
175+ for (const auto &[thread_isolate, info] : threads)
176+ {
177+ if (info.last_seen == milliseconds::zero ())
178+ continue ; // Skip threads that have not registered more than once
179+
180+ int64_t ms_since = (now - info.last_seen ).count ();
181+ result->Set (isolate->GetCurrentContext (),
182+ String::NewFromUtf8 (isolate, info.thread_name .c_str (), NewStringType::kNormal ).ToLocalChecked (),
183+ Number::New (isolate, ms_since))
184+ .Check ();
185+ }
130186 }
131- node::AddEnvironmentCleanupHook (isolate, Cleanup, isolate );
187+ args. GetReturnValue (). Set (result );
132188}
133189
134190extern " C" NODE_MODULE_EXPORT void NODE_MODULE_INITIALIZER (Local<Object> exports,
@@ -146,4 +202,9 @@ extern "C" NODE_MODULE_EXPORT void NODE_MODULE_INITIALIZER(Local<Object> exports
146202 String::NewFromUtf8 (isolate, " registerThread" , NewStringType::kInternalized ).ToLocalChecked (),
147203 FunctionTemplate::New (isolate, RegisterThread)->GetFunction (context).ToLocalChecked ())
148204 .Check ();
205+
206+ exports->Set (context,
207+ String::NewFromUtf8 (isolate, " getThreadLastSeen" , NewStringType::kInternalized ).ToLocalChecked (),
208+ FunctionTemplate::New (isolate, GetThreadLastSeen)->GetFunction (context).ToLocalChecked ())
209+ .Check ();
149210}
0 commit comments