Skip to content

Commit 76b37d7

Browse files
Copilotcelonymire
andcommitted
Combine timing and memory stats functions, report CPU time as elapsedMs
- Merged getWin32ProcessTimes and getWin32MemoryStats into getWin32ProcessStats - Merged getLinuxProcessTimes and getLinuxMemoryStats into getLinuxProcessStats - Merged getDarwinProcessTimes and getDarwinMemoryStats into getDarwinProcessStats - Changed elapsedMs to report CPU time (user + system) instead of wall-clock time - Removed cpuMs field from all return values (redundant with elapsedMs) - Updated runtime.ts to use combined functions - All three platforms now return { elapsedMs, rss, peakRss } from stats function - waitForProcess now returns { elapsedMs, peakMemoryBytes, exitCode, timedOut, memoryLimitExceeded, stopped } Co-authored-by: celonymire <18388795+celonymire@users.noreply.github.com>
1 parent 626d355 commit 76b37d7

File tree

4 files changed

+82
-249
lines changed

4 files changed

+82
-249
lines changed

src/addons/darwin-process-monitor.cpp

Lines changed: 16 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class WaitForProcessWorker : public Napi::AsyncWorker {
2727
public:
2828
WaitForProcessWorker(Napi::Env &env, pid_t pid, uint32_t timeoutMs, uint64_t memoryLimitBytes)
2929
: Napi::AsyncWorker(env), pid_(pid), timeoutMs_(timeoutMs),
30-
memoryLimitBytes_(memoryLimitBytes), deferred_(env), elapsedMs_(0.0), cpuMs_(0.0), peakMemoryBytes_(0),
30+
memoryLimitBytes_(memoryLimitBytes), deferred_(env), elapsedMs_(0.0), peakMemoryBytes_(0),
3131
exitCode_(0), timedOut_(false), memoryLimitExceeded_(false), stopped_(false), errorMsg_("") {}
3232

3333
Napi::Promise GetPromise() { return deferred_.Promise(); }
@@ -112,12 +112,10 @@ class WaitForProcessWorker : public Napi::AsyncWorker {
112112
// Calculate elapsed wall-clock time
113113
elapsedUs = (endTime.tv_sec - startTime.tv_sec) * 1000000ULL +
114114
(endTime.tv_usec - startTime.tv_usec);
115-
elapsedMs_ = static_cast<double>(elapsedUs) / 1000.0;
116-
117-
// Calculate CPU time from rusage
115+
// Calculate elapsed CPU time from rusage (user + system)
118116
uint64_t cpuUs = (rusage.ru_utime.tv_sec + rusage.ru_stime.tv_sec) * 1000000ULL +
119117
(rusage.ru_utime.tv_usec + rusage.ru_stime.tv_usec);
120-
cpuMs_ = static_cast<double>(cpuUs) / 1000.0;
118+
elapsedMs_ = static_cast<double>(cpuUs) / 1000.0;
121119

122120
// Get peak memory (ru_maxrss is in bytes on macOS)
123121
peakMemoryBytes_ = static_cast<uint64_t>(rusage.ru_maxrss);
@@ -142,7 +140,6 @@ class WaitForProcessWorker : public Napi::AsyncWorker {
142140

143141
Napi::Object result = Napi::Object::New(env);
144142
result.Set("elapsedMs", Napi::Number::New(env, elapsedMs_));
145-
result.Set("cpuMs", Napi::Number::New(env, cpuMs_));
146143
result.Set("peakMemoryBytes", Napi::Number::New(env, static_cast<double>(peakMemoryBytes_)));
147144
result.Set("exitCode", Napi::Number::New(env, exitCode_));
148145
result.Set("timedOut", Napi::Boolean::New(env, timedOut_));
@@ -162,7 +159,6 @@ class WaitForProcessWorker : public Napi::AsyncWorker {
162159
uint64_t memoryLimitBytes_;
163160
Napi::Promise::Deferred deferred_;
164161
double elapsedMs_;
165-
double cpuMs_;
166162
uint64_t peakMemoryBytes_;
167163
int exitCode_;
168164
bool timedOut_;
@@ -182,12 +178,14 @@ class WaitForProcessWorker : public Napi::AsyncWorker {
182178
private:
183179
pid_t pid_;
184180
uint32_t timeoutMs_;
181+
uint64_t memoryLimitBytes_;
185182
Napi::Promise::Deferred deferred_;
186183
double elapsedMs_;
187-
double cpuMs_;
188184
uint64_t peakMemoryBytes_;
189185
int exitCode_;
190186
bool timedOut_;
187+
bool memoryLimitExceeded_;
188+
bool stopped_;
191189
std::string errorMsg_;
192190
};
193191

@@ -223,7 +221,8 @@ Napi::Value WaitForProcess(const Napi::CallbackInfo &info) {
223221
}
224222

225223
// Synchronous function to get current process times
226-
Napi::Value GetDarwinProcessTimes(const Napi::CallbackInfo &info) {
224+
// Combined function to get process stats (elapsed CPU time and memory)
225+
Napi::Value GetDarwinProcessStats(const Napi::CallbackInfo &info) {
227226
Napi::Env env = info.Env();
228227

229228
if (info.Length() < 1) {
@@ -255,84 +254,22 @@ Napi::Value GetDarwinProcessTimes(const Napi::CallbackInfo &info) {
255254
return env.Null();
256255
}
257256

258-
// Get basic process info for start time
259-
struct proc_bsdinfo bsdinfo;
260-
ret = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsdinfo, sizeof(bsdinfo));
261-
262-
if (ret != sizeof(bsdinfo)) {
263-
Napi::Error::New(env, "Failed to get process BSD info")
264-
.ThrowAsJavaScriptException();
265-
return env.Null();
266-
}
267-
268-
// Get current time
269-
struct timeval now;
270-
gettimeofday(&now, nullptr);
271-
uint64_t nowMicros = static_cast<uint64_t>(now.tv_sec) * 1000000ULL +
272-
static_cast<uint64_t>(now.tv_usec);
273-
274-
// Calculate process start time in microseconds
275-
uint64_t startMicros = static_cast<uint64_t>(bsdinfo.pbi_start_tvsec) * 1000000ULL +
276-
static_cast<uint64_t>(bsdinfo.pbi_start_tvusec);
277-
278-
// Calculate elapsed wall-clock time
279-
uint64_t elapsedMicros = nowMicros - startMicros;
280-
double elapsedMs = static_cast<double>(elapsedMicros) / 1000.0;
281-
282-
// Calculate total CPU time (user + system)
257+
// Calculate elapsed CPU time (user + system)
283258
// taskinfo times are in nanoseconds
284259
uint64_t userTimeNanos = taskinfo.pti_total_user;
285260
uint64_t systemTimeNanos = taskinfo.pti_total_system;
286261
uint64_t cpuNanos = userTimeNanos + systemTimeNanos;
287-
double cpuMs = static_cast<double>(cpuNanos) / 1000000.0;
288-
289-
Napi::Object result = Napi::Object::New(env);
290-
result.Set("elapsedMs", Napi::Number::New(env, elapsedMs));
291-
result.Set("cpuMs", Napi::Number::New(env, cpuMs));
292-
return result;
293-
}
294-
295-
// Synchronous function to get current memory stats
296-
Napi::Value GetDarwinMemoryStats(const Napi::CallbackInfo &info) {
297-
Napi::Env env = info.Env();
298-
299-
if (info.Length() < 1) {
300-
Napi::TypeError::New(env, "PID argument is required")
301-
.ThrowAsJavaScriptException();
302-
return env.Null();
303-
}
304-
305-
if (!info[0].IsNumber()) {
306-
Napi::TypeError::New(env, "PID must be a number")
307-
.ThrowAsJavaScriptException();
308-
return env.Null();
309-
}
310-
311-
int32_t pid = info[0].As<Napi::Number>().Int32Value();
312-
313-
if (pid < 1) {
314-
Napi::RangeError::New(env, "PID must be positive")
315-
.ThrowAsJavaScriptException();
316-
return env.Null();
317-
}
318-
319-
// Get process task info which includes memory stats
320-
struct proc_taskinfo taskinfo;
321-
int ret = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskinfo, sizeof(taskinfo));
322-
323-
if (ret != sizeof(taskinfo)) {
324-
Napi::Error::New(env, "Failed to get process info (process may have exited)")
325-
.ThrowAsJavaScriptException();
326-
return env.Null();
327-
}
262+
double elapsedMs = static_cast<double>(cpuNanos) / 1000000.0;
328263

264+
// Get memory stats
329265
// pti_resident_size: current resident memory in bytes
330266
// Note: macOS doesn't provide a direct "peak RSS" in proc_pidinfo like Linux/Windows
331267
// We would need to track it ourselves or use different APIs
332268
// For now, return current RSS as both values
333269
uint64_t rssBytes = taskinfo.pti_resident_size;
334270

335271
Napi::Object result = Napi::Object::New(env);
272+
result.Set("elapsedMs", Napi::Number::New(env, elapsedMs));
336273
result.Set("rss", Napi::Number::New(env, static_cast<double>(rssBytes)));
337274
result.Set("peakRss", Napi::Number::New(env, static_cast<double>(rssBytes)));
338275
return result;
@@ -341,14 +278,12 @@ Napi::Value GetDarwinMemoryStats(const Napi::CallbackInfo &info) {
341278
Napi::Object Init(Napi::Env env, Napi::Object exports) {
342279
exports.Set("waitForProcess",
343280
Napi::Function::New(env, WaitForProcess, "waitForProcess"));
344-
exports.Set("getDarwinProcessTimes",
345-
Napi::Function::New(env, GetDarwinProcessTimes,
346-
"getDarwinProcessTimes"));
347-
exports.Set("getDarwinMemoryStats",
348-
Napi::Function::New(env, GetDarwinMemoryStats,
349-
"getDarwinMemoryStats"));
281+
exports.Set("getDarwinProcessStats",
282+
Napi::Function::New(env, GetDarwinProcessStats,
283+
"getDarwinProcessStats"));
350284
return exports;
351285
}
286+
}
352287

353288
} // namespace
354289

src/addons/linux-process-monitor.cpp

Lines changed: 32 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class WaitForProcessWorker : public Napi::AsyncWorker {
2525
public:
2626
WaitForProcessWorker(Napi::Env &env, pid_t pid, uint32_t timeoutMs, uint64_t memoryLimitBytes)
2727
: Napi::AsyncWorker(env), pid_(pid), timeoutMs_(timeoutMs),
28-
memoryLimitBytes_(memoryLimitBytes), deferred_(env), elapsedMs_(0.0), cpuMs_(0.0), peakMemoryBytes_(0),
28+
memoryLimitBytes_(memoryLimitBytes), deferred_(env), elapsedMs_(0.0), peakMemoryBytes_(0),
2929
exitCode_(0), timedOut_(false), memoryLimitExceeded_(false), stopped_(false), errorMsg_("") {}
3030

3131
Napi::Promise GetPromise() { return deferred_.Promise(); }
@@ -124,15 +124,10 @@ class WaitForProcessWorker : public Napi::AsyncWorker {
124124
struct timeval endTime;
125125
gettimeofday(&endTime, nullptr);
126126

127-
// Calculate elapsed wall-clock time
128-
elapsedUs = (endTime.tv_sec - startTime.tv_sec) * 1000000ULL +
129-
(endTime.tv_usec - startTime.tv_usec);
130-
elapsedMs_ = static_cast<double>(elapsedUs) / 1000.0;
131-
132-
// Calculate CPU time from rusage
127+
// Calculate elapsed CPU time from rusage (user + system)
133128
uint64_t cpuUs = (rusage.ru_utime.tv_sec + rusage.ru_stime.tv_sec) * 1000000ULL +
134129
(rusage.ru_utime.tv_usec + rusage.ru_stime.tv_usec);
135-
cpuMs_ = static_cast<double>(cpuUs) / 1000.0;
130+
elapsedMs_ = static_cast<double>(cpuUs) / 1000.0;
136131

137132
// Get peak memory (in kilobytes on Linux, convert to bytes)
138133
peakMemoryBytes_ = static_cast<uint64_t>(rusage.ru_maxrss) * 1024ULL;
@@ -157,7 +152,6 @@ class WaitForProcessWorker : public Napi::AsyncWorker {
157152

158153
Napi::Object result = Napi::Object::New(env);
159154
result.Set("elapsedMs", Napi::Number::New(env, elapsedMs_));
160-
result.Set("cpuMs", Napi::Number::New(env, cpuMs_));
161155
result.Set("peakMemoryBytes", Napi::Number::New(env, static_cast<double>(peakMemoryBytes_)));
162156
result.Set("exitCode", Napi::Number::New(env, exitCode_));
163157
result.Set("timedOut", Napi::Boolean::New(env, timedOut_));
@@ -177,7 +171,6 @@ class WaitForProcessWorker : public Napi::AsyncWorker {
177171
uint64_t memoryLimitBytes_;
178172
Napi::Promise::Deferred deferred_;
179173
double elapsedMs_;
180-
double cpuMs_;
181174
uint64_t peakMemoryBytes_;
182175
int exitCode_;
183176
bool timedOut_;
@@ -302,66 +295,6 @@ bool ReadSystemUptime(double &uptimeSeconds, std::string &err) {
302295
return true;
303296
}
304297

305-
// Keep the synchronous version for compatibility
306-
Napi::Value GetLinuxProcessTimes(const Napi::CallbackInfo &info) {
307-
Napi::Env env = info.Env();
308-
309-
if (info.Length() < 1) {
310-
Napi::TypeError::New(env, "PID argument is required")
311-
.ThrowAsJavaScriptException();
312-
return env.Null();
313-
}
314-
if (!info[0].IsNumber()) {
315-
Napi::TypeError::New(env, "PID must be a number")
316-
.ThrowAsJavaScriptException();
317-
return env.Null();
318-
}
319-
320-
uint32_t pid = info[0].As<Napi::Number>().Uint32Value();
321-
322-
if (pid < 1 || pid > 4194304) {
323-
Napi::RangeError::New(env, "PID is out of range")
324-
.ThrowAsJavaScriptException();
325-
return env.Null();
326-
}
327-
328-
uint64_t startTimeJiffies = 0;
329-
uint64_t utimeJiffies = 0;
330-
uint64_t stimeJiffies = 0;
331-
std::string err;
332-
if (!ReadProcStat(pid, startTimeJiffies, utimeJiffies, stimeJiffies, err)) {
333-
Napi::Error::New(env, err).ThrowAsJavaScriptException();
334-
return env.Null();
335-
}
336-
337-
double systemUptimeSeconds = 0.0;
338-
if (!ReadSystemUptime(systemUptimeSeconds, err)) {
339-
Napi::Error::New(env, err).ThrowAsJavaScriptException();
340-
return env.Null();
341-
}
342-
343-
long clockTicksPerSecond = sysconf(_SC_CLK_TCK);
344-
if (clockTicksPerSecond <= 0) {
345-
Napi::Error::New(env, "Failed to get _SC_CLK_TCK")
346-
.ThrowAsJavaScriptException();
347-
return env.Null();
348-
}
349-
350-
double processStartSeconds =
351-
static_cast<double>(startTimeJiffies) / clockTicksPerSecond;
352-
double elapsedSeconds = systemUptimeSeconds - processStartSeconds;
353-
double elapsedMs = elapsedSeconds * 1000.0;
354-
355-
double cpuSeconds = static_cast<double>(utimeJiffies + stimeJiffies) /
356-
clockTicksPerSecond;
357-
double cpuMs = cpuSeconds * 1000.0;
358-
359-
Napi::Object result = Napi::Object::New(env);
360-
result.Set("elapsedMs", Napi::Number::New(env, elapsedMs));
361-
result.Set("cpuMs", Napi::Number::New(env, cpuMs));
362-
return result;
363-
}
364-
365298
// Helper to parse memory lines from /proc/<pid>/status
366299
bool ParseKbLineToBytes(const char *line, const char *prefix,
367300
uint64_t &outBytes) {
@@ -447,7 +380,8 @@ bool ReadProcStatusMemory(uint32_t pid, uint64_t &rssBytes, uint64_t &peakRssByt
447380
return true;
448381
}
449382

450-
Napi::Value GetLinuxMemoryStats(const Napi::CallbackInfo &info) {
383+
// Combined function to get process stats (elapsed CPU time and memory)
384+
Napi::Value GetLinuxProcessStats(const Napi::CallbackInfo &info) {
451385
Napi::Env env = info.Env();
452386

453387
if (info.Length() < 1) {
@@ -469,15 +403,38 @@ Napi::Value GetLinuxMemoryStats(const Napi::CallbackInfo &info) {
469403
return env.Null();
470404
}
471405

406+
// Get CPU times from /proc/<pid>/stat
407+
uint64_t startTimeJiffies = 0;
408+
uint64_t utimeJiffies = 0;
409+
uint64_t stimeJiffies = 0;
410+
std::string err;
411+
if (!ReadProcStat(pid, startTimeJiffies, utimeJiffies, stimeJiffies, err)) {
412+
Napi::Error::New(env, err).ThrowAsJavaScriptException();
413+
return env.Null();
414+
}
415+
416+
long clockTicksPerSecond = sysconf(_SC_CLK_TCK);
417+
if (clockTicksPerSecond <= 0) {
418+
Napi::Error::New(env, "Failed to get _SC_CLK_TCK")
419+
.ThrowAsJavaScriptException();
420+
return env.Null();
421+
}
422+
423+
// Calculate elapsed CPU time (user + system)
424+
double cpuSeconds = static_cast<double>(utimeJiffies + stimeJiffies) /
425+
clockTicksPerSecond;
426+
double elapsedMs = cpuSeconds * 1000.0;
427+
428+
// Get memory stats from /proc/<pid>/status
472429
uint64_t rssBytes = 0;
473430
uint64_t peakRssBytes = 0;
474-
std::string err;
475431
if (!ReadProcStatusMemory(pid, rssBytes, peakRssBytes, err)) {
476432
Napi::Error::New(env, err).ThrowAsJavaScriptException();
477433
return env.Null();
478434
}
479435

480436
Napi::Object result = Napi::Object::New(env);
437+
result.Set("elapsedMs", Napi::Number::New(env, elapsedMs));
481438
result.Set("rss", Napi::Number::New(env, static_cast<double>(rssBytes)));
482439
result.Set("peakRss",
483440
Napi::Number::New(env, static_cast<double>(peakRssBytes)));
@@ -487,12 +444,9 @@ Napi::Value GetLinuxMemoryStats(const Napi::CallbackInfo &info) {
487444
Napi::Object Init(Napi::Env env, Napi::Object exports) {
488445
exports.Set("waitForProcess",
489446
Napi::Function::New(env, WaitForProcess, "waitForProcess"));
490-
exports.Set("getLinuxProcessTimes",
491-
Napi::Function::New(env, GetLinuxProcessTimes,
492-
"getLinuxProcessTimes"));
493-
exports.Set("getLinuxMemoryStats",
494-
Napi::Function::New(env, GetLinuxMemoryStats,
495-
"getLinuxMemoryStats"));
447+
exports.Set("getLinuxProcessStats",
448+
Napi::Function::New(env, GetLinuxProcessStats,
449+
"getLinuxProcessStats"));
496450
return exports;
497451
}
498452

0 commit comments

Comments
 (0)