aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-07-09 21:50:19 -0700
committerGravatar GitHub <noreply@github.com> 2023-07-09 21:50:19 -0700
commit2f5e4fffe9554fcc7afa6980b3af6b33bc3a3a5e (patch)
treec1a58a1ed2ebee1aaf658d48c1e62c400639e961
parentca42c820d29400d8bd93f493064db029f6d4420d (diff)
downloadbun-2f5e4fffe9554fcc7afa6980b3af6b33bc3a3a5e.tar.gz
bun-2f5e4fffe9554fcc7afa6980b3af6b33bc3a3a5e.tar.zst
bun-2f5e4fffe9554fcc7afa6980b3af6b33bc3a3a5e.zip
Implement process.memoryUsage() and process.cpuUsage() (#3586)
* Implement process.memoryUsage() and process.cpuUsage() * Avoid mi_process_info * Update bench * Update Process.cpp * fixup * More tests + linux fixup * Skip it for now since it seems less accurate --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--bench/snippets/process-info.mjs33
-rw-r--r--src/bun.js/bindings/Process.cpp388
-rw-r--r--src/bun.js/bindings/Process.h18
-rw-r--r--src/bun.js/bindings/Process.lut.h97
-rw-r--r--src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h1
-rw-r--r--src/bun.js/bindings/webcore/DOMIsoSubspaces.h1
-rw-r--r--test/js/node/process/process.test.js142
7 files changed, 620 insertions, 60 deletions
diff --git a/bench/snippets/process-info.mjs b/bench/snippets/process-info.mjs
new file mode 100644
index 000000000..0366472e5
--- /dev/null
+++ b/bench/snippets/process-info.mjs
@@ -0,0 +1,33 @@
+import { bench, run } from "./runner.mjs";
+import { performance } from "perf_hooks";
+
+bench("process.memoryUsage()", () => {
+ process.memoryUsage();
+});
+
+bench("process.memoryUsage.rss()", () => {
+ process.memoryUsage.rss();
+});
+
+bench("process.cpuUsage()", () => {
+ process.cpuUsage();
+});
+
+const init = process.cpuUsage();
+bench("process.cpuUsage(delta)", () => {
+ process.cpuUsage(init);
+});
+
+bench("performance.now()", () => {
+ performance.now();
+});
+
+bench("process.hrtime()", () => {
+ process.hrtime();
+});
+
+bench("process.hrtime.bigint()", () => {
+ process.hrtime.bigint();
+});
+
+await run();
diff --git a/src/bun.js/bindings/Process.cpp b/src/bun.js/bindings/Process.cpp
index 8d94594cc..7d7bdd982 100644
--- a/src/bun.js/bindings/Process.cpp
+++ b/src/bun.js/bindings/Process.cpp
@@ -12,8 +12,24 @@
#include "ZigConsoleClient.h"
#include <JavaScriptCore/GetterSetter.h>
#include <JavaScriptCore/JSSet.h>
+#include <JavaScriptCore/LazyProperty.h>
+#include <JavaScriptCore/LazyPropertyInlines.h>
+#include <JavaScriptCore/VMTrapsInlines.h>
+
#pragma mark - Node.js Process
+#if defined(__APPLE__)
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#endif
+
+#if defined(__linux__)
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#endif
+
#if !defined(_MSC_VER)
#include <unistd.h> // setuid, getuid
#endif
@@ -335,9 +351,12 @@ extern "C" uint64_t Bun__readOriginTimer(void*);
JSC_DEFINE_HOST_FUNCTION(Process_functionHRTime,
(JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame))
{
+
Zig::GlobalObject* globalObject
= reinterpret_cast<Zig::GlobalObject*>(globalObject_);
auto& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
uint64_t time = Bun__readOriginTimer(globalObject->bunVM());
int64_t seconds = static_cast<int64_t>(time / 1000000000);
int64_t nanoseconds = time % 1000000000;
@@ -346,7 +365,6 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionHRTime,
JSC::JSValue arg0 = callFrame->uncheckedArgument(0);
if (!arg0.isUndefinedOrNull()) {
JSArray* relativeArray = JSC::jsDynamicCast<JSC::JSArray*>(arg0);
- auto throwScope = DECLARE_THROW_SCOPE(vm);
if ((!relativeArray && !arg0.isUndefinedOrNull()) || relativeArray->length() < 2) {
JSC::throwTypeError(globalObject, throwScope, "hrtime() argument must be an array or undefined"_s);
return JSC::JSValue::encode(JSC::JSValue {});
@@ -366,14 +384,28 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionHRTime,
seconds--;
nanoseconds += 1000000000;
}
- throwScope.release();
}
}
- auto* array = JSArray::create(vm, globalObject->originalArrayStructureForIndexingType(ArrayWithContiguous), 2);
- array->setIndexQuickly(vm, 0, JSC::jsNumber(seconds));
- array->setIndexQuickly(vm, 1, JSC::jsNumber(nanoseconds));
- return JSC::JSValue::encode(JSC::JSValue(array));
+ JSC::JSArray* array = nullptr;
+ {
+ JSC::ObjectInitializationScope initializationScope(vm);
+ if ((array = JSC::JSArray::tryCreateUninitializedRestricted(
+ initializationScope, nullptr,
+ globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
+ 2))) {
+
+ array->initializeIndex(initializationScope, 0, JSC::jsNumber(seconds));
+ array->initializeIndex(initializationScope, 1, JSC::jsNumber(nanoseconds));
+ }
+ }
+
+ if (UNLIKELY(!array)) {
+ JSC::throwOutOfMemoryError(globalObject, throwScope);
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(array));
}
JSC_DEFINE_HOST_FUNCTION(Process_functionHRTimeBigInt,
@@ -680,10 +712,10 @@ static JSValue constructProcessHrtimeObject(VM& vm, JSObject* processObject)
{
auto* globalObject = processObject->globalObject();
JSC::JSFunction* hrtime = JSC::JSFunction::create(vm, globalObject, 0,
- MAKE_STATIC_STRING_IMPL("hrtime"), Process_functionHRTime, ImplementationVisibility::Public);
+ String("hrtime"_s), Process_functionHRTime, ImplementationVisibility::Public);
JSC::JSFunction* hrtimeBigInt = JSC::JSFunction::create(vm, globalObject, 0,
- MAKE_STATIC_STRING_IMPL("bigint"), Process_functionHRTimeBigInt, ImplementationVisibility::Public);
+ String("bigint"_s), Process_functionHRTimeBigInt, ImplementationVisibility::Public);
hrtime->putDirect(vm, JSC::Identifier::fromString(vm, "bigint"_s), hrtimeBigInt);
@@ -945,6 +977,317 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionReallyExit, (JSGlobalObject * globalObj
__builtin_unreachable();
}
+template<typename Visitor>
+void Process::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ Process* thisObject = jsCast<Process*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+ thisObject->cpuUsageStructure.visit(visitor);
+ thisObject->memoryUsageStructure.visit(visitor);
+}
+
+DEFINE_VISIT_CHILDREN(Process);
+
+static Structure* constructCPUUsageStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ JSC::Structure* structure = globalObject->structureCache().emptyObjectStructureForPrototype(globalObject, globalObject->objectPrototype(), 2);
+ PropertyOffset offset;
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "user"_s),
+ 0,
+ offset);
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "system"_s),
+ 0,
+ offset);
+ return structure;
+}
+static Structure* constructMemoryUsageStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ JSC::Structure* structure = globalObject->structureCache().emptyObjectStructureForPrototype(globalObject, globalObject->objectPrototype(), 5);
+ PropertyOffset offset;
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "rss"_s),
+ 0,
+ offset);
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "heapTotal"_s),
+ 0,
+ offset);
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "heapUsed"_s),
+ 0,
+ offset);
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "external"_s),
+ 0,
+ offset);
+ structure = structure->addPropertyTransition(
+ vm,
+ structure,
+ JSC::Identifier::fromString(vm, "arrayBuffers"_s),
+ 0,
+ offset);
+
+ return structure;
+}
+
+static Process* getProcessObject(JSC::JSGlobalObject* lexicalGlobalObject, JSValue thisValue)
+{
+ Process* process = jsDynamicCast<Process*>(thisValue);
+
+ // Handle "var memoryUsage = process.memoryUsage; memoryUsage()"
+ if (UNLIKELY(!process)) {
+ // Handle calling this function from inside a node:vm
+ Zig::GlobalObject* zigGlobalObject = jsDynamicCast<Zig::GlobalObject*>(lexicalGlobalObject);
+
+ if (UNLIKELY(!zigGlobalObject)) {
+ zigGlobalObject = Bun__getDefaultGlobal();
+ }
+
+ return jsCast<Process*>(zigGlobalObject->processObject());
+ }
+
+ return process;
+}
+
+JSC_DEFINE_HOST_FUNCTION(Process_functionCpuUsage,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ struct rusage rusage;
+ if (getrusage(RUSAGE_SELF, &rusage) != 0) {
+ SystemError error;
+ error.errno_ = errno;
+ error.syscall = Bun::toString("getrusage"_s);
+ error.message = Bun::toString("Failed to get CPU usage"_s);
+ throwException(globalObject, throwScope, JSValue::decode(SystemError__toErrorInstance(&error, globalObject)));
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto* process = getProcessObject(globalObject, callFrame->thisValue());
+
+ Structure* cpuUsageStructure = process->cpuUsageStructure.getInitializedOnMainThread(process);
+
+ constexpr double MICROS_PER_SEC = 1000000.0;
+
+ double user = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec;
+ double system = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec;
+
+ if (callFrame->argumentCount() > 0) {
+ JSValue comparatorValue = callFrame->argument(0);
+ if (!comparatorValue.isUndefined()) {
+ if (UNLIKELY(!comparatorValue.isObject())) {
+ throwTypeError(globalObject, throwScope, "Expected an object as the first argument"_s);
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ JSC::JSObject* comparator = comparatorValue.getObject();
+ JSValue userValue;
+ JSValue systemValue;
+
+ if (LIKELY(comparator->structureID() == cpuUsageStructure->id())) {
+ userValue = comparator->getDirect(0);
+ systemValue = comparator->getDirect(1);
+ } else {
+ userValue = comparator->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "user"_s));
+ RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined()));
+
+ systemValue = comparator->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "system"_s));
+ RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined()));
+ }
+
+ if (UNLIKELY(!userValue || !userValue.isNumber())) {
+ throwTypeError(globalObject, throwScope, "Expected a number for the user property"_s);
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ if (UNLIKELY(!systemValue || !systemValue.isNumber())) {
+ throwTypeError(globalObject, throwScope, "Expected a number for the system property"_s);
+ return JSC::JSValue::encode(JSC::jsUndefined());
+ }
+
+ double userComparator = userValue.asNumber();
+ double systemComparator = systemValue.asNumber();
+
+ 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));
+}
+
+static int getRSS(size_t* rss)
+{
+#if defined(__APPLE__)
+ mach_msg_type_number_t count;
+ task_basic_info_data_t info;
+ kern_return_t err;
+
+ count = TASK_BASIC_INFO_COUNT;
+ err = task_info(mach_task_self(),
+ TASK_BASIC_INFO,
+ reinterpret_cast<task_info_t>(&info),
+ &count);
+
+ if (err == KERN_SUCCESS) {
+ *rss = (size_t)info.resident_size;
+ return 0;
+ }
+
+ return -1;
+#elif defined(__linux__)
+ // Taken from libuv.
+ char buf[1024];
+ const char* s;
+ ssize_t n;
+ long val;
+ int fd;
+ int i;
+
+ do
+ fd = open("/proc/self/stat", O_RDONLY);
+ while (fd == -1 && errno == EINTR);
+
+ if (fd == -1)
+ return errno;
+
+ do
+ n = read(fd, buf, sizeof(buf) - 1);
+ while (n == -1 && errno == EINTR);
+
+ int closeErrno = 0;
+ do {
+ closeErrno = close(fd);
+ } while (closeErrno == -1 && errno == EINTR);
+
+ if (n == -1)
+ return errno;
+ buf[n] = '\0';
+
+ s = strchr(buf, ' ');
+ if (s == NULL)
+ goto err;
+
+ s += 1;
+ if (*s != '(')
+ goto err;
+
+ s = strchr(s, ')');
+ if (s == NULL)
+ goto err;
+
+ for (i = 1; i <= 22; i++) {
+ s = strchr(s + 1, ' ');
+ if (s == NULL)
+ goto err;
+ }
+
+ errno = 0;
+ val = strtol(s, NULL, 10);
+ if (errno != 0)
+ goto err;
+ if (val < 0)
+ goto err;
+
+ *rss = val * getpagesize();
+ return 0;
+
+err:
+ return EINVAL;
+#else
+#error "Unsupported platform"
+#endif
+}
+
+JSC_DEFINE_HOST_FUNCTION(Process_functionMemoryUsage,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ auto* process = getProcessObject(globalObject, callFrame->thisValue());
+
+ size_t current_rss = 0;
+ if (getRSS(&current_rss) != 0) {
+ SystemError error;
+ error.errno_ = errno;
+ error.syscall = Bun::toString("memoryUsage"_s);
+ error.message = Bun::toString("Failed to get memory usage"_s);
+ throwException(globalObject, throwScope, JSValue::decode(SystemError__toErrorInstance(&error, globalObject)));
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ JSC::JSObject* result = JSC::constructEmptyObject(vm, process->memoryUsageStructure.getInitializedOnMainThread(process));
+ if (UNLIKELY(throwScope.exception())) {
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ // Node.js:
+ // {
+ // rss: 4935680,
+ // heapTotal: 1826816,
+ // heapUsed: 650472,
+ // external: 49879,
+ // arrayBuffers: 9386
+ // }
+
+ result->putDirectOffset(vm, 0, JSC::jsNumber(current_rss));
+ result->putDirectOffset(vm, 1, JSC::jsNumber(vm.heap.blockBytesAllocated()));
+
+ // heap.size() loops through every cell...
+ // TODO: add a binding for heap.sizeAfterLastCollection()
+ result->putDirectOffset(vm, 2, JSC::jsNumber(vm.heap.sizeAfterLastEdenCollection()));
+
+ result->putDirectOffset(vm, 3, JSC::jsNumber(vm.heap.externalMemorySize()));
+
+ // We report 0 for this because m_arrayBuffers in JSC::Heap is private and we need to add a binding
+ // If we use objectTypeCounts(), it's hideously slow because it loops through every single object in the heap
+ // TODO: add a binding for m_arrayBuffers, registerWrapper() in TypedArrayController doesn't work
+ result->putDirectOffset(vm, 4, JSC::jsNumber(0));
+
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(result));
+}
+
+JSC_DEFINE_HOST_FUNCTION(Process_functionMemoryUsageRSS,
+ (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
+{
+ JSC::VM& vm = globalObject->vm();
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ size_t current_rss = 0;
+ if (getRSS(&current_rss) != 0) {
+ SystemError error;
+ error.errno_ = errno;
+ error.syscall = Bun::toString("memoryUsage"_s);
+ error.message = Bun::toString("Failed to get memory usage"_s);
+ throwException(globalObject, throwScope, JSValue::decode(SystemError__toErrorInstance(&error, globalObject)));
+ return JSC::JSValue::encode(JSC::JSValue {});
+ }
+
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(jsNumber(current_rss)));
+}
+
JSC_DEFINE_HOST_FUNCTION(Process_functionOpenStdin, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
auto& vm = globalObject->vm();
@@ -1010,6 +1353,19 @@ static JSValue Process_stubEmptySet(VM& vm, JSObject* processObject)
return JSSet::create(vm, globalObject->setStructure());
}
+static JSValue constructMemoryUsage(VM& vm, JSObject* processObject)
+{
+ auto* globalObject = processObject->globalObject();
+ JSC::JSFunction* memoryUsage = JSC::JSFunction::create(vm, globalObject, 0,
+ String("memoryUsage"_s), Process_functionMemoryUsage, ImplementationVisibility::Public);
+
+ JSC::JSFunction* rss = JSC::JSFunction::create(vm, globalObject, 0,
+ String("rss"_s), Process_functionMemoryUsageRSS, ImplementationVisibility::Public);
+
+ memoryUsage->putDirect(vm, JSC::Identifier::fromString(vm, "rss"_s), rss, JSC::PropertyAttribute::Function | 0);
+ return memoryUsage;
+}
+
static JSValue constructFeatures(VM& vm, JSObject* processObject)
{
// {
@@ -1133,16 +1489,16 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionCwd,
browser constructBrowser PropertyCallback
chdir Process_functionChdir Function 1
config constructProcessConfigObject PropertyCallback
- debugPort processDebugPort CustomAccessor
- exitCode processExitCode CustomAccessor
- title processTitle CustomAccessor
+ cpuUsage Process_functionCpuUsage Function 1
cwd Process_functionCwd Function 1
+ debugPort processDebugPort CustomAccessor
dlopen Process_functionDlopen Function 1
emitWarning Process_emitWarning Function 1
env constructEnv PropertyCallback
execArgv constructExecArgv PropertyCallback
execPath constructExecPath PropertyCallback
exit Process_functionExit Function 1
+ exitCode processExitCode CustomAccessor
features constructFeatures PropertyCallback
getActiveResourcesInfo Process_stubFunctionReturningArray Function 0
getegid Process_functiongetegid Function 0
@@ -1153,6 +1509,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionCwd,
hrtime constructProcessHrtimeObject PropertyCallback
isBun constructIsBun PropertyCallback
mainModule JSBuiltin ReadOnly|Builtin|Accessor|Function 0
+ memoryUsage constructMemoryUsage PropertyCallback
moduleLoadList Process_stubEmptyArray PropertyCallback
nextTick Process_functionNextTick Function 1
openStdin Process_functionOpenStdin Function 0
@@ -1166,6 +1523,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionCwd,
stderr constructStderr PropertyCallback
stdin constructStdin PropertyCallback
stdout constructStdout PropertyCallback
+ title processTitle CustomAccessor
umask Process_functionUmask Function 1
uptime Process_functionUptime Function 1
version constructVersion PropertyCallback
@@ -1192,6 +1550,14 @@ void Process::finishCreation(JSC::VM& vm)
{
Base::finishCreation(vm);
+ this->cpuUsageStructure.initLater([](const JSC::LazyProperty<JSC::JSObject, JSC::Structure>::Initializer& init) {
+ init.set(constructCPUUsageStructure(init.vm, init.owner->globalObject()));
+ });
+
+ this->memoryUsageStructure.initLater([](const JSC::LazyProperty<JSC::JSObject, JSC::Structure>::Initializer& init) {
+ init.set(constructMemoryUsageStructure(init.vm, init.owner->globalObject()));
+ });
+
this->putDirect(vm, vm.propertyNames->toStringTagSymbol, jsString(vm, String("process"_s)), 0);
}
diff --git a/src/bun.js/bindings/Process.h b/src/bun.js/bindings/Process.h
index 8b703b8b1..fbad9b1ff 100644
--- a/src/bun.js/bindings/Process.h
+++ b/src/bun.js/bindings/Process.h
@@ -45,6 +45,24 @@ public:
return accessor;
}
+ LazyProperty<JSObject, Structure> cpuUsageStructure;
+ LazyProperty<JSObject, Structure> memoryUsageStructure;
+
+ DECLARE_VISIT_CHILDREN;
+
+ template<typename, SubspaceAccess mode>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<Process, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForProcessObject.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForProcessObject = std::forward<decltype(space)>(space); },
+ [](auto& spaces) { return spaces.m_subspaceForProcessObject.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForProcessObject = std::forward<decltype(space)>(space); });
+ }
+
void finishCreation(JSC::VM& vm);
};
diff --git a/src/bun.js/bindings/Process.lut.h b/src/bun.js/bindings/Process.lut.h
index 5c0bd2c5a..81eb1b7b9 100644
--- a/src/bun.js/bindings/Process.lut.h
+++ b/src/bun.js/bindings/Process.lut.h
@@ -1,6 +1,6 @@
// File generated via `make generate-builtins`
-static const struct CompactHashIndex processObjectTableIndex[141] = {
- { 42, -1 },
+static const struct CompactHashIndex processObjectTableIndex[142] = {
+ { 43, -1 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
@@ -11,12 +11,12 @@ static const struct CompactHashIndex processObjectTableIndex[141] = {
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
- { 16, 130 },
+ { 15, 129 },
{ -1, -1 },
{ -1, -1 },
- { 19, 137 },
+ { 18, 138 },
{ -1, -1 },
- { 43, -1 },
+ { 45, -1 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
@@ -33,37 +33,37 @@ static const struct CompactHashIndex processObjectTableIndex[141] = {
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
- { 3, 140 },
+ { 3, 141 },
{ 1, 128 },
{ -1, -1 },
- { 57, -1 },
- { -1, -1 },
+ { 59, -1 },
{ -1, -1 },
+ { 10, -1 },
{ -1, -1 },
{ -1, -1 },
- { 30, -1 },
+ { 31, -1 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
- { 50, -1 },
+ { 52, -1 },
{ 27, -1 },
- { 10, -1 },
+ { 12, -1 },
{ -1, -1 },
- { 11, -1 },
+ { 19, -1 },
{ -1, -1 },
- { 15, 136 },
+ { 14, 137 },
{ -1, -1 },
- { 35, -1 },
+ { 36, -1 },
{ -1, -1 },
- { 37, -1 },
- { 53, -1 },
- { 34, -1 },
- { 6, 138 },
+ { 38, -1 },
+ { 55, -1 },
+ { 35, -1 },
+ { 6, 139 },
{ -1, -1 },
- { 49, -1 },
+ { 51, -1 },
{ 4, -1 },
- { 45, -1 },
+ { 47, -1 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
@@ -75,23 +75,23 @@ static const struct CompactHashIndex processObjectTableIndex[141] = {
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
- { 39, -1 },
+ { 40, -1 },
{ -1, -1 },
- { 36, -1 },
+ { 37, -1 },
{ -1, -1 },
{ 0, -1 },
- { 12, 129 },
- { 17, 131 },
- { 38, -1 },
+ { 26, 135 },
+ { 16, 130 },
+ { 39, -1 },
{ -1, -1 },
{ 23, -1 },
- { 13, -1 },
+ { 11, -1 },
{ -1, -1 },
{ -1, -1 },
- { 56, -1 },
+ { 58, -1 },
{ -1, -1 },
{ -1, -1 },
- { 47, -1 },
+ { 30, 136 },
{ -1, -1 },
{ 29, -1 },
{ 22, -1 },
@@ -105,45 +105,46 @@ static const struct CompactHashIndex processObjectTableIndex[141] = {
{ 5, -1 },
{ -1, -1 },
{ -1, -1 },
- { 46, -1 },
+ { 48, -1 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
- { 14, 132 },
+ { 13, 131 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
{ 9, -1 },
- { 25, 134 },
+ { 25, 133 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
- { 21, 135 },
+ { 21, 134 },
{ -1, -1 },
{ -1, -1 },
{ -1, -1 },
- { 44, 139 },
+ { 46, 140 },
{ -1, -1 },
- { 18, -1 },
+ { 17, -1 },
{ 8, -1 },
- { 26, -1 },
{ 28, -1 },
- { 31, 133 },
- { 32, -1 },
+ { 32, 132 },
{ 33, -1 },
- { 40, -1 },
+ { 34, -1 },
{ 41, -1 },
- { 48, -1 },
- { 51, -1 },
- { 52, -1 },
+ { 42, -1 },
+ { 44, -1 },
+ { 49, -1 },
+ { 50, -1 },
+ { 53, -1 },
{ 54, -1 },
- { 55, -1 },
+ { 56, -1 },
+ { 57, -1 },
};
-static const struct HashTableValue processObjectTableValues[58] = {
+static const struct HashTableValue processObjectTableValues[60] = {
{ "abort"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionAbort, 1 } },
{ "allowedNodeEnvironmentFlags"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, Process_stubEmptySet } },
{ "arch"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructArch } },
@@ -154,16 +155,16 @@ static const struct HashTableValue processObjectTableValues[58] = {
{ "browser"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructBrowser } },
{ "chdir"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionChdir, 1 } },
{ "config"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructProcessConfigObject } },
- { "debugPort"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, processDebugPort, setProcessDebugPort } },
- { "exitCode"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, processExitCode, setProcessExitCode } },
- { "title"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, processTitle, setProcessTitle } },
+ { "cpuUsage"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionCpuUsage, 1 } },
{ "cwd"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionCwd, 1 } },
+ { "debugPort"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, processDebugPort, setProcessDebugPort } },
{ "dlopen"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionDlopen, 1 } },
{ "emitWarning"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_emitWarning, 1 } },
{ "env"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructEnv } },
{ "execArgv"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructExecArgv } },
{ "execPath"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructExecPath } },
{ "exit"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionExit, 1 } },
+ { "exitCode"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, processExitCode, setProcessExitCode } },
{ "features"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructFeatures } },
{ "getActiveResourcesInfo"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_stubFunctionReturningArray, 0 } },
{ "getegid"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functiongetegid, 0 } },
@@ -174,6 +175,7 @@ static const struct HashTableValue processObjectTableValues[58] = {
{ "hrtime"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructProcessHrtimeObject } },
{ "isBun"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructIsBun } },
{ "mainModule"_s, ((static_cast<unsigned>(PropertyAttribute::ReadOnly|PropertyAttribute::Builtin|PropertyAttribute::Accessor|PropertyAttribute::Function)) & ~PropertyAttribute::Function) | PropertyAttribute::Builtin, NoIntrinsic, { HashTableValue::BuiltinGeneratorType, processObjectMainModuleCodeGenerator, 0 } },
+ { "memoryUsage"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructMemoryUsage } },
{ "moduleLoadList"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, Process_stubEmptyArray } },
{ "nextTick"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionNextTick, 1 } },
{ "openStdin"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionOpenStdin, 0 } },
@@ -187,6 +189,7 @@ static const struct HashTableValue processObjectTableValues[58] = {
{ "stderr"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructStderr } },
{ "stdin"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructStdin } },
{ "stdout"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructStdout } },
+ { "title"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, processTitle, setProcessTitle } },
{ "umask"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionUmask, 1 } },
{ "uptime"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, Process_functionUptime, 1 } },
{ "version"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, constructVersion } },
@@ -205,4 +208,4 @@ static const struct HashTableValue processObjectTableValues[58] = {
};
static const struct HashTable processObjectTable =
- { 58, 127, true, nullptr, processObjectTableValues, processObjectTableIndex };
+ { 60, 127, true, nullptr, processObjectTableValues, processObjectTableIndex };
diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
index 65875d091..82a2c6a24 100644
--- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h
@@ -38,6 +38,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSMockImplementation;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSMockFunction;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMockWithImplementationCleanupData;
+ std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForProcessObject;
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
/* --- bun --- */
diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
index 433832450..f1b290d25 100644
--- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h
@@ -38,6 +38,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForJSMockImplementation;
std::unique_ptr<IsoSubspace> m_subspaceForJSMockFunction;
std::unique_ptr<IsoSubspace> m_subspaceForMockWithImplementationCleanupData;
+ std::unique_ptr<IsoSubspace> m_subspaceForProcessObject;
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
/*-- BUN --*/
diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js
index c4701f664..e038383de 100644
--- a/test/js/node/process/process.test.js
+++ b/test/js/node/process/process.test.js
@@ -162,7 +162,8 @@ it("process.umask()", () => {
expect(process.umask()).toBe(orig);
});
-const versions = existsSync(import.meta.dir + "/../../src/generated_versions_list.zig");
+const generated_versions_list = join(import.meta.dir, "../../../../src/generated_versions_list.zig");
+const versions = existsSync(generated_versions_list);
(versions ? it : it.skip)("process.versions", () => {
// Generate a list of all the versions in the versions object
// example:
@@ -178,7 +179,7 @@ const versions = existsSync(import.meta.dir + "/../../src/generated_versions_lis
// pub const c_ares = "0e7a5dee0fbb04080750cf6eabbe89d8bae87faa";
// pub const usockets = "fafc241e8664243fc0c51d69684d5d02b9805134";
const versions = Object.fromEntries(
- readFileSync(import.meta.dir + "/../../src/generated_versions_list.zig", "utf8")
+ readFileSync(generated_versions_list, "utf8")
.split("\n")
.filter(line => line.startsWith("pub const") && !line.includes("zig") && line.includes(' = "'))
.map(line => line.split(" = "))
@@ -291,3 +292,140 @@ describe("process.onBeforeExit", () => {
expect(stdout.toString().trim()).toBe("beforeExit: 0\nbeforeExit: 1\nexit: 2");
});
});
+
+it("process.memoryUsage", () => {
+ expect(process.memoryUsage()).toEqual({
+ rss: expect.any(Number),
+ heapTotal: expect.any(Number),
+ heapUsed: expect.any(Number),
+ external: expect.any(Number),
+ arrayBuffers: expect.any(Number),
+ });
+});
+
+it("process.memoryUsage.rss", () => {
+ expect(process.memoryUsage.rss()).toEqual(expect.any(Number));
+});
+
+describe("process.cpuUsage", () => {
+ it("works", () => {
+ expect(process.cpuUsage()).toEqual({
+ user: expect.any(Number),
+ system: expect.any(Number),
+ });
+ });
+
+ it("works with diff", () => {
+ const init = process.cpuUsage();
+ for (let i = 0; i < 1000; i++) {}
+ const delta = process.cpuUsage(init);
+ expect(delta.user).toBeGreaterThan(0);
+ expect(delta.system).toBeGreaterThan(0);
+ });
+
+ it("works with diff of different structure", () => {
+ const init = {
+ user: 0,
+ system: 0,
+ };
+ for (let i = 0; i < 1000; i++) {}
+ const delta = process.cpuUsage(init);
+ expect(delta.user).toBeGreaterThan(0);
+ expect(delta.system).toBeGreaterThan(0);
+ });
+
+ it("throws on invalid property", () => {
+ const fixtures = [
+ {},
+ { user: null },
+ { user: {} },
+ { user: "potato" },
+
+ { user: 123 },
+ { user: 123, system: null },
+ { user: 123, system: "potato" },
+ ];
+ for (const fixture of fixtures) {
+ expect(() => process.cpuUsage(fixture)).toThrow();
+ }
+ });
+
+ // Skipped on Linux because it seems to not change as often as on macOS
+ it.skipIf(process.platform === "linux")("increases monotonically", () => {
+ const init = process.cpuUsage();
+ for (let i = 0; i < 10000; i++) {}
+ const another = process.cpuUsage();
+ expect(another.user).toBeGreaterThan(init.user);
+ expect(another.system).toBeGreaterThan(init.system);
+ });
+});
+
+it("process.getegid", () => {
+ expect(typeof process.getegid()).toBe("number");
+});
+it("process.geteuid", () => {
+ expect(typeof process.geteuid()).toBe("number");
+});
+it("process.getgid", () => {
+ expect(typeof process.getgid()).toBe("number");
+});
+it("process.getgroups", () => {
+ expect(process.getgroups()).toBeInstanceOf(Array);
+ expect(process.getgroups().length).toBeGreaterThan(0);
+});
+it("process.getuid", () => {
+ expect(typeof process.getuid()).toBe("number");
+});
+
+it("process.getuid", () => {
+ expect(typeof process.getuid()).toBe("number");
+});
+
+const undefinedStubs = [
+ "_debugEnd",
+ "_debugProcess",
+ "_fatalException",
+ "_linkedBinding",
+ "_rawDebug",
+ "_startProfilerIdleNotifier",
+ "_stopProfilerIdleNotifier",
+ "_tickCallback",
+];
+
+for (const stub of undefinedStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]()).toBeUndefined();
+ });
+}
+
+const arrayStubs = ["getActiveResourcesInfo", "_getActiveRequests", "_getActiveHandles"];
+
+for (const stub of arrayStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]()).toBeInstanceOf(Array);
+ });
+}
+
+const emptyObjectStubs = ["_preload_modules"];
+const emptySetStubs = ["allowedNodeEnvironmentFlags"];
+const emptyArrayStubs = ["moduleLoadList"];
+
+for (const stub of emptyObjectStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]).toEqual({});
+ });
+}
+
+for (const stub of emptySetStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]).toBeInstanceOf(Set);
+ expect(process[stub].size).toBe(0);
+ });
+}
+
+for (const stub of emptyArrayStubs) {
+ it(`process.${stub}`, () => {
+ expect(process[stub]).toBeInstanceOf(Array);
+ expect(process[stub]).toHaveLength(0);
+ });
+}