blob: cd0de9d87dc6d8faae81f801d50e63ea8bcb741c [file] [log] [blame]
/*
* 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"
#if ENABLE(INSPECTOR)
#include "InspectorMemoryAgent.h"
#include "BindingVisitors.h"
#include "CharacterData.h"
#include "Document.h"
#include "EventListenerMap.h"
#include "Frame.h"
#include "InspectorClient.h"
#include "InspectorDOMStorageAgent.h"
#include "InspectorFrontend.h"
#include "InspectorState.h"
#include "InspectorValues.h"
#include "InstrumentingAgents.h"
#include "MemoryCache.h"
#include "MemoryInstrumentationImpl.h"
#include "MemoryUsageSupport.h"
#include "Node.h"
#include "Page.h"
#include "ScriptGCEvent.h"
#include "ScriptProfiler.h"
#include "StyledElement.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/Vector.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringImpl.h>
#include <wtf/text/WTFString.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;
typedef WebCore::TypeBuilder::Array<InspectorMemoryBlock> InspectorMemoryBlocks;
namespace WebCore {
namespace {
class MemoryUsageStatsGenerator {
public:
MemoryUsageStatsGenerator(MemoryInstrumentationClientImpl* client) : m_client(client) { }
void dump(InspectorMemoryBlocks* children)
{
m_sizesMap = m_client->sizesMap();
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);
addMemoryInstrumentationDebugData(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;
}
void addMemoryInstrumentationDebugData(InspectorMemoryBlocks* children)
{
if (m_client->checkInstrumentedObjects()) {
RefPtr<InspectorMemoryBlock> totalInstrumented = InspectorMemoryBlock::create().setName("InstrumentedObjectsCount");
totalInstrumented->setSize(m_client->totalCountedObjects());
RefPtr<InspectorMemoryBlock> incorrectlyInstrumented = InspectorMemoryBlock::create().setName("InstrumentedButNotAllocatedObjectsCount");
incorrectlyInstrumented->setSize(m_client->totalObjectsNotInAllocatedSet());
children->addItem(totalInstrumented);
children->addItem(incorrectlyInstrumented);
}
}
MemoryInstrumentationClientImpl* m_client;
TypeNameToSizeMap m_sizesMap;
};
String nodeName(Node* node)
{
if (node->document()->isXHTMLDocument())
return node->nodeName();
return node->nodeName().lower();
}
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 += dataImpl->sizeInBytes();
}
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->key)
.setCount(it->value);
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->key)
.setCount(it->value);
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 WrappedNodeVisitor, public ExternalStringVisitor {
public:
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(); }
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)
{
if (node->document()->frame() && m_page != node->document()->frame()->page())
return;
Node* rootNode = node;
while (rootNode->parentNode())
rootNode = rootNode->parentNode();
if (m_roots.contains(rootNode))
return;
m_roots.add(rootNode);
DOMTreeStatistics domTreeStats(rootNode, m_characterDataStatistics);
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());
m_domGroups->addItem(domGroup);
}
virtual void visitJSExternalString(StringImpl* string)
{
int size = string->sizeInBytes();
m_jsExternalStringSize += size;
if (m_characterDataStatistics.contains(string))
m_sharedStringSize += size;
}
private:
String elementTitle(Element* element)
{
StringBuilder result;
result.append(nodeName(element));
const AtomicString& idValue = element->getIdAttribute();
String idString;
if (!idValue.isNull() && !idValue.isEmpty()) {
result.append("#");
result.append(idValue);
}
HashSet<AtomicString> usedClassNames;
if (element->hasClass() && element->isStyledElement()) {
const SpaceSplitString& classNamesString = static_cast<StyledElement*>(element)->classNames();
size_t classNameCount = classNamesString.size();
for (size_t i = 0; i < classNameCount; ++i) {
const AtomicString& className = classNamesString[i];
if (usedClassNames.contains(className))
continue;
usedClassNames.add(className);
result.append(".");
result.append(className);
}
}
return result.toString();
}
HashSet<Node*> m_roots;
Page* m_page;
RefPtr<TypeBuilder::Array<TypeBuilder::Memory::DOMGroup> > m_domGroups;
CharacterDataStatistics m_characterDataStatistics;
int m_jsExternalStringSize;
int m_sharedStringSize;
};
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;
}
private:
virtual void visitJSExternalString(StringImpl* string)
{
m_memoryClassInfo->addMember(string);
}
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;
}
private:
virtual void visitJSExternalArray(ArrayBufferView* arrayBufferView)
{
m_memoryClassInfo->addMember(arrayBufferView);
}
mutable MemoryClassInfo* m_memoryClassInfo;
};
} // namespace
InspectorMemoryAgent::~InspectorMemoryAgent()
{
}
void InspectorMemoryAgent::getDOMNodeCount(ErrorString*, RefPtr<TypeBuilder::Array<TypeBuilder::Memory::DOMGroup> >& domGroups, RefPtr<TypeBuilder::Memory::StringStatistics>& strings)
{
CounterVisitor counterVisitor(m_page);
ScriptProfiler::visitNodeWrappers(&counterVisitor);
// Make sure all documents reachable from the main frame are accounted.
for (Frame* frame = m_page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
if (Document* doc = frame->document())
counterVisitor.visitNode(doc);
}
ScriptProfiler::visitExternalStrings(&counterVisitor);
domGroups = counterVisitor.domGroups();
strings = counterVisitor.strings();
}
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;
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(PassRefPtr<InspectorMemoryBlocks> children)
{
Vector<MemoryUsageSupport::ComponentInfo> components;
MemoryUsageSupport::memoryUsageByComponents(components);
for (Vector<MemoryUsageSupport::ComponentInfo>::iterator it = components.begin(); it != components.end(); ++it) {
RefPtr<InspectorMemoryBlock> block = InspectorMemoryBlock::create().setName(it->m_name);
block->setSize(it->m_sizeInBytes);
children->addItem(block);
}
}
void InspectorMemoryAgent::getProcessMemoryDistribution(ErrorString*, RefPtr<InspectorMemoryBlock>& processMemory)
{
MemoryInstrumentationClientImpl memoryInstrumentationClient;
m_inspectorClient->getAllocatedObjects(memoryInstrumentationClient.allocatedObjects());
MemoryInstrumentationImpl memoryInstrumentation(&memoryInstrumentationClient);
reportJSHeapInfo(memoryInstrumentationClient);
reportRenderTreeInfo(memoryInstrumentationClient, m_page);
collectDomTreeInfo(memoryInstrumentation, m_page); // FIXME: collect for all pages?
PlatformMemoryInstrumentation::reportMemoryUsage(&memoryInstrumentation);
RefPtr<InspectorMemoryBlocks> children = InspectorMemoryBlocks::create();
addPlatformComponentsInfo(children);
memoryInstrumentation.addRootObject(this);
memoryInstrumentation.addRootObject(memoryInstrumentation);
memoryInstrumentation.addRootObject(memoryInstrumentationClient);
m_inspectorClient->dumpUncountedAllocatedObjects(memoryInstrumentationClient.countedObjects());
MemoryUsageStatsGenerator statsGenerator(&memoryInstrumentationClient);
statsGenerator.dump(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);
}
InspectorMemoryAgent::InspectorMemoryAgent(InstrumentingAgents* instrumentingAgents, InspectorClient* client, InspectorState* state, Page* page)
: InspectorBaseAgent<InspectorMemoryAgent>("Memory", instrumentingAgents, state)
, m_inspectorClient(client)
, m_page(page)
{
}
} // namespace WebCore
#endif // ENABLE(INSPECTOR)