| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library heap_map_element; |
| |
| import 'dart:async'; |
| import 'dart:html'; |
| import 'dart:math'; |
| import 'observatory_element.dart'; |
| import 'package:observatory/service.dart'; |
| import 'package:logging/logging.dart'; |
| import 'package:polymer/polymer.dart'; |
| |
| // 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; |
| } |
| |
| class ObjectInfo { |
| final address; |
| final size; |
| ObjectInfo(this.address, this.size); |
| } |
| |
| @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; |
| // Many browsers will not display a very tall canvas. |
| // TODO(koda): Improve interface for huge heaps. |
| static const _MAX_CANVAS_HEIGHT = 6000; |
| |
| @observable String status; |
| @published Isolate isolate; |
| @observable ServiceMap fragmentation; |
| |
| HeapMapElement.created() : super.created() { |
| } |
| |
| @override |
| void attached() { |
| super.attached(); |
| _fragmentationCanvas = shadowRoot.querySelector("#fragmentation"); |
| _fragmentationCanvas.onMouseMove.listen(_handleMouseMove); |
| _fragmentationCanvas.onMouseDown.listen(_handleClick); |
| } |
| |
| // 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.split('@')[0]; |
| _classIdToColor[classId] = color; |
| _colorToClassId[_packColor(color)] = classId; |
| } |
| |
| void _updateClassList(classList, int freeClassId) { |
| for (var member in classList['classes']) { |
| if (member is! Class) { |
| // TODO(turnidge): The printing for some of these non-class |
| // members is broken. Fix this: |
| // |
| // Logger.root.info('$member'); |
| Logger.root.info('Ignoring non-class in class list'); |
| continue; |
| } |
| var classId = int.parse(member.id.split('/').last); |
| var color = _classIdToRGBA(classId); |
| _addClass(classId, member.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)]]; |
| } |
| |
| ObjectInfo _objectAt(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 (pageIndex < 0 || pageIndex >= pages.length) { |
| return null; |
| } |
| // Scan the page to find start and size. |
| var page = pages[pageIndex]; |
| var objects = page['objects']; |
| var offset = 0; |
| var size = 0; |
| for (var i = 0; i < objects.length; i += 2) { |
| size = objects[i]; |
| offset += size; |
| if (offset > pageOffset) { |
| pageOffset = offset - size; |
| break; |
| } |
| } |
| return new ObjectInfo(int.parse(page['object_start']) + |
| pageOffset * fragmentation['unit_size_bytes'], |
| size * fragmentation['unit_size_bytes']); |
| } |
| |
| void _handleMouseMove(MouseEvent event) { |
| var info = _objectAt(event.offset); |
| var addressString = '${info.size}B @ 0x${info.address.toRadixString(16)}'; |
| var className = _classNameAt(event.offset); |
| status = (className == '') ? '-' : '$className $addressString'; |
| } |
| |
| void _handleClick(MouseEvent event) { |
| var address = _objectAt(event.offset).address.toRadixString(16); |
| isolate.getObjectByAddress(address).then((result) { |
| if (result is DartError) { |
| Logger.root.severe(result.message); |
| } else { |
| app.locationManager.go(gotoLink('/inspect', result)); |
| } |
| }); |
| } |
| |
| void _updateFragmentationData() { |
| if (fragmentation == null || _fragmentationCanvas == null) { |
| return; |
| } |
| _updateClassList( |
| fragmentation['class_list'], fragmentation['free_class_id']); |
| var pages = fragmentation['pages']; |
| var width = _fragmentationCanvas.parent.client.width; |
| _pageHeight = _PAGE_SEPARATION_HEIGHT + |
| fragmentation['page_size_bytes'] ~/ |
| fragmentation['unit_size_bytes'] ~/ width; |
| var height = min(_pageHeight * pages.length, _MAX_CANVAS_HEIGHT); |
| _fragmentationData = |
| _fragmentationCanvas.context2D.createImageData(width, height); |
| _fragmentationCanvas.width = _fragmentationData.width; |
| _fragmentationCanvas.height = _fragmentationData.height; |
| _renderPages(0); |
| } |
| |
| // Renders and draws asynchronously, one page at a time to avoid |
| // blocking the UI. |
| void _renderPages(int startPage) { |
| var pages = fragmentation['pages']; |
| status = 'Loaded $startPage of ${pages.length} pages'; |
| var startY = startPage * _pageHeight; |
| var endY = startY + _pageHeight; |
| if (startPage >= pages.length || endY > _fragmentationData.height) { |
| return; |
| } |
| 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(); |
| } |
| } |
| while (pixel.point.y < endY) { |
| pixel.color = _pageSeparationColor; |
| pixel = pixel.next(); |
| } |
| _fragmentationCanvas.context2D.putImageData( |
| _fragmentationData, 0, 0, 0, startY, _fragmentationData.width, endY); |
| // Continue with the next page, asynchronously. |
| new Future(() { |
| _renderPages(startPage + 1); |
| }); |
| } |
| |
| void isolateChanged(oldValue) { |
| if (isolate == null) { |
| fragmentation = null; |
| return; |
| } |
| isolate.invokeRpc('getHeapMap', {}).then((ServiceMap response) { |
| assert(response['type'] == 'HeapMap'); |
| fragmentation = response; |
| }).catchError((e, st) { |
| Logger.root.info('$e $st'); |
| }); |
| } |
| |
| void refresh(var done) { |
| if (isolate == null) { |
| return; |
| } |
| isolate.invokeRpc('getHeapMap', {}).then((ServiceMap response) { |
| assert(response['type'] == 'HeapMap'); |
| fragmentation = response; |
| }).catchError((e, st) { |
| Logger.root.info('$e $st'); |
| }).whenComplete(done); |
| } |
| |
| void fragmentationChanged(oldValue) { |
| // Async, in case attached has not yet run (observed in JS version). |
| new Future(() { |
| _updateFragmentationData(); |
| }); |
| } |
| } |