Skip to content
Open
10 changes: 10 additions & 0 deletions packages/bun-types/jsc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ declare module "bun:jsc" {
function edenGC(): number;
function heapSize(): number;
function heapStats(): HeapStats;
function heapSpaceStats(): HeapSpaceStatistics[];
function memoryUsage(): MemoryUsage;
function getRandomSeed(): number;
function setRandomSeed(value: number): void;
Expand Down Expand Up @@ -74,6 +75,15 @@ declare module "bun:jsc" {
protectedObjectTypeCounts: Record<string, number>;
}

//https://bun.com/reference/node/v8/HeapSpaceStatistics
interface HeapSpaceStatistics {
spaceName: string;
spaceSize: number;
spaceUsedSize: number;
spaceAvailableSize: number;
physicalSpaceSize: number;
}

interface MemoryUsage {
current: number;
peak: number;
Expand Down
83 changes: 82 additions & 1 deletion src/bun.js/modules/BunJSCModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,85 @@ JSC_DEFINE_HOST_FUNCTION(functionMemoryUsageStatistics,
return JSValue::encode(object);
}

JSC_DECLARE_HOST_FUNCTION(functionHeapSpaceStatistics);
JSC_DEFINE_HOST_FUNCTION(functionHeapSpaceStatistics,
(JSGlobalObject * globalObject, CallFrame*))
{
VM& vm = globalObject->vm();
JSLockHolder lock(vm);
auto scope = DECLARE_THROW_SCOPE(vm);
// In JSC, we don't have the same heap space segmentation as V8
// V8 has: new_space, old_space, code_space, map_space, large_object_space, etc.
// JSC has a simpler structure, so we'll create a compatible representation

// Force a collection to get accurate statistics
if (vm.heap.size() == 0) {
vm.heap.collectNow(Sync, CollectionScope::Full);
}

JSArray* result = constructEmptyArray(globalObject, nullptr);
RETURN_IF_EXCEPTION(scope, encodedJSValue());

// Create a single "heap" space that represents JSC's heap
// This provides basic compatibility with V8's getHeapSpaceStatistics API
JSObject* heapSpace = constructEmptyObject(globalObject, globalObject->objectPrototype());
RETURN_IF_EXCEPTION(scope, encodedJSValue());

size_t heapCapacity = vm.heap.capacity();
size_t heapUsed = vm.heap.size();
size_t heapAvailable = heapCapacity > heapUsed ? heapCapacity - heapUsed : 0;

heapSpace->putDirect(vm, Identifier::fromString(vm, "spaceName"_s),
jsString(vm, String("heap"_s)));
heapSpace->putDirect(vm, Identifier::fromString(vm, "spaceSize"_s),
jsNumber(heapCapacity));
heapSpace->putDirect(vm, Identifier::fromString(vm, "spaceUsedSize"_s),
jsNumber(heapUsed));
heapSpace->putDirect(vm, Identifier::fromString(vm, "spaceAvailableSize"_s),
jsNumber(heapAvailable));
heapSpace->putDirect(vm, Identifier::fromString(vm, "physicalSpaceSize"_s),
jsNumber(heapCapacity));

result->putDirectIndex(globalObject, 0, heapSpace);
RETURN_IF_EXCEPTION(scope, encodedJSValue());

// V8 compatibility: Add placeholder spaces that V8 has but JSC doesn't
const char* v8SpaceNames[] = {
"read_only_space",
"new_space",
"old_space",
"code_space",
"shared_space",
"trusted_space",
"new_large_object_space",
"large_object_space",
"code_large_object_space",
"shared_large_object_space",
"trusted_large_object_space"
};

for (size_t i = 0; i < sizeof(v8SpaceNames) / sizeof(v8SpaceNames[0]); i++) {
JSObject* space = constructEmptyObject(globalObject, globalObject->objectPrototype());
RETURN_IF_EXCEPTION(scope, encodedJSValue());
space->putDirect(vm, Identifier::fromString(vm, "spaceName"_s),
jsString(vm, String::fromUTF8(v8SpaceNames[i])));
space->putDirect(vm, Identifier::fromString(vm, "spaceSize"_s),
jsNumber(0));
space->putDirect(vm, Identifier::fromString(vm, "spaceUsedSize"_s),
jsNumber(0));
space->putDirect(vm, Identifier::fromString(vm, "spaceAvailableSize"_s),
jsNumber(0));
space->putDirect(vm, Identifier::fromString(vm, "physicalSpaceSize"_s),
jsNumber(0));

result->putDirectIndex(globalObject, i + 1, space);
RETURN_IF_EXCEPTION(scope, encodedJSValue());
}

RELEASE_AND_RETURN(scope, JSValue::encode(result));
}


JSC_DECLARE_HOST_FUNCTION(functionCreateMemoryFootprint);
JSC_DEFINE_HOST_FUNCTION(functionCreateMemoryFootprint,
(JSGlobalObject * globalObject, CallFrame*))
Expand Down Expand Up @@ -924,6 +1003,7 @@ JSC_DEFINE_HOST_FUNCTION(functionPercentAvailableMemoryInUse, (JSGlobalObject *
getRandomSeed functionGetRandomSeed Function 0
heapSize functionHeapSize Function 0
heapStats functionMemoryUsageStatistics Function 0
heapSpaceStats functionHeapSpaceStatistics Function 0
startSamplingProfiler functionStartSamplingProfiler Function 0
samplingProfilerStackTraces functionSamplingProfilerStackTraces Function 0
noInline functionNeverInlineFunction Function 0
Expand Down Expand Up @@ -952,7 +1032,7 @@ JSC_DEFINE_HOST_FUNCTION(functionPercentAvailableMemoryInUse, (JSGlobalObject *
namespace Zig {
DEFINE_NATIVE_MODULE(BunJSC)
{
INIT_NATIVE_MODULE(36);
INIT_NATIVE_MODULE(37);

putNativeFn(Identifier::fromString(vm, "callerSourceOrigin"_s), functionCallerSourceOrigin);
putNativeFn(Identifier::fromString(vm, "jscDescribe"_s), functionDescribe);
Expand All @@ -964,6 +1044,7 @@ DEFINE_NATIVE_MODULE(BunJSC)
putNativeFn(Identifier::fromString(vm, "getRandomSeed"_s), functionGetRandomSeed);
putNativeFn(Identifier::fromString(vm, "heapSize"_s), functionHeapSize);
putNativeFn(Identifier::fromString(vm, "heapStats"_s), functionMemoryUsageStatistics);
putNativeFn(Identifier::fromString(vm, "heapSpaceStats"_s), functionHeapSpaceStatistics);
putNativeFn(Identifier::fromString(vm, "startSamplingProfiler"_s), functionStartSamplingProfiler);
putNativeFn(Identifier::fromString(vm, "samplingProfilerStackTraces"_s), functionSamplingProfilerStackTraces);
putNativeFn(Identifier::fromString(vm, "noInline"_s), functionNeverInlineFunction);
Expand Down
9 changes: 8 additions & 1 deletion src/js/node/v8.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,14 @@ function getHeapStatistics() {
};
}
function getHeapSpaceStatistics() {
notimpl("getHeapSpaceStatistics");
const spaces = jsc.heapSpaceStats();
return spaces.map(space => ({
space_name: space.spaceName,
space_size: space.spaceSize,
space_used_size: space.spaceUsedSize,
space_available_size: space.spaceAvailableSize,
physical_space_size: space.physicalSpaceSize,
}));
}
function getHeapCodeStatistics() {
notimpl("getHeapCodeStatistics");
Expand Down
26 changes: 26 additions & 0 deletions test/v8/heap-space-statistics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { test, expect } from "bun:test";
import v8 from "node:v8";

test("getHeapSpaceStatistics returns an array", () => {
const stats = v8.getHeapSpaceStatistics();
expect(Array.isArray(stats)).toBe(true);
expect(stats.length).toBeGreaterThan(0);
});

test("getHeapSpaceStatistics has expected properties", () => {
const stats = v8.getHeapSpaceStatistics();

for (const space of stats) {
expect(typeof space.space_name).toBe("string");
expect(typeof space.space_size).toBe("number");
expect(typeof space.space_used_size).toBe("number");
expect(typeof space.space_available_size).toBe("number");
expect(typeof space.physical_space_size).toBe("number");

// All numeric values should be non-negative
expect(space.space_size).toBeGreaterThanOrEqual(0);
expect(space.space_used_size).toBeGreaterThanOrEqual(0);
expect(space.space_available_size).toBeGreaterThanOrEqual(0);
expect(space.physical_space_size).toBeGreaterThanOrEqual(0);
}
});