Merge branch 'master' of github.com:mono/mono
[mono.git] / scripts / mono-heapviz
1 #!/usr/bin/env python
2
3 # Generate a heap visualization for SGen from the heap dump written by
4 # mono if the MONO_GC_DEBUG is set to something like
5 # "heap-dump=/path/to/file".  This script accepts the file as stdin
6 # and generates HTML and PNG files.
7
8 from __future__ import print_function
9 import sys, os
10 import Image, ImageDraw
11 from xml.sax import ContentHandler, make_parser
12 from xml.sax.handler import feature_namespaces
13 from optparse import OptionParser
14
15 chunk_size = 1024                       # number of bytes in a chunk
16 chunk_pixel_size = 2                    # a chunk is a square with this side length
17 large_sections = False
18
19 def mark_chunk (img_draw, i, color, section_width):
20     row = i / section_width
21     col = i % section_width
22     pixel_col = col * chunk_pixel_size
23     pixel_row = row * chunk_pixel_size
24     img_draw.rectangle ([(pixel_col, pixel_row), (pixel_col + chunk_pixel_size - 1, pixel_row + chunk_pixel_size - 1)], fill = color)
25
26 class Range:
27     pass
28
29 class OccupiedRange (Range):
30     def __init__ (self, offset, size):
31         self.offset = offset
32         self.size = size
33
34     def mark (self, img_draw, color, section_width):
35         start = self.offset / chunk_size
36         end = (self.offset + self.size - 1) / chunk_size
37         for i in range (start, end + 1):
38             mark_chunk (img_draw, i, color, section_width)
39
40 class ObjectRange (OccupiedRange):
41     def __init__ (self, klass, offset, size):
42         OccupiedRange.__init__ (self, offset, size)
43         self.klass = klass;
44
45 class SectionHandler:
46     def __init__ (self, width):
47         self.width = width
48         self.ranges = []
49         self.size = 0
50         self.used = 0
51
52     def add_object (self, klass, offset, size):
53         self.ranges.append (ObjectRange (klass, offset, size))
54         self.used += size
55
56     def add_occupied (self, offset, size):
57         self.ranges.append (OccupiedRange (offset, size))
58         self.used += size
59
60     def draw (self):
61         height = (((self.size / chunk_size) + (self.width - 1)) / self.width) * chunk_pixel_size
62
63         color_background = (255, 255, 255)
64         color_free = (0, 0, 0)
65         color_occupied = (0, 255, 0)
66
67         img = Image.new ('RGB', (self.width * chunk_pixel_size, height), color_free)
68         img_draw = ImageDraw.Draw (img)
69         #FIXME: remove filling after end of heap
70
71         for r in self.ranges:
72             r.mark (img_draw, color_occupied, self.width)
73
74         return img
75
76     def emit (self, collection_file, collection_kind, collection_num, section_num):
77         print ('<h2>%s</h2>' % self.header (), file = collection_file)
78         print ('<p>Size %d kB - ' % (self.size / 1024), file = collection_file)
79         print ('used %d kB</p>' % (self.used / 1024), file = collection_file)
80
81         filename = '%s_%d_%d.png' % (collection_kind, collection_num, section_num)
82         print ('<p><img src="%s"></img></p>' % filename, file = collection_file)
83         img = self.draw ()
84         img.save (filename)
85
86 class SmallSectionHandler (SectionHandler):
87     def __init__ (self):
88         SectionHandler.__init__ (self, -1)
89         self.offset = 0
90
91     def start_section (self, kind, size):
92         assert kind == 'old'
93         if self.width <= 0:
94             self.width = (size + chunk_size - 1) / chunk_size
95             if self.width < 128:
96                 self.width = 512
97                 self.current_section_size = size
98             else:
99                 self.current_section_size = self.width * chunk_size
100         self.size += size
101
102     def add_object (self, klass, offset, size):
103         SectionHandler.add_object (self, klass, self.offset + offset, size)
104
105     def add_occupied (self, offset, size):
106         SectionHandler.add_occupied (self, self.offset + offset, size)
107
108     def end_section (self):
109         self.offset += self.current_section_size
110
111     def header (self):
112         return 'old sections'
113
114 class LargeSectionHandler (SectionHandler):
115     def __init__ (self):
116         SectionHandler.__init__ (self, 512)
117
118     def start_section (self, kind, size):
119         self.kind = kind
120         self.ranges = []
121         self.size = size
122         self.used = 0
123
124     def end_section (self):
125         pass
126
127     def header (self):
128         return self.kind + ' section'
129
130 class DocHandler (ContentHandler):
131     def start (self):
132         self.collection_index = 0
133         self.index_file = open ('index.html', 'w')
134         print ('<html><body>', file = self.index_file)
135
136     def end (self):
137         print ('</body></html>', file = self.index_file)
138         self.index_file.close ()
139
140     def startElement (self, name, attrs):
141         if name == 'collection':
142             self.collection_kind = attrs.get('type', None)
143             self.collection_num = int(attrs.get('num', None))
144             reason = attrs.get('reason', None)
145             if reason:
146                 reason = ' (%s)' % reason
147             else:
148                 reason = ''
149             self.section_num = 0
150             filename = 'collection_%d.html' % self.collection_index
151             print ('<a href="%s">%s%s collection %d</a>' % (filename, self.collection_kind, reason, self.collection_num), file = self.index_file)
152             self.collection_file = open (filename, 'w')
153             print ('<html><body>', file = self.collection_file)
154             print ('<p><a href="collection_%d.html">Prev</a> <a href="collection_%d.html">Next</a> <a href="index.html">Index</a></p>' % (self.collection_index - 1, self.collection_index + 1), file = self.collection_file)
155             print ('<h1>%s collection %d</h1>' % (self.collection_kind, self.collection_num), file = self.collection_file)
156             self.usage = {}
157             self.los_usage = {}
158             self.pinned_usage = {}
159             self.in_los = False
160             self.in_pinned = False
161             self.heap_used = 0
162             self.heap_size = 0
163             self.los_size = 0
164             if large_sections:
165                 self.section_handler = LargeSectionHandler ()
166             else:
167                 self.section_handler = self.small_section_handler = SmallSectionHandler ()
168         elif name == 'pinned':
169             kind = attrs.get('type', None)
170             bytes = int(attrs.get('bytes', None))
171             print ('Pinned from %s: %d kB<br>' % (kind, bytes / 1024), file = self.collection_file)
172         elif name == 'section':
173             kind = attrs.get('type', None)
174             size = int(attrs.get('size', None))
175
176             self.heap_size += size
177
178             if not large_sections:
179                 if kind == 'nursery':
180                     self.section_handler = LargeSectionHandler ()
181                 else:
182                     self.section_handler = self.small_section_handler
183
184             self.section_handler.start_section (kind, size)
185         elif name == 'object':
186             klass = attrs.get('class', None)
187             size = int(attrs.get('size', None))
188
189             if self.in_los:
190                 usage_dict = self.los_usage
191                 self.los_size += size
192             elif self.in_pinned:
193                 location = attrs.get('location', None)
194                 if location not in self.pinned_usage:
195                     self.pinned_usage[location] = {}
196                 usage_dict = self.pinned_usage[location]
197             else:
198                 usage_dict = self.usage
199                 offset = int(attrs.get('offset', None))
200
201                 self.section_handler.add_object (klass, offset, size)
202                 self.heap_used += size
203             if not (klass in usage_dict):
204                 usage_dict [klass] = (0, 0)
205             usage = usage_dict [klass]
206             usage_dict [klass] = (usage [0] + 1, usage [1] + size)
207         elif name == 'occupied':
208             offset = int(attrs.get('offset', None))
209             size = int(attrs.get('size', None))
210
211             self.section_handler.add_occupied (offset, size)
212             self.heap_used += size
213         elif name == 'los':
214             self.in_los = True
215         elif name == 'pinned-objects':
216             self.in_pinned = True
217
218     def dump_usage (self, usage_dict, limit):
219         klasses = sorted (usage_dict.keys (), lambda x, y: usage_dict [y][1] - usage_dict [x][1])
220         if limit:
221             klasses = klasses [0:limit]
222         for klass in klasses:
223             usage = usage_dict [klass]
224             if usage [1] < 100000:
225                 print ('%s %d bytes' % (klass, usage [1]), file = self.collection_file)
226             else:
227                 print ('%s %d kB' % (klass, usage [1] / 1024), file = self.collection_file)
228             print (' (%d)<br>' % usage [0], file = self.collection_file)
229
230     def endElement (self, name):
231         if name == 'section':
232             self.section_handler.end_section ()
233
234             if large_sections or self.section_handler != self.small_section_handler:
235                 self.section_handler.emit (self.collection_file, self.collection_kind, self.collection_num, self.section_num)
236                 self.section_num += 1
237         elif name == 'collection':
238             if not large_sections:
239                 self.small_section_handler.emit (self.collection_file, self.collection_kind, self.collection_num, self.section_num)
240
241             self.dump_usage (self.usage, 10)
242             print ('<h3>LOS</h3>', file = self.collection_file)
243             self.dump_usage (self.los_usage, None)
244             print ('<h3>Pinned</h3>', file = self.collection_file)
245             for location in sorted (self.pinned_usage.keys ()):
246                 print ('<h4>%s</h4>' % location, file = self.collection_file)
247                 self.dump_usage (self.pinned_usage[location], None)
248             print ('</body></html>', file = self.collection_file)
249             print (' - %d kB / %d kB (%d%%) - %d kB LOS</a><br>' % (self.heap_used / 1024, self.heap_size / 1024, int(100.0 * self.heap_used / self.heap_size), self.los_size / 1024), file = self.index_file)
250             self.collection_file.close ()
251             self.collection_index += 1
252         elif name == 'los':
253             self.in_los = False
254         elif name == 'pinned-objects':
255             self.in_pinned = False
256
257 def main ():
258     usage = "usage: %prog [options]"
259     parser = OptionParser (usage)
260     parser.add_option ("-l", "--large-sections", action = "store_true", dest = "large_sections")
261     parser.add_option ("-s", "--small-sections", action = "store_false", dest = "large_sections")
262     (options, args) = parser.parse_args ()
263     if options.large_sections:
264         large_sections = True
265
266     dh = DocHandler ()
267     parser = make_parser ()
268     parser.setFeature (feature_namespaces, 0)
269     parser.setContentHandler (dh)
270     dh.start ()
271     parser.parse (sys.stdin)
272     dh.end ()
273
274 if __name__ == "__main__":
275     main ()