| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library heap_map_element; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:html'; | |
| 9 import 'dart:math'; | |
| 10 import 'observatory_element.dart'; | |
| 11 import 'package:observatory/service.dart'; | |
| 12 import 'package:logging/logging.dart'; | |
| 13 import 'package:polymer/polymer.dart'; | |
| 14 | |
| 15 // A reference to a particular pixel of ImageData. | |
| 16 class PixelReference { | |
| 17 final _data; | |
| 18 var _dataIndex; | |
| 19 static const NUM_COLOR_COMPONENTS = 4; | |
| 20 | |
| 21 PixelReference(ImageData data, Point<int> point) | |
| 22 : _data = data, | |
| 23 _dataIndex = (point.y * data.width + point.x) * NUM_COLOR_COMPONENTS; | |
| 24 | |
| 25 PixelReference._fromDataIndex(this._data, this._dataIndex); | |
| 26 | |
| 27 Point<int> get point => | |
| 28 new Point(index % _data.width, index ~/ _data.width); | |
| 29 | |
| 30 void set color(Iterable<int> color) { | |
| 31 _data.data.setRange( | |
| 32 _dataIndex, _dataIndex + NUM_COLOR_COMPONENTS, color); | |
| 33 } | |
| 34 | |
| 35 Iterable<int> get color => | |
| 36 _data.data.getRange(_dataIndex, _dataIndex + NUM_COLOR_COMPONENTS); | |
| 37 | |
| 38 // Returns the next pixel in row-major order. | |
| 39 PixelReference next() => new PixelReference._fromDataIndex( | |
| 40 _data, _dataIndex + NUM_COLOR_COMPONENTS); | |
| 41 | |
| 42 // The row-major index of this pixel. | |
| 43 int get index => _dataIndex ~/ NUM_COLOR_COMPONENTS; | |
| 44 } | |
| 45 | |
| 46 class ObjectInfo { | |
| 47 final address; | |
| 48 final size; | |
| 49 ObjectInfo(this.address, this.size); | |
| 50 } | |
| 51 | |
| 52 @CustomTag('heap-map') | |
| 53 class HeapMapElement extends ObservatoryElement { | |
| 54 var _fragmentationCanvas; | |
| 55 var _fragmentationData; | |
| 56 var _pageHeight; | |
| 57 var _classIdToColor = {}; | |
| 58 var _colorToClassId = {}; | |
| 59 var _classIdToName = {}; | |
| 60 | |
| 61 static final _freeColor = [255, 255, 255, 255]; | |
| 62 static final _pageSeparationColor = [0, 0, 0, 255]; | |
| 63 static const _PAGE_SEPARATION_HEIGHT = 4; | |
| 64 // Many browsers will not display a very tall canvas. | |
| 65 // TODO(koda): Improve interface for huge heaps. | |
| 66 static const _MAX_CANVAS_HEIGHT = 6000; | |
| 67 | |
| 68 @observable String status; | |
| 69 @published ServiceMap fragmentation; | |
| 70 | |
| 71 HeapMapElement.created() : super.created() { | |
| 72 } | |
| 73 | |
| 74 @override | |
| 75 void attached() { | |
| 76 super.attached(); | |
| 77 _fragmentationCanvas = shadowRoot.querySelector("#fragmentation"); | |
| 78 _fragmentationCanvas.onMouseMove.listen(_handleMouseMove); | |
| 79 _fragmentationCanvas.onMouseDown.listen(_handleClick); | |
| 80 } | |
| 81 | |
| 82 // Encode color as single integer, to enable using it as a map key. | |
| 83 int _packColor(Iterable<int> color) { | |
| 84 int packed = 0; | |
| 85 for (var component in color) { | |
| 86 packed = packed * 256 + component; | |
| 87 } | |
| 88 return packed; | |
| 89 } | |
| 90 | |
| 91 void _addClass(int classId, String name, Iterable<int> color) { | |
| 92 _classIdToName[classId] = name.split('@')[0]; | |
| 93 _classIdToColor[classId] = color; | |
| 94 _colorToClassId[_packColor(color)] = classId; | |
| 95 } | |
| 96 | |
| 97 void _updateClassList(classList, int freeClassId) { | |
| 98 for (var member in classList['members']) { | |
| 99 if (member is! Class) { | |
| 100 Logger.root.info('$member'); | |
| 101 continue; | |
| 102 } | |
| 103 var classId = int.parse(member.id.split('/').last); | |
| 104 var color = _classIdToRGBA(classId); | |
| 105 _addClass(classId, member.name, color); | |
| 106 } | |
| 107 _addClass(freeClassId, 'Free', _freeColor); | |
| 108 _addClass(0, '', _pageSeparationColor); | |
| 109 } | |
| 110 | |
| 111 Iterable<int> _classIdToRGBA(int classId) { | |
| 112 // TODO(koda): Pick random hue, but fixed saturation and value. | |
| 113 var rng = new Random(classId); | |
| 114 return [rng.nextInt(128), rng.nextInt(128), rng.nextInt(128), 255]; | |
| 115 } | |
| 116 | |
| 117 String _classNameAt(Point<int> point) { | |
| 118 var color = new PixelReference(_fragmentationData, point).color; | |
| 119 return _classIdToName[_colorToClassId[_packColor(color)]]; | |
| 120 } | |
| 121 | |
| 122 ObjectInfo _objectAt(Point<int> point) { | |
| 123 var pagePixels = _pageHeight * _fragmentationData.width; | |
| 124 var index = new PixelReference(_fragmentationData, point).index; | |
| 125 var pageIndex = index ~/ pagePixels; | |
| 126 var pageOffset = index % pagePixels; | |
| 127 var pages = fragmentation['pages']; | |
| 128 if (pageIndex < 0 || pageIndex >= pages.length) { | |
| 129 return null; | |
| 130 } | |
| 131 // Scan the page to find start and size. | |
| 132 var page = pages[pageIndex]; | |
| 133 var objects = page['objects']; | |
| 134 var offset = 0; | |
| 135 var size = 0; | |
| 136 for (var i = 0; i < objects.length; i += 2) { | |
| 137 size = objects[i]; | |
| 138 offset += size; | |
| 139 if (offset > pageOffset) { | |
| 140 pageOffset = offset - size; | |
| 141 break; | |
| 142 } | |
| 143 } | |
| 144 return new ObjectInfo(int.parse(page['object_start']) + | |
| 145 pageOffset * fragmentation['unit_size_bytes'], | |
| 146 size * fragmentation['unit_size_bytes']); | |
| 147 } | |
| 148 | |
| 149 void _handleMouseMove(MouseEvent event) { | |
| 150 var info = _objectAt(event.offset); | |
| 151 var addressString = '${info.size}B @ 0x${info.address.toRadixString(16)}'; | |
| 152 var className = _classNameAt(event.offset); | |
| 153 status = (className == '') ? '-' : '$className $addressString'; | |
| 154 } | |
| 155 | |
| 156 void _handleClick(MouseEvent event) { | |
| 157 var address = _objectAt(event.offset).address.toRadixString(16); | |
| 158 app.locationManager.go(app.locationManager.makeLink( | |
| 159 "${fragmentation.isolate.relativeLink('address/$address')}")); | |
| 160 } | |
| 161 | |
| 162 void _updateFragmentationData() { | |
| 163 if (fragmentation == null || _fragmentationCanvas == null) { | |
| 164 return; | |
| 165 } | |
| 166 _updateClassList( | |
| 167 fragmentation['class_list'], fragmentation['free_class_id']); | |
| 168 var pages = fragmentation['pages']; | |
| 169 var width = _fragmentationCanvas.parent.client.width; | |
| 170 _pageHeight = _PAGE_SEPARATION_HEIGHT + | |
| 171 fragmentation['page_size_bytes'] ~/ | |
| 172 fragmentation['unit_size_bytes'] ~/ width; | |
| 173 var height = min(_pageHeight * pages.length, _MAX_CANVAS_HEIGHT); | |
| 174 _fragmentationData = | |
| 175 _fragmentationCanvas.context2D.createImageData(width, height); | |
| 176 _fragmentationCanvas.width = _fragmentationData.width; | |
| 177 _fragmentationCanvas.height = _fragmentationData.height; | |
| 178 _renderPages(0); | |
| 179 } | |
| 180 | |
| 181 // Renders and draws asynchronously, one page at a time to avoid | |
| 182 // blocking the UI. | |
| 183 void _renderPages(int startPage) { | |
| 184 var pages = fragmentation['pages']; | |
| 185 status = 'Loaded $startPage of ${pages.length} pages'; | |
| 186 var startY = startPage * _pageHeight; | |
| 187 var endY = startY + _pageHeight; | |
| 188 if (startPage >= pages.length || endY > _fragmentationData.height) { | |
| 189 return; | |
| 190 } | |
| 191 var pixel = new PixelReference(_fragmentationData, new Point(0, startY)); | |
| 192 var objects = pages[startPage]['objects']; | |
| 193 for (var i = 0; i < objects.length; i += 2) { | |
| 194 var count = objects[i]; | |
| 195 var classId = objects[i + 1]; | |
| 196 var color = _classIdToColor[classId]; | |
| 197 while (count-- > 0) { | |
| 198 pixel.color = color; | |
| 199 pixel = pixel.next(); | |
| 200 } | |
| 201 } | |
| 202 while (pixel.point.y < endY) { | |
| 203 pixel.color = _pageSeparationColor; | |
| 204 pixel = pixel.next(); | |
| 205 } | |
| 206 _fragmentationCanvas.context2D.putImageData( | |
| 207 _fragmentationData, 0, 0, 0, startY, _fragmentationData.width, endY); | |
| 208 // Continue with the next page, asynchronously. | |
| 209 new Future(() { | |
| 210 _renderPages(startPage + 1); | |
| 211 }); | |
| 212 } | |
| 213 | |
| 214 void refresh(var done) { | |
| 215 if (fragmentation == null) { | |
| 216 return; | |
| 217 } | |
| 218 fragmentation.isolate.get('heapmap').then((ServiceMap response) { | |
| 219 assert(response['type'] == 'HeapMap'); | |
| 220 fragmentation = response; | |
| 221 }).catchError((e, st) { | |
| 222 Logger.root.info('$e $st'); | |
| 223 }).whenComplete(done); | |
| 224 } | |
| 225 | |
| 226 void fragmentationChanged(oldValue) { | |
| 227 // Async, in case attached has not yet run (observed in JS version). | |
| 228 new Future(() { | |
| 229 _updateFragmentationData(); | |
| 230 }); | |
| 231 } | |
| 232 } | |
| OLD | NEW |