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
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 (saxutils
.DefaultHandler
):
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
= {}
160 self
.in_pinned
= False
165 self
.section_handler
= LargeSectionHandler ()
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))
176 self
.heap_size
+= size
178 if not large_sections
:
179 if kind
== 'nursery':
180 self
.section_handler
= LargeSectionHandler ()
182 self
.section_handler
= self
.small_section_handler
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))
190 usage_dict
= self
.los_usage
191 self
.los_size
+= size
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
]
198 usage_dict
= self
.usage
199 offset
= int(attrs
.get('offset', None))
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))
211 self
.section_handler
.add_occupied (offset
, size
)
212 self
.heap_used
+= size
215 elif name
== 'pinned-objects':
216 self
.in_pinned
= True
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])
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
)
227 print ('%s %d kB' % (klass
, usage
[1] / 1024), file = self
.collection_file
)
228 print (' (%d)<br>' % usage
[0], file = self
.collection_file
)
230 def endElement (self
, name
):
231 if name
== 'section':
232 self
.section_handler
.end_section ()
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
)
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
254 elif name
== 'pinned-objects':
255 self
.in_pinned
= False
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
267 parser
= make_parser ()
268 parser
.setFeature (feature_namespaces
, 0)
269 parser
.setContentHandler (dh
)
271 parser
.parse (sys
.stdin
)
274 if __name__
== "__main__":