From 433b304408a1eb0aaefd4f08943fcaa6a9e71be5 Mon Sep 17 00:00:00 2001 From: Matthew Nicholson Date: Thu, 24 Jul 2025 21:35:23 -0400 Subject: [PATCH 1/2] 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. --- module.cc | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/module.cc b/module.cc index 1056f81..fbbb95d 100644 --- a/module.cc +++ b/module.cc @@ -3,6 +3,16 @@ #include #include +// Platform-specific includes for time functions +#ifdef _WIN32 +#include +#include +#elif defined(__APPLE__) +#include +#elif defined(__linux__) +#include +#endif + using namespace v8; using namespace node; using namespace std::chrono; @@ -243,6 +253,31 @@ void RegisterThread(const FunctionCallbackInfo &args) { } } +// Cross-platform monotonic time function. Provides a monotonic clock that only +// increases and does not tick when the system is suspended. +steady_clock::time_point GetUnbiasedMonotonicTime() { +#ifdef _WIN32 + // Windows: QueryUnbiasedInterruptTimePrecise returns time in 100-nanosecond + // units + ULONGLONG interrupt_time; + QueryUnbiasedInterruptTimePrecise(&interrupt_time); + // Convert from 100-nanosecond units to nanoseconds + uint64_t time_ns = interrupt_time * 100; + return steady_clock::time_point(nanoseconds(time_ns)); +#elif defined(__APPLE__) + uint64_t time_ns = clock_gettime_nsec_np(CLOCK_UPTIME_RAW); + return steady_clock::time_point(nanoseconds(time_ns)); +#elif defined(__linux__) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return steady_clock::time_point(seconds(ts.tv_sec) + nanoseconds(ts.tv_nsec)); +#else + // Fallback for other platforms using steady_clock. Note: this will be + // monotonic but is not gaurenteed to ignore time spent while suspended. + return steady_clock::now(); +#endif +} + // Function to track a thread and set its state void ThreadPoll(const FunctionCallbackInfo &args) { auto isolate = args.GetIsolate(); @@ -275,8 +310,8 @@ void ThreadPoll(const FunctionCallbackInfo &args) { if (disable_last_seen) { thread_info.last_seen = milliseconds::zero(); } else { - thread_info.last_seen = - duration_cast(system_clock::now().time_since_epoch()); + thread_info.last_seen = duration_cast( + GetUnbiasedMonotonicTime().time_since_epoch()); } } } @@ -286,8 +321,8 @@ void ThreadPoll(const FunctionCallbackInfo &args) { void GetThreadsLastSeen(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); Local result = Object::New(isolate); - milliseconds now = - duration_cast(system_clock::now().time_since_epoch()); + milliseconds now = duration_cast( + GetUnbiasedMonotonicTime().time_since_epoch()); { std::lock_guard lock(threads_mutex); for (const auto &[thread_isolate, info] : threads) { From bf868278b768071e8a3bdce7530bdf640e89a93d Mon Sep 17 00:00:00 2001 From: Matthew Nicholson Date: Mon, 28 Jul 2025 12:37:57 -0700 Subject: [PATCH 2/2] fix windows build --- binding.gyp | 12 +++++++++++- module.cc | 1 - 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/binding.gyp b/binding.gyp index 7a65e50..7cfc914 100644 --- a/binding.gyp +++ b/binding.gyp @@ -2,7 +2,17 @@ "targets": [ { "target_name": "stack-trace", - "sources": [ "module.cc" ] + "sources": [ "module.cc" ], + "conditions": [ + ["OS=='win'", { + "defines": [ + "WIN32_LEAN_AND_MEAN" + ], + "libraries": [ + "Mincore.lib" + ] + }] + ] } ] } diff --git a/module.cc b/module.cc index fbbb95d..56582d6 100644 --- a/module.cc +++ b/module.cc @@ -5,7 +5,6 @@ // Platform-specific includes for time functions #ifdef _WIN32 -#include #include #elif defined(__APPLE__) #include