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
, 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
)
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
96 assert self
.width
== (size
+ chunk_size
- 1) / chunk_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
109 return 'old sections'
111 class LargeSectionHandler (SectionHandler
):
113 SectionHandler
.__init
__ (self
, 500)
115 def start_section (self
, kind
, size
):
121 def end_section (self
):
125 return self
.kind
+ ' section'
127 class DocHandler (saxutils
.DefaultHandler
):
129 self
.collection_index
= 0
130 self
.index_file
= open ('index.html', 'w')
131 print ('<html><body>', file = self
.index_file
)
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)
143 reason
= ' (%s)' % reason
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
)
155 self
.pinned_usage
= {}
157 self
.in_pinned
= False
162 self
.section_handler
= LargeSectionHandler ()
164 self
.section_handler
= self
.small_section_handler
= SmallSectionHandler ()
165 elif name
== 'pinned':
166 kind
= attrs
.get('type', None)
167 bytes
= int(attrs
.get('bytes', None))
168 print ('Pinned from %s: %d kB<br>' % (kind
, bytes
/ 1024), file = self
.collection_file
)
169 elif name
== 'section':
170 kind
= attrs
.get('type', None)
171 size
= int(attrs
.get('size', None))
173 self
.heap_size
+= size
175 if not large_sections
:
176 if kind
== 'nursery':
177 self
.section_handler
= LargeSectionHandler ()
179 self
.section_handler
= self
.small_section_handler
181 self
.section_handler
.start_section (kind
, size
)
182 elif name
== 'object':
183 klass
= attrs
.get('class', None)
184 size
= int(attrs
.get('size', None))
187 usage_dict
= self
.los_usage
188 self
.los_size
+= size
190 location
= attrs
.get('location', None)
191 if location
not in self
.pinned_usage
:
192 self
.pinned_usage
[location
] = {}
193 usage_dict
= self
.pinned_usage
[location
]
195 usage_dict
= self
.usage
196 offset
= int(attrs
.get('offset', None))
198 self
.section_handler
.add_object (klass
, offset
, size
)
199 self
.heap_used
+= size
200 if not (klass
in usage_dict
):
201 usage_dict
[klass
] = (0, 0)
202 usage
= usage_dict
[klass
]
203 usage_dict
[klass
] = (usage
[0] + 1, usage
[1] + size
)
204 elif name
== 'occupied':
205 offset
= int(attrs
.get('offset', None))
206 size
= int(attrs
.get('size', None))
208 self
.section_handler
.add_occupied (offset
, size
)
209 self
.heap_used
+= size
212 elif name
== 'pinned-objects':
213 self
.in_pinned
= True
215 def dump_usage (self
, usage_dict
, limit
):
216 klasses
= sorted (usage_dict
.keys (), lambda x
, y
: usage_dict
[y
][1] - usage_dict
[x
][1])
218 klasses
= klasses
[0:limit
]
219 for klass
in klasses
:
220 usage
= usage_dict
[klass
]
221 if usage
[1] < 100000:
222 print ('%s %d bytes' % (klass
, usage
[1]), file = self
.collection_file
)
224 print ('%s %d kB' % (klass
, usage
[1] / 1024), file = self
.collection_file
)
225 print (' (%d)<br>' % usage
[0], file = self
.collection_file
)
227 def endElement (self
, name
):
228 if name
== 'section':
229 self
.section_handler
.end_section ()
231 if large_sections
or self
.section_handler
!= self
.small_section_handler
:
232 self
.section_handler
.emit (self
.collection_file
, self
.collection_kind
, self
.collection_num
, self
.section_num
)
233 self
.section_num
+= 1
234 elif name
== 'collection':
235 if not large_sections
:
236 self
.small_section_handler
.emit (self
.collection_file
, self
.collection_kind
, self
.collection_num
, self
.section_num
)
238 self
.dump_usage (self
.usage
, 10)
239 print ('<h3>LOS</h3>', file = self
.collection_file
)
240 self
.dump_usage (self
.los_usage
, None)
241 print ('<h3>Pinned</h3>', file = self
.collection_file
)
242 for location
in sorted (self
.pinned_usage
.keys ()):
243 print ('<h4>%s</h4>' % location
, file = self
.collection_file
)
244 self
.dump_usage (self
.pinned_usage
[location
], None)
245 print ('</body></html>', file = self
.collection_file
)
246 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
)
247 self
.collection_file
.close ()
248 self
.collection_index
+= 1
251 elif name
== 'pinned-objects':
252 self
.in_pinned
= False
255 usage
= "usage: %prog [options]"
256 parser
= OptionParser (usage
)
257 parser
.add_option ("-l", "--large-sections", action
= "store_true", dest
= "large_sections")
258 parser
.add_option ("-s", "--small-sections", action
= "store_false", dest
= "large_sections")
259 (options
, args
) = parser
.parse_args ()
260 if options
.large_sections
:
261 large_sections
= True
264 parser
= make_parser ()
265 parser
.setFeature (feature_namespaces
, 0)
266 parser
.setContentHandler (dh
)
268 parser
.parse (sys
.stdin
)
271 if __name__
== "__main__":