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
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
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
)
29 class OccupiedRange (Range
):
30 def __init__ (self
, offset
, 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
)
46 def __init__ (self
, width
):
52 def add_object (self
, klass
, offset
, size
):
53 self
.ranges
.append (ObjectRange (klass
, offset
, size
))
56 def add_occupied (self
, offset
, size
):
57 self
.ranges
.append (OccupiedRange (offset
, size
))
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
72 r
.mark (img_draw
, color_occupied
, self
.width
)
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
)
86 class SmallSectionHandler (SectionHandler
):
88 SectionHandler
.__init
__ (self
, -1)
91 def start_section (self
, kind
, size
):
94 self
.width
= (size
+ chunk_size
- 1) / chunk_size
97 self
.current_section_size
= size
99 self
.current_section_size
= self
.width
* chunk_size
102 def add_object (self
, klass
, offset
, size
):
103 SectionHandler
.add_object (self
, klass
, self
.offset
+ offset
, size
)
105 def add_occupied (self
, offset
, size
):
106 SectionHandler
.add_occupied (self
, self
.offset
+ offset
, size
)
108 def end_section (self
):
109 self
.offset
+= self
.current_section_size
112 return 'old sections'
114 class LargeSectionHandler (SectionHandler
):
116 SectionHandler
.__init
__ (self
, 512)
118 def start_section (self
, kind
, size
):
124 def end_section (self
):
128 return self
.kind
+ ' section'
130 class DocHandler (ContentHandler
):
132 self
.collection_index
= 0
133 self
.index_file
= open ('index.html', 'w')
134 print ('<html><body>', file = self
.index_file
)
137 print ('</body></html>', file = self
.index_file
)
138 self
.index_file
.close ()
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)
146 reason
= ' (%s)' % reason
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
)
158 self
.pinned_usage
= {}
159 self
.occupancies
= {}
161 self
.in_pinned
= False
166 self
.section_handler
= LargeSectionHandler ()
168 self
.section_handler
= self
.small_section_handler
= SmallSectionHandler ()
169 elif name
== 'pinned':
170 kind
= attrs
.get('type', None)
171 bytes
= int(attrs
.get('bytes', None))
172 print ('Pinned from %s: %d kB<br>' % (kind
, bytes
/ 1024), file = self
.collection_file
)
173 elif name
== 'occupancy':
174 size
= int (attrs
.get ('size', None))
175 available
= int (attrs
.get ('available', None))
176 used
= int (attrs
.get ('used', None))
177 unused
= available
- used
179 print ('Occupancy of %d byte slots: %d / %d (%d kB / %d%% wasted)<br>' % (size
, used
, available
, unused
* size
/ 1024, unused
* 100 / available
), file = self
.collection_file
)
180 elif name
== 'section':
181 kind
= attrs
.get('type', None)
182 size
= int(attrs
.get('size', None))
184 self
.heap_size
+= size
186 if not large_sections
:
187 if kind
== 'nursery':
188 self
.section_handler
= LargeSectionHandler ()
190 self
.section_handler
= self
.small_section_handler
192 self
.section_handler
.start_section (kind
, size
)
193 elif name
== 'object':
194 klass
= attrs
.get('class', None)
195 size
= int(attrs
.get('size', None))
198 usage_dict
= self
.los_usage
199 self
.los_size
+= size
201 location
= attrs
.get('location', None)
202 if location
not in self
.pinned_usage
:
203 self
.pinned_usage
[location
] = {}
204 usage_dict
= self
.pinned_usage
[location
]
206 usage_dict
= self
.usage
207 offset
= int(attrs
.get('offset', None))
209 self
.section_handler
.add_object (klass
, offset
, size
)
210 self
.heap_used
+= size
211 if not (klass
in usage_dict
):
212 usage_dict
[klass
] = (0, 0)
213 usage
= usage_dict
[klass
]
214 usage_dict
[klass
] = (usage
[0] + 1, usage
[1] + size
)
215 elif name
== 'occupied':
216 offset
= int(attrs
.get('offset', None))
217 size
= int(attrs
.get('size', None))
219 self
.section_handler
.add_occupied (offset
, size
)
220 self
.heap_used
+= size
223 elif name
== 'pinned-objects':
224 self
.in_pinned
= True
226 def dump_usage (self
, usage_dict
, limit
):
227 klasses
= sorted (usage_dict
.keys (), lambda x
, y
: usage_dict
[y
][1] - usage_dict
[x
][1])
229 klasses
= klasses
[0:limit
]
230 for klass
in klasses
:
231 usage
= usage_dict
[klass
]
232 if usage
[1] < 100000:
233 print ('%s %d bytes' % (klass
, usage
[1]), file = self
.collection_file
)
235 print ('%s %d kB' % (klass
, usage
[1] / 1024), file = self
.collection_file
)
236 print (' (%d)<br>' % usage
[0], file = self
.collection_file
)
238 def endElement (self
, name
):
239 if name
== 'section':
240 self
.section_handler
.end_section ()
242 if large_sections
or self
.section_handler
!= self
.small_section_handler
:
243 self
.section_handler
.emit (self
.collection_file
, self
.collection_kind
, self
.collection_num
, self
.section_num
)
244 self
.section_num
+= 1
245 elif name
== 'collection':
246 if not large_sections
:
247 self
.small_section_handler
.emit (self
.collection_file
, self
.collection_kind
, self
.collection_num
, self
.section_num
)
249 self
.dump_usage (self
.usage
, 10)
250 print ('<h3>LOS</h3>', file = self
.collection_file
)
251 self
.dump_usage (self
.los_usage
, None)
252 print ('<h3>Pinned</h3>', file = self
.collection_file
)
253 for location
in sorted (self
.pinned_usage
.keys ()):
254 print ('<h4>%s</h4>' % location
, file = self
.collection_file
)
255 self
.dump_usage (self
.pinned_usage
[location
], None)
256 print ('</body></html>', file = self
.collection_file
)
257 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
)
258 self
.collection_file
.close ()
259 self
.collection_index
+= 1
262 elif name
== 'pinned-objects':
263 self
.in_pinned
= False
266 usage
= "usage: %prog [options]"
267 parser
= OptionParser (usage
)
268 parser
.add_option ("-l", "--large-sections", action
= "store_true", dest
= "large_sections")
269 parser
.add_option ("-s", "--small-sections", action
= "store_false", dest
= "large_sections")
270 (options
, args
) = parser
.parse_args ()
271 if options
.large_sections
:
272 large_sections
= True
275 parser
= make_parser ()
276 parser
.setFeature (feature_namespaces
, 0)
277 parser
.setContentHandler (dh
)
279 parser
.parse (sys
.stdin
)
282 if __name__
== "__main__":