Skip to content

Commit cf4aa0f

Browse files
fix: when tracking threads, use a monotonic clock that ignores system suspension (#14)
* when tracking threads, use a monotonic clock that ignores system suspension Using the system time causes each suspension to be detected as an event loop delay which is not useful. System suspensions may be common when running in low cost cloud environments where systems are suspended when not actively serving requests. * fix windows build
1 parent 56f301a commit cf4aa0f

File tree

2 files changed

+49
-5
lines changed

2 files changed

+49
-5
lines changed

binding.gyp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22
"targets": [
33
{
44
"target_name": "stack-trace",
5-
"sources": [ "module.cc" ]
5+
"sources": [ "module.cc" ],
6+
"conditions": [
7+
["OS=='win'", {
8+
"defines": [
9+
"WIN32_LEAN_AND_MEAN"
10+
],
11+
"libraries": [
12+
"Mincore.lib"
13+
]
14+
}]
15+
]
616
}
717
]
818
}

module.cc

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
#include <mutex>
44
#include <node.h>
55

6+
// Platform-specific includes for time functions
7+
#ifdef _WIN32
8+
#include <windows.h>
9+
#elif defined(__APPLE__)
10+
#include <time.h>
11+
#elif defined(__linux__)
12+
#include <time.h>
13+
#endif
14+
615
using namespace v8;
716
using namespace node;
817
using namespace std::chrono;
@@ -243,6 +252,31 @@ void RegisterThread(const FunctionCallbackInfo<Value> &args) {
243252
}
244253
}
245254

255+
// Cross-platform monotonic time function. Provides a monotonic clock that only
256+
// increases and does not tick when the system is suspended.
257+
steady_clock::time_point GetUnbiasedMonotonicTime() {
258+
#ifdef _WIN32
259+
// Windows: QueryUnbiasedInterruptTimePrecise returns time in 100-nanosecond
260+
// units
261+
ULONGLONG interrupt_time;
262+
QueryUnbiasedInterruptTimePrecise(&interrupt_time);
263+
// Convert from 100-nanosecond units to nanoseconds
264+
uint64_t time_ns = interrupt_time * 100;
265+
return steady_clock::time_point(nanoseconds(time_ns));
266+
#elif defined(__APPLE__)
267+
uint64_t time_ns = clock_gettime_nsec_np(CLOCK_UPTIME_RAW);
268+
return steady_clock::time_point(nanoseconds(time_ns));
269+
#elif defined(__linux__)
270+
struct timespec ts;
271+
clock_gettime(CLOCK_MONOTONIC, &ts);
272+
return steady_clock::time_point(seconds(ts.tv_sec) + nanoseconds(ts.tv_nsec));
273+
#else
274+
// Fallback for other platforms using steady_clock. Note: this will be
275+
// monotonic but is not gaurenteed to ignore time spent while suspended.
276+
return steady_clock::now();
277+
#endif
278+
}
279+
246280
// Function to track a thread and set its state
247281
void ThreadPoll(const FunctionCallbackInfo<Value> &args) {
248282
auto isolate = args.GetIsolate();
@@ -275,8 +309,8 @@ void ThreadPoll(const FunctionCallbackInfo<Value> &args) {
275309
if (disable_last_seen) {
276310
thread_info.last_seen = milliseconds::zero();
277311
} else {
278-
thread_info.last_seen =
279-
duration_cast<milliseconds>(system_clock::now().time_since_epoch());
312+
thread_info.last_seen = duration_cast<milliseconds>(
313+
GetUnbiasedMonotonicTime().time_since_epoch());
280314
}
281315
}
282316
}
@@ -286,8 +320,8 @@ void ThreadPoll(const FunctionCallbackInfo<Value> &args) {
286320
void GetThreadsLastSeen(const FunctionCallbackInfo<Value> &args) {
287321
Isolate *isolate = args.GetIsolate();
288322
Local<Object> result = Object::New(isolate);
289-
milliseconds now =
290-
duration_cast<milliseconds>(system_clock::now().time_since_epoch());
323+
milliseconds now = duration_cast<milliseconds>(
324+
GetUnbiasedMonotonicTime().time_since_epoch());
291325
{
292326
std::lock_guard<std::mutex> lock(threads_mutex);
293327
for (const auto &[thread_isolate, info] : threads) {

0 commit comments

Comments
 (0)