Web Inspector: partially instrument DOM Tree native memory.
[WebKit-https.git] / Source / WebCore / inspector / InspectorMemoryAgent.cpp
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32
33 #if ENABLE(INSPECTOR)
34
35 #include "InspectorMemoryAgent.h"
36
37 #include "CharacterData.h"
38 #include "DOMWrapperVisitor.h"
39 #include "Document.h"
40 #include "EventListenerMap.h"
41 #include "Frame.h"
42 #include "InspectorFrontend.h"
43 #include "InspectorState.h"
44 #include "InspectorValues.h"
45 #include "InstrumentingAgents.h"
46 #include "MemoryCache.h"
47 #include "MemoryInstrumentation.h"
48 #include "MemoryUsageSupport.h"
49 #include "Node.h"
50 #include "Page.h"
51 #include "ScriptGCEvent.h"
52 #include "ScriptProfiler.h"
53 #include "StyledElement.h"
54 #include <wtf/HashSet.h>
55 #include <wtf/text/StringBuilder.h>
56
57 using WebCore::TypeBuilder::Memory::DOMGroup;
58 using WebCore::TypeBuilder::Memory::ListenerCount;
59 using WebCore::TypeBuilder::Memory::NodeCount;
60 using WebCore::TypeBuilder::Memory::StringStatistics;
61
62 // Use a type alias instead of 'using' here which would cause a conflict on Mac.
63 typedef WebCore::TypeBuilder::Memory::MemoryBlock InspectorMemoryBlock;
64
65 namespace WebCore {
66
67 namespace MemoryBlockName {
68 static const char jsHeapAllocated[] = "JSHeapAllocated";
69 static const char jsHeapUsed[] = "JSHeapUsed";
70 static const char inspectorData[] = "InspectorData";
71 static const char memoryCache[] = "MemoryCache";
72 static const char processPrivateMemory[] = "ProcessPrivateMemory";
73
74 static const char cachedImages[] = "CachedImages";
75 static const char cachedCssStyleSheets[] = "CachedCssStyleSheets";
76 static const char cachedScripts[] = "CachedScripts";
77 static const char cachedXslStyleSheets[] = "CachedXslStyleSheets";
78 static const char cachedFonts[] = "CachedFonts";
79 static const char renderTreeUsed[] = "RenderTreeUsed";
80 static const char renderTreeAllocated[] = "RenderTreeAllocated";
81
82 static const char dom[] = "DOM";
83 static const char domTreeOther[] = "DOMTreeOther";
84 static const char domTreeDOM[] = "DOMTreeDOM";
85 static const char domTreeCSS[] = "DOMTreeCSS";
86 }
87
88 namespace {
89
90 String nodeName(Node* node)
91 {
92     if (node->document()->isXHTMLDocument())
93          return node->nodeName();
94     return node->nodeName().lower();
95 }
96
97 int stringSize(StringImpl* string)
98 {
99     int size = string->length();
100     if (!string->is8Bit())
101         size *= 2;
102     return size + sizeof(*string);
103 }
104
105 typedef HashSet<StringImpl*, PtrHash<StringImpl*> > StringImplIdentitySet;
106
107 class CharacterDataStatistics {
108     WTF_MAKE_NONCOPYABLE(CharacterDataStatistics);
109 public:
110     CharacterDataStatistics() : m_characterDataSize(0) { }
111
112     void collectCharacterData(Node* node)
113     {
114         if (!node->isCharacterDataNode())
115             return;
116
117         CharacterData* characterData = static_cast<CharacterData*>(node);
118         StringImpl* dataImpl = characterData->dataImpl();
119         if (m_domStringImplSet.contains(dataImpl))
120             return;
121         m_domStringImplSet.add(dataImpl);
122
123         m_characterDataSize += stringSize(dataImpl);
124     }
125
126     bool contains(StringImpl* s) { return m_domStringImplSet.contains(s); }
127
128     int characterDataSize() { return m_characterDataSize; }
129
130 private:
131     StringImplIdentitySet m_domStringImplSet;
132     int m_characterDataSize;
133 };
134
135 class DOMTreeStatistics {
136     WTF_MAKE_NONCOPYABLE(DOMTreeStatistics);
137 public:
138     DOMTreeStatistics(Node* rootNode, CharacterDataStatistics& characterDataStatistics)
139         : m_totalNodeCount(0)
140         , m_characterDataStatistics(characterDataStatistics)
141     {
142         collectTreeStatistics(rootNode);
143     }
144
145     int totalNodeCount() { return m_totalNodeCount; }
146
147     PassRefPtr<TypeBuilder::Array<TypeBuilder::Memory::NodeCount> > nodeCount()
148     {
149         RefPtr<TypeBuilder::Array<TypeBuilder::Memory::NodeCount> > childrenStats = TypeBuilder::Array<TypeBuilder::Memory::NodeCount>::create();
150         for (HashMap<String, int>::iterator it = m_nodeNameToCount.begin(); it != m_nodeNameToCount.end(); ++it) {
151             RefPtr<NodeCount> nodeCount = NodeCount::create().setNodeName(it->first)
152                                                              .setCount(it->second);
153             childrenStats->addItem(nodeCount);
154         }
155         return childrenStats.release();
156     }
157
158     PassRefPtr<TypeBuilder::Array<TypeBuilder::Memory::ListenerCount> > listenerCount()
159     {
160         RefPtr<TypeBuilder::Array<TypeBuilder::Memory::ListenerCount> > listenerStats = TypeBuilder::Array<TypeBuilder::Memory::ListenerCount>::create();
161         for (HashMap<AtomicString, int>::iterator it = m_eventTypeToCount.begin(); it != m_eventTypeToCount.end(); ++it) {
162             RefPtr<ListenerCount> listenerCount = ListenerCount::create().setType(it->first)
163                                                                          .setCount(it->second);
164             listenerStats->addItem(listenerCount);
165         }
166         return listenerStats.release();
167     }
168
169 private:
170     void collectTreeStatistics(Node* rootNode)
171     {
172         Node* currentNode = rootNode;
173         collectListenersInfo(rootNode);
174         while ((currentNode = currentNode->traverseNextNode(rootNode))) {
175             ++m_totalNodeCount;
176             collectNodeStatistics(currentNode);
177         }
178     }
179     void collectNodeStatistics(Node* node)
180     {
181         m_characterDataStatistics.collectCharacterData(node);
182         collectNodeNameInfo(node);
183         collectListenersInfo(node);
184     }
185
186     void collectNodeNameInfo(Node* node)
187     {
188         String name = nodeName(node);
189         int currentCount = m_nodeNameToCount.get(name);
190         m_nodeNameToCount.set(name, currentCount + 1);
191     }
192
193     void collectListenersInfo(Node* node)
194     {
195         EventTargetData* d = node->eventTargetData();
196         if (!d)
197             return;
198         EventListenerMap& eventListenerMap = d->eventListenerMap;
199         if (eventListenerMap.isEmpty())
200             return;
201         Vector<AtomicString> eventNames = eventListenerMap.eventTypes();
202         for (Vector<AtomicString>::iterator it = eventNames.begin(); it != eventNames.end(); ++it) {
203             AtomicString name = *it;
204             EventListenerVector* listeners = eventListenerMap.find(name);
205             int count = 0;
206             for (EventListenerVector::iterator j = listeners->begin(); j != listeners->end(); ++j) {
207                 if (j->listener->type() == EventListener::JSEventListenerType)
208                     ++count;
209             }
210             if (count)
211                 m_eventTypeToCount.set(name, m_eventTypeToCount.get(name) + count);
212         }
213     }
214
215     int m_totalNodeCount;
216     HashMap<AtomicString, int> m_eventTypeToCount;
217     HashMap<String, int> m_nodeNameToCount;
218     CharacterDataStatistics& m_characterDataStatistics;
219 };
220
221 class CounterVisitor : public DOMWrapperVisitor {
222 public:
223     CounterVisitor(Page* page)
224         : m_page(page)
225         , m_domGroups(TypeBuilder::Array<TypeBuilder::Memory::DOMGroup>::create())
226         , m_jsExternalStringSize(0)
227         , m_sharedStringSize(0) { }
228
229     TypeBuilder::Array<TypeBuilder::Memory::DOMGroup>* domGroups() { return m_domGroups.get(); }
230
231     PassRefPtr<StringStatistics> strings()
232     {
233         RefPtr<StringStatistics> stringStatistics = StringStatistics::create()
234             .setDom(m_characterDataStatistics.characterDataSize())
235             .setJs(m_jsExternalStringSize)
236             .setShared(m_sharedStringSize);
237         return stringStatistics.release();
238     }
239
240     virtual void visitNode(Node* node)
241     {
242         if (node->document()->frame() && m_page != node->document()->frame()->page())
243             return;
244
245         Node* rootNode = node;
246         while (rootNode->parentNode())
247             rootNode = rootNode->parentNode();
248
249         if (m_roots.contains(rootNode))
250             return;
251         m_roots.add(rootNode);
252
253         DOMTreeStatistics domTreeStats(rootNode, m_characterDataStatistics);
254
255         RefPtr<DOMGroup> domGroup = DOMGroup::create()
256             .setSize(domTreeStats.totalNodeCount())
257             .setTitle(rootNode->nodeType() == Node::ELEMENT_NODE ? elementTitle(static_cast<Element*>(rootNode)) : rootNode->nodeName())
258             .setNodeCount(domTreeStats.nodeCount())
259             .setListenerCount(domTreeStats.listenerCount());
260         if (rootNode->nodeType() == Node::DOCUMENT_NODE)
261             domGroup->setDocumentURI(static_cast<Document*>(rootNode)->documentURI());
262
263         m_domGroups->addItem(domGroup);
264     }
265
266     virtual void visitJSExternalString(StringImpl* string)
267     {
268         int size = stringSize(string);
269         m_jsExternalStringSize += size;
270         if (m_characterDataStatistics.contains(string))
271             m_sharedStringSize += size;
272     }
273
274 private:
275     String elementTitle(Element* element)
276     {
277         StringBuilder result;
278         result.append(nodeName(element));
279
280         const AtomicString& idValue = element->getIdAttribute();
281         String idString;
282         if (!idValue.isNull() && !idValue.isEmpty()) {
283             result.append("#");
284             result.append(idValue);
285         }
286
287         HashSet<AtomicString> usedClassNames;
288         if (element->hasClass() && element->isStyledElement()) {
289             const SpaceSplitString& classNamesString = static_cast<StyledElement*>(element)->classNames();
290             size_t classNameCount = classNamesString.size();
291             for (size_t i = 0; i < classNameCount; ++i) {
292                 const AtomicString& className = classNamesString[i];
293                 if (usedClassNames.contains(className))
294                     continue;
295                 usedClassNames.add(className);
296                 result.append(".");
297                 result.append(className);
298             }
299         }
300         return result.toString();
301     }
302
303     HashSet<Node*> m_roots;
304     Page* m_page;
305     RefPtr<TypeBuilder::Array<TypeBuilder::Memory::DOMGroup> > m_domGroups;
306     CharacterDataStatistics m_characterDataStatistics;
307     int m_jsExternalStringSize;
308     int m_sharedStringSize;
309 };
310
311 } // namespace
312
313 InspectorMemoryAgent::~InspectorMemoryAgent()
314 {
315 }
316
317 void InspectorMemoryAgent::getDOMNodeCount(ErrorString*, RefPtr<TypeBuilder::Array<TypeBuilder::Memory::DOMGroup> >& domGroups, RefPtr<TypeBuilder::Memory::StringStatistics>& strings)
318 {
319     CounterVisitor counterVisitor(m_page);
320     ScriptProfiler::visitJSDOMWrappers(&counterVisitor);
321
322     // Make sure all documents reachable from the main frame are accounted.
323     for (Frame* frame = m_page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
324         if (Document* doc = frame->document())
325             counterVisitor.visitNode(doc);
326     }
327
328     ScriptProfiler::visitExternalJSStrings(&counterVisitor);
329
330     domGroups = counterVisitor.domGroups();
331     strings = counterVisitor.strings();
332 }
333
334 static PassRefPtr<InspectorMemoryBlock> jsHeapInfo()
335 {
336     size_t usedJSHeapSize;
337     size_t totalJSHeapSize;
338     size_t jsHeapSizeLimit;
339     ScriptGCEvent::getHeapSize(usedJSHeapSize, totalJSHeapSize, jsHeapSizeLimit);
340
341     RefPtr<InspectorMemoryBlock> jsHeapAllocated = InspectorMemoryBlock::create().setName(MemoryBlockName::jsHeapAllocated);
342     jsHeapAllocated->setSize(totalJSHeapSize);
343
344     RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > children = TypeBuilder::Array<InspectorMemoryBlock>::create();
345     RefPtr<InspectorMemoryBlock> jsHeapUsed = InspectorMemoryBlock::create().setName(MemoryBlockName::jsHeapUsed);
346     jsHeapUsed->setSize(usedJSHeapSize);
347     children->addItem(jsHeapUsed);
348
349     jsHeapAllocated->setChildren(children);
350     return jsHeapAllocated.release();
351 }
352
353 static PassRefPtr<InspectorMemoryBlock> inspectorData()
354 {
355     size_t dataSize = ScriptProfiler::profilerSnapshotsSize();
356     RefPtr<InspectorMemoryBlock> inspectorData = InspectorMemoryBlock::create().setName(MemoryBlockName::inspectorData);
357     inspectorData->setSize(dataSize);
358     return inspectorData.release();
359 }
360
361 static PassRefPtr<InspectorMemoryBlock> renderTreeInfo(Page* page)
362 {
363     ArenaSize arenaSize = page->renderTreeSize();
364
365     RefPtr<InspectorMemoryBlock> renderTreeAllocated = InspectorMemoryBlock::create().setName(MemoryBlockName::renderTreeAllocated);
366     renderTreeAllocated->setSize(arenaSize.allocated);
367
368     RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > children = TypeBuilder::Array<InspectorMemoryBlock>::create();
369     RefPtr<InspectorMemoryBlock> renderTreeUsed = InspectorMemoryBlock::create().setName(MemoryBlockName::renderTreeUsed);
370     renderTreeUsed->setSize(arenaSize.treeSize);
371     children->addItem(renderTreeUsed);
372
373     renderTreeAllocated->setChildren(children);
374     return renderTreeAllocated.release();
375 }
376
377 static void addMemoryBlockFor(TypeBuilder::Array<InspectorMemoryBlock>* array, size_t size, const char* name)
378 {
379     RefPtr<InspectorMemoryBlock> result = InspectorMemoryBlock::create().setName(name);
380     result->setSize(size);
381     array->addItem(result);
382 }
383
384 namespace {
385
386 class MemoryInstrumentationImpl : public MemoryInstrumentation {
387 public:
388     MemoryInstrumentationImpl()
389     {
390         for (int i = 0; i < LastTypeEntry; ++i)
391             m_totalSizes[i] = 0;
392     }
393
394     PassRefPtr<InspectorMemoryBlock> dumpStatistics()
395     {
396         size_t totalSize = 0;
397         for (int i = Other; i < LastTypeEntry; ++i)
398             totalSize += m_totalSizes[i];
399
400         RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > domChildren = TypeBuilder::Array<InspectorMemoryBlock>::create();
401         addMemoryBlockFor(domChildren.get(), m_totalSizes[Other], MemoryBlockName::domTreeOther);
402         addMemoryBlockFor(domChildren.get(), m_totalSizes[DOM], MemoryBlockName::domTreeDOM);
403         addMemoryBlockFor(domChildren.get(), m_totalSizes[CSS], MemoryBlockName::domTreeCSS);
404
405         RefPtr<InspectorMemoryBlock> dom = InspectorMemoryBlock::create().setName(MemoryBlockName::dom);
406         dom->setSize(totalSize);
407         dom->setChildren(domChildren.release());
408         return dom.release();
409     }
410
411 private:
412     virtual void countObjectSize(ObjectType objectType, size_t size)
413     {
414         ASSERT(objectType >= 0 && objectType < LastTypeEntry);
415         m_totalSizes[objectType] += size;
416     }
417
418     virtual bool visited(const void* object)
419     {
420         return !m_visitedObjects.add(object).isNewEntry;
421     }
422     size_t m_totalSizes[LastTypeEntry];
423     typedef HashSet<const void*> VisitedObjects;
424     VisitedObjects m_visitedObjects;
425 };
426
427 class DOMTreesIterator : public DOMWrapperVisitor {
428 public:
429     explicit DOMTreesIterator(Page* page) : m_page(page) { }
430
431     virtual void visitNode(Node* node)
432     {
433         if (node->document() && node->document()->frame() && m_page != node->document()->frame()->page())
434             return;
435
436         m_domMemoryUsage.reportInstrumentedPointer(node);
437     }
438
439     virtual void visitJSExternalString(StringImpl*) { }
440
441     PassRefPtr<InspectorMemoryBlock> dumpStatistics() { return m_domMemoryUsage.dumpStatistics(); }
442
443 private:
444     Page* m_page;
445     MemoryInstrumentationImpl m_domMemoryUsage;
446 };
447
448 }
449
450 static PassRefPtr<InspectorMemoryBlock> domTreeInfo(Page* page)
451 {
452     DOMTreesIterator domTreesIterator(page);
453     ScriptProfiler::visitJSDOMWrappers(&domTreesIterator);
454
455     // Make sure all documents reachable from the main frame are accounted.
456     for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
457         if (Document* doc = frame->document())
458             domTreesIterator.visitNode(doc);
459     }
460
461     return domTreesIterator.dumpStatistics();
462 }
463
464 static PassRefPtr<InspectorMemoryBlock> memoryCacheInfo()
465 {
466     MemoryCache::Statistics stats = memoryCache()->getStatistics();
467     int totalSize = stats.images.size +
468                     stats.cssStyleSheets.size +
469                     stats.scripts.size +
470                     stats.xslStyleSheets.size +
471                     stats.fonts.size;
472     RefPtr<InspectorMemoryBlock> memoryCacheStats = InspectorMemoryBlock::create().setName(MemoryBlockName::memoryCache);
473     memoryCacheStats->setSize(totalSize);
474
475     RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > children = TypeBuilder::Array<InspectorMemoryBlock>::create();
476     addMemoryBlockFor(children.get(), stats.images.size, MemoryBlockName::cachedImages);
477     addMemoryBlockFor(children.get(), stats.cssStyleSheets.size, MemoryBlockName::cachedCssStyleSheets);
478     addMemoryBlockFor(children.get(), stats.scripts.size, MemoryBlockName::cachedScripts);
479     addMemoryBlockFor(children.get(), stats.xslStyleSheets.size, MemoryBlockName::cachedXslStyleSheets);
480     addMemoryBlockFor(children.get(), stats.fonts.size, MemoryBlockName::cachedFonts);
481     memoryCacheStats->setChildren(children.get());
482     return memoryCacheStats.release();
483 }
484
485 void InspectorMemoryAgent::getProcessMemoryDistribution(ErrorString*, RefPtr<InspectorMemoryBlock>& processMemory)
486 {
487     size_t privateBytes = 0;
488     size_t sharedBytes = 0;
489     MemoryUsageSupport::processMemorySizesInBytes(&privateBytes, &sharedBytes);
490     processMemory = InspectorMemoryBlock::create().setName(MemoryBlockName::processPrivateMemory);
491     processMemory->setSize(privateBytes);
492
493     RefPtr<TypeBuilder::Array<InspectorMemoryBlock> > children = TypeBuilder::Array<InspectorMemoryBlock>::create();
494     children->addItem(jsHeapInfo());
495     children->addItem(inspectorData());
496     children->addItem(memoryCacheInfo());
497     children->addItem(renderTreeInfo(m_page)); // TODO: collect for all pages?
498     children->addItem(domTreeInfo(m_page)); // TODO: collect for all pages?
499     processMemory->setChildren(children);
500 }
501
502 InspectorMemoryAgent::InspectorMemoryAgent(InstrumentingAgents* instrumentingAgents, InspectorState* state, Page* page, InspectorDOMAgent*)
503     : InspectorBaseAgent<InspectorMemoryAgent>("Memory", instrumentingAgents, state)
504     , m_page(page)
505 {
506 }
507
508 } // namespace WebCore
509
510 #endif // ENABLE(INSPECTOR)