| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library heap_map_element; | 5 library heap_map_element; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:html'; | 8 import 'dart:html'; |
| 9 import 'dart:math'; | 9 import 'dart:math'; |
| 10 import 'observatory_element.dart'; | 10 import 'package:observatory/models.dart' as M; |
| 11 import 'package:observatory/service.dart'; | 11 import 'package:observatory/service.dart' as S; |
| 12 import 'package:logging/logging.dart'; | 12 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
| 13 import 'package:polymer/polymer.dart'; | 13 import 'package:observatory/src/elements/helpers/tag.dart'; |
| 14 import 'package:observatory/src/elements/helpers/uris.dart'; |
| 15 import 'package:observatory/src/elements/nav/bar.dart'; |
| 16 import 'package:observatory/src/elements/nav/isolate_menu.dart'; |
| 17 import 'package:observatory/src/elements/nav/menu.dart'; |
| 18 import 'package:observatory/src/elements/nav/notify.dart'; |
| 19 import 'package:observatory/src/elements/nav/refresh.dart'; |
| 20 import 'package:observatory/src/elements/nav/top_menu.dart'; |
| 21 import 'package:observatory/src/elements/nav/vm_menu.dart'; |
| 22 class HeapMapElement extends HtmlElement implements Renderable { |
| 23 static const tag = const Tag<HeapMapElement>('heap-map', |
| 24 dependencies: const [ |
| 25 NavBarElement.tag, |
| 26 NavTopMenuElement.tag, |
| 27 NavVMMenuElement.tag, |
| 28 NavIsolateMenuElement.tag, |
| 29 NavMenuElement.tag, |
| 30 NavRefreshElement.tag, |
| 31 NavNotifyElement.tag, |
| 32 ]); |
| 14 | 33 |
| 15 // A reference to a particular pixel of ImageData. | 34 RenderingScheduler<HeapMapElement> _r; |
| 16 class PixelReference { | |
| 17 final _data; | |
| 18 var _dataIndex; | |
| 19 static const NUM_COLOR_COMPONENTS = 4; | |
| 20 | 35 |
| 21 PixelReference(ImageData data, Point<int> point) | 36 Stream<RenderedEvent<HeapMapElement>> get onRendered => |
| 22 : _data = data, | 37 _r.onRendered; |
| 23 _dataIndex = (point.y * data.width + point.x) * NUM_COLOR_COMPONENTS; | |
| 24 | 38 |
| 25 PixelReference._fromDataIndex(this._data, this._dataIndex); | 39 M.VM _vm; |
| 40 M.IsolateRef _isolate; |
| 41 M.EventRepository _events; |
| 42 M.NotificationRepository _notifications; |
| 43 M.VMRef get vm => _vm; |
| 44 M.IsolateRef get isolate => _isolate; |
| 45 M.NotificationRepository get notifications => _notifications; |
| 26 | 46 |
| 27 Point<int> get point => | 47 factory HeapMapElement(M.VM vm, M.IsolateRef isolate, |
| 28 new Point(index % _data.width, index ~/ _data.width); | 48 M.EventRepository events, |
| 29 | 49 M.NotificationRepository notifications, |
| 30 void set color(Iterable<int> color) { | 50 {RenderingQueue queue}) { |
| 31 _data.data.setRange( | 51 assert(vm != null); |
| 32 _dataIndex, _dataIndex + NUM_COLOR_COMPONENTS, color); | 52 assert(isolate != null); |
| 53 assert(events != null); |
| 54 assert(notifications != null); |
| 55 HeapMapElement e = document.createElement(tag.name); |
| 56 e._r = new RenderingScheduler(e, queue: queue); |
| 57 e._vm = vm; |
| 58 e._isolate = isolate; |
| 59 e._events = events; |
| 60 e._notifications = notifications; |
| 61 return e; |
| 33 } | 62 } |
| 34 | 63 |
| 35 Iterable<int> get color => | 64 HeapMapElement.created() : super.created(); |
| 36 _data.data.getRange(_dataIndex, _dataIndex + NUM_COLOR_COMPONENTS); | |
| 37 | 65 |
| 38 // Returns the next pixel in row-major order. | 66 @override |
| 39 PixelReference next() => new PixelReference._fromDataIndex( | 67 attached() { |
| 40 _data, _dataIndex + NUM_COLOR_COMPONENTS); | 68 super.attached(); |
| 69 _r.enable(); |
| 70 _refresh(); |
| 71 } |
| 41 | 72 |
| 42 // The row-major index of this pixel. | 73 @override |
| 43 int get index => _dataIndex ~/ NUM_COLOR_COMPONENTS; | 74 detached() { |
| 44 } | 75 super.detached(); |
| 76 _r.disable(notify: true); |
| 77 children = []; |
| 78 } |
| 45 | 79 |
| 46 class ObjectInfo { | 80 CanvasElement _canvas; |
| 47 final address; | |
| 48 final size; | |
| 49 ObjectInfo(this.address, this.size); | |
| 50 } | |
| 51 | |
| 52 @CustomTag('heap-map') | |
| 53 class HeapMapElement extends ObservatoryElement { | |
| 54 CanvasElement _fragmentationCanvas; | |
| 55 var _fragmentationData; | 81 var _fragmentationData; |
| 56 var _pageHeight; | 82 double _pageHeight; |
| 57 var _classIdToColor = {}; | 83 final _classIdToColor = {}; |
| 58 var _colorToClassId = {}; | 84 final _colorToClassId = {}; |
| 59 var _classIdToName = {}; | 85 final _classIdToName = {}; |
| 60 | 86 |
| 61 static final _freeColor = [255, 255, 255, 255]; | 87 static final _freeColor = [255, 255, 255, 255]; |
| 62 static final _pageSeparationColor = [0, 0, 0, 255]; | 88 static final _pageSeparationColor = [0, 0, 0, 255]; |
| 63 static const _PAGE_SEPARATION_HEIGHT = 4; | 89 static const _PAGE_SEPARATION_HEIGHT = 4; |
| 64 // Many browsers will not display a very tall canvas. | 90 // Many browsers will not display a very tall canvas. |
| 65 // TODO(koda): Improve interface for huge heaps. | 91 // TODO(koda): Improve interface for huge heaps. |
| 66 static const _MAX_CANVAS_HEIGHT = 6000; | 92 static const _MAX_CANVAS_HEIGHT = 6000; |
| 67 | 93 |
| 68 @observable String status; | 94 String _status; |
| 69 @published Isolate isolate; | 95 S.ServiceMap _fragmentation; |
| 70 @observable ServiceMap fragmentation; | |
| 71 | 96 |
| 72 HeapMapElement.created() : super.created() { | 97 void render() { |
| 73 } | 98 if (_canvas == null) { |
| 74 | 99 _canvas = new CanvasElement() |
| 75 @override | 100 ..width = 1 |
| 76 void attached() { | 101 ..height= 1 |
| 77 super.attached(); | 102 ..onMouseMove.listen(_handleMouseMove) |
| 78 _fragmentationCanvas = shadowRoot.querySelector("#fragmentation"); | 103 ..onMouseDown.listen(_handleClick); |
| 79 _fragmentationCanvas.onMouseMove.listen(_handleMouseMove); | 104 } |
| 80 _fragmentationCanvas.onMouseDown.listen(_handleClick); | 105 children = [ |
| 106 new NavBarElement(queue: _r.queue) |
| 107 ..children = [ |
| 108 new NavTopMenuElement(queue: _r.queue), |
| 109 new NavVMMenuElement(_vm, _events, queue: _r.queue), |
| 110 new NavIsolateMenuElement(_isolate, _events, queue: _r.queue), |
| 111 new NavMenuElement('heap map', last: true, |
| 112 link: Uris.heapMap(_isolate), queue: _r.queue), |
| 113 new NavRefreshElement(queue: _r.queue) |
| 114 ..onRefresh.listen((_) => _refresh()), |
| 115 new NavNotifyElement(_notifications, queue: _r.queue) |
| 116 ], |
| 117 new DivElement()..classes = const ['content-centered-big'] |
| 118 ..children = [ |
| 119 new HeadingElement.h2()..text = _status, |
| 120 new HRElement(), |
| 121 ], |
| 122 new DivElement()..classes = ['flex-row'] |
| 123 ..children = [_canvas] |
| 124 ]; |
| 81 } | 125 } |
| 82 | 126 |
| 83 // Encode color as single integer, to enable using it as a map key. | 127 // Encode color as single integer, to enable using it as a map key. |
| 84 int _packColor(Iterable<int> color) { | 128 int _packColor(Iterable<int> color) { |
| 85 int packed = 0; | 129 int packed = 0; |
| 86 for (var component in color) { | 130 for (var component in color) { |
| 87 packed = packed * 256 + component; | 131 packed = packed * 256 + component; |
| 88 } | 132 } |
| 89 return packed; | 133 return packed; |
| 90 } | 134 } |
| 91 | 135 |
| 92 void _addClass(int classId, String name, Iterable<int> color) { | 136 void _addClass(int classId, String name, Iterable<int> color) { |
| 93 _classIdToName[classId] = name.split('@')[0]; | 137 _classIdToName[classId] = name.split('@')[0]; |
| 94 _classIdToColor[classId] = color; | 138 _classIdToColor[classId] = color; |
| 95 _colorToClassId[_packColor(color)] = classId; | 139 _colorToClassId[_packColor(color)] = classId; |
| 96 } | 140 } |
| 97 | 141 |
| 98 void _updateClassList(classList, int freeClassId) { | 142 void _updateClassList(classList, int freeClassId) { |
| 99 for (var member in classList['classes']) { | 143 for (var member in classList['classes']) { |
| 100 if (member is! Class) { | 144 if (member is! S.Class) { |
| 101 // TODO(turnidge): The printing for some of these non-class | 145 // TODO(turnidge): The printing for some of these non-class |
| 102 // members is broken. Fix this: | 146 // members is broken. Fix this: |
| 103 // | 147 // |
| 104 // Logger.root.info('$member'); | 148 // Logger.root.info('$member'); |
| 105 Logger.root.info('Ignoring non-class in class list'); | 149 print('Ignoring non-class in class list'); |
| 106 continue; | 150 continue; |
| 107 } | 151 } |
| 108 var classId = int.parse(member.id.split('/').last); | 152 var classId = int.parse(member.id.split('/').last); |
| 109 var color = _classIdToRGBA(classId); | 153 var color = _classIdToRGBA(classId); |
| 110 _addClass(classId, member.name, color); | 154 _addClass(classId, member.name, color); |
| 111 } | 155 } |
| 112 _addClass(freeClassId, 'Free', _freeColor); | 156 _addClass(freeClassId, 'Free', _freeColor); |
| 113 _addClass(0, '', _pageSeparationColor); | 157 _addClass(0, '', _pageSeparationColor); |
| 114 } | 158 } |
| 115 | 159 |
| 116 Iterable<int> _classIdToRGBA(int classId) { | 160 Iterable<int> _classIdToRGBA(int classId) { |
| 117 // TODO(koda): Pick random hue, but fixed saturation and value. | 161 // TODO(koda): Pick random hue, but fixed saturation and value. |
| 118 var rng = new Random(classId); | 162 var rng = new Random(classId); |
| 119 return [rng.nextInt(128), rng.nextInt(128), rng.nextInt(128), 255]; | 163 return [rng.nextInt(128), rng.nextInt(128), rng.nextInt(128), 255]; |
| 120 } | 164 } |
| 121 | 165 |
| 122 String _classNameAt(Point<int> point) { | 166 String _classNameAt(Point<int> point) { |
| 123 var color = new PixelReference(_fragmentationData, point).color; | 167 var color = new PixelReference(_fragmentationData, point).color; |
| 124 return _classIdToName[_colorToClassId[_packColor(color)]]; | 168 return _classIdToName[_colorToClassId[_packColor(color)]]; |
| 125 } | 169 } |
| 126 | 170 |
| 127 ObjectInfo _objectAt(Point<int> point) { | 171 ObjectInfo _objectAt(Point<int> point) { |
| 128 if (fragmentation == null || _fragmentationCanvas == null) { | 172 if (_fragmentation == null || _canvas == null) { |
| 129 return null; | 173 return null; |
| 130 } | 174 } |
| 131 var pagePixels = _pageHeight * _fragmentationData.width; | 175 var pagePixels = _pageHeight * _fragmentationData.width; |
| 132 var index = new PixelReference(_fragmentationData, point).index; | 176 var index = new PixelReference(_fragmentationData, point).index; |
| 133 var pageIndex = index ~/ pagePixels; | 177 var pageIndex = index ~/ pagePixels; |
| 134 var pageOffset = index % pagePixels; | 178 var pageOffset = index % pagePixels; |
| 135 var pages = fragmentation['pages']; | 179 var pages = _fragmentation['pages']; |
| 136 if (pageIndex < 0 || pageIndex >= pages.length) { | 180 if (pageIndex < 0 || pageIndex >= pages.length) { |
| 137 return null; | 181 return null; |
| 138 } | 182 } |
| 139 // Scan the page to find start and size. | 183 // Scan the page to find start and size. |
| 140 var page = pages[pageIndex]; | 184 var page = pages[pageIndex]; |
| 141 var objects = page['objects']; | 185 var objects = page['objects']; |
| 142 var offset = 0; | 186 var offset = 0; |
| 143 var size = 0; | 187 var size = 0; |
| 144 for (var i = 0; i < objects.length; i += 2) { | 188 for (var i = 0; i < objects.length; i += 2) { |
| 145 size = objects[i]; | 189 size = objects[i]; |
| 146 offset += size; | 190 offset += size; |
| 147 if (offset > pageOffset) { | 191 if (offset > pageOffset) { |
| 148 pageOffset = offset - size; | 192 pageOffset = offset - size; |
| 149 break; | 193 break; |
| 150 } | 194 } |
| 151 } | 195 } |
| 152 return new ObjectInfo(int.parse(page['objectStart']) + | 196 return new ObjectInfo(int.parse(page['objectStart']) + |
| 153 pageOffset * fragmentation['unitSizeBytes'], | 197 pageOffset * _fragmentation['unitSizeBytes'], |
| 154 size * fragmentation['unitSizeBytes']); | 198 size * _fragmentation['unitSizeBytes']); |
| 155 } | 199 } |
| 156 | 200 |
| 157 void _handleMouseMove(MouseEvent event) { | 201 void _handleMouseMove(MouseEvent event) { |
| 158 var info = _objectAt(event.offset); | 202 var info = _objectAt(event.offset); |
| 159 if (info == null) { | 203 if (info == null) { |
| 160 status = ''; | 204 _status = ''; |
| 205 _r.dirty(); |
| 161 return; | 206 return; |
| 162 } | 207 } |
| 163 var addressString = '${info.size}B @ 0x${info.address.toRadixString(16)}'; | 208 var addressString = '${info.size}B @ 0x${info.address.toRadixString(16)}'; |
| 164 var className = _classNameAt(event.offset); | 209 var className = _classNameAt(event.offset); |
| 165 status = (className == '') ? '-' : '$className $addressString'; | 210 _status = (className == '') ? '-' : '$className $addressString'; |
| 211 _r.dirty(); |
| 166 } | 212 } |
| 167 | 213 |
| 168 void _handleClick(MouseEvent event) { | 214 void _handleClick(MouseEvent event) { |
| 169 var address = _objectAt(event.offset).address.toRadixString(16); | 215 final isolate = _isolate as S.Isolate; |
| 216 final address = _objectAt(event.offset).address.toRadixString(16); |
| 170 isolate.getObjectByAddress(address).then((result) { | 217 isolate.getObjectByAddress(address).then((result) { |
| 171 if (result.type != 'Sentinel') { | 218 if (result.type != 'Sentinel') { |
| 172 app.locationManager.go(gotoLink('/inspect', result)); | 219 new AnchorElement( |
| 220 href: Uris.inspect(_isolate, object: result as S.HeapObject) |
| 221 ).click(); |
| 173 } | 222 } |
| 174 }); | 223 }); |
| 175 } | 224 } |
| 176 | 225 |
| 177 void _updateFragmentationData() { | 226 void _updateFragmentationData() { |
| 178 if (fragmentation == null || _fragmentationCanvas == null) { | 227 if (_fragmentation == null || _canvas == null) { |
| 179 return; | 228 return; |
| 180 } | 229 } |
| 181 _updateClassList( | 230 _updateClassList( |
| 182 fragmentation['classList'], fragmentation['freeClassId']); | 231 _fragmentation['classList'], _fragmentation['freeClassId']); |
| 183 var pages = fragmentation['pages']; | 232 var pages = _fragmentation['pages']; |
| 184 var width = _fragmentationCanvas.parent.client.width; | 233 var width = _canvas.parent.client.width; |
| 185 _pageHeight = _PAGE_SEPARATION_HEIGHT + | 234 _pageHeight = _PAGE_SEPARATION_HEIGHT + |
| 186 fragmentation['pageSizeBytes'] ~/ | 235 _fragmentation['pageSizeBytes'] ~/ |
| 187 fragmentation['unitSizeBytes'] ~/ width; | 236 _fragmentation['unitSizeBytes'] ~/ width; |
| 188 var height = min(_pageHeight * pages.length, _MAX_CANVAS_HEIGHT); | 237 var height = min(_pageHeight * pages.length, _MAX_CANVAS_HEIGHT); |
| 189 _fragmentationData = | 238 _fragmentationData = |
| 190 _fragmentationCanvas.context2D.createImageData(width, height); | 239 _canvas.context2D.createImageData(width, height); |
| 191 _fragmentationCanvas.width = _fragmentationData.width; | 240 _canvas.width = _fragmentationData.width; |
| 192 _fragmentationCanvas.height = _fragmentationData.height; | 241 _canvas.height = _fragmentationData.height; |
| 193 _renderPages(0); | 242 _renderPages(0); |
| 194 } | 243 } |
| 195 | 244 |
| 196 // Renders and draws asynchronously, one page at a time to avoid | 245 // Renders and draws asynchronously, one page at a time to avoid |
| 197 // blocking the UI. | 246 // blocking the UI. |
| 198 void _renderPages(int startPage) { | 247 void _renderPages(int startPage) { |
| 199 var pages = fragmentation['pages']; | 248 var pages = _fragmentation['pages']; |
| 200 status = 'Loaded $startPage of ${pages.length} pages'; | 249 _status = 'Loaded $startPage of ${pages.length} pages'; |
| 250 _r.dirty(); |
| 201 var startY = startPage * _pageHeight; | 251 var startY = startPage * _pageHeight; |
| 202 var endY = startY + _pageHeight; | 252 var endY = startY + _pageHeight; |
| 203 if (startPage >= pages.length || endY > _fragmentationData.height) { | 253 if (startPage >= pages.length || endY > _fragmentationData.height) { |
| 204 return; | 254 return; |
| 205 } | 255 } |
| 206 var pixel = new PixelReference(_fragmentationData, new Point(0, startY)); | 256 var pixel = new PixelReference(_fragmentationData, new Point(0, startY)); |
| 207 var objects = pages[startPage]['objects']; | 257 var objects = pages[startPage]['objects']; |
| 208 for (var i = 0; i < objects.length; i += 2) { | 258 for (var i = 0; i < objects.length; i += 2) { |
| 209 var count = objects[i]; | 259 var count = objects[i]; |
| 210 var classId = objects[i + 1]; | 260 var classId = objects[i + 1]; |
| 211 var color = _classIdToColor[classId]; | 261 var color = _classIdToColor[classId]; |
| 212 while (count-- > 0) { | 262 while (count-- > 0) { |
| 213 pixel.color = color; | 263 pixel.color = color; |
| 214 pixel = pixel.next(); | 264 pixel = pixel.next(); |
| 215 } | 265 } |
| 216 } | 266 } |
| 217 while (pixel.point.y < endY) { | 267 while (pixel.point.y < endY) { |
| 218 pixel.color = _pageSeparationColor; | 268 pixel.color = _pageSeparationColor; |
| 219 pixel = pixel.next(); | 269 pixel = pixel.next(); |
| 220 } | 270 } |
| 221 _fragmentationCanvas.context2D.putImageData( | 271 _canvas.context2D.putImageData( |
| 222 _fragmentationData, 0, 0, 0, startY, _fragmentationData.width, endY); | 272 _fragmentationData, 0, 0, 0, startY, _fragmentationData.width, endY); |
| 223 // Continue with the next page, asynchronously. | 273 // Continue with the next page, asynchronously. |
| 224 new Future(() { | 274 new Future(() { |
| 225 _renderPages(startPage + 1); | 275 _renderPages(startPage + 1); |
| 226 }); | 276 }); |
| 227 } | 277 } |
| 228 | 278 |
| 229 void isolateChanged(oldValue) { | 279 Future _refresh() { |
| 230 if (isolate == null) { | 280 final isolate = _isolate as S.Isolate; |
| 231 fragmentation = null; | 281 return isolate.invokeRpc('_getHeapMap', {}).then((S.ServiceMap response) { |
| 232 return; | |
| 233 } | |
| 234 isolate.invokeRpc('_getHeapMap', {}).then((ServiceMap response) { | |
| 235 assert(response['type'] == 'HeapMap'); | 282 assert(response['type'] == 'HeapMap'); |
| 236 fragmentation = response; | 283 _fragmentation = response; |
| 237 }).catchError((e, st) { | |
| 238 Logger.root.info('$e $st'); | |
| 239 }); | |
| 240 } | |
| 241 | |
| 242 Future refresh() { | |
| 243 if (isolate == null) { | |
| 244 return new Future.value(null); | |
| 245 } | |
| 246 return isolate.invokeRpc('_getHeapMap', {}).then((ServiceMap response) { | |
| 247 assert(response['type'] == 'HeapMap'); | |
| 248 fragmentation = response; | |
| 249 }); | |
| 250 } | |
| 251 | |
| 252 void fragmentationChanged(oldValue) { | |
| 253 // Async, in case attached has not yet run (observed in JS version). | |
| 254 new Future(() { | |
| 255 _updateFragmentationData(); | 284 _updateFragmentationData(); |
| 256 }); | 285 }); |
| 257 } | 286 } |
| 258 } | 287 } |
| 288 |
| 289 // A reference to a particular pixel of ImageData. |
| 290 class PixelReference { |
| 291 final _data; |
| 292 var _dataIndex; |
| 293 static const NUM_COLOR_COMPONENTS = 4; |
| 294 |
| 295 PixelReference(ImageData data, Point<int> point) |
| 296 : _data = data, |
| 297 _dataIndex = (point.y * data.width + point.x) * NUM_COLOR_COMPONENTS; |
| 298 |
| 299 PixelReference._fromDataIndex(this._data, this._dataIndex); |
| 300 |
| 301 Point<int> get point => |
| 302 new Point(index % _data.width, index ~/ _data.width); |
| 303 |
| 304 void set color(Iterable<int> color) { |
| 305 _data.data.setRange( |
| 306 _dataIndex, _dataIndex + NUM_COLOR_COMPONENTS, color); |
| 307 } |
| 308 |
| 309 Iterable<int> get color => |
| 310 _data.data.getRange(_dataIndex, _dataIndex + NUM_COLOR_COMPONENTS); |
| 311 |
| 312 // Returns the next pixel in row-major order. |
| 313 PixelReference next() => new PixelReference._fromDataIndex( |
| 314 _data, _dataIndex + NUM_COLOR_COMPONENTS); |
| 315 |
| 316 // The row-major index of this pixel. |
| 317 int get index => _dataIndex ~/ NUM_COLOR_COMPONENTS; |
| 318 } |
| 319 |
| 320 class ObjectInfo { |
| 321 final address; |
| 322 final size; |
| 323 ObjectInfo(this.address, this.size); |
| 324 } |
| OLD | NEW |