diff options
author | Mark Probst <mark.probst@gmail.com> | 2009-11-12 02:42:49 +0300 |
---|---|---|
committer | Mark Probst <mark.probst@gmail.com> | 2009-11-12 02:42:49 +0300 |
commit | 0b797fa874b1b58434a6a6963857aa130bcf35ac (patch) | |
tree | cdc3281aa5f33e5b901a024eaa54d461d9b96835 /scripts | |
parent | 6e7a101d5cabeaaa4aa1f8c2800fe6d79dfafc4e (diff) |
2009-11-12 Mark Probst <mark.probst@gmail.com>
* scripts/mono-heapviz: New script for generating SGen heap
visualizations.
* scripts/Makefile.am: mono-heapviz added.
svn path=/trunk/mono/; revision=146003
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/Makefile.am | 1 | ||||
-rwxr-xr-x | scripts/mono-heapviz | 257 |
2 files changed, 258 insertions, 0 deletions
diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 71705c96897..cdeaae3784d 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -73,6 +73,7 @@ scripts_2_0 = \ mdoc$(SCRIPT_SUFFIX) \ monolinker$(SCRIPT_SUFFIX) \ mono-api-info$(SCRIPT_SUFFIX) \ + mono-heapviz$(SCRIPT_SUFFIX) \ mono-shlib-cop$(SCRIPT_SUFFIX) \ monop2$(SCRIPT_SUFFIX) \ mozroots$(SCRIPT_SUFFIX) \ diff --git a/scripts/mono-heapviz b/scripts/mono-heapviz new file mode 100755 index 00000000000..c0ae0ef4baf --- /dev/null +++ b/scripts/mono-heapviz @@ -0,0 +1,257 @@ +#!/usr/bin/env python + +# Generate a heap visualization for SGen from the heap dump written by +# mono if the MONO_GC_DEBUG is set to something like +# "heap-dump=/path/to/file". This script accepts the file as stdin +# and generates HTML and PNG files. + +from __future__ import print_function +import sys, os +import Image, ImageDraw +from xml.sax import ContentHandler, saxutils, make_parser +from xml.sax.handler import feature_namespaces +from optparse import OptionParser + +chunk_size = 1024 # number of bytes in a chunk +chunk_pixel_size = 2 # a chunk is a square with this side length +large_sections = False + +def mark_chunk (img_draw, i, color, section_width): + row = i / section_width + col = i % section_width + pixel_col = col * chunk_pixel_size + pixel_row = row * chunk_pixel_size + img_draw.rectangle ([(pixel_col, pixel_row), (pixel_col + chunk_pixel_size - 1, pixel_row + chunk_pixel_size - 1)], fill = color) + +class Range: + pass + +class OccupiedRange (Range): + def __init__ (self, offset, size): + self.offset = offset + self.size = size + + def mark (self, img_draw, color, section_width): + start = self.offset / chunk_size + end = (self.offset + self.size - 1) / chunk_size + for i in range (start, end + 1): + mark_chunk (img_draw, i, color, section_width) + +class ObjectRange (OccupiedRange): + def __init__ (self, klass, offset, size): + OccupiedRange.__init__ (self, offset, size) + self.klass = klass; + +class SectionHandler: + def __init__ (self, width): + self.width = width + self.ranges = [] + self.size = 0 + self.used = 0 + + def add_object (self, klass, offset, size): + self.ranges.append (ObjectRange (klass, offset, size)) + self.used += size + + def add_occupied (self, offset, size): + self.ranges.append (OccupiedRange (offset, size)) + self.used += size + + def draw (self): + height = (((self.size / chunk_size) + (self.width - 1)) / self.width) * chunk_pixel_size + + color_background = (255, 255, 255) + color_free = (0, 0, 0) + color_occupied = (0, 255, 0) + + img = Image.new ('RGB', (self.width * chunk_pixel_size, height), color_free) + img_draw = ImageDraw.Draw (img) + #FIXME: remove filling after end of heap + + for r in self.ranges: + r.mark (img_draw, color_occupied, self.width) + + return img + + def emit (self, collection_file, collection_kind, collection_num, section_num): + print ('<h2>%s</h2>' % self.header (), file = collection_file) + print ('<p>Size %d kB - ' % (self.size / 1024), file = collection_file) + print ('used %d kB</p>' % (self.used / 1024), file = collection_file) + + filename = '%s_%d_%d.png' % (collection_kind, collection_num, section_num) + print ('<p><img src="%s"></img></p>' % filename, file = collection_file) + img = self.draw () + img.save (filename) + +class SmallSectionHandler (SectionHandler): + def __init__ (self): + SectionHandler.__init__ (self, -1) + self.offset = 0 + + def start_section (self, kind, size): + assert kind == 'old' + if self.width <= 0: + self.width = (size + chunk_size - 1) / chunk_size + else: + assert self.width == (size + chunk_size - 1) / chunk_size + self.size += size + + def add_object (self, klass, offset, size): + SectionHandler.add_object (self, klass, self.offset + offset, size) + + def add_occupied (self, offset, size): + SectionHandler.add_occupied (self, self.offset + offset, size) + + def end_section (self): + self.offset += self.width * chunk_size + + def header (self): + return 'old sections' + +class LargeSectionHandler (SectionHandler): + def __init__ (self): + SectionHandler.__init__ (self, 500) + + def start_section (self, kind, size): + self.kind = kind + self.ranges = [] + self.size = size + self.used = 0 + + def end_section (self): + pass + + def header (self): + return self.kind + ' section' + +class DocHandler (saxutils.DefaultHandler): + def start (self): + self.collection_index = 0 + self.index_file = open ('index.html', 'w') + print ('<html><body>', file = self.index_file) + + def end (self): + print ('</body></html>', file = self.index_file) + self.index_file.close () + + def startElement (self, name, attrs): + if name == 'collection': + self.collection_kind = attrs.get('type', None) + self.collection_num = int(attrs.get('num', None)) + reason = attrs.get('reason', None) + if reason: + reason = ' (%s)' % reason + else: + reason = '' + self.section_num = 0 + filename = 'collection_%d.html' % self.collection_index + print ('<a href="%s">%s%s collection %d</a>' % (filename, self.collection_kind, reason, self.collection_num), file = self.index_file) + self.collection_file = open (filename, 'w') + print ('<html><body>', file = self.collection_file) + 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) + print ('<h1>%s collection %d</h1>' % (self.collection_kind, self.collection_num), file = self.collection_file) + self.usage = {} + self.los_usage = {} + self.in_los = False + self.heap_used = 0 + self.heap_size = 0 + self.los_size = 0 + if large_sections: + self.section_handler = LargeSectionHandler () + else: + self.section_handler = self.small_section_handler = SmallSectionHandler () + elif name == 'pinned': + kind = attrs.get('type', None) + bytes = int(attrs.get('bytes', None)) + print ('Pinned from %s: %d kB<br>' % (kind, bytes / 1024), file = self.collection_file) + elif name == 'section': + kind = attrs.get('type', None) + size = int(attrs.get('size', None)) + + self.heap_size += size + + if not large_sections: + if kind == 'nursery': + self.section_handler = LargeSectionHandler () + else: + self.section_handler = self.small_section_handler + + self.section_handler.start_section (kind, size) + elif name == 'object': + klass = attrs.get('class', None) + size = int(attrs.get('size', None)) + + if self.in_los: + usage_dict = self.los_usage + self.los_size += size + else: + usage_dict = self.usage + offset = int(attrs.get('offset', None)) + + self.section_handler.add_object (klass, offset, size) + self.heap_used += size + if not (klass in usage_dict): + usage_dict [klass] = (0, 0) + usage = usage_dict [klass] + usage_dict [klass] = (usage [0] + 1, usage [1] + size) + elif name == 'occupied': + offset = int(attrs.get('offset', None)) + size = int(attrs.get('size', None)) + + self.section_handler.add_occupied (offset, size) + self.heap_used += size + elif name == 'los': + self.in_los = True + + def dump_usage (self, usage_dict, limit): + klasses = sorted (usage_dict.keys (), lambda x, y: usage_dict [y][1] - usage_dict [x][1]) + if limit: + klasses = klasses [0:limit] + for klass in klasses: + usage = usage_dict [klass] + if usage [1] < 100000: + print ('%s %d bytes' % (klass, usage [1]), file = self.collection_file) + else: + print ('%s %d kB' % (klass, usage [1] / 1024), file = self.collection_file) + print (' (%d)<br>' % usage [0], file = self.collection_file) + + def endElement (self, name): + if name == 'section': + self.section_handler.end_section () + + if large_sections or self.section_handler != self.small_section_handler: + self.section_handler.emit (self.collection_file, self.collection_kind, self.collection_num, self.section_num) + self.section_num += 1 + elif name == 'collection': + if not large_sections: + self.small_section_handler.emit (self.collection_file, self.collection_kind, self.collection_num, self.section_num) + + self.dump_usage (self.usage, 10) + print ('<h3>LOS</h3>', file = self.collection_file) + self.dump_usage (self.los_usage, None) + print ('</body></html>', file = self.collection_file) + 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) + self.collection_file.close () + self.collection_index += 1 + elif name == 'los': + self.in_los = False + +def main (): + usage = "usage: %prog [options]" + parser = OptionParser (usage) + parser.add_option ("-l", "--large-sections", action = "store_true", dest = "large_sections") + parser.add_option ("-s", "--small-sections", action = "store_false", dest = "large_sections") + (options, args) = parser.parse_args () + if options.large_sections: + large_sections = True + + dh = DocHandler () + parser = make_parser () + parser.setFeature (feature_namespaces, 0) + parser.setContentHandler (dh) + dh.start () + parser.parse (sys.stdin) + dh.end () + +if __name__ == "__main__": + main () |