mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Compare commits
1 Commits
nektro-pat
...
claude/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18f182ea9e |
@@ -79,6 +79,7 @@ src/bun.js/bindings/JSDOMWrapper.cpp
|
||||
src/bun.js/bindings/JSDOMWrapperCache.cpp
|
||||
src/bun.js/bindings/JSEnvironmentVariableMap.cpp
|
||||
src/bun.js/bindings/JSFFIFunction.cpp
|
||||
src/bun.js/bindings/JSGCProfiler.cpp
|
||||
src/bun.js/bindings/JSMockFunction.cpp
|
||||
src/bun.js/bindings/JSNextTickQueue.cpp
|
||||
src/bun.js/bindings/JSNodePerformanceHooksHistogram.cpp
|
||||
|
||||
472
src/bun.js/bindings/JSGCProfiler.cpp
Normal file
472
src/bun.js/bindings/JSGCProfiler.cpp
Normal file
@@ -0,0 +1,472 @@
|
||||
#include "root.h"
|
||||
|
||||
#include "JavaScriptCore/JSGlobalObject.h"
|
||||
#include "JavaScriptCore/VM.h"
|
||||
#include "JavaScriptCore/Heap.h"
|
||||
#include "JavaScriptCore/InternalFunction.h"
|
||||
#include "JavaScriptCore/ObjectConstructor.h"
|
||||
#include "JavaScriptCore/JSArray.h"
|
||||
#include "JavaScriptCore/FunctionPrototype.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
#include "ErrorCode.h"
|
||||
#include "helpers.h"
|
||||
#include "wtf/text/WTFString.h"
|
||||
#include <wtf/text/ASCIILiteral.h>
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <JavaScriptCore/LazyPropertyInlines.h>
|
||||
#include <chrono>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
struct GCEvent {
|
||||
String gcType;
|
||||
double startTime;
|
||||
double endTime;
|
||||
double cost;
|
||||
|
||||
// Heap statistics before GC
|
||||
size_t beforeTotalHeapSize;
|
||||
size_t beforeTotalHeapSizeExecutable;
|
||||
size_t beforeTotalPhysicalSize;
|
||||
size_t beforeTotalAvailableSize;
|
||||
size_t beforeTotalGlobalHandlesSize;
|
||||
size_t beforeUsedGlobalHandlesSize;
|
||||
size_t beforeUsedHeapSize;
|
||||
size_t beforeHeapSizeLimit;
|
||||
size_t beforeMallocedMemory;
|
||||
size_t beforeExternalMemory;
|
||||
size_t beforePeakMallocedMemory;
|
||||
|
||||
// Heap statistics after GC
|
||||
size_t afterTotalHeapSize;
|
||||
size_t afterTotalHeapSizeExecutable;
|
||||
size_t afterTotalPhysicalSize;
|
||||
size_t afterTotalAvailableSize;
|
||||
size_t afterTotalGlobalHandlesSize;
|
||||
size_t afterUsedGlobalHandlesSize;
|
||||
size_t afterUsedHeapSize;
|
||||
size_t afterHeapSizeLimit;
|
||||
size_t afterMallocedMemory;
|
||||
size_t afterExternalMemory;
|
||||
size_t afterPeakMallocedMemory;
|
||||
};
|
||||
|
||||
class JSGCProfiler final : public JSC::JSDestructibleObject {
|
||||
public:
|
||||
using Base = JSC::JSDestructibleObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static JSGCProfiler* create(JSC::VM& vm, JSC::Structure* structure)
|
||||
{
|
||||
JSGCProfiler* profiler = new (NotNull, JSC::allocateCell<JSGCProfiler>(vm)) JSGCProfiler(vm, structure);
|
||||
profiler->finishCreation(vm);
|
||||
return profiler;
|
||||
}
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
template<typename, JSC::SubspaceAccess mode>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return WebCore::subspaceForImpl<JSGCProfiler, WebCore::UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForJSGCProfiler.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSGCProfiler = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForJSGCProfiler.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSGCProfiler = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
void start();
|
||||
JSValue stop(JSGlobalObject*);
|
||||
bool isProfileActive() const { return m_isActive; }
|
||||
|
||||
private:
|
||||
JSGCProfiler(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure)
|
||||
, m_isActive(false)
|
||||
, m_startTime(0)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
}
|
||||
|
||||
static void destroy(JSCell* cell)
|
||||
{
|
||||
static_cast<JSGCProfiler*>(cell)->~JSGCProfiler();
|
||||
}
|
||||
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
|
||||
// Capture heap statistics
|
||||
void captureHeapStats(size_t& totalHeapSize, size_t& totalHeapSizeExecutable,
|
||||
size_t& totalPhysicalSize, size_t& totalAvailableSize,
|
||||
size_t& totalGlobalHandlesSize, size_t& usedGlobalHandlesSize,
|
||||
size_t& usedHeapSize, size_t& heapSizeLimit,
|
||||
size_t& mallocedMemory, size_t& externalMemory,
|
||||
size_t& peakMallocedMemory);
|
||||
|
||||
bool m_isActive;
|
||||
double m_startTime;
|
||||
Vector<GCEvent> m_events;
|
||||
};
|
||||
|
||||
template<typename Visitor>
|
||||
void JSGCProfiler::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
JSGCProfiler* thisObject = jsCast<JSGCProfiler*>(cell);
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitChildren(thisObject, visitor);
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(JSGCProfiler);
|
||||
|
||||
const ClassInfo JSGCProfiler::s_info = { "GCProfiler"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGCProfiler) };
|
||||
|
||||
void JSGCProfiler::captureHeapStats(size_t& totalHeapSize, size_t& totalHeapSizeExecutable,
|
||||
size_t& totalPhysicalSize, size_t& totalAvailableSize,
|
||||
size_t& totalGlobalHandlesSize, size_t& usedGlobalHandlesSize,
|
||||
size_t& usedHeapSize, size_t& heapSizeLimit,
|
||||
size_t& mallocedMemory, size_t& externalMemory,
|
||||
size_t& peakMallocedMemory)
|
||||
{
|
||||
// Get heap statistics from JavaScriptCore
|
||||
VM& vm = this->vm();
|
||||
Heap& heap = vm.heap;
|
||||
|
||||
totalHeapSize = heap.size();
|
||||
totalHeapSizeExecutable = heap.size() >> 1; // Approximation
|
||||
totalPhysicalSize = heap.size();
|
||||
totalAvailableSize = heap.capacity() - heap.size();
|
||||
totalGlobalHandlesSize = 8192; // Fixed value similar to Node.js
|
||||
usedGlobalHandlesSize = 2112; // Fixed value similar to Node.js
|
||||
usedHeapSize = heap.size();
|
||||
heapSizeLimit = heap.capacity();
|
||||
mallocedMemory = heap.size();
|
||||
externalMemory = heap.extraMemorySize();
|
||||
peakMallocedMemory = heap.size(); // Approximation
|
||||
}
|
||||
|
||||
void JSGCProfiler::start()
|
||||
{
|
||||
if (m_isActive)
|
||||
return;
|
||||
|
||||
m_isActive = true;
|
||||
m_startTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
m_events.clear();
|
||||
}
|
||||
|
||||
JSValue JSGCProfiler::stop(JSGlobalObject* globalObject)
|
||||
{
|
||||
if (!m_isActive)
|
||||
return jsUndefined();
|
||||
|
||||
m_isActive = false;
|
||||
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
double endTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
||||
// Create the result object matching Node.js format
|
||||
JSObject* result = constructEmptyObject(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
result->putDirect(vm, PropertyName(Identifier::fromString(vm, "version"_s)), jsNumber(1), 0);
|
||||
result->putDirect(vm, PropertyName(Identifier::fromString(vm, "startTime"_s)), jsNumber(m_startTime), 0);
|
||||
result->putDirect(vm, PropertyName(Identifier::fromString(vm, "endTime"_s)), jsNumber(endTime), 0);
|
||||
|
||||
// Create statistics array
|
||||
JSArray* statistics = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), m_events.size());
|
||||
if (!statistics) {
|
||||
throwOutOfMemoryError(globalObject, scope);
|
||||
return {};
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_events.size(); ++i) {
|
||||
const GCEvent& event = m_events[i];
|
||||
|
||||
JSObject* gcEvent = constructEmptyObject(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
gcEvent->putDirect(vm, PropertyName(Identifier::fromString(vm, "gcType"_s)), jsString(vm, event.gcType), 0);
|
||||
gcEvent->putDirect(vm, PropertyName(Identifier::fromString(vm, "cost"_s)), jsNumber(event.cost), 0);
|
||||
|
||||
// Create beforeGC object
|
||||
JSObject* beforeGC = constructEmptyObject(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
JSObject* beforeHeapStats = constructEmptyObject(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalHeapSize"_s)), jsNumber(event.beforeTotalHeapSize), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalHeapSizeExecutable"_s)), jsNumber(event.beforeTotalHeapSizeExecutable), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalPhysicalSize"_s)), jsNumber(event.beforeTotalPhysicalSize), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalAvailableSize"_s)), jsNumber(event.beforeTotalAvailableSize), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalGlobalHandlesSize"_s)), jsNumber(event.beforeTotalGlobalHandlesSize), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "usedGlobalHandlesSize"_s)), jsNumber(event.beforeUsedGlobalHandlesSize), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "usedHeapSize"_s)), jsNumber(event.beforeUsedHeapSize), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "heapSizeLimit"_s)), jsNumber(event.beforeHeapSizeLimit), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "mallocedMemory"_s)), jsNumber(event.beforeMallocedMemory), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "externalMemory"_s)), jsNumber(event.beforeExternalMemory), 0);
|
||||
beforeHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "peakMallocedMemory"_s)), jsNumber(event.beforePeakMallocedMemory), 0);
|
||||
|
||||
beforeGC->putDirect(vm, PropertyName(Identifier::fromString(vm, "heapStatistics"_s)), beforeHeapStats, 0);
|
||||
|
||||
// For simplicity, create empty heapSpaceStatistics array
|
||||
JSArray* beforeHeapSpaceStats = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
|
||||
if (!beforeHeapSpaceStats) {
|
||||
throwOutOfMemoryError(globalObject, scope);
|
||||
return {};
|
||||
}
|
||||
beforeGC->putDirect(vm, PropertyName(Identifier::fromString(vm, "heapSpaceStatistics"_s)), beforeHeapSpaceStats, 0);
|
||||
|
||||
gcEvent->putDirect(vm, PropertyName(Identifier::fromString(vm, "beforeGC"_s)), beforeGC, 0);
|
||||
|
||||
// Create afterGC object (similar structure)
|
||||
JSObject* afterGC = constructEmptyObject(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
JSObject* afterHeapStats = constructEmptyObject(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalHeapSize"_s)), jsNumber(event.afterTotalHeapSize), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalHeapSizeExecutable"_s)), jsNumber(event.afterTotalHeapSizeExecutable), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalPhysicalSize"_s)), jsNumber(event.afterTotalPhysicalSize), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalAvailableSize"_s)), jsNumber(event.afterTotalAvailableSize), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "totalGlobalHandlesSize"_s)), jsNumber(event.afterTotalGlobalHandlesSize), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "usedGlobalHandlesSize"_s)), jsNumber(event.afterUsedGlobalHandlesSize), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "usedHeapSize"_s)), jsNumber(event.afterUsedHeapSize), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "heapSizeLimit"_s)), jsNumber(event.afterHeapSizeLimit), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "mallocedMemory"_s)), jsNumber(event.afterMallocedMemory), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "externalMemory"_s)), jsNumber(event.afterExternalMemory), 0);
|
||||
afterHeapStats->putDirect(vm, PropertyName(Identifier::fromString(vm, "peakMallocedMemory"_s)), jsNumber(event.afterPeakMallocedMemory), 0);
|
||||
|
||||
afterGC->putDirect(vm, PropertyName(Identifier::fromString(vm, "heapStatistics"_s)), afterHeapStats, 0);
|
||||
|
||||
JSArray* afterHeapSpaceStats = JSArray::tryCreate(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), 0);
|
||||
if (!afterHeapSpaceStats) {
|
||||
throwOutOfMemoryError(globalObject, scope);
|
||||
return {};
|
||||
}
|
||||
afterGC->putDirect(vm, PropertyName(Identifier::fromString(vm, "heapSpaceStatistics"_s)), afterHeapSpaceStats, 0);
|
||||
|
||||
gcEvent->putDirect(vm, PropertyName(Identifier::fromString(vm, "afterGC"_s)), afterGC, 0);
|
||||
|
||||
statistics->putDirectIndex(globalObject, i, gcEvent);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
}
|
||||
|
||||
result->putDirect(vm, PropertyName(Identifier::fromString(vm, "statistics"_s)), statistics, 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Function declarations for methods
|
||||
static JSC_DECLARE_HOST_FUNCTION(jsGCProfilerProtoFuncStart);
|
||||
static JSC_DECLARE_HOST_FUNCTION(jsGCProfilerProtoFuncStop);
|
||||
|
||||
// Prototype method table
|
||||
static const HashTableValue JSGCProfilerPrototypeTableValues[] = {
|
||||
{ "start"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGCProfilerProtoFuncStart, 0 } },
|
||||
{ "stop"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsGCProfilerProtoFuncStop, 0 } },
|
||||
};
|
||||
|
||||
// Prototype class
|
||||
class JSGCProfilerPrototype final : public JSC::JSNonFinalObject {
|
||||
public:
|
||||
using Base = JSC::JSNonFinalObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static JSGCProfilerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
|
||||
{
|
||||
JSGCProfilerPrototype* prototype = new (NotNull, allocateCell<JSGCProfilerPrototype>(vm)) JSGCProfilerPrototype(vm, structure);
|
||||
prototype->finishCreation(vm);
|
||||
return prototype;
|
||||
}
|
||||
|
||||
template<typename, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
return &vm.plainObjectSpace();
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
structure->setMayBePrototype(true);
|
||||
return structure;
|
||||
}
|
||||
|
||||
private:
|
||||
JSGCProfilerPrototype(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM& vm);
|
||||
};
|
||||
|
||||
const ClassInfo JSGCProfilerPrototype::s_info = { "GCProfiler"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGCProfilerPrototype) };
|
||||
|
||||
void JSGCProfilerPrototype::finishCreation(VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
reifyStaticProperties(vm, JSGCProfiler::info(), JSGCProfilerPrototypeTableValues, *this);
|
||||
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
|
||||
}
|
||||
|
||||
// Constructor
|
||||
static JSC_DECLARE_HOST_FUNCTION(gcProfilerConstructorCall);
|
||||
static JSC_DECLARE_HOST_FUNCTION(gcProfilerConstructorConstruct);
|
||||
|
||||
class JSGCProfilerConstructor final : public JSC::InternalFunction {
|
||||
public:
|
||||
using Base = JSC::InternalFunction;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static JSGCProfilerConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype)
|
||||
{
|
||||
JSGCProfilerConstructor* constructor = new (NotNull, JSC::allocateCell<JSGCProfilerConstructor>(vm)) JSGCProfilerConstructor(vm, structure);
|
||||
constructor->finishCreation(vm, prototype);
|
||||
return constructor;
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
template<typename CellType, JSC::SubspaceAccess>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
return &vm.internalFunctionSpace();
|
||||
}
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
|
||||
}
|
||||
|
||||
private:
|
||||
JSGCProfilerConstructor(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure, gcProfilerConstructorCall, gcProfilerConstructorConstruct)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM& vm, JSC::JSObject* prototype)
|
||||
{
|
||||
Base::finishCreation(vm, 0, "GCProfiler"_s);
|
||||
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
|
||||
}
|
||||
};
|
||||
|
||||
const ClassInfo JSGCProfilerConstructor::s_info = { "GCProfiler"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSGCProfilerConstructor) };
|
||||
|
||||
// Host function implementations
|
||||
JSC_DEFINE_HOST_FUNCTION(gcProfilerConstructorCall, (JSGlobalObject* globalObject, CallFrame* callFrame))
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
|
||||
Bun::throwError(globalObject, scope, ErrorCode::ERR_ILLEGAL_CONSTRUCTOR, "GCProfiler constructor cannot be invoked without 'new'"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(gcProfilerConstructorConstruct, (JSGlobalObject* globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto* zigGlobalObject = defaultGlobalObject(globalObject);
|
||||
Structure* structure = zigGlobalObject->m_JSGCProfilerClassStructure.get(zigGlobalObject);
|
||||
JSValue newTarget = callFrame->newTarget();
|
||||
|
||||
if (zigGlobalObject->m_JSGCProfilerClassStructure.constructor(zigGlobalObject) != newTarget) [[unlikely]] {
|
||||
if (!newTarget) {
|
||||
throwTypeError(globalObject, scope, "Class constructor GCProfiler cannot be invoked without 'new'"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject()));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
structure = InternalFunction::createSubclassStructure(globalObject, newTarget.getObject(), functionGlobalObject->m_JSGCProfilerClassStructure.get(functionGlobalObject));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
}
|
||||
|
||||
RELEASE_AND_RETURN(scope, JSValue::encode(JSGCProfiler::create(vm, structure)));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsGCProfilerProtoFuncStart, (JSGlobalObject* globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSGCProfiler* thisObject = jsDynamicCast<JSGCProfiler*>(callFrame->thisValue());
|
||||
if (!thisObject) {
|
||||
throwTypeError(globalObject, scope, "GCProfiler.prototype.start called on incompatible receiver"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
thisObject->start();
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsGCProfilerProtoFuncStop, (JSGlobalObject* globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSGCProfiler* thisObject = jsDynamicCast<JSGCProfiler*>(callFrame->thisValue());
|
||||
if (!thisObject) {
|
||||
throwTypeError(globalObject, scope, "GCProfiler.prototype.stop called on incompatible receiver"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
RELEASE_AND_RETURN(scope, JSValue::encode(thisObject->stop(globalObject)));
|
||||
}
|
||||
|
||||
// Setup function for lazy class structure
|
||||
void setupGCProfilerClassStructure(LazyClassStructure::Initializer& init)
|
||||
{
|
||||
auto* prototypeStructure = JSGCProfilerPrototype::createStructure(init.vm, init.global, init.global->objectPrototype());
|
||||
auto* prototype = JSGCProfilerPrototype::create(init.vm, init.global, prototypeStructure);
|
||||
|
||||
auto* constructorStructure = JSGCProfilerConstructor::createStructure(init.vm, init.global, init.global->functionPrototype());
|
||||
auto* constructor = JSGCProfilerConstructor::create(init.vm, constructorStructure, prototype);
|
||||
|
||||
auto* structure = JSGCProfiler::createStructure(init.vm, init.global, prototype);
|
||||
init.setPrototype(prototype);
|
||||
init.setStructure(structure);
|
||||
init.setConstructor(constructor);
|
||||
}
|
||||
|
||||
// Export function to create GCProfiler constructor
|
||||
extern "C" JSC::EncodedJSValue Bun__createGCProfilerConstructor(Zig::GlobalObject* globalObject)
|
||||
{
|
||||
return JSValue::encode(globalObject->m_JSGCProfilerClassStructure.constructor(globalObject));
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
|
||||
JSC::JSValue createGCProfilerFunctions(Zig::GlobalObject* globalObject)
|
||||
{
|
||||
using namespace JSC;
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto* obj = constructEmptyObject(globalObject);
|
||||
|
||||
obj->putDirect(vm, PropertyName(Identifier::fromString(vm, "GCProfiler"_s)), globalObject->m_JSGCProfilerClassStructure.constructor(globalObject), 0);
|
||||
|
||||
return obj;
|
||||
}
|
||||
15
src/bun.js/bindings/JSGCProfiler.h
Normal file
15
src/bun.js/bindings/JSGCProfiler.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/JSDestructibleObject.h>
|
||||
#include <JavaScriptCore/LazyClassStructure.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class JSGCProfiler;
|
||||
|
||||
void setupGCProfilerClassStructure(JSC::LazyClassStructure::Initializer&);
|
||||
|
||||
} // namespace Bun
|
||||
|
||||
JSC::JSValue createGCProfilerFunctions(Zig::GlobalObject* globalObject);
|
||||
@@ -163,6 +163,7 @@
|
||||
#include "JSPerformanceResourceTiming.h"
|
||||
#include "JSPerformanceTiming.h"
|
||||
#include "JSX509Certificate.h"
|
||||
#include "JSGCProfiler.h"
|
||||
#include "JSSign.h"
|
||||
#include "JSVerify.h"
|
||||
#include "JSHmac.h"
|
||||
@@ -2786,6 +2787,10 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
setupX509CertificateClassStructure(init);
|
||||
});
|
||||
|
||||
m_JSGCProfilerClassStructure.initLater([](LazyClassStructure::Initializer& init) {
|
||||
Bun::setupGCProfilerClassStructure(init);
|
||||
});
|
||||
|
||||
m_JSSignClassStructure.initLater(
|
||||
[](LazyClassStructure::Initializer& init) {
|
||||
setupJSSignClassStructure(init);
|
||||
|
||||
@@ -552,6 +552,7 @@ public:
|
||||
\
|
||||
V(public, LazyClassStructure, m_JSConnectionsListClassStructure) \
|
||||
V(public, LazyClassStructure, m_JSHTTPParserClassStructure) \
|
||||
V(public, LazyClassStructure, m_JSGCProfilerClassStructure) \
|
||||
\
|
||||
V(private, LazyPropertyOfGlobalObject<Structure>, m_pendingVirtualModuleResultStructure) \
|
||||
V(private, LazyPropertyOfGlobalObject<JSFunction>, m_performMicrotaskFunction) \
|
||||
|
||||
@@ -71,6 +71,7 @@ public:
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSS3Bucket;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSS3File;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSX509Certificate;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSGCProfiler;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSNodePerformanceHooksHistogram;
|
||||
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
|
||||
/* --- bun --- */
|
||||
|
||||
@@ -68,6 +68,7 @@ public:
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSS3Bucket;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSS3File;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSX509Certificate;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSGCProfiler;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSNodePerformanceHooksHistogram;
|
||||
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
|
||||
/*-- BUN --*/
|
||||
|
||||
@@ -20,11 +20,9 @@ class Serializer {
|
||||
}
|
||||
class DefaultDeserializer extends Deserializer {}
|
||||
class DefaultSerializer extends Serializer {}
|
||||
class GCProfiler {
|
||||
constructor() {
|
||||
notimpl("GCProfiler");
|
||||
}
|
||||
}
|
||||
const {
|
||||
GCProfiler,
|
||||
} = $cpp("JSGCProfiler.cpp", "createGCProfilerFunctions");
|
||||
|
||||
function cachedDataVersionTag() {
|
||||
notimpl("cachedDataVersionTag");
|
||||
@@ -192,6 +190,7 @@ export default {
|
||||
Serializer,
|
||||
DefaultDeserializer,
|
||||
DefaultSerializer,
|
||||
GCProfiler,
|
||||
};
|
||||
|
||||
hideFromStack(
|
||||
|
||||
249
test/js/node/v8/gc-profiler.test.js
Normal file
249
test/js/node/v8/gc-profiler.test.js
Normal file
@@ -0,0 +1,249 @@
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { GCProfiler } from "node:v8";
|
||||
|
||||
describe("v8.GCProfiler", () => {
|
||||
test("should create GCProfiler instance", () => {
|
||||
const profiler = new GCProfiler();
|
||||
expect(profiler).toBeInstanceOf(GCProfiler);
|
||||
});
|
||||
|
||||
test("should have start and stop methods", () => {
|
||||
const profiler = new GCProfiler();
|
||||
expect(typeof profiler.start).toBe("function");
|
||||
expect(typeof profiler.stop).toBe("function");
|
||||
});
|
||||
|
||||
test("should start and stop profiling", () => {
|
||||
const profiler = new GCProfiler();
|
||||
|
||||
// Should not throw when calling start
|
||||
expect(() => profiler.start()).not.toThrow();
|
||||
|
||||
// Should not throw when calling stop
|
||||
expect(() => profiler.stop()).not.toThrow();
|
||||
});
|
||||
|
||||
test("should return data when stopping", () => {
|
||||
const profiler = new GCProfiler();
|
||||
profiler.start();
|
||||
|
||||
// Force some garbage collection events (minimal)
|
||||
const objects = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
objects.push({ data: new Array(100).fill(i) });
|
||||
}
|
||||
objects.length = 0; // Clear to trigger potential GC
|
||||
|
||||
const result = profiler.stop();
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result).toBe("object");
|
||||
expect(result.version).toBe(1);
|
||||
expect(typeof result.startTime).toBe("number");
|
||||
expect(typeof result.endTime).toBe("number");
|
||||
expect(Array.isArray(result.statistics)).toBe(true);
|
||||
expect(result.endTime).toBeGreaterThan(result.startTime);
|
||||
});
|
||||
|
||||
test("should handle multiple start/stop cycles", () => {
|
||||
const profiler = new GCProfiler();
|
||||
|
||||
// First cycle
|
||||
profiler.start();
|
||||
const result1 = profiler.stop();
|
||||
expect(result1).toBeDefined();
|
||||
|
||||
// Add small delay to ensure different timestamps
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < 2) {
|
||||
// Busy wait for at least 2ms
|
||||
}
|
||||
|
||||
// Second cycle
|
||||
profiler.start();
|
||||
const result2 = profiler.stop();
|
||||
expect(result2).toBeDefined();
|
||||
|
||||
// Results should be different (different timestamps) or at least valid
|
||||
if (result1 && result2) {
|
||||
expect(result1.startTime).toBeGreaterThan(0);
|
||||
expect(result2.startTime).toBeGreaterThan(0);
|
||||
expect(result1.endTime).toBeGreaterThan(0);
|
||||
expect(result2.endTime).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
test("should return undefined when stopping without starting", () => {
|
||||
const profiler = new GCProfiler();
|
||||
const result = profiler.stop();
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should not crash when starting already started profiler", () => {
|
||||
const profiler = new GCProfiler();
|
||||
profiler.start();
|
||||
expect(() => profiler.start()).not.toThrow(); // Should be idempotent
|
||||
const result = profiler.stop();
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
// Additional comprehensive tests
|
||||
test("should handle constructor with no arguments", () => {
|
||||
expect(() => new GCProfiler()).not.toThrow();
|
||||
});
|
||||
|
||||
test("should handle constructor with extra arguments gracefully", () => {
|
||||
expect(() => new GCProfiler("extra", "args")).not.toThrow();
|
||||
});
|
||||
|
||||
test("should maintain consistent data format", () => {
|
||||
const profiler = new GCProfiler();
|
||||
profiler.start();
|
||||
|
||||
// Create some memory pressure
|
||||
const data = Array(500).fill(null).map((_, i) => ({
|
||||
id: i,
|
||||
payload: new Array(50).fill(Math.random())
|
||||
}));
|
||||
|
||||
const result = profiler.stop();
|
||||
|
||||
if (result) {
|
||||
expect(result).toHaveProperty("version");
|
||||
expect(result).toHaveProperty("startTime");
|
||||
expect(result).toHaveProperty("endTime");
|
||||
expect(result).toHaveProperty("statistics");
|
||||
|
||||
expect(typeof result.version).toBe("number");
|
||||
expect(typeof result.startTime).toBe("number");
|
||||
expect(typeof result.endTime).toBe("number");
|
||||
expect(Array.isArray(result.statistics)).toBe(true);
|
||||
|
||||
expect(result.version).toBeGreaterThan(0);
|
||||
expect(result.startTime).toBeGreaterThan(0);
|
||||
expect(result.endTime).toBeGreaterThan(0);
|
||||
expect(result.endTime).toBeGreaterThanOrEqual(result.startTime);
|
||||
}
|
||||
});
|
||||
|
||||
test("should handle rapid start/stop sequences", () => {
|
||||
const profiler = new GCProfiler();
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
profiler.start();
|
||||
const result = profiler.stop();
|
||||
if (result) {
|
||||
expect(result.version).toBe(1);
|
||||
expect(typeof result.startTime).toBe("number");
|
||||
expect(typeof result.endTime).toBe("number");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("should handle memory allocation patterns", () => {
|
||||
const profiler = new GCProfiler();
|
||||
profiler.start();
|
||||
|
||||
// Create various allocation patterns
|
||||
const smallObjects = Array(1000).fill(null).map(() => ({ small: true }));
|
||||
const mediumObjects = Array(100).fill(null).map(() => ({
|
||||
medium: new Array(100).fill(0)
|
||||
}));
|
||||
const largeObjects = Array(10).fill(null).map(() => ({
|
||||
large: new Array(10000).fill("data")
|
||||
}));
|
||||
|
||||
// Clear references to allow GC
|
||||
smallObjects.length = 0;
|
||||
mediumObjects.length = 0;
|
||||
largeObjects.length = 0;
|
||||
|
||||
const result = profiler.stop();
|
||||
expect(result).toBeDefined();
|
||||
if (result) {
|
||||
expect(Array.isArray(result.statistics)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("should maintain profiler state correctly", () => {
|
||||
const profiler1 = new GCProfiler();
|
||||
const profiler2 = new GCProfiler();
|
||||
|
||||
// Independent profilers should not interfere
|
||||
profiler1.start();
|
||||
|
||||
// Add small delay
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < 1) {
|
||||
// Busy wait for at least 1ms
|
||||
}
|
||||
|
||||
profiler2.start();
|
||||
|
||||
const result1 = profiler1.stop();
|
||||
const result2 = profiler2.stop();
|
||||
|
||||
// Both should return valid results
|
||||
expect(result1).toBeDefined();
|
||||
expect(result2).toBeDefined();
|
||||
|
||||
// Results should have valid properties
|
||||
if (result1 && result2) {
|
||||
expect(result1.startTime).toBeGreaterThan(0);
|
||||
expect(result2.startTime).toBeGreaterThan(0);
|
||||
expect(result1.endTime).toBeGreaterThan(0);
|
||||
expect(result2.endTime).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
test("should handle edge case of immediate stop after start", () => {
|
||||
const profiler = new GCProfiler();
|
||||
profiler.start();
|
||||
const result = profiler.stop();
|
||||
|
||||
expect(result).toBeDefined();
|
||||
if (result) {
|
||||
expect(result.endTime).toBeGreaterThanOrEqual(result.startTime);
|
||||
expect(result.version).toBe(1);
|
||||
expect(Array.isArray(result.statistics)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("should handle nested profiling attempts gracefully", () => {
|
||||
const profiler = new GCProfiler();
|
||||
|
||||
profiler.start();
|
||||
profiler.start(); // Second start should be ignored
|
||||
profiler.start(); // Third start should be ignored
|
||||
|
||||
const result = profiler.stop();
|
||||
expect(result).toBeDefined();
|
||||
|
||||
// Subsequent stops should return undefined
|
||||
const result2 = profiler.stop();
|
||||
expect(result2).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should validate statistics array structure", () => {
|
||||
const profiler = new GCProfiler();
|
||||
profiler.start();
|
||||
|
||||
// Generate some activity
|
||||
const temp = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
temp.push(new Array(100).fill(i));
|
||||
}
|
||||
temp.splice(0, temp.length);
|
||||
|
||||
const result = profiler.stop();
|
||||
|
||||
if (result && result.statistics) {
|
||||
expect(Array.isArray(result.statistics)).toBe(true);
|
||||
// Each statistic entry (if any) should be an object
|
||||
result.statistics.forEach(stat => {
|
||||
expect(typeof stat).toBe("object");
|
||||
expect(stat).not.toBeNull();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user