| Index: runtime/bin/vmservice/client/lib/src/elements/heap_map.dart
|
| ===================================================================
|
| --- runtime/bin/vmservice/client/lib/src/elements/heap_map.dart (revision 33794)
|
| +++ runtime/bin/vmservice/client/lib/src/elements/heap_map.dart (working copy)
|
| @@ -13,12 +13,50 @@
|
| import 'package:polymer/polymer.dart';
|
| import 'package:observatory/app.dart';
|
|
|
| -/// Displays an Error response.
|
| +// A reference to a particular pixel of ImageData.
|
| +class PixelReference {
|
| + final _data;
|
| + var _dataIndex;
|
| + static const NUM_COLOR_COMPONENTS = 4;
|
| +
|
| + PixelReference(ImageData data, Point<int> point)
|
| + : _data = data,
|
| + _dataIndex = (point.y * data.width + point.x) * NUM_COLOR_COMPONENTS;
|
| +
|
| + PixelReference._fromDataIndex(this._data, this._dataIndex);
|
| +
|
| + Point<int> get point =>
|
| + new Point(index % _data.width, index ~/ _data.width);
|
| +
|
| + void set color(Iterable<int> color) {
|
| + _data.data.setRange(
|
| + _dataIndex, _dataIndex + NUM_COLOR_COMPONENTS, color);
|
| + }
|
| +
|
| + Iterable<int> get color =>
|
| + _data.data.getRange(_dataIndex, _dataIndex + NUM_COLOR_COMPONENTS);
|
| +
|
| + // Returns the next pixel in row-major order.
|
| + PixelReference next() => new PixelReference._fromDataIndex(
|
| + _data, _dataIndex + NUM_COLOR_COMPONENTS);
|
| +
|
| + // The row-major index of this pixel.
|
| + int get index => _dataIndex ~/ NUM_COLOR_COMPONENTS;
|
| +}
|
| +
|
| @CustomTag('heap-map')
|
| class HeapMapElement extends ObservatoryElement {
|
| var _fragmentationCanvas;
|
| var _fragmentationData;
|
| -
|
| + var _pageHeight;
|
| + var _classIdToColor = {};
|
| + var _colorToClassId = {};
|
| + var _classIdToName = {};
|
| +
|
| + static final _freeColor = [255, 255, 255, 255];
|
| + static final _pageSeparationColor = [0, 0, 0, 255];
|
| + static const _PAGE_SEPARATION_HEIGHT = 4;
|
| +
|
| @observable String status;
|
| @published ServiceMap fragmentation;
|
|
|
| @@ -28,72 +66,119 @@
|
| void enteredView() {
|
| super.enteredView();
|
| _fragmentationCanvas = shadowRoot.querySelector("#fragmentation");
|
| + _fragmentationCanvas.onMouseMove.listen(_handleMouseMove);
|
| }
|
|
|
| - List<int> _classIdToRGBA(int classId) {
|
| - if (classId == fragmentation['free_class_id']) {
|
| - return [255, 255, 255, 255];
|
| + // Encode color as single integer, to enable using it as a map key.
|
| + int _packColor(Iterable<int> color) {
|
| + int packed = 0;
|
| + for (var component in color) {
|
| + packed = packed * 256 + component;
|
| + }
|
| + return packed;
|
| + }
|
| +
|
| + void _addClass(int classId, String name, Iterable<int> color) {
|
| + _classIdToName[classId] = name;
|
| + _classIdToColor[classId] = color;
|
| + _colorToClassId[_packColor(color)] = classId;
|
| + }
|
| +
|
| + void _updateClassList(classList, int freeClassId) {
|
| + for (var member in classList['members']) {
|
| + if (member['type'] != '@Class') {
|
| + Logger.root.info('$member');
|
| + continue;
|
| + }
|
| + var classId = int.parse(member['id'].split('/').last);
|
| + var color = _classIdToRGBA(classId);
|
| + _addClass(classId, member['user_name'], color);
|
| + }
|
| + _addClass(freeClassId, 'Free', _freeColor);
|
| + _addClass(0, '', _pageSeparationColor);
|
| + }
|
| +
|
| + Iterable<int> _classIdToRGBA(int classId) {
|
| + // TODO(koda): Pick random hue, but fixed saturation and value.
|
| + var rng = new Random(classId);
|
| + return [rng.nextInt(128), rng.nextInt(128), rng.nextInt(128), 255];
|
| + }
|
| +
|
| + String _classNameAt(Point<int> point) {
|
| + var color = new PixelReference(_fragmentationData, point).color;
|
| + return _classIdToName[_colorToClassId[_packColor(color)]];
|
| + }
|
| +
|
| + // TODO(koda): Find start of object.
|
| + int _addressAt(Point<int> point) {
|
| + var pagePixels = _pageHeight * _fragmentationData.width;
|
| + var index = new PixelReference(_fragmentationData, point).index;
|
| + var pageIndex = index ~/ pagePixels;
|
| + var pageOffset = index % pagePixels;
|
| + var pages = fragmentation['pages'];
|
| + if (0 <= pageIndex && pageIndex < pages.length) {
|
| + return int.parse(pages[pageIndex]['object_start']) +
|
| + pageOffset * fragmentation['unit_size_bytes'];
|
| } else {
|
| - // TODO(koda): Pick random hue, but fixed saturation and value.
|
| - var rng = new Random(classId);
|
| - return [rng.nextInt(128),
|
| - rng.nextInt(128),
|
| - rng.nextInt(128),
|
| - 255];
|
| + return 0;
|
| }
|
| }
|
| -
|
| +
|
| + void _handleMouseMove(MouseEvent event) {
|
| + var addressString = '@ 0x${_addressAt(event.offset).toRadixString(16)}';
|
| + var className = _classNameAt(event.offset);
|
| + status = (className == '') ? '-' : '$className $addressString';
|
| + }
|
| +
|
| void _updateFragmentationData() {
|
| if (fragmentation == null || _fragmentationCanvas == null) {
|
| return;
|
| }
|
| + _updateClassList(
|
| + fragmentation['class_list'], fragmentation['free_class_id']);
|
| var pages = fragmentation['pages'];
|
| - // Calculate dimensions.
|
| - var numPixels = 0;
|
| - var colorMap = {};
|
| - for (var page in pages) {
|
| - for (int i = 0; i < page.length; i += 2) {
|
| - numPixels += page[i];
|
| - var classId = page[i + 1];
|
| - colorMap.putIfAbsent(classId, () => _classIdToRGBA(classId));
|
| - }
|
| - }
|
| var width = _fragmentationCanvas.parent.client.width;
|
| - var height = (numPixels + width - 1) ~/ width;
|
| - // Render image.
|
| + _pageHeight = _PAGE_SEPARATION_HEIGHT +
|
| + fragmentation['page_size_bytes'] ~/
|
| + fragmentation['unit_size_bytes'] ~/ width;
|
| + var height = _pageHeight * pages.length;
|
| _fragmentationData =
|
| _fragmentationCanvas.context2D.createImageData(width, height);
|
| _fragmentationCanvas.width = _fragmentationData.width;
|
| _fragmentationCanvas.height = _fragmentationData.height;
|
| - _renderPages(0, 0, colorMap);
|
| + _renderPages(0);
|
| }
|
| -
|
| +
|
| // Renders and draws asynchronously, one page at a time to avoid
|
| // blocking the UI.
|
| - void _renderPages(int startPage, int dataIndex, var colorMap) {
|
| + void _renderPages(int startPage) {
|
| var pages = fragmentation['pages'];
|
| status = 'Loaded $startPage of ${pages.length} pages';
|
| if (startPage >= pages.length) {
|
| return;
|
| }
|
| - var width = _fragmentationData.width;
|
| - var dirtyBegin = (dataIndex / 4) ~/ width;
|
| - var page = pages[startPage];
|
| - for (var i = 0; i < page.length; i += 2) {
|
| - var count = page[i];
|
| - var color = colorMap[page[i + 1]];
|
| - for (var j = 0; j < count; ++j) {
|
| - for (var component in color) {
|
| - _fragmentationData.data[dataIndex++] = component;
|
| - }
|
| + var startY = startPage * _pageHeight;
|
| + var pixel = new PixelReference(_fragmentationData, new Point(0, startY));
|
| + var objects = pages[startPage]['objects'];
|
| + for (var i = 0; i < objects.length; i += 2) {
|
| + var count = objects[i];
|
| + var classId = objects[i + 1];
|
| + var color = _classIdToColor[classId];
|
| + while (count-- > 0) {
|
| + pixel.color = color;
|
| + pixel = pixel.next();
|
| }
|
| }
|
| - var dirtyEnd = (dataIndex / 4 + width - 1) ~/ width;
|
| + var endY = startY + _pageHeight;
|
| + while (pixel.point.y < endY) {
|
| + pixel.color = _pageSeparationColor;
|
| + pixel = pixel.next();
|
| + }
|
| _fragmentationCanvas.context2D.putImageData(
|
| - _fragmentationData, 0, 0, 0, dirtyBegin, width, dirtyEnd - dirtyBegin);
|
| + _fragmentationData, 0, 0, 0, startY, _fragmentationData.width, endY);
|
| // Continue with the next page, asynchronously.
|
| new Future(() {
|
| - _renderPages(startPage + 1, dataIndex, colorMap);
|
| + _renderPages(startPage + 1);
|
| });
|
| }
|
|
|
| @@ -108,7 +193,7 @@
|
| Logger.root.info('$e $st');
|
| }).whenComplete(done);
|
| }
|
| -
|
| +
|
| void fragmentationChanged(oldValue) {
|
| // Async, in case enteredView has not yet run (observed in JS version).
|
| new Future(() {
|
|
|