Compare commits

...

9 Commits

Author SHA1 Message Date
Claude Bot
93a5c26327 Address code review feedback for process.threadCpuUsage
- Change #ifdef to #if defined(RUSAGE_THREAD) for consistency
- Update hardcoded libuv version from 1.48.0 to 1.51.0 to match vendored version

This ensures process.versions.uv correctly reports 1.51.0, which is the actual
version bundled in vendor/node/deps/uv that includes uv_getrusage_thread
(added in libuv 1.50.0).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 10:23:23 +00:00
Claude Bot
ed6b0aeb61 Merge branch 'main' into claude/implement-process-threadCpuUsage 2025-11-03 10:19:35 +00:00
Claude Bot
39d2c3a05e Use #elifdef for RUSAGE_LWP
Since we're on C++23, we can use the cleaner #elifdef directive
instead of #elif defined.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 19:40:08 +00:00
Claude Bot
78693838c4 Address code review feedback
- Add totalMicroseconds helper function for cleaner duration calculations
- Rename 'err' to 'status' for getrusage/uv_getrusage_thread return values
- Change #if defined to #ifdef for RUSAGE_THREAD (more concise)

The helper function uses std::chrono to correctly convert seconds and
microseconds to total microseconds, making the code more readable and
less error-prone.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 19:33:17 +00:00
Claude Bot
ec9577262e Gate libuv path to Windows and add fallback for unsupported platforms
- Change #else to #elif OS(WINDOWS) to explicitly guard libuv code
- Add final #else with ENOTSUP error for platforms without support
- This matches the pattern used in Process_functionCpuUsage
- Prevents compilation failures on non-Linux/non-Darwin/non-Windows targets (e.g., BSD)

Note: Bundled libuv is v1.51.0 which includes uv_getrusage_thread (added in v1.50.0)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 06:33:31 +00:00
Claude Bot
980294f711 Use throwSystemError for macOS thread_info errors
Since kern_return_t is defined as int, we can use throwSystemError
directly which provides better error messages and matches the pattern
used for other system calls.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 06:25:54 +00:00
Claude Bot
0818698e4f Fix macOS compilation error in threadCpuUsage
Use throwScope.throwException instead of throwException to match
the correct function signature for JSC exception throwing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 06:22:47 +00:00
autofix-ci[bot]
5e45fbbfad [autofix.ci] apply automated fixes 2025-10-21 06:18:46 +00:00
Claude Bot
c5f9d2f758 Add process.threadCpuUsage() implementation
Implements process.threadCpuUsage() to return the current thread's CPU usage, matching Node.js behavior.

Platform-specific implementations:
- macOS: Uses mach_thread_self() and thread_info()
- Linux: Uses getrusage(RUSAGE_THREAD) or getrusage(RUSAGE_LWP) as fallback
- Windows: Uses libuv's uv_getrusage_thread()

Returns an object with user and system CPU time in microseconds, and supports an optional previous value parameter to calculate the difference.

Fixes issue #23890

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 06:16:35 +00:00
2 changed files with 207 additions and 1 deletions

View File

