mono-heapviz 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #!/usr/bin/env python3
  2. # Generate a heap visualization for SGen from the heap dump written by
  3. # mono if the MONO_GC_DEBUG is set to something like
  4. # "heap-dump=/path/to/file". This script accepts the file as stdin
  5. # and generates HTML and PNG files.
  6. from __future__ import print_function
  7. import sys, os
  8. import Image, ImageDraw
  9. from xml.sax import ContentHandler, make_parser
  10. from xml.sax.handler import feature_namespaces
  11. from optparse import OptionParser
  12. chunk_size = 1024 # number of bytes in a chunk
  13. chunk_pixel_size = 2 # a chunk is a square with this side length
  14. large_sections = False
  15. def mark_chunk (img_draw, i, color, section_width):
  16. row = i / section_width
  17. col = i % section_width
  18. pixel_col = col * chunk_pixel_size
  19. pixel_row = row * chunk_pixel_size
  20. img_draw.rectangle ([(pixel_col, pixel_row), (pixel_col + chunk_pixel_size - 1, pixel_row + chunk_pixel_size - 1)], fill = color)
  21. class Range:
  22. pass
  23. class OccupiedRange (Range):
  24. def __init__ (self, offset, size):
  25. self.offset = offset
  26. self.size = size
  27. def mark (self, img_draw, color, section_width):
  28. start = self.offset / chunk_size
  29. end = (self.offset + self.size - 1) / chunk_size
  30. for i in range (start, end + 1):
  31. mark_chunk (img_draw, i, color, section_width)
  32. class ObjectRange (OccupiedRange):
  33. def __init__ (self, klass, offset, size):
  34. OccupiedRange.__init__ (self, offset, size)
  35. self.klass = klass;
  36. class SectionHandler:
  37. def __init__ (self, width):
  38. self.width = width
  39. self.ranges = []
  40. self.size = 0
  41. self.used = 0
  42. def add_object (self, klass, offset, size):
  43. self.ranges.append (ObjectRange (klass, offset, size))
  44. self.used += size
  45. def add_occupied (self, offset, size):
  46. self.ranges.append (OccupiedRange (offset, size))
  47. self.used += size
  48. def draw (self):
  49. height = (((self.size / chunk_size) + (self.width - 1)) / self.width) * chunk_pixel_size
  50. color_background = (255, 255, 255)
  51. color_free = (0, 0, 0)
  52. color_occupied = (0, 255, 0)
  53. img = Image.new ('RGB', (self.width * chunk_pixel_size, height), color_free)
  54. img_draw = ImageDraw.Draw (img)
  55. #FIXME: remove filling after end of heap
  56. for r in self.ranges:
  57. r.mark (img_draw, color_occupied, self.width)
  58. return img
  59. def emit (self, collection_file, collection_kind, collection_num, section_num):
  60. print ('<h2>%s</h2>' % self.header (), file = collection_file)
  61. print ('<p>Size %d kB - ' % (self.size / 1024), file = collection_file)
  62. print ('used %d kB</p>' % (self.used / 1024), file = collection_file)
  63. filename = '%s_%d_%d.png' % (collection_kind, collection_num, section_num)
  64. print ('<p><img src="%s"></img></p>' % filename, file = collection_file)
  65. img = self.draw ()
  66. img.save (filename)
  67. class SmallSectionHandler (SectionHandler):
  68. def __init__ (self):
  69. SectionHandler.__init__ (self, -1)
  70. self.offset = 0
  71. def start_section (self, kind, size):
  72. assert kind == 'old'
  73. if self.width <= 0:
  74. self.width = (size + chunk_size - 1) / chunk_size
  75. if self.width < 128:
  76. self.width = 512
  77. self.current_section_size = size
  78. else:
  79. self.current_section_size = self.width * chunk_size
  80. self.size += size
  81. def add_object (self, klass, offset, size):
  82. SectionHandler.add_object (self, klass, self.offset + offset, size)
  83. def add_occupied (self, offset, size):
  84. SectionHandler.add_occupied (self, self.offset + offset, size)
  85. def end_section (self):
  86. self.offset += self.current_section_size
  87. def header (self):
  88. return 'old sections'
  89. class LargeSectionHandler (SectionHandler):
  90. def __init__ (self):
  91. SectionHandler.__init__ (self, 512)
  92. def start_section (self, kind, size):
  93. self.kind = kind
  94. self.ranges = []
  95. self.size = size
  96. self.used = 0
  97. def end_section (self):
  98. pass
  99. def header (self):
  100. return self.kind + ' section'
  101. class DocHandler (ContentHandler):
  102. def start (self):
  103. self.collection_index = 0
  104. self.index_file = open ('index.html', 'w')
  105. print ('<html><body>', file = self.index_file)
  106. def end (self):
  107. print ('</body></html>', file = self.index_file)
  108. self.index_file.close ()
  109. def startElement (self, name, attrs):
  110. if name == 'collection':
  111. self.collection_kind = attrs.get('type', None)
  112. self.collection_num = int(attrs.get('num', None))
  113. reason = attrs.get('reason', None)
  114. if reason:
  115. reason = ' (%s)' % reason
  116. else:
  117. reason = ''
  118. self.section_num = 0
  119. filename = 'collection_%d.html' % self.collection_index
  120. print ('<a href="%s">%s%s collection %d</a>' % (filename, self.collection_kind, reason, self.collection_num), file = self.index_file)
  121. self.collection_file = open (filename, 'w')
  122. print ('<html><body>', file = self.collection_file)
  123. 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)
  124. print ('<h1>%s collection %d</h1>' % (self.collection_kind, self.collection_num), file = self.collection_file)
  125. self.usage = {}
  126. self.los_usage = {}
  127. self.pinned_usage = {}
  128. self.occupancies = {}
  129. self.in_los = False
  130. self.in_pinned = False
  131. self.heap_used = 0
  132. self.heap_size = 0
  133. self.los_size = 0
  134. if large_sections:
  135. self.section_handler = LargeSectionHandler ()
  136. else:
  137. self.section_handler = self.small_section_handler = SmallSectionHandler ()
  138. elif name == 'pinned':
  139. kind = attrs.get('type', None)
  140. bytes = int(attrs.get('bytes', None))
  141. print ('Pinned from %s: %d kB<br>' % (kind, bytes / 1024), file = self.collection_file)
  142. elif name == 'occupancy':
  143. size = int (attrs.get ('size', None))
  144. available = int (attrs.get ('available', None))
  145. used = int (attrs.get ('used', None))
  146. unused = available - used
  147. 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)
  148. elif name == 'section':
  149. kind = attrs.get('type', None)
  150. size = int(attrs.get('size', None))
  151. self.heap_size += size
  152. if not large_sections:
  153. if kind == 'nursery':
  154. self.section_handler = LargeSectionHandler ()
  155. else:
  156. self.section_handler = self.small_section_handler
  157. self.section_handler.start_section (kind, size)
  158. elif name == 'object':
  159. klass = attrs.get('class', None)
  160. size = int(attrs.get('size', None))
  161. if self.in_los:
  162. usage_dict = self.los_usage
  163. self.los_size += size
  164. elif self.in_pinned:
  165. location = attrs.get('location', None)
  166. if location not in self.pinned_usage:
  167. self.pinned_usage[location] = {}
  168. usage_dict = self.pinned_usage[location]
  169. else:
  170. usage_dict = self.usage
  171. offset = int(attrs.get('offset', None))
  172. self.section_handler.add_object (klass, offset, size)
  173. self.heap_used += size
  174. if not (klass in usage_dict):
  175. usage_dict [klass] = (0, 0)
  176. usage = usage_dict [klass]
  177. usage_dict [klass] = (usage [0] + 1, usage [1] + size)
  178. elif name == 'occupied':
  179. offset = int(attrs.get('offset', None))
  180. size = int(attrs.get('size', None))
  181. self.section_handler.add_occupied (offset, size)
  182. self.heap_used += size
  183. elif name == 'los':
  184. self.in_los = True
  185. elif name == 'pinned-objects':
  186. self.in_pinned = True
  187. def dump_usage (self, usage_dict, limit):
  188. klasses = sorted (usage_dict.keys (), lambda x, y: usage_dict [y][1] - usage_dict [x][1])
  189. if limit:
  190. klasses = klasses [0:limit]
  191. for klass in klasses:
  192. usage = usage_dict [klass]
  193. if usage [1] < 100000:
  194. print ('%s %d bytes' % (klass, usage [1]), file = self.collection_file)
  195. else:
  196. print ('%s %d kB' % (klass, usage [1] / 1024), file = self.collection_file)
  197. print (' (%d)<br>' % usage [0], file = self.collection_file)
  198. def endElement (self, name):
  199. if name == 'section':
  200. self.section_handler.end_section ()
  201. if large_sections or self.section_handler != self.small_section_handler:
  202. self.section_handler.emit (self.collection_file, self.collection_kind, self.collection_num, self.section_num)
  203. self.section_num += 1
  204. elif name == 'collection':
  205. if not large_sections:
  206. self.small_section_handler.emit (self.collection_file, self.collection_kind, self.collection_num, self.section_num)
  207. self.dump_usage (self.usage, 10)
  208. print ('<h3>LOS</h3>', file = self.collection_file)
  209. self.dump_usage (self.los_usage, None)
  210. print ('<h3>Pinned</h3>', file = self.collection_file)
  211. for location in sorted (self.pinned_usage.keys ()):
  212. print ('<h4>%s</h4>' % location, file = self.collection_file)
  213. self.dump_usage (self.pinned_usage[location], None)
  214. print ('</body></html>', file = self.collection_file)
  215. 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)
  216. self.collection_file.close ()
  217. self.collection_index += 1
  218. elif name == 'los':
  219. self.in_los = False
  220. elif name == 'pinned-objects':
  221. self.in_pinned = False
  222. def main ():
  223. usage = "usage: %prog [options]"
  224. parser = OptionParser (usage)
  225. parser.add_option ("-l", "--large-sections", action = "store_true", dest = "large_sections")
  226. parser.add_option ("-s", "--small-sections", action = "store_false", dest = "large_sections")
  227. (options, args) = parser.parse_args ()
  228. if options.large_sections:
  229. large_sections = True
  230. dh = DocHandler ()
  231. parser = make_parser ()
  232. parser.setFeature (feature_namespaces, 0)
  233. parser.setContentHandler (dh)
  234. dh.start ()
  235. parser.parse (sys.stdin)
  236. dh.end ()
  237. if __name__ == "__main__":
  238. main ()