2010-03-12 Jb Evain <jbevain@novell.com>
[mono.git] / scripts / mono-heapviz
blobc0ae0ef4baf8f13b70384ad1ae0ff8bf64fd2782
1 #!/usr/bin/env python
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.
8 from __future__ import print_function
9 import sys, os
10 import Image, ImageDraw
11 from xml.sax import ContentHandler, saxutils, make_parser
12 from xml.sax.handler import feature_namespaces
13 from optparse import OptionParser
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
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)
26 class Range:
27 pass
29 class OccupiedRange (Range):
30 def __init__ (self, offset, size):
31 self.offset = offset
32 self.size = size
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)
40 class ObjectRange (OccupiedRange):
41 def __init__ (self, klass, offset, size):
42 OccupiedRange.__init__ (self, offset, size)
43 self.klass = klass;
45 class SectionHandler:
46 def __init__ (self, width):
47 self.width = width
48 self.ranges = []
49 self.size = 0
50 self.used = 0
52 def add_object (self, klass, offset, size):
53 self.ranges.append (ObjectRange (klass, offset, size))
54 self.used += size
56 def add_occupied (self, offset, size):
57 self.ranges.append (OccupiedRange (offset, size))
58 self.used += size
60 def draw (self):
61 height = (((self.size / chunk_size) + (self.width - 1)) / self.width) * chunk_pixel_size
63 color_background = (255, 255, 255)
64 color_free = (0, 0, 0)
65 color_occupied = (0, 255, 0)
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
71 for r in self.ranges:
72 r.mark (img_draw, color_occupied, self.width)
74 return img
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)
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)
86 class SmallSectionHandler (SectionHandler):
87 def __init__ (self):
88 SectionHandler.__init__ (self, -1)
89 self.offset = 0
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 else:
96 assert self.width == (size + chunk_size - 1) / chunk_size
97 self.size += size
99 def add_object (self, klass, offset, size):
100 SectionHandler.add_object (self, klass, self.offset + offset, size)
102 def add_occupied (self, offset, size):
103 SectionHandler.add_occupied (self, self.offset + offset, size)
105 def end_section (self):
106 self.offset += self.width * chunk_size
108 def header (self):
109 return 'old sections'
111 class LargeSectionHandler (SectionHandler):
112 def __init__ (self):
113 SectionHandler.__init__ (self, 500)
115 def start_section (self, kind, size):
116 self.kind = kind
117 self.ranges = []
118 self.size = size
119 self.used = 0
121 def end_section (self):
122 pass
124 def header (self):
125 return self.kind + ' section'
127 class DocHandler (saxutils.DefaultHandler):
128 def start (self):
129 self.collection_index = 0
130 self.index_file = open ('index.html', 'w')
131 print ('<html><body>', file = self.index_file)
133 def end (self):
134 print ('</body></html>', file = self.index_file)
135 self.index_file.close ()
137 def startElement (self, name, attrs):
138 if name == 'collection':
139 self.collection_kind = attrs.get('type', None)
140 self.collection_num = int(attrs.get('num', None))
141 reason = attrs.get('reason', None)
142 if reason:
143 reason = ' (%s)' % reason
144 else:
145 reason = ''
146 self.section_num = 0
147 filename = 'collection_%d.html' % self.collection_index
148 print ('<a href="%s">%s%s collection %d</a>' % (filename, self.collection_kind, reason, self.collection_num), file = self.index_file)
149 self.collection_file = open (filename, 'w')
150 print ('<html><body>', file = self.collection_file)
151 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)
152 print ('<h1>%s collection %d</h1>' % (self.collection_kind, self.collection_num), file = self.collection_file)
153 self.usage = {}
154 self.los_usage = {}
155 self.in_los = False
156 self.heap_used = 0
157 self.heap_size = 0
158 self.los_size = 0
159 if large_sections:
160 self.section_handler = LargeSectionHandler ()
161 else:
162 self.section_handler = self.small_section_handler = SmallSectionHandler ()
163 elif name == 'pinned':
164 kind = attrs.get('type', None)
165 bytes = int(attrs.get('bytes', None))
166 print ('Pinned from %s: %d kB<br>' % (kind, bytes / 1024), file = self.collection_file)
167 elif name == 'section':
168 kind = attrs.get('type', None)
169 size = int(attrs.get('size', None))
171 self.heap_size += size
173 if not large_sections:
174 if kind == 'nursery':
175 self.section_handler = LargeSectionHandler ()
176 else:
177 self.section_handler = self.small_section_handler
179 self.section_handler.start_section (kind, size)
180 elif name == 'object':
181 klass = attrs.get('class', None)
182 size = int(attrs.get('size', None))
184 if self.in_los:
185 usage_dict = self.los_usage
186 self.los_size += size
187 else:
188 usage_dict = self.usage
189 offset = int(attrs.get('offset', None))
191 self.section_handler.add_object (klass, offset, size)
192 self.heap_used += size
193 if not (klass in usage_dict):
194 usage_dict [klass] = (0, 0)
195 usage = usage_dict [klass]
196 usage_dict [klass] = (usage [0] + 1, usage [1] + size)
197 elif name == 'occupied':
198 offset = int(attrs.get('offset', None))
199 size = int(attrs.get('size', None))
201 self.section_handler.add_occupied (offset, size)
202 self.heap_used += size
203 elif name == 'los':
204 self.in_los = True
206 def dump_usage (self, usage_dict, limit):
207 klasses = sorted (usage_dict.keys (), lambda x, y: usage_dict [y][1] - usage_dict [x][1])
208 if limit:
209 klasses = klasses [0:limit]
210 for klass in klasses:
211 usage = usage_dict [klass]
212 if usage [1] < 100000:
213 print ('%s %d bytes' % (klass, usage [1]), file = self.collection_file)
214 else:
215 print ('%s %d kB' % (klass, usage [1] / 1024), file = self.collection_file)
216 print (' (%d)<br>' % usage [0], file = self.collection_file)
218 def endElement (self, name):
219 if name == 'section':
220 self.section_handler.end_section ()
222 if large_sections or self.section_handler != self.small_section_handler:
223 self.section_handler.emit (self.collection_file, self.collection_kind, self.collection_num, self.section_num)
224 self.section_num += 1
225 elif name == 'collection':
226 if not large_sections:
227 self.small_section_handler.emit (self.collection_file, self.collection_kind, self.collection_num, self.section_num)
229 self.dump_usage (self.usage, 10)
230 print ('<h3>LOS</h3>', file = self.collection_file)
231 self.dump_usage (self.los_usage, None)
232 print ('</body></html>', file = self.collection_file)
233 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)
234 self.collection_file.close ()
235 self.collection_index += 1
236 elif name == 'los':
237 self.in_los = False
239 def main ():
240 usage = "usage: %prog [options]"
241 parser = OptionParser (usage)
242 parser.add_option ("-l", "--large-sections", action = "store_true", dest = "large_sections")
243 parser.add_option ("-s", "--small-sections", action = "store_false", dest = "large_sections")
244 (options, args) = parser.parse_args ()
245 if options.large_sections:
246 large_sections = True
248 dh = DocHandler ()
249 parser = make_parser ()
250 parser.setFeature (feature_namespaces, 0)
251 parser.setContentHandler (dh)
252 dh.start ()
253 parser.parse (sys.stdin)
254 dh.end ()
256 if __name__ == "__main__":
257 main ()