@@ -232,7 +232,7 @@ static JSValue constructVersions(VM& vm, JSObject* processObject)
#if OS(WINDOWS)
object->putDirect(vm, JSC::Identifier::fromString(vm, "uv"_s), JSValue(JSC::jsOwnedString(vm, String::fromLatin1(uv_version_string()))), 0);
#else
object->putDirect(vm, JSC::Identifier::fromString(vm, "uv"_s), JSValue(JSC::jsOwnedString(vm, String("1.48.0"_s))), 0);
object->putDirect(vm, JSC::Identifier::fromString(vm, "uv"_s), JSValue(JSC::jsOwnedString(vm, String("1.51.0"_s))), 0);
#endif
object->putDirect(vm, JSC::Identifier::fromString(vm, "napi"_s), JSValue(JSC::jsOwnedString(vm, String("10"_s))), 0);
@@ -3120,6 +3120,144 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionCpuUsage, (JSC::JSGlobalObject * global
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(result));
}
template<typename Sec, typename Usec>
double totalMicroseconds(Sec seconds, Usec microseconds)
{
std::chrono::microseconds total = std::chrono::seconds(seconds) + std::chrono::microseconds(microseconds);
return total.count();
}
JSC_DEFINE_HOST_FUNCTION(Process_functionThreadCpuUsage, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
{
auto& vm = JSC::getVM(globalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
#if OS(DARWIN)
mach_msg_type_number_t count;
thread_basic_info_data_t info;
kern_return_t kr;
thread_t thread;
thread = mach_thread_self();
count = THREAD_BASIC_INFO_COUNT;
kr = thread_info(thread,
THREAD_BASIC_INFO,
(thread_info_t)&info,
&count);
if (kr != KERN_SUCCESS) {
mach_port_deallocate(mach_task_self(), thread);
throwSystemError(throwScope, globalObject, "Failed to get thread CPU usage"_s, "thread_info"_s, kr);
return {};
}
auto* process = getProcessObject(globalObject, callFrame->thisValue());
Structure* cpuUsageStructure = process->cpuUsageStructure();
double user = totalMicroseconds(info.user_time.seconds, info.user_time.microseconds);
double system = totalMicroseconds(info.system_time.seconds, info.system_time.microseconds);
mach_port_deallocate(mach_task_self(), thread);
#elif OS(LINUX)
struct rusage rusage;
#if defined(RUSAGE_THREAD)
int status = getrusage(RUSAGE_THREAD, &rusage);
#elifdef RUSAGE_LWP
int status = getrusage(RUSAGE_LWP, &rusage);
#else
throwSystemError(throwScope, globalObject, "Thread CPU usage not supported on this platform"_s, "getrusage"_s, ENOTSUP);
return {};
#endif
if (status != 0) {
throwSystemError(throwScope, globalObject, "Failed to get thread CPU usage"_s, "getrusage"_s, errno);
return {};
}
auto* process = getProcessObject(globalObject, callFrame->thisValue());
Structure* cpuUsageStructure = process->cpuUsageStructure();
double user = totalMicroseconds(rusage.ru_utime.tv_sec, rusage.ru_utime.tv_usec);
double system = totalMicroseconds(rusage.ru_stime.tv_sec, rusage.ru_stime.tv_usec);
#elif OS(WINDOWS)
// Windows: use libuv
uv_rusage_t rusage;
int status = uv_getrusage_thread(&rusage);
if (status) {
throwSystemError(throwScope, globalObject, "Failed to get thread CPU usage"_s, "uv_getrusage_thread"_s, status);
return {};
}
auto* process = getProcessObject(globalObject, callFrame->thisValue());
Structure* cpuUsageStructure = process->cpuUsageStructure();
double user = totalMicroseconds(rusage.ru_utime.tv_sec, rusage.ru_utime.tv_usec);
double system = totalMicroseconds(rusage.ru_stime.tv_sec, rusage.ru_stime.tv_usec);
#else
// Other platforms: not supported yet
throwSystemError(throwScope, globalObject, "Thread CPU usage not supported on this platform"_s, "threadCpuUsage"_s, ENOTSUP);
return {};
#endif
if (callFrame->argumentCount() > 0) {
JSValue comparatorValue = callFrame->argument(0);
if (!comparatorValue.isUndefined()) {
JSC::JSObject* comparator = comparatorValue.getObject();
if (!comparator) [[unlikely]] {
return Bun::ERR::INVALID_ARG_TYPE(throwScope, globalObject, "prevValue"_s, "object"_s, comparatorValue);
}
JSValue userValue;
JSValue systemValue;
if (comparator->structureID() == cpuUsageStructure->id()) [[likely]] {
userValue = comparator->getDirect(0);
systemValue = comparator->getDirect(1);
} else {
userValue = comparator->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "user"_s));
RETURN_IF_EXCEPTION(throwScope, {});
if (userValue.isEmpty()) userValue = jsUndefined();
systemValue = comparator->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "system"_s));
RETURN_IF_EXCEPTION(throwScope, {});
if (systemValue.isEmpty()) systemValue = jsUndefined();
}
Bun::V::validateNumber(throwScope, globalObject, userValue, "prevValue.user"_s, jsUndefined(), jsUndefined());
RETURN_IF_EXCEPTION(throwScope, {});
Bun::V::validateNumber(throwScope, globalObject, systemValue, "prevValue.system"_s, jsUndefined(), jsUndefined());
RETURN_IF_EXCEPTION(throwScope, {});
double userComparator = userValue.toNumber(globalObject);
RETURN_IF_EXCEPTION(throwScope, {});
double systemComparator = systemValue.toNumber(globalObject);
RETURN_IF_EXCEPTION(throwScope, {});
if (!(userComparator >= 0 && userComparator <= JSC::maxSafeInteger())) {
return Bun::ERR::INVALID_ARG_VALUE_RangeError(throwScope, globalObject, "prevValue.user"_s, userValue, "is invalid"_s);
}
if (!(systemComparator >= 0 && systemComparator <= JSC::maxSafeInteger())) {
return Bun::ERR::INVALID_ARG_VALUE_RangeError(throwScope, globalObject, "prevValue.system"_s, systemValue, "is invalid"_s);
}
user -= userComparator;
system -= systemComparator;
}
}
JSC::JSObject* result = JSC::constructEmptyObject(vm, cpuUsageStructure);
RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined()));
result->putDirectOffset(vm, 0, JSC::jsNumber(user));
result->putDirectOffset(vm, 1, JSC::jsNumber(system));
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(result));
}
extern "C" int getRSS(size_t* rss)
{
#if defined(__APPLE__)
@@ -3947,6 +4085,7 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu
stderr constructStderr PropertyCallback
stdin constructStdin PropertyCallback
stdout constructStdout PropertyCallback
threadCpuUsage Process_functionThreadCpuUsage Function 1
throwDeprecation processThrowDeprecation CustomAccessor
title processTitle CustomAccessor
umask Process_functionUmask Function 1

View File

@@ -0,0 +1,67 @@
import { expect, test } from "bun:test";
test("process.threadCpuUsage returns valid result", () => {
const result = process.threadCpuUsage();
expect(result).toHaveProperty("user");
expect(result).toHaveProperty("system");
expect(typeof result.user).toBe("number");
expect(typeof result.system).toBe("number");
expect(result.user).toBeGreaterThanOrEqual(0);
expect(result.system).toBeGreaterThanOrEqual(0);
expect(Number.isFinite(result.user)).toBe(true);
expect(Number.isFinite(result.system)).toBe(true);
});
test("process.threadCpuUsage with previous value", () => {
const start = process.threadCpuUsage();
// Do some CPU work
for (let i = 0; i < 100000; i++) {
Math.sqrt(i);
}
const diff = process.threadCpuUsage(start);
expect(diff).toHaveProperty("user");
expect(diff).toHaveProperty("system");
expect(diff.user).toBeGreaterThanOrEqual(0);
expect(diff.system).toBeGreaterThanOrEqual(0);
});
test("process.threadCpuUsage increases over time", () => {
const usage1 = process.threadCpuUsage();
// Do some CPU work
for (let i = 0; i < 100000; i++) {
Math.sqrt(i);
}
const usage2 = process.threadCpuUsage();
expect(usage2.user).toBeGreaterThanOrEqual(usage1.user);
expect(usage2.system).toBeGreaterThanOrEqual(usage1.system);
});
test("process.threadCpuUsage throws on invalid argument type", () => {
expect(() => process.threadCpuUsage(123 as any)).toThrow();
expect(() => process.threadCpuUsage("invalid" as any)).toThrow();
});
test("process.threadCpuUsage throws on invalid prevValue.user", () => {
expect(() => process.threadCpuUsage({} as any)).toThrow();
expect(() => process.threadCpuUsage({ user: "a" } as any)).toThrow();
expect(() => process.threadCpuUsage({ user: null } as any)).toThrow();
});
test("process.threadCpuUsage throws on invalid prevValue.system", () => {
expect(() => process.threadCpuUsage({ user: 3, system: "b" } as any)).toThrow();
expect(() => process.threadCpuUsage({ user: 3, system: null } as any)).toThrow();
});
test("process.threadCpuUsage throws on out-of-range values", () => {
expect(() => process.threadCpuUsage({ user: -1, system: 2 })).toThrow();
expect(() => process.threadCpuUsage({ user: Number.POSITIVE_INFINITY, system: 4 })).toThrow();
expect(() => process.threadCpuUsage({ user: 3, system: -2 })).toThrow();
expect(() => process.threadCpuUsage({ user: 5, system: Number.NEGATIVE_INFINITY })).toThrow();
});