| 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 'observatory_element.dart'; |
| 11 import 'package:observatory/service.dart'; | 11 import 'package:observatory/service.dart'; |
| 12 import 'package:logging/logging.dart'; | 12 import 'package:logging/logging.dart'; |
| 13 import 'package:polymer/polymer.dart'; | 13 import 'package:polymer/polymer.dart'; |
| 14 import 'package:observatory/app.dart'; | 14 import 'package:observatory/app.dart'; |
| 15 | 15 |
| 16 /// Displays an Error response. | 16 // A reference to a particular pixel of ImageData. |
| 17 class PixelReference { |
| 18 final _data; |
| 19 var _dataIndex; |
| 20 static const NUM_COLOR_COMPONENTS = 4; |
| 21 |
| 22 PixelReference(ImageData data, Point<int> point) |
| 23 : _data = data, |
| 24 _dataIndex = (point.y * data.width + point.x) * NUM_COLOR_COMPONENTS; |
| 25 |
| 26 PixelReference._fromDataIndex(this._data, this._dataIndex); |
| 27 |
| 28 Point<int> get point => |
| 29 new Point(index % _data.width, index ~/ _data.width); |
| 30 |
| 31 void set color(Iterable<int> color) { |
| 32 _data.data.setRange( |
| 33 _dataIndex, _dataIndex + NUM_COLOR_COMPONENTS, color); |
| 34 } |
| 35 |
| 36 Iterable<int> get color => |
| 37 _data.data.getRange(_dataIndex, _dataIndex + NUM_COLOR_COMPONENTS); |
| 38 |
| 39 // Returns the next pixel in row-major order. |
| 40 PixelReference next() => new PixelReference._fromDataIndex( |
| 41 _data, _dataIndex + NUM_COLOR_COMPONENTS); |
| 42 |
| 43 // The row-major index of this pixel. |
| 44 int get index => _dataIndex ~/ NUM_COLOR_COMPONENTS; |
| 45 } |
| 46 |
| 17 @CustomTag('heap-map') | 47 @CustomTag('heap-map') |
| 18 class HeapMapElement extends ObservatoryElement { | 48 class HeapMapElement extends ObservatoryElement { |
| 19 var _fragmentationCanvas; | 49 var _fragmentationCanvas; |
| 20 var _fragmentationData; | 50 var _fragmentationData; |
| 21 | 51 var _pageHeight; |
| 52 var _classIdToColor = {}; |
| 53 var _colorToClassId = {}; |
| 54 var _classIdToName = {}; |
| 55 |
| 56 static final _freeColor = [255, 255, 255, 255]; |
| 57 static final _pageSeparationColor = [0, 0, 0, 255]; |
| 58 static const _PAGE_SEPARATION_HEIGHT = 4; |
| 59 |
| 22 @observable String status; | 60 @observable String status; |
| 23 @published ServiceMap fragmentation; | 61 @published ServiceMap fragmentation; |
| 24 | 62 |
| 25 HeapMapElement.created() : super.created() { | 63 HeapMapElement.created() : super.created() { |
| 26 } | 64 } |
| 27 | 65 |
| 28 void enteredView() { | 66 void enteredView() { |
| 29 super.enteredView(); | 67 super.enteredView(); |
| 30 _fragmentationCanvas = shadowRoot.querySelector("#fragmentation"); | 68 _fragmentationCanvas = shadowRoot.querySelector("#fragmentation"); |
| 69 _fragmentationCanvas.onMouseMove.listen(_handleMouseMove); |
| 31 } | 70 } |
| 32 | 71 |
| 33 List<int> _classIdToRGBA(int classId) { | 72 // Encode color as single integer, to enable using it as a map key. |
| 34 if (classId == fragmentation['free_class_id']) { | 73 int _packColor(Iterable<int> color) { |
| 35 return [255, 255, 255, 255]; | 74 int packed = 0; |
| 75 for (var component in color) { |
| 76 packed = packed * 256 + component; |
| 77 } |
| 78 return packed; |
| 79 } |
| 80 |
| 81 void _addClass(int classId, String name, Iterable<int> color) { |
| 82 _classIdToName[classId] = name; |
| 83 _classIdToColor[classId] = color; |
| 84 _colorToClassId[_packColor(color)] = classId; |
| 85 } |
| 86 |
| 87 void _updateClassList(classList, int freeClassId) { |
| 88 for (var member in classList['members']) { |
| 89 if (member['type'] != '@Class') { |
| 90 Logger.root.info('$member'); |
| 91 continue; |
| 92 } |
| 93 var classId = int.parse(member['id'].split('/').last); |
| 94 var color = _classIdToRGBA(classId); |
| 95 _addClass(classId, member['user_name'], color); |
| 96 } |
| 97 _addClass(freeClassId, 'Free', _freeColor); |
| 98 _addClass(0, '', _pageSeparationColor); |
| 99 } |
| 100 |
| 101 Iterable<int> _classIdToRGBA(int classId) { |
| 102 // TODO(koda): Pick random hue, but fixed saturation and value. |
| 103 var rng = new Random(classId); |
| 104 return [rng.nextInt(128), rng.nextInt(128), rng.nextInt(128), 255]; |
| 105 } |
| 106 |
| 107 String _classNameAt(Point<int> point) { |
| 108 var color = new PixelReference(_fragmentationData, point).color; |
| 109 return _classIdToName[_colorToClassId[_packColor(color)]]; |
| 110 } |
| 111 |
| 112 // TODO(koda): Find start of object. |
| 113 int _addressAt(Point<int> point) { |
| 114 var pagePixels = _pageHeight * _fragmentationData.width; |
| 115 var index = new PixelReference(_fragmentationData, point).index; |
| 116 var pageIndex = index ~/ pagePixels; |
| 117 var pageOffset = index % pagePixels; |
| 118 var pages = fragmentation['pages']; |
| 119 if (0 <= pageIndex && pageIndex < pages.length) { |
| 120 return int.parse(pages[pageIndex]['object_start']) + |
| 121 pageOffset * fragmentation['unit_size_bytes']; |
| 36 } else { | 122 } else { |
| 37 // TODO(koda): Pick random hue, but fixed saturation and value. | 123 return 0; |
| 38 var rng = new Random(classId); | |
| 39 return [rng.nextInt(128), | |
| 40 rng.nextInt(128), | |
| 41 rng.nextInt(128), | |
| 42 255]; | |
| 43 } | 124 } |
| 44 } | 125 } |
| 45 | 126 |
| 127 void _handleMouseMove(MouseEvent event) { |
| 128 var addressString = '@ 0x${_addressAt(event.offset).toRadixString(16)}'; |
| 129 var className = _classNameAt(event.offset); |
| 130 status = (className == '') ? '-' : '$className $addressString'; |
| 131 } |
| 132 |
| 46 void _updateFragmentationData() { | 133 void _updateFragmentationData() { |
| 47 if (fragmentation == null || _fragmentationCanvas == null) { | 134 if (fragmentation == null || _fragmentationCanvas == null) { |
| 48 return; | 135 return; |
| 49 } | 136 } |
| 137 _updateClassList( |
| 138 fragmentation['class_list'], fragmentation['free_class_id']); |
| 50 var pages = fragmentation['pages']; | 139 var pages = fragmentation['pages']; |
| 51 // Calculate dimensions. | |
| 52 var numPixels = 0; | |
| 53 var colorMap = {}; | |
| 54 for (var page in pages) { | |
| 55 for (int i = 0; i < page.length; i += 2) { | |
| 56 numPixels += page[i]; | |
| 57 var classId = page[i + 1]; | |
| 58 colorMap.putIfAbsent(classId, () => _classIdToRGBA(classId)); | |
| 59 } | |
| 60 } | |
| 61 var width = _fragmentationCanvas.parent.client.width; | 140 var width = _fragmentationCanvas.parent.client.width; |
| 62 var height = (numPixels + width - 1) ~/ width; | 141 _pageHeight = _PAGE_SEPARATION_HEIGHT + |
| 63 // Render image. | 142 fragmentation['page_size_bytes'] ~/ |
| 143 fragmentation['unit_size_bytes'] ~/ width; |
| 144 var height = _pageHeight * pages.length; |
| 64 _fragmentationData = | 145 _fragmentationData = |
| 65 _fragmentationCanvas.context2D.createImageData(width, height); | 146 _fragmentationCanvas.context2D.createImageData(width, height); |
| 66 _fragmentationCanvas.width = _fragmentationData.width; | 147 _fragmentationCanvas.width = _fragmentationData.width; |
| 67 _fragmentationCanvas.height = _fragmentationData.height; | 148 _fragmentationCanvas.height = _fragmentationData.height; |
| 68 _renderPages(0, 0, colorMap); | 149 _renderPages(0); |
| 69 } | 150 } |
| 70 | 151 |
| 71 // Renders and draws asynchronously, one page at a time to avoid | 152 // Renders and draws asynchronously, one page at a time to avoid |
| 72 // blocking the UI. | 153 // blocking the UI. |
| 73 void _renderPages(int startPage, int dataIndex, var colorMap) { | 154 void _renderPages(int startPage) { |
| 74 var pages = fragmentation['pages']; | 155 var pages = fragmentation['pages']; |
| 75 status = 'Loaded $startPage of ${pages.length} pages'; | 156 status = 'Loaded $startPage of ${pages.length} pages'; |
| 76 if (startPage >= pages.length) { | 157 if (startPage >= pages.length) { |
| 77 return; | 158 return; |
| 78 } | 159 } |
| 79 var width = _fragmentationData.width; | 160 var startY = startPage * _pageHeight; |
| 80 var dirtyBegin = (dataIndex / 4) ~/ width; | 161 var pixel = new PixelReference(_fragmentationData, new Point(0, startY)); |
| 81 var page = pages[startPage]; | 162 var objects = pages[startPage]['objects']; |
| 82 for (var i = 0; i < page.length; i += 2) { | 163 for (var i = 0; i < objects.length; i += 2) { |
| 83 var count = page[i]; | 164 var count = objects[i]; |
| 84 var color = colorMap[page[i + 1]]; | 165 var classId = objects[i + 1]; |
| 85 for (var j = 0; j < count; ++j) { | 166 var color = _classIdToColor[classId]; |
| 86 for (var component in color) { | 167 while (count-- > 0) { |
| 87 _fragmentationData.data[dataIndex++] = component; | 168 pixel.color = color; |
| 88 } | 169 pixel = pixel.next(); |
| 89 } | 170 } |
| 90 } | 171 } |
| 91 var dirtyEnd = (dataIndex / 4 + width - 1) ~/ width; | 172 var endY = startY + _pageHeight; |
| 173 while (pixel.point.y < endY) { |
| 174 pixel.color = _pageSeparationColor; |
| 175 pixel = pixel.next(); |
| 176 } |
| 92 _fragmentationCanvas.context2D.putImageData( | 177 _fragmentationCanvas.context2D.putImageData( |
| 93 _fragmentationData, 0, 0, 0, dirtyBegin, width, dirtyEnd - dirtyBegin); | 178 _fragmentationData, 0, 0, 0, startY, _fragmentationData.width, endY); |
| 94 // Continue with the next page, asynchronously. | 179 // Continue with the next page, asynchronously. |
| 95 new Future(() { | 180 new Future(() { |
| 96 _renderPages(startPage + 1, dataIndex, colorMap); | 181 _renderPages(startPage + 1); |
| 97 }); | 182 }); |
| 98 } | 183 } |
| 99 | 184 |
| 100 void refresh(var done) { | 185 void refresh(var done) { |
| 101 if (fragmentation == null) { | 186 if (fragmentation == null) { |
| 102 return; | 187 return; |
| 103 } | 188 } |
| 104 fragmentation.isolate.get('heapmap').then((ServiceMap response) { | 189 fragmentation.isolate.get('heapmap').then((ServiceMap response) { |
| 105 assert(response['type'] == 'HeapMap'); | 190 assert(response['type'] == 'HeapMap'); |
| 106 fragmentation = response; | 191 fragmentation = response; |
| 107 }).catchError((e, st) { | 192 }).catchError((e, st) { |
| 108 Logger.root.info('$e $st'); | 193 Logger.root.info('$e $st'); |
| 109 }).whenComplete(done); | 194 }).whenComplete(done); |
| 110 } | 195 } |
| 111 | 196 |
| 112 void fragmentationChanged(oldValue) { | 197 void fragmentationChanged(oldValue) { |
| 113 // Async, in case enteredView has not yet run (observed in JS version). | 198 // Async, in case enteredView has not yet run (observed in JS version). |
| 114 new Future(() { | 199 new Future(() { |
| 115 _updateFragmentationData(); | 200 _updateFragmentationData(); |
| 116 }); | 201 }); |
| 117 } | 202 } |
| 118 } | 203 } |
| OLD | NEW |