diff --git a/src/workerd/api/basics.c++ b/src/workerd/api/basics.c++ index 87a428eeee3..a6235133f18 100644 --- a/src/workerd/api/basics.c++ +++ b/src/workerd/api/basics.c++ @@ -768,4 +768,45 @@ CustomEvent::CustomEventInit::operator Event::Init() { }; } +size_t EventTarget::EventHandler::JavaScriptHandler::jsgGetMemorySelfSize() const { + return sizeof(JavaScriptHandler); +} + +void EventTarget::EventHandler::JavaScriptHandler::jsgGetMemoryInfo( + jsg::MemoryTracker& tracker) const { + tracker.trackField("identity", identity); + tracker.trackField("callback", callback); + if (abortHandler != kj::none) { + tracker.trackFieldWithSize("abortHandler", sizeof(kj::Own) + + sizeof(NativeHandler)); + } +} + +size_t EventTarget::EventHandler::jsgGetMemorySelfSize() const { + return sizeof(EventHandler); +} + +void EventTarget::EventHandler::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const { + KJ_SWITCH_ONEOF(handler) { + KJ_CASE_ONEOF(js, JavaScriptHandler) { + tracker.trackField("js", js); + } + KJ_CASE_ONEOF(native, NativeHandlerRef) { + tracker.trackFieldWithSize("native", sizeof(NativeHandlerRef)); + } + } +} + +size_t EventTarget::EventHandlerSet::jsgGetMemorySelfSize() const { + return sizeof(EventHandlerSet); +} + +void EventTarget::EventHandlerSet::jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("handlers", handlers, kj::none, kj::none, false); +} + +void EventTarget::visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("typeMap", typeMap); +} + } // namespace workerd::api diff --git a/src/workerd/api/basics.h b/src/workerd/api/basics.h index 405e96d3cf2..c1c8dd60c5c 100644 --- a/src/workerd/api/basics.h +++ b/src/workerd/api/basics.h @@ -148,6 +148,11 @@ class Event: public jsg::Object { JSG_STATIC_CONSTANT(BUBBLING_PHASE); } + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("type", ownType); + tracker.trackField("target", target); + } + private: kj::StringPtr type; kj::String ownType; @@ -215,6 +220,10 @@ class CustomEvent: public Event { JSG_READONLY_PROTOTYPE_PROPERTY(detail, getDetail); } + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("detail", detail); + } + private: jsg::Optional> detail; }; @@ -308,6 +317,8 @@ class EventTarget: public jsg::Object { jsg::Function)> func, bool once = false); + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const; + private: void addNativeListener(jsg::Lock& js, NativeHandler& handler); bool removeNativeListener(NativeHandler& handler); @@ -323,6 +334,10 @@ class EventTarget: public jsg::Object { void visitForGc(jsg::GcVisitor& visitor) { visitor.visit(identity, callback); } + + kj::StringPtr jsgGetMemoryName() const { return "JavaScriptHandler"_kjc; } + size_t jsgGetMemorySelfSize() const; + void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const; }; struct NativeHandlerRef { @@ -338,6 +353,10 @@ class EventTarget: public jsg::Object { // When once is true, the handler will be removed after it is invoked one time. bool once = false; + + kj::StringPtr jsgGetMemoryName() const { return "EventHandler"_kjc; } + size_t jsgGetMemorySelfSize() const; + void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const; }; struct EventHandlerHashCallbacks { @@ -360,6 +379,10 @@ class EventTarget: public jsg::Object { EventHandlerSet() : handlers(EventHandlerHashCallbacks(), {}) {} + + kj::StringPtr jsgGetMemoryName() const { return "EventHandlerSet"_kjc; } + size_t jsgGetMemorySelfSize() const; + void jsgGetMemoryInfo(jsg::MemoryTracker& tracker) const; }; EventHandlerSet& getOrCreate(kj::StringPtr str) KJ_LIFETIMEBOUND; @@ -463,6 +486,13 @@ class AbortSignal final: public EventTarget { RefcountedCanceler& getCanceler(); + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + EventTarget::visitForMemoryInfo(tracker); + tracker.trackInlineFieldWithSize("IoOwn", + sizeof(IoOwn)); + tracker.trackField("reason", reason); + } + private: IoOwn canceler; Flag flag; @@ -496,6 +526,10 @@ class AbortController final: public jsg::Object { JSG_METHOD(abort); } + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("signal", signal); + } + private: jsg::Ref signal; diff --git a/src/workerd/api/global-scope.h b/src/workerd/api/global-scope.h index ccd66c8639b..7e525dae2ea 100644 --- a/src/workerd/api/global-scope.h +++ b/src/workerd/api/global-scope.h @@ -122,6 +122,11 @@ class PromiseRejectionEvent: public Event { JSG_READONLY_INSTANCE_PROPERTY(reason, getReason); } + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("promise", promise); + tracker.trackField("reason", reason); + } + private: jsg::V8Ref promise; jsg::Value reason; @@ -656,6 +661,10 @@ class ServiceWorkerGlobalScope: public WorkerGlobalScope { TimeoutId::Generator timeoutIdGenerator; // The generator for all timeout IDs associated with this scope. + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("unhandledRejections", unhandledRejections); + } + private: jsg::UnhandledRejectionHandler unhandledRejections; diff --git a/src/workerd/api/gpu/gpu-device.h b/src/workerd/api/gpu/gpu-device.h index 54288fb5a70..42fdd843f7a 100644 --- a/src/workerd/api/gpu/gpu-device.h +++ b/src/workerd/api/gpu/gpu-device.h @@ -120,6 +120,10 @@ class GPUUncapturedErrorEvent : public Event { JSG_READONLY_INSTANCE_PROPERTY(error, getError); } + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("error", error_); + } + private: jsg::Ref error_; diff --git a/src/workerd/api/web-socket.h b/src/workerd/api/web-socket.h index 754e37ac978..382bd32e88f 100644 --- a/src/workerd/api/web-socket.h +++ b/src/workerd/api/web-socket.h @@ -67,6 +67,10 @@ class MessageEvent: public Event { }); } + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("data", data); + } + private: jsg::JsRef data; @@ -112,6 +116,10 @@ class CloseEvent: public Event { // CloseEvent will be referenced from the `WebSocketEventMap` define } + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("reason", reason); + } + private: int code; kj::String reason; @@ -147,6 +155,11 @@ class ErrorEvent: public Event { // ErrorEvent will be referenced from the `WebSocketEventMap` define } + void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { + tracker.trackField("message", message); + tracker.trackField("error", error); + } + private: kj::String message; jsg::JsRef error; diff --git a/src/workerd/jsg/iterator.h b/src/workerd/jsg/iterator.h index cf16ef7427e..33801769e6c 100644 --- a/src/workerd/jsg/iterator.h +++ b/src/workerd/jsg/iterator.h @@ -813,7 +813,7 @@ class AsyncIteratorBase: public Object { if constexpr (MemoryRetainer) { tracker.trackField("state", state); } else { - tracker.trackField("state", sizeof(State)); + tracker.trackFieldWithSize("state", sizeof(State)); } tracker.trackField("impl", impl); } diff --git a/src/workerd/jsg/memory.h b/src/workerd/jsg/memory.h index 174f723a446..09dc810d773 100644 --- a/src/workerd/jsg/memory.h +++ b/src/workerd/jsg/memory.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -159,6 +160,19 @@ class MemoryTracker final { kj::Maybe elementName = kj::none, bool subtractFromSelf = true) KJ_LIFETIMEBOUND; + template + MemoryTracker& trackField( + kj::StringPtr edgeName, + const kj::HashMap& value, + kj::Maybe nodeName = kj::none) KJ_LIFETIMEBOUND; + + // template + // MemoryTracker& trackField( + // kj::StringPtr edgeName, + // const Key& key, + // const Value& value, + // kj::Maybe nodeName = kj::none) KJ_LIFETIMEBOUND; + template inline MemoryTracker& trackField( kj::StringPtr edgeName, @@ -195,15 +209,6 @@ class MemoryTracker final { const std::basic_string& value, kj::Maybe nodeName = kj::none) KJ_LIFETIMEBOUND; - template ::is_specialized, bool>::type, - typename dummy = bool> - inline MemoryTracker& trackField( - kj::StringPtr edgeName, - const T& value, - kj::Maybe nodeName = kj::none) KJ_LIFETIMEBOUND; - template inline MemoryTracker& trackField( kj::StringPtr edgeName, @@ -243,38 +248,16 @@ class MemoryTracker final { KJ_DISALLOW_COPY_AND_MOVE(MemoryTracker); private: - - struct NodeMapEntry { - const void* retainer; - MemoryRetainerNode* node; - }; - - struct NodeMapCallbacks { - const void* keyForRow(const NodeMapEntry& row) const { - return row.retainer; - } - bool matches(const NodeMapEntry& row, const void* key) const { - return row.retainer == key; - } - kj::uint hashCode(const void* row) const { - return kj::hashCode(row); - } - }; - - using NodeMap = kj::Table, - kj::InsertionOrderIndex>; - v8::Isolate* isolate_; v8::EmbedderGraph* graph_; std::stack nodeStack_; - NodeMap seen_; + kj::HashMap seen_; KJ_DISALLOW_AS_COROUTINE_PARAM; inline explicit MemoryTracker(v8::Isolate* isolate, v8::EmbedderGraph* graph) : isolate_(isolate), - graph_(graph), - seen_(NodeMapCallbacks(), {}) {} + graph_(graph) {} inline kj::Maybe getCurrentNode() const; @@ -311,7 +294,9 @@ class MemoryRetainerNode final : public v8::EmbedderGraph::Node { const char* NamePrefix() override { return PREFIX; } - size_t SizeInBytes() override { return size_; } + size_t SizeInBytes() override { + return size_; + } bool IsRootNode() override { KJ_IF_SOME(check, checkIsRootNode) { @@ -346,8 +331,8 @@ class MemoryRetainerNode final : public v8::EmbedderGraph::Node { template inline MemoryRetainerNode(MemoryTracker* tracker, const T* retainer) - : retainer_(retainer) { - KJ_ASSERT(retainer_ != nullptr); + : name_(retainer->jsgGetMemoryName()), + size_(retainer->jsgGetMemorySelfSize()) { v8::HandleScope handle_scope(tracker->isolate()); if constexpr (MemoryRetainerObject) { v8::Local obj = @@ -358,8 +343,6 @@ class MemoryRetainerNode final : public v8::EmbedderGraph::Node { checkIsRootNode = [retainer]() { return retainer->jsgGetMemoryInfoIsRootNode(); }; } - name_ = retainer->jsgGetMemoryName(); - size_ = retainer->jsgGetMemorySelfSize(); if constexpr (MemoryRetainerDetachedState) { detachedness_ = fromDetachedState(retainer->jsgGetMemoryInfoDetachedState()); } @@ -369,18 +352,17 @@ class MemoryRetainerNode final : public v8::EmbedderGraph::Node { kj::StringPtr name, size_t size, bool isRootNode = false) - : isRootNode_(isRootNode), - name_(name), - size_(size) {} + : name_(name), + size_(size), + isRootNode_(isRootNode) {} - const void* retainer_ = nullptr; + kj::StringPtr name_; + size_t size_ = 0; v8::EmbedderGraph::Node* wrapper_node_ = nullptr; kj::Maybe> checkIsRootNode = kj::none; bool isRootNode_ = false; - kj::StringPtr name_; - size_t size_ = 0; v8::EmbedderGraph::Node::Detachedness detachedness_ = v8::EmbedderGraph::Node::Detachedness::kUnknown; @@ -405,7 +387,7 @@ MemoryTracker& MemoryTracker::trackInlineFieldWithSize( kj::StringPtr edgeName, size_t size, kj::Maybe nodeName) { if (size > 0) addNode(getNodeName(nodeName, edgeName), size, edgeName); - KJ_ASSERT_NONNULL(getCurrentNode()).size_ = size; + KJ_ASSERT_NONNULL(getCurrentNode()).size_ -= size; return *this; } @@ -505,6 +487,23 @@ MemoryTracker& MemoryTracker::trackField( return *this; } +template +MemoryTracker& MemoryTracker::trackField( + kj::StringPtr edgeName, + const kj::HashMap& value, + kj::Maybe nodeName) { + if (value.size() == 0) return *this; + pushNode(getNodeName(nodeName, edgeName), + sizeof(kj::HashMap), edgeName); + + for (const auto& entry : value) { + trackField("key", entry.key); + trackField("value", entry.value); + } + popNode(); + return *this; +} + template MemoryTracker& MemoryTracker::trackField( kj::StringPtr edgeName, @@ -587,9 +586,9 @@ MemoryTracker& MemoryTracker::trackField( if (value == nullptr) return *this; KJ_IF_SOME(found, seen_.find(value)) { KJ_IF_SOME(currentNode, getCurrentNode()) { - graph_->AddEdge(¤tNode, found.node, edgeName.cStr()); + graph_->AddEdge(¤tNode, found, edgeName.cStr()); } else { - graph_->AddEdge(nullptr, found.node, edgeName.cStr()); + graph_->AddEdge(nullptr, found, edgeName.cStr()); } return *this; } @@ -597,15 +596,6 @@ MemoryTracker& MemoryTracker::trackField( return track(value, edgeName); } -template -MemoryTracker& MemoryTracker::trackField( - kj::StringPtr edgeName, - const T& value, - kj::Maybe nodeName) { - KJ_ASSERT_NONNULL(getCurrentNode()).size_ += sizeof(T); - return *this; -} - template MemoryTracker& MemoryTracker::trackField( kj::StringPtr edgeName, @@ -653,9 +643,9 @@ MemoryTracker& MemoryTracker::track(const T* retainer, kj::Maybe KJ_IF_SOME(found, seen_.find(retainer)) { KJ_IF_SOME(currentNode, getCurrentNode()) { KJ_IF_SOME(name, edgeName) { - graph_->AddEdge(¤tNode, found.node, name.cStr()); + graph_->AddEdge(¤tNode, found, name.cStr()); } else { - graph_->AddEdge(¤tNode, found.node, nullptr); + graph_->AddEdge(¤tNode, found, nullptr); } } return *this; @@ -689,11 +679,11 @@ MemoryTracker& MemoryTracker::trackInlineField( template MemoryRetainerNode* MemoryTracker::addNode(const T* retainer, kj::Maybe edgeName) { - KJ_IF_SOME(found, seen_.find(retainer)) { return found.node; } + KJ_IF_SOME(found, seen_.find(retainer)) { return found; } MemoryRetainerNode* n = new MemoryRetainerNode(this, retainer); graph_->AddNode(std::unique_ptr(n)); - seen_.upsert({retainer, n},[](auto&,auto&&) {}); + seen_.insert(retainer, n); KJ_IF_SOME(currentNode, getCurrentNode()) { KJ_IF_SOME(name, edgeName) {