| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "core/inspector/InspectorMemoryAgent.h" |
| |
| #include "InspectorFrontend.h" |
| #include "bindings/v8/ScriptGCEvent.h" |
| #include "bindings/v8/ScriptProfiler.h" |
| #include "core/dom/CharacterData.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/EventListenerMap.h" |
| #include "core/dom/Node.h" |
| #include "core/dom/NodeTraversal.h" |
| #include "core/dom/StyledElement.h" |
| #include "core/inspector/BindingVisitors.h" |
| #include "core/inspector/HeapGraphSerializer.h" |
| #include "core/inspector/InspectorClient.h" |
| #include "core/inspector/InspectorDOMStorageAgent.h" |
| #include "core/inspector/InspectorState.h" |
| #include "core/inspector/InspectorValues.h" |
| #include "core/inspector/InstrumentingAgents.h" |
| #include "core/inspector/MemoryInstrumentationImpl.h" |
| #include "core/loader/cache/MemoryCache.h" |
| #include "core/page/Frame.h" |
| #include "core/page/Page.h" |
| #include "core/platform/MemoryUsageSupport.h" |
| #include <wtf/ArrayBufferView.h> |
| #include <wtf/HashSet.h> |
| #include <wtf/MemoryInstrumentationArrayBufferView.h> |
| #include <wtf/NonCopyingSort.h> |
| #include <wtf/OwnPtr.h> |
| #include <wtf/PassOwnPtr.h> |
| #include <wtf/text/StringBuilder.h> |
| #include <wtf/text/StringImpl.h> |
| #include <wtf/text/WTFString.h> |
| #include <wtf/Vector.h> |
| |
| // Use a type alias instead of 'using' here which would cause a conflict on Mac. |
| typedef WebCore::TypeBuilder::Memory::MemoryBlock InspectorMemoryBlock; |
| typedef WebCore::TypeBuilder::Array<InspectorMemoryBlock> InspectorMemoryBlocks; |
| |
| namespace WebCore { |
| |
| namespace { |
| |
| class MemoryUsageStatsGenerator { |
| public: |
| MemoryUsageStatsGenerator() { } |
| |
| void dump(const TypeNameToSizeMap& sizesMap, InspectorMemoryBlocks* children) |
| { |
| m_sizesMap = sizesMap; |
| |
| // FIXME: We filter out Rendering type because the coverage is not good enough at the moment |
| // and report RenderArena size instead. |
| for (TypeNameToSizeMap::iterator i = m_sizesMap.begin(); i != m_sizesMap.end(); ++i) { |
| if (i->key == PlatformMemoryTypes::Rendering) { |
| m_sizesMap.remove(i); |
| break; |
| } |
| } |
| Vector<String> objectTypes; |
| objectTypes.appendRange(m_sizesMap.keys().begin(), m_sizesMap.keys().end()); |
| |
| for (Vector<String>::const_iterator i = objectTypes.begin(); i != objectTypes.end(); ++i) |
| updateParentSizes(*i, m_sizesMap.get(*i)); |
| |
| objectTypes.clear(); |
| objectTypes.appendRange(m_sizesMap.keys().begin(), m_sizesMap.keys().end()); |
| nonCopyingSort(objectTypes.begin(), objectTypes.end(), stringCompare); |
| |
| size_t index = 0; |
| while (index < objectTypes.size()) |
| index = buildObjectForIndex(index, objectTypes, children); |
| |
| } |
| |
| private: |
| static bool stringCompare(const String& a, const String& b) { return WTF::codePointCompare(a, b) < 0; } |
| |
| void updateParentSizes(String objectType, const size_t size) |
| { |
| for (size_t dotPosition = objectType.reverseFind('.'); dotPosition != notFound; dotPosition = objectType.reverseFind('.', dotPosition)) { |
| objectType = objectType.substring(0, dotPosition); |
| TypeNameToSizeMap::AddResult result = m_sizesMap.add(objectType, size); |
| if (!result.isNewEntry) |
| result.iterator->value += size; |
| } |
| } |
| |
| size_t buildObjectForIndex(size_t index, const Vector<String>& objectTypes, InspectorMemoryBlocks* array) |
| { |
| String typeName = objectTypes[index]; |
| size_t dotPosition = typeName.reverseFind('.'); |
| String blockName = (dotPosition == notFound) ? typeName : typeName.substring(dotPosition + 1); |
| RefPtr<InspectorMemoryBlock> block = InspectorMemoryBlock::create().setName(blockName); |
| block->setSize(m_sizesMap.get(typeName)); |
| String prefix = typeName; |
| prefix.append('.'); |
| array->addItem(block); |
| ++index; |
| RefPtr<InspectorMemoryBlocks> children; |
| while (index < objectTypes.size() && objectTypes[index].startsWith(prefix)) { |
| if (!children) |
| children = InspectorMemoryBlocks::create(); |
| index = buildObjectForIndex(index, objectTypes, children.get()); |
| } |
| if (children) |
| block->setChildren(children.release()); |
| return index; |
| } |
| |
| TypeNameToSizeMap m_sizesMap; |
| }; |
| |
| class ExternalStringsRoot : public ExternalStringVisitor { |
| public: |
| ExternalStringsRoot() : m_memoryClassInfo(0) { } |
| |
| void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const |
| { |
| MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::ExternalStrings); |
| m_memoryClassInfo = &info; |
| ScriptProfiler::visitExternalStrings(const_cast<ExternalStringsRoot*>(this)); |
| m_memoryClassInfo = 0; |
| info.ignoreMember(m_memoryClassInfo); |
| } |
| |
| private: |
| virtual void visitJSExternalString(StringImpl* string) |
| { |
| m_memoryClassInfo->addMember(string, "externalString", WTF::RetainingPointer); |
| } |
| |
| mutable MemoryClassInfo* m_memoryClassInfo; |
| }; |
| |
| class ExternalArraysRoot : public ExternalArrayVisitor { |
| public: |
| ExternalArraysRoot() : m_memoryClassInfo(0) { } |
| |
| void reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const |
| { |
| MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::ExternalArrays); |
| m_memoryClassInfo = &info; |
| ScriptProfiler::visitExternalArrays(const_cast<ExternalArraysRoot*>(this)); |
| m_memoryClassInfo = 0; |
| info.ignoreMember(m_memoryClassInfo); |
| } |
| |
| private: |
| virtual void visitJSExternalArray(ArrayBufferView* arrayBufferView) |
| { |
| m_memoryClassInfo->addMember(arrayBufferView, "externalArray", WTF::RetainingPointer); |
| } |
| |
| mutable MemoryClassInfo* m_memoryClassInfo; |
| }; |
| |
| } // namespace |
| |
| InspectorMemoryAgent::~InspectorMemoryAgent() |
| { |
| } |
| |
| void InspectorMemoryAgent::getDOMCounters(ErrorString*, int* documents, int* nodes, int* jsEventListeners) |
| { |
| *documents = InspectorCounters::counterValue(InspectorCounters::DocumentCounter); |
| *nodes = InspectorCounters::counterValue(InspectorCounters::NodeCounter); |
| *jsEventListeners = ThreadLocalInspectorCounters::current().counterValue(ThreadLocalInspectorCounters::JSEventListenerCounter); |
| } |
| |
| static void reportJSHeapInfo(WTF::MemoryInstrumentationClient& memoryInstrumentationClient) |
| { |
| HeapInfo info; |
| ScriptGCEvent::getHeapSize(info); |
| |
| memoryInstrumentationClient.countObjectSize(0, WebCoreMemoryTypes::JSHeapUsed, info.usedJSHeapSize); |
| memoryInstrumentationClient.countObjectSize(0, WebCoreMemoryTypes::JSHeapUnused, info.totalJSHeapSize - info.usedJSHeapSize); |
| } |
| |
| static void reportRenderTreeInfo(WTF::MemoryInstrumentationClient& memoryInstrumentationClient, Page* page) |
| { |
| ArenaSize arenaSize = page->renderTreeSize(); |
| |
| memoryInstrumentationClient.countObjectSize(0, WebCoreMemoryTypes::RenderTreeUsed, arenaSize.treeSize); |
| memoryInstrumentationClient.countObjectSize(0, WebCoreMemoryTypes::RenderTreeUnused, arenaSize.allocated - arenaSize.treeSize); |
| } |
| |
| namespace { |
| |
| class DOMTreesIterator : public WrappedNodeVisitor { |
| public: |
| DOMTreesIterator(MemoryInstrumentationImpl& memoryInstrumentation, Page* page) |
| : m_page(page) |
| , m_memoryInstrumentation(memoryInstrumentation) |
| { |
| } |
| |
| virtual void visitNode(Node* node) OVERRIDE |
| { |
| if (node->document() && node->document()->frame() && m_page != node->document()->frame()->page()) |
| return; |
| |
| while (Node* parentNode = node->parentNode()) |
| node = parentNode; |
| |
| m_memoryInstrumentation.addRootObject(node); |
| } |
| |
| void visitFrame(Frame* frame) |
| { |
| m_memoryInstrumentation.addRootObject(frame); |
| } |
| |
| void visitBindings() |
| { |
| ScriptProfiler::collectBindingMemoryInfo(&m_memoryInstrumentation); |
| } |
| |
| void visitMemoryCache() |
| { |
| m_memoryInstrumentation.addRootObject(memoryCache()); |
| } |
| |
| |
| private: |
| Page* m_page; |
| MemoryInstrumentationImpl& m_memoryInstrumentation; |
| }; |
| |
| } |
| |
| static void collectDomTreeInfo(MemoryInstrumentationImpl& memoryInstrumentation, Page* page) |
| { |
| ExternalStringsRoot stringsRoot; |
| memoryInstrumentation.addRootObject(stringsRoot); |
| |
| ExternalArraysRoot arraysRoot; |
| memoryInstrumentation.addRootObject(arraysRoot); |
| |
| DOMTreesIterator domTreesIterator(memoryInstrumentation, page); |
| |
| ScriptProfiler::visitNodeWrappers(&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); |
| domTreesIterator.visitFrame(frame); |
| } |
| } |
| |
| domTreesIterator.visitBindings(); |
| domTreesIterator.visitMemoryCache(); |
| } |
| |
| static void addPlatformComponentsInfo(TypeNameToSizeMap* memoryInfo) |
| { |
| Vector<MemoryUsageSupport::ComponentInfo> components; |
| MemoryUsageSupport::memoryUsageByComponents(components); |
| for (Vector<MemoryUsageSupport::ComponentInfo>::iterator it = components.begin(); it != components.end(); ++it) |
| memoryInfo->add(it->m_name, it->m_sizeInBytes); |
| } |
| |
| static void addMemoryInstrumentationDebugData(MemoryInstrumentationClientImpl* client, TypeNameToSizeMap* memoryInfo) |
| { |
| if (client->checkInstrumentedObjects()) { |
| memoryInfo->add("InstrumentedObjectsCount", client->totalCountedObjects()); |
| memoryInfo->add("InstrumentedButNotAllocatedObjectsCount", client->totalObjectsNotInAllocatedSet()); |
| } |
| } |
| |
| void InspectorMemoryAgent::getProcessMemoryDistributionMap(TypeNameToSizeMap* memoryInfo) |
| { |
| getProcessMemoryDistributionImpl(false, memoryInfo); |
| } |
| |
| void InspectorMemoryAgent::getProcessMemoryDistribution(ErrorString*, const bool* reportGraph, RefPtr<InspectorMemoryBlock>& processMemory, RefPtr<InspectorObject>& graphMetaInformation) |
| { |
| TypeNameToSizeMap memoryInfo; |
| graphMetaInformation = getProcessMemoryDistributionImpl(reportGraph && *reportGraph, &memoryInfo); |
| |
| MemoryUsageStatsGenerator statsGenerator; |
| RefPtr<InspectorMemoryBlocks> children = InspectorMemoryBlocks::create(); |
| statsGenerator.dump(memoryInfo, children.get()); |
| |
| processMemory = InspectorMemoryBlock::create().setName(WebCoreMemoryTypes::ProcessPrivateMemory); |
| processMemory->setChildren(children); |
| |
| size_t privateBytes = 0; |
| size_t sharedBytes = 0; |
| MemoryUsageSupport::processMemorySizesInBytes(&privateBytes, &sharedBytes); |
| processMemory->setSize(privateBytes); |
| } |
| |
| void InspectorMemoryAgent::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const |
| { |
| MemoryClassInfo info(memoryObjectInfo, this, WebCoreMemoryTypes::Inspector); |
| InspectorBaseAgent<InspectorMemoryAgent>::reportMemoryUsage(memoryObjectInfo); |
| info.addWeakPointer(m_inspectorClient); |
| info.addMember(m_page, "page"); |
| } |
| |
| namespace { |
| |
| class FrontendWrapper : public HeapGraphSerializer::Client { |
| public: |
| explicit FrontendWrapper(InspectorFrontend::Memory* frontend) : m_frontend(frontend) { } |
| virtual void addNativeSnapshotChunk(PassRefPtr<TypeBuilder::Memory::HeapSnapshotChunk> heapSnapshotChunk) OVERRIDE |
| { |
| m_frontend->addNativeSnapshotChunk(heapSnapshotChunk); |
| } |
| private: |
| InspectorFrontend::Memory* m_frontend; |
| }; |
| |
| } |
| |
| PassRefPtr<InspectorObject> InspectorMemoryAgent::getProcessMemoryDistributionImpl(bool reportGraph, TypeNameToSizeMap* memoryInfo) |
| { |
| RefPtr<InspectorObject> meta; |
| OwnPtr<HeapGraphSerializer> graphSerializer; |
| OwnPtr<FrontendWrapper> frontendWrapper; |
| |
| if (reportGraph) { |
| frontendWrapper = adoptPtr(new FrontendWrapper(m_frontend)); |
| graphSerializer = adoptPtr(new HeapGraphSerializer(frontendWrapper.get())); |
| } |
| |
| MemoryInstrumentationClientImpl memoryInstrumentationClient(graphSerializer.get()); |
| m_inspectorClient->getAllocatedObjects(memoryInstrumentationClient.allocatedObjects()); |
| MemoryInstrumentationImpl memoryInstrumentation(&memoryInstrumentationClient); |
| |
| reportJSHeapInfo(memoryInstrumentationClient); |
| reportRenderTreeInfo(memoryInstrumentationClient, m_page); |
| collectDomTreeInfo(memoryInstrumentation, m_page); // FIXME: collect for all pages? |
| |
| PlatformMemoryInstrumentation::reportStaticMembersMemoryUsage(&memoryInstrumentation); |
| WebCoreMemoryInstrumentation::reportStaticMembersMemoryUsage(&memoryInstrumentation); |
| |
| memoryInstrumentation.addRootObject(this); |
| memoryInstrumentation.addRootObject(memoryInstrumentation); |
| memoryInstrumentation.addRootObject(memoryInstrumentationClient); |
| if (graphSerializer) { |
| memoryInstrumentation.addRootObject(graphSerializer.get()); |
| meta = graphSerializer->finish(); |
| graphSerializer.release(); // Release it earlier than frontendWrapper |
| frontendWrapper.release(); |
| } |
| |
| m_inspectorClient->dumpUncountedAllocatedObjects(memoryInstrumentationClient.countedObjects()); |
| |
| *memoryInfo = memoryInstrumentationClient.sizesMap(); |
| addPlatformComponentsInfo(memoryInfo); |
| addMemoryInstrumentationDebugData(&memoryInstrumentationClient, memoryInfo); |
| return meta.release(); |
| } |
| |
| InspectorMemoryAgent::InspectorMemoryAgent(InstrumentingAgents* instrumentingAgents, InspectorClient* client, InspectorCompositeState* state, Page* page) |
| : InspectorBaseAgent<InspectorMemoryAgent>("Memory", instrumentingAgents, state) |
| , m_inspectorClient(client) |
| , m_page(page) |
| , m_frontend(0) |
| { |
| } |
| |
| void InspectorMemoryAgent::setFrontend(InspectorFrontend* frontend) |
| { |
| ASSERT(!m_frontend); |
| m_frontend = frontend->memory(); |
| } |
| |
| void InspectorMemoryAgent::clearFrontend() |
| { |
| m_frontend = 0; |
| } |
| |
| } // namespace WebCore |
| |