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 |