Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
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);
}
});