Web Inspector: partially instrument DOM Tree native memory.
[WebKit-https.git] / Source / WebCore / inspector / InspectorMemoryAgent.cpp
index 5e64188..e396542 100644 (file)
  */
 
 #include "config.h"
-#include "InspectorMemoryAgent.h"
 
 #if ENABLE(INSPECTOR)
 
+#include "InspectorMemoryAgent.h"
+
+#include "CharacterData.h"
 #include "DOMWrapperVisitor.h"
 #include "Document.h"
+#include "EventListenerMap.h"
 #include "Frame.h"
+#include "InspectorFrontend.h"
 #include "InspectorState.h"
 #include "InspectorValues.h"
 #include "InstrumentingAgents.h"
+#include "MemoryCache.h"
+#include "MemoryInstrumentation.h"
+#include "MemoryUsageSupport.h"
 #include "Node.h"
 #include "Page.h"
+#include "ScriptGCEvent.h"
 #include "ScriptProfiler.h"
 #include "StyledElement.h"
 #include <wtf/HashSet.h>
 #include <wtf/text/StringBuilder.h>
 
+using WebCore::TypeBuilder::Memory::DOMGroup;
+using WebCore::TypeBuilder::Memory::ListenerCount;
+using WebCore::TypeBuilder::Memory::NodeCount;
+using WebCore::TypeBuilder::Memory::StringStatistics;
+
+// Use a type alias instead of 'using' here which would cause a conflict on Mac.
+typedef WebCore::TypeBuilder::Memory::MemoryBlock InspectorMemoryBlock;
+
 namespace WebCore {
 
+namespace MemoryBlockName {
+static const char jsHeapAllocated[] = "JSHeapAllocated";
+static const char jsHeapUsed[] = "JSHeapUsed";
+static const char inspectorData[] = "InspectorData";
+static const char memoryCache[] = "MemoryCache";
+static const char processPrivateMemory[] = "ProcessPrivateMemory";
+
+static const char cachedImages[] = "CachedImages";
+static const char cachedCssStyleSheets[] = "CachedCssStyleSheets";
+static const char cachedScripts[] = "CachedScripts";
+static const char cachedXslStyleSheets[] = "CachedXslStyleSheets";
+static const char cachedFonts[] = "CachedFonts";
+static const char renderTreeUsed[] = "RenderTreeUsed";
+static const char renderTreeAllocated[] = "RenderTreeAllocated";
+
+static const char dom[] = "DOM";
+static const char domTreeOther[] = "DOMTreeOther";
+static const char domTreeDOM[] = "DOMTreeDOM";
+static const char domTreeCSS[] = "DOMTreeCSS";
+}
+
 namespace {
 
+String nodeName(Node* node)
+{
+    if (node->document()->isXHTMLDocument())
+         return node->nodeName();
+    return node->nodeName().lower();
+}
+
+int stringSize(StringImpl* string)
+{
+    int size = string->length();
+    if (!string->is8Bit())
+        size *= 2;
+    return size + sizeof(*string);
+}
+
+typedef HashSet<StringImpl*, PtrHash<StringImpl*> > StringImplIdentitySet;
+
+class CharacterDataStatistics {
+    WTF_MAKE_NONCOPYABLE(CharacterDataStatistics);
+public:
+    CharacterDataStatistics() : m_characterDataSize(0) { }
+
+    void collectCharacterData(Node* node)
+    {
+        if (!node->isCharacterDataNode())
+            return;
+
+        CharacterData* characterData = static_cast<CharacterData*>(node);
+        StringImpl* dataImpl = characterData->dataImpl();
+        if (m_domStringImplSet.contains(dataImpl))
+            return;
+        m_domStringImplSet.add(dataImpl);
+
+        m_characterDataSize += stringSize(dataImpl);
+    }
+
+    bool contains(StringImpl* s) { return m_domStringImplSet.contains(s); }
+
+    int characterDataSize() { return m_characterDataSize; }
+
+private:
+    StringImplIdentitySet m_domStringImplSet;
+    int m_characterDataSize;
+};
+
+class DOMTreeStatistics {
+    WTF_MAKE_NONCOPYABLE(DOMTreeStatistics);
+public:
+    DOMTreeStatistics(Node* rootNode, CharacterDataStatistics& characterDataStatistics)
+        : m_totalNodeCount(0)
+        , m_characterDataStatistics(characterDataStatistics)
+    {
+        collectTreeStatistics(rootNode);
+    }
+
+    int totalNodeCount() { return m_totalNodeCount; }
+
+    PassRefPtr<TypeBuilder::Array<TypeBuilder::Memory::NodeCount> > nodeCount()
+    {
+        RefPtr<TypeBuilder::Array<TypeBuilder::Memory::NodeCount> > childrenStats = TypeBuilder::Array<TypeBuilder::Memory::NodeCount>::create();
+        for (HashMap<String, int>::iterator it = m_nodeNameToCount.begin(); it != m_nodeNameToCount.end(); ++it) {
+            RefPtr<NodeCount> nodeCount = NodeCount::create().setNodeName(it->first)
+                                                             .setCount(it->second);
+            childrenStats->addItem(nodeCount);
+        }
+        return childrenStats.release();
+    }
+
+    PassRefPtr<TypeBuilder::Array<TypeBuilder::Memory::ListenerCount> > listenerCount()
+    {
+        RefPtr<TypeBuilder::Array<TypeBuilder::Memory::ListenerCount> > listenerStats = TypeBuilder::Array<TypeBuilder::Memory::ListenerCount>::create();
+        for (HashMap<AtomicString, int>::iterator it = m_eventTypeToCount.begin(); it != m_eventTypeToCount.end(); ++it) {
+            RefPtr<ListenerCount> listenerCount = ListenerCount::create().setType(it->first)
+                                                                         .setCount(it->second);
+            listenerStats->addItem(listenerCount);
+        }
+        return listenerStats.release();
+    }
+
+private:
+    void collectTreeStatistics(Node* rootNode)
+    {
+        Node* currentNode = rootNode;
+        collectListenersInfo(rootNode);
+        while ((currentNode = currentNode->traverseNextNode(rootNode))) {
+            ++m_totalNodeCount;
+            collectNodeStatistics(currentNode);
+        }
+    }
+    void collectNodeStatistics(Node* node)
+    {
+        m_characterDataStatistics.collectCharacterData(node);
+        collectNodeNameInfo(node);
+        collectListenersInfo(node);
+    }
+
+    void collectNodeNameInfo(Node* node)
+    {
+        String name = nodeName(node);
+        int currentCount = m_nodeNameToCount.get(name);
+        m_nodeNameToCount.set(name, currentCount + 1);
+    }
+
+    void collectListenersInfo(Node* node)
+    {
+        EventTargetData* d = node->eventTargetData();
+        if (!d)
+            return;
+        EventListenerMap& eventListenerMap = d->eventListenerMap;
+        if (eventListenerMap.isEmpty())
+            return;
+        Vector<AtomicString> eventNames = eventListenerMap.eventTypes();
+        for (Vector<AtomicString>::iterator it = eventNames.begin(); it != eventNames.end(); ++it) {
+            AtomicString name = *it;
+            EventListenerVector* listeners = eventListenerMap.find(name);
+            int count = 0;
+            for (EventListenerVector::iterator j = listeners->begin(); j != listeners->end(); ++j) {
+                if (j->listener->type() == EventListener::JSEventListenerType)
+                    ++count;
+            }
+            if (count)
+                m_eventTypeToCount.set(name, m_eventTypeToCount.get(name) + count);
+        }
+    }
+
+    int m_totalNodeCount;
+    HashMap<AtomicString, int> m_eventTypeToCount;
+    HashMap<String, int> m_nodeNameToCount;
+    CharacterDataStatistics& m_characterDataStatistics;
+};
+
 class CounterVisitor : public DOMWrapperVisitor {
 public:
-    CounterVisitor(Page* page) : m_page(page), m_counters(InspectorArray::create()) { }
+    CounterVisitor(Page* page)
+        : m_page(page)
+        , m_domGroups(TypeBuilder::Array<TypeBuilder::Memory::DOMGroup>::create())
+        , m_jsExternalStringSize(0)
+        , m_sharedStringSize(0) { }
+
+    TypeBuilder::Array<TypeBuilder::Memory::DOMGroup>* domGroups() { return m_domGroups.get(); }
 
-    InspectorArray* counters() { return m_counters.get(); }
+    PassRefPtr<StringStatistics> strings()
+    {
+        RefPtr<StringStatistics> stringStatistics = StringStatistics::create()
+            .setDom(m_characterDataStatistics.characterDataSize())
+            .setJs(m_jsExternalStringSize)
+            .setShared(m_sharedStringSize);
+        return stringStatistics.release();
+    }
 
     virtual void visitNode(Node* node)
     {
@@ -69,46 +250,28 @@ public:
             return;
         m_roots.add(rootNode);
 
-        RefPtr<InspectorObject> entry = InspectorObject::create();
-        entry->setString("title", rootNode->nodeType() == Node::ELEMENT_NODE ? elementTitle(static_cast<Element*>(rootNode)) : rootNode->nodeName());
-        if (rootNode->nodeType() == Node::DOCUMENT_NODE)
-            entry->setString("documentURI", static_cast<Document*>(rootNode)->documentURI());
-        collectTreeStatistics(rootNode, entry.get());
-        m_counters->pushObject(entry);
-    }
-
-private:
-    static void collectTreeStatistics(Node* rootNode, InspectorObject* result)
-    {
-        unsigned count = 0;
-        HashMap<String, int> nameToCount;
-        Node* currentNode = rootNode;
-        while ((currentNode = currentNode->traverseNextNode(rootNode))) {
-            ++count;
-            String name = nodeName(currentNode);
-            int currentCount = nameToCount.get(name);
-            nameToCount.set(name, currentCount + 1);
-        }
+        DOMTreeStatistics domTreeStats(rootNode, m_characterDataStatistics);
 
-        RefPtr<InspectorArray> childrenStats = InspectorArray::create();
-        for (HashMap<String, int>::iterator it = nameToCount.begin(); it != nameToCount.end(); ++it) {
-            RefPtr<InspectorObject> nodeCount = InspectorObject::create();
-            nodeCount->setString("nodeName", it->first);
-            nodeCount->setNumber("count", it->second);
-            childrenStats->pushObject(nodeCount);
-        }
+        RefPtr<DOMGroup> domGroup = DOMGroup::create()
+            .setSize(domTreeStats.totalNodeCount())
+            .setTitle(rootNode->nodeType() == Node::ELEMENT_NODE ? elementTitle(static_cast<Element*>(rootNode)) : rootNode->nodeName())
+            .setNodeCount(domTreeStats.nodeCount())
+            .setListenerCount(domTreeStats.listenerCount());
+        if (rootNode->nodeType() == Node::DOCUMENT_NODE)
+            domGroup->setDocumentURI(static_cast<Document*>(rootNode)->documentURI());
 
-        result->setNumber("size", count);
-        result->setArray("nodeCount", childrenStats);
+        m_domGroups->addItem(domGroup);
     }
 
-    static String nodeName(Node* node)
+    virtual void visitJSExternalString(StringImpl* string)
     {
-        if (node->document()->isXHTMLDocument())
-             return node->nodeName();
-        return node->nodeName().lower();
+        int size = stringSize(string);
+        m_jsExternalStringSize += size;
+        if (m_characterDataStatistics.contains(string))
+            m_sharedStringSize += size;
     }
 
+private:
     String elementTitle(Element* element)
     {
         StringBuilder result;
@@ -139,7 +302,10 @@ private:
 
     HashSet<Node*> m_roots;
     Page* m_page;
-    RefPtr<InspectorArray> m_counters;
+    RefPtr<TypeBuilder::Array<TypeBuilder::Memory::DOMGroup> > m_domGroups;
+    CharacterDataStatistics m_characterDataStatistics;
+    int m_jsExternalStringSize;
+    int m_sharedStringSize;
 };
 
 } // namespace
@@ -148,7 +314,7 @@ InspectorMemoryAgent::~InspectorMemoryAgent()
 {
 }
 
-void InspectorMemoryAgent::getDOMNodeCount(ErrorString*, RefPtr<InspectorArray>* result)
+void InspectorMemoryAgent::getDOMNodeCount(ErrorString*, RefPtr<TypeBuilder::Array<TypeBuilder::Memory::DOMGroup> >& domGroups, RefPtr<TypeBuilder::Memory::StringStatistics>& strings)
 {
     CounterVisitor counterVisitor(m_page);
     ScriptProfiler::visitJSDOMWrappers(&counterVisitor);
@@ -159,13 +325,183 @@ void InspectorMemoryAgent::getDOMNodeCount(ErrorString*, RefPtr<InspectorArray>*
             counterVisitor.visitNode(doc);
     }
 
-    *result = counterVisitor.counters();
+    ScriptProfiler::visitExternalJSStrings(&counterVisitor);
+
+    domGroups = counterVisitor.domGroups();
+    strings = counterVisitor.strings();
+}
+
+static PassRefPtr<InspectorMemoryBlock> jsHeapInfo()
+{
+    size_t usedJSHeapSize;
+    size_t totalJSHeapSize;
+    size_t jsHeapSizeLimit;
+    ScriptGCEvent::getHeapSize(usedJSHeapSize, totalJSHeapSize, jsHeapSizeLimit);
+
+    RefPtr<InspectorMemoryBlock> jsHeapAllocated = InspectorMemoryBlock::create().setName(MemoryBlockName::jsHeapAllocated);
+    jsHeapAllocated->setSize(totalJSHeapSize);
+
+    RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > children = TypeBuilder::Array<InspectorMemoryBlock>::create();
+    RefPtr<InspectorMemoryBlock> jsHeapUsed = InspectorMemoryBlock::create().setName(MemoryBlockName::jsHeapUsed);
+    jsHeapUsed->setSize(usedJSHeapSize);
+    children->addItem(jsHeapUsed);
+
+    jsHeapAllocated->setChildren(children);
+    return jsHeapAllocated.release();
+}
+
+static PassRefPtr<InspectorMemoryBlock> inspectorData()
+{
+    size_t dataSize = ScriptProfiler::profilerSnapshotsSize();
+    RefPtr<InspectorMemoryBlock> inspectorData = InspectorMemoryBlock::create().setName(MemoryBlockName::inspectorData);
+    inspectorData->setSize(dataSize);
+    return inspectorData.release();
+}
+
+static PassRefPtr<InspectorMemoryBlock> renderTreeInfo(Page* page)
+{
+    ArenaSize arenaSize = page->renderTreeSize();
+
+    RefPtr<InspectorMemoryBlock> renderTreeAllocated = InspectorMemoryBlock::create().setName(MemoryBlockName::renderTreeAllocated);
+    renderTreeAllocated->setSize(arenaSize.allocated);
+
+    RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > children = TypeBuilder::Array<InspectorMemoryBlock>::create();
+    RefPtr<InspectorMemoryBlock> renderTreeUsed = InspectorMemoryBlock::create().setName(MemoryBlockName::renderTreeUsed);
+    renderTreeUsed->setSize(arenaSize.treeSize);
+    children->addItem(renderTreeUsed);
+
+    renderTreeAllocated->setChildren(children);
+    return renderTreeAllocated.release();
+}
+
+static void addMemoryBlockFor(TypeBuilder::Array<InspectorMemoryBlock>* array, size_t size, const char* name)
+{
+    RefPtr<InspectorMemoryBlock> result = InspectorMemoryBlock::create().setName(name);
+    result->setSize(size);
+    array->addItem(result);
+}
+
+namespace {
+
+class MemoryInstrumentationImpl : public MemoryInstrumentation {
+public:
+    MemoryInstrumentationImpl()
+    {
+        for (int i = 0; i < LastTypeEntry; ++i)
+            m_totalSizes[i] = 0;
+    }
+
+    PassRefPtr<InspectorMemoryBlock> dumpStatistics()
+    {
+        size_t totalSize = 0;
+        for (int i = Other; i < LastTypeEntry; ++i)
+            totalSize += m_totalSizes[i];
+
+        RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > domChildren = TypeBuilder::Array<InspectorMemoryBlock>::create();
+        addMemoryBlockFor(domChildren.get(), m_totalSizes[Other], MemoryBlockName::domTreeOther);
+        addMemoryBlockFor(domChildren.get(), m_totalSizes[DOM], MemoryBlockName::domTreeDOM);
+        addMemoryBlockFor(domChildren.get(), m_totalSizes[CSS], MemoryBlockName::domTreeCSS);
+
+        RefPtr<InspectorMemoryBlock> dom = InspectorMemoryBlock::create().setName(MemoryBlockName::dom);
+        dom->setSize(totalSize);
+        dom->setChildren(domChildren.release());
+        return dom.release();
+    }
+
+private:
+    virtual void countObjectSize(ObjectType objectType, size_t size)
+    {
+        ASSERT(objectType >= 0 && objectType < LastTypeEntry);
+        m_totalSizes[objectType] += size;
+    }
+
+    virtual bool visited(const void* object)
+    {
+        return !m_visitedObjects.add(object).isNewEntry;
+    }
+    size_t m_totalSizes[LastTypeEntry];
+    typedef HashSet<const void*> VisitedObjects;
+    VisitedObjects m_visitedObjects;
+};
+
+class DOMTreesIterator : public DOMWrapperVisitor {
+public:
+    explicit DOMTreesIterator(Page* page) : m_page(page) { }
+
+    virtual void visitNode(Node* node)
+    {
+        if (node->document() && node->document()->frame() && m_page != node->document()->frame()->page())
+            return;
+
+        m_domMemoryUsage.reportInstrumentedPointer(node);
+    }
+
+    virtual void visitJSExternalString(StringImpl*) { }
+
+    PassRefPtr<InspectorMemoryBlock> dumpStatistics() { return m_domMemoryUsage.dumpStatistics(); }
+
+private:
+    Page* m_page;
+    MemoryInstrumentationImpl m_domMemoryUsage;
+};
+
+}
+
+static PassRefPtr<InspectorMemoryBlock> domTreeInfo(Page* page)
+{
+    DOMTreesIterator domTreesIterator(page);
+    ScriptProfiler::visitJSDOMWrappers(&domTreesIterator);
+
+    // Make sure all documents reachable from the main frame are accounted.
+    for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
+        if (Document* doc = frame->document())
+            domTreesIterator.visitNode(doc);
+    }
+
+    return domTreesIterator.dumpStatistics();
+}
+
+static PassRefPtr<InspectorMemoryBlock> memoryCacheInfo()
+{
+    MemoryCache::Statistics stats = memoryCache()->getStatistics();
+    int totalSize = stats.images.size +
+                    stats.cssStyleSheets.size +
+                    stats.scripts.size +
+                    stats.xslStyleSheets.size +
+                    stats.fonts.size;
+    RefPtr<InspectorMemoryBlock> memoryCacheStats = InspectorMemoryBlock::create().setName(MemoryBlockName::memoryCache);
+    memoryCacheStats->setSize(totalSize);
+
+    RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > children = TypeBuilder::Array<InspectorMemoryBlock>::create();
+    addMemoryBlockFor(children.get(), stats.images.size, MemoryBlockName::cachedImages);
+    addMemoryBlockFor(children.get(), stats.cssStyleSheets.size, MemoryBlockName::cachedCssStyleSheets);
+    addMemoryBlockFor(children.get(), stats.scripts.size, MemoryBlockName::cachedScripts);
+    addMemoryBlockFor(children.get(), stats.xslStyleSheets.size, MemoryBlockName::cachedXslStyleSheets);
+    addMemoryBlockFor(children.get(), stats.fonts.size, MemoryBlockName::cachedFonts);
+    memoryCacheStats->setChildren(children.get());
+    return memoryCacheStats.release();
+}
+
+void InspectorMemoryAgent::getProcessMemoryDistribution(ErrorString*, RefPtr<InspectorMemoryBlock>& processMemory)
+{
+    size_t privateBytes = 0;
+    size_t sharedBytes = 0;
+    MemoryUsageSupport::processMemorySizesInBytes(&privateBytes, &sharedBytes);
+    processMemory = InspectorMemoryBlock::create().setName(MemoryBlockName::processPrivateMemory);
+    processMemory->setSize(privateBytes);
+
+    RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > children = TypeBuilder::Array<InspectorMemoryBlock>::create();
+    children->addItem(jsHeapInfo());
+    children->addItem(inspectorData());
+    children->addItem(memoryCacheInfo());
+    children->addItem(renderTreeInfo(m_page)); // TODO: collect for all pages?
+    children->addItem(domTreeInfo(m_page)); // TODO: collect for all pages?
+    processMemory->setChildren(children);
 }
 
-InspectorMemoryAgent::InspectorMemoryAgent(InstrumentingAgents* instrumentingAgents, InspectorState* state, Page* page, InspectorDOMAgent* domAgent)
+InspectorMemoryAgent::InspectorMemoryAgent(InstrumentingAgents* instrumentingAgents, InspectorState* state, Page* page, InspectorDOMAgent*)
     : InspectorBaseAgent<InspectorMemoryAgent>("Memory", instrumentingAgents, state)
     , m_page(page)
-    , m_domAgent(domAgent)
 {
 }