Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(585)

Side by Side Diff: runtime/observatory/lib/src/service/object.dart

Issue 839543002: Revert "Build Observatory with runtime" (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « runtime/observatory/lib/src/elements/vm_view.html ('k') | runtime/observatory/lib/tracer.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 part of service;
6
7 /// A [ServiceObject] is an object known to the VM service and is tied
8 /// to an owning [Isolate].
9 abstract class ServiceObject extends Observable {
10 static int LexicalSortName(ServiceObject o1, ServiceObject o2) {
11 return o1.name.compareTo(o2.name);
12 }
13
14 List removeDuplicatesAndSortLexical(List<ServiceObject> list) {
15 return list.toSet().toList()..sort(LexicalSortName);
16 }
17
18 /// The owner of this [ServiceObject]. This can be an [Isolate], a
19 /// [VM], or null.
20 @reflectable ServiceObjectOwner get owner => _owner;
21 ServiceObjectOwner _owner;
22
23 /// The [VM] which owns this [ServiceObject].
24 @reflectable VM get vm => _owner.vm;
25
26 /// The [Isolate] which owns this [ServiceObject]. May be null.
27 @reflectable Isolate get isolate => _owner.isolate;
28
29 /// The id of this object.
30 @reflectable String get id => _id;
31 String _id;
32
33 /// The user-level type of this object.
34 @reflectable String get type => _type;
35 String _type;
36
37 /// The vm type of this object.
38 @reflectable String get vmType => _vmType;
39 String _vmType;
40
41 static bool _isInstanceType(String type) {
42 switch (type) {
43 case 'BoundedType':
44 case 'Instance':
45 case 'List':
46 case 'String':
47 case 'Type':
48 case 'TypeParameter':
49 case 'TypeRef':
50 case 'bool':
51 case 'double':
52 case 'int':
53 case 'null':
54 return true;
55 default:
56 return false;
57 }
58 }
59
60 static bool _isTypeType(String type) {
61 switch (type) {
62 case 'BoundedType':
63 case 'Type':
64 case 'TypeParameter':
65 case 'TypeRef':
66 return true;
67 default:
68 return false;
69 }
70 }
71
72 bool get isAbstractType => _isTypeType(type);
73 bool get isBool => type == 'bool';
74 bool get isContext => type == 'Context';
75 bool get isDouble => type == 'double';
76 bool get isError => type == 'Error';
77 bool get isInstance => _isInstanceType(type);
78 bool get isInt => type == 'int';
79 bool get isList => type == 'List';
80 bool get isNull => type == 'null';
81 bool get isSentinel => type == 'Sentinel';
82 bool get isString => type == 'String';
83
84 // Kinds of Instance.
85 bool get isMirrorReference => vmType == 'MirrorReference';
86 bool get isWeakProperty => vmType == 'WeakProperty';
87 bool get isClosure => false;
88 bool get isPlainInstance {
89 return (type == 'Instance' &&
90 !isMirrorReference && !isWeakProperty && !isClosure);
91 }
92
93 /// The complete service url of this object.
94 @reflectable String get link => _owner.relativeLink(_id);
95
96 /// Has this object been fully loaded?
97 bool get loaded => _loaded;
98 bool _loaded = false;
99 // TODO(turnidge): Make loaded observable and get rid of loading
100 // from Isolate.
101
102 /// Is this object cacheable? That is, is it impossible for the [id]
103 /// of this object to change?
104 bool get canCache => false;
105
106 /// Is this object immutable after it is [loaded]?
107 bool get immutable => false;
108
109 @observable String name;
110 @observable String vmName;
111
112 /// Creates an empty [ServiceObject].
113 ServiceObject._empty(this._owner);
114
115 /// Creates a [ServiceObject] initialized from [map].
116 factory ServiceObject._fromMap(ServiceObjectOwner owner,
117 ObservableMap map) {
118 if (map == null) {
119 return null;
120 }
121 if (!_isServiceMap(map)) {
122 Logger.root.severe('Malformed service object: $map');
123 }
124 assert(_isServiceMap(map));
125 var type = _stripRef(map['type']);
126 var vmType = map['_vmType'] != null ? _stripRef(map['_vmType']) : type;
127 var obj = null;
128 assert(type != 'VM');
129 switch (type) {
130 case 'Class':
131 obj = new Class._empty(owner);
132 break;
133 case 'Code':
134 obj = new Code._empty(owner);
135 break;
136 case 'Context':
137 obj = new Context._empty(owner);
138 break;
139 case 'Counter':
140 obj = new ServiceMetric._empty(owner);
141 break;
142 case 'Error':
143 obj = new DartError._empty(owner);
144 break;
145 case 'Field':
146 obj = new Field._empty(owner);
147 break;
148 case 'Function':
149 obj = new ServiceFunction._empty(owner);
150 break;
151 case 'Gauge':
152 obj = new ServiceMetric._empty(owner);
153 break;
154 case 'Isolate':
155 obj = new Isolate._empty(owner.vm);
156 break;
157 case 'Library':
158 obj = new Library._empty(owner);
159 break;
160 case 'Object':
161 switch (vmType) {
162 case 'PcDescriptors':
163 obj = new PcDescriptors._empty(owner);
164 break;
165 case 'LocalVarDescriptors':
166 obj = new LocalVarDescriptors._empty(owner);
167 break;
168 case 'TokenStream':
169 obj = new TokenStream._empty(owner);
170 break;
171 }
172 break;
173 case 'ServiceError':
174 obj = new ServiceError._empty(owner);
175 break;
176 case 'ServiceEvent':
177 obj = new ServiceEvent._empty(owner);
178 break;
179 case 'ServiceException':
180 obj = new ServiceException._empty(owner);
181 break;
182 case 'Script':
183 obj = new Script._empty(owner);
184 break;
185 case 'Socket':
186 obj = new Socket._empty(owner);
187 break;
188 default:
189 if (_isInstanceType(type) ||
190 type == 'Sentinel') { // TODO(rmacnak): Separate this out.
191 obj = new Instance._empty(owner);
192 }
193 break;
194 }
195 if (obj == null) {
196 obj = new ServiceMap._empty(owner);
197 }
198 obj.update(map);
199 return obj;
200 }
201
202 /// If [this] was created from a reference, load the full object
203 /// from the service by calling [reload]. Else, return [this].
204 Future<ServiceObject> load() {
205 if (loaded) {
206 return new Future.value(this);
207 }
208 // Call reload which will fill in the entire object.
209 return reload();
210 }
211
212 Future<ServiceObject> _inProgressReload;
213
214 /// Reload [this]. Returns a future which completes to [this] or
215 /// a [ServiceError].
216 Future<ServiceObject> reload() {
217 if (id == '') {
218 // Errors don't have ids.
219 assert(type == 'Error');
220 return new Future.value(this);
221 }
222 if (loaded && immutable) {
223 return new Future.value(this);
224 }
225 if (_inProgressReload == null) {
226 _inProgressReload = vm.getAsMap(link).then((ObservableMap map) {
227 var mapType = _stripRef(map['type']);
228 if (mapType != _type) {
229 // If the type changes, return a new object instead of
230 // updating the existing one.
231 //
232 // TODO(turnidge): Check for vmType changing as well?
233 assert(mapType == 'Error' || mapType == 'Sentinel');
234 return new ServiceObject._fromMap(owner, map);
235 }
236 update(map);
237 return this;
238 }).whenComplete(() {
239 // This reload is complete.
240 _inProgressReload = null;
241 });
242 }
243 return _inProgressReload;
244 }
245
246 /// Update [this] using [map] as a source. [map] can be a reference.
247 void update(ObservableMap map) {
248 assert(_isServiceMap(map));
249
250 // Don't allow the type to change on an object update.
251 // TODO(turnidge): Make this a ServiceError?
252 var mapIsRef = _hasRef(map['type']);
253 var mapType = _stripRef(map['type']);
254 assert(_type == null || _type == mapType);
255
256 if (_id != null && _id != map['id']) {
257 // It is only safe to change an id when the object isn't cacheable.
258 assert(!canCache);
259 }
260 _id = map['id'];
261
262 _type = mapType;
263
264 // When the response specifies a specific vmType, use it.
265 // Otherwise the vmType of the response is the same as the 'user'
266 // type.
267 if (map.containsKey('_vmType')) {
268 _vmType = _stripRef(map['_vmType']);
269 } else {
270 _vmType = _type;
271 }
272
273 _update(map, mapIsRef);
274 }
275
276 // Updates internal state from [map]. [map] can be a reference.
277 void _update(ObservableMap map, bool mapIsRef);
278
279 String relativeLink(String id) {
280 assert(id != null);
281 return "${link}/${id}";
282 }
283 }
284
285 abstract class Coverage {
286 // Following getters and functions will be provided by [ServiceObject].
287 ServiceObjectOwner get owner;
288 String get type;
289 VM get vm;
290 String relativeLink(String id);
291
292 /// Default handler for coverage data.
293 void processCoverageData(List coverageData) {
294 coverageData.forEach((scriptCoverage) {
295 assert(scriptCoverage['script'] != null);
296 scriptCoverage['script']._processHits(scriptCoverage['hits']);
297 });
298 }
299
300 Future refreshCoverage() {
301 return vm.getAsMap(relativeLink('coverage')).then((ObservableMap map) {
302 var coverageOwner = (type == 'Isolate') ? this : owner;
303 var coverage = new ServiceObject._fromMap(coverageOwner, map);
304 assert(coverage.type == 'CodeCoverage');
305 var coverageList = coverage['coverage'];
306 assert(coverageList != null);
307 processCoverageData(coverageList);
308 });
309 }
310 }
311
312 abstract class ServiceObjectOwner extends ServiceObject {
313 /// Creates an empty [ServiceObjectOwner].
314 ServiceObjectOwner._empty(ServiceObjectOwner owner) : super._empty(owner);
315
316 /// Builds a [ServiceObject] corresponding to the [id] from [map].
317 /// The result may come from the cache. The result will not necessarily
318 /// be [loaded].
319 ServiceObject getFromMap(ObservableMap map);
320
321 /// Creates a link to [id] relative to [this].
322 String relativeLink(String id);
323 }
324
325 /// State for a VM being inspected.
326 abstract class VM extends ServiceObjectOwner {
327 @reflectable VM get vm => this;
328 @reflectable Isolate get isolate => null;
329
330 @reflectable Iterable<Isolate> get isolates => _isolateCache.values;
331
332 @reflectable String get link => '$id';
333 @reflectable String relativeLink(String id) => '$id';
334
335 @observable String version = 'unknown';
336 @observable String targetCPU;
337 @observable int architectureBits;
338 @observable double uptime = 0.0;
339 @observable bool assertsEnabled = false;
340 @observable bool typeChecksEnabled = false;
341 @observable String pid = '';
342 @observable DateTime lastUpdate;
343
344 VM() : super._empty(null) {
345 name = 'vm';
346 vmName = 'vm';
347 _cache['vm'] = this;
348 update(toObservable({'id':'vm', 'type':'@VM'}));
349 }
350
351 final StreamController<ServiceException> exceptions =
352 new StreamController.broadcast();
353 final StreamController<ServiceError> errors =
354 new StreamController.broadcast();
355 final StreamController<ServiceEvent> events =
356 new StreamController.broadcast();
357
358 void postEventMessage(String eventMessage, [dynamic data]) {
359 var map;
360 try {
361 map = _parseJSON(eventMessage);
362 assert(!map.containsKey('_data'));
363 if (data != null) {
364 map['_data'] = data;
365 }
366 } catch (e, st) {
367 Logger.root.severe('Ignoring malformed event message: ${eventMessage}');
368 return;
369 }
370 if (map['type'] != 'ServiceEvent') {
371 Logger.root.severe(
372 "Expected 'ServiceEvent' but found '${map['type']}'");
373 return;
374 }
375
376 // Extract the owning isolate from the event itself.
377 String owningIsolateId = map['isolate']['id'];
378 _getIsolate(owningIsolateId).then((owningIsolate) {
379 if (owningIsolate == null) {
380 // TODO(koda): Do we care about GC events in VM isolate?
381 Logger.root.severe(
382 'Ignoring event with unknown isolate id: $owningIsolateId');
383 } else {
384 var event = new ServiceObject._fromMap(owningIsolate, map);
385 events.add(event);
386 }
387 });
388 }
389
390 static final RegExp _currentIsolateMatcher = new RegExp(r'isolates/\d+');
391 static final RegExp _currentObjectMatcher = new RegExp(r'isolates/\d+/');
392 static final String _isolatesPrefix = 'isolates/';
393
394 String _parseObjectId(String id) {
395 Match m = _currentObjectMatcher.matchAsPrefix(id);
396 if (m == null) {
397 return null;
398 }
399 return m.input.substring(m.end);
400 }
401
402 String _parseIsolateId(String id) {
403 Match m = _currentIsolateMatcher.matchAsPrefix(id);
404 if (m == null) {
405 return '';
406 }
407 return id.substring(0, m.end);
408 }
409
410 Map<String,ServiceObject> _cache = new Map<String,ServiceObject>();
411 Map<String,Isolate> _isolateCache = new Map<String,Isolate>();
412
413 ServiceObject getFromMap(ObservableMap map) {
414 throw new UnimplementedError();
415 }
416
417 Future<ServiceObject> _getIsolate(String isolateId) {
418 if (isolateId == '') {
419 return new Future.value(null);
420 }
421 Isolate isolate = _isolateCache[isolateId];
422 if (isolate != null) {
423 return new Future.value(isolate);
424 }
425 // The isolate is not in the cache. Reload the vm and see if the
426 // requested isolate is found.
427 return reload().then((result) {
428 if (result is! VM) {
429 return null;
430 }
431 assert(result == this);
432 return _isolateCache[isolateId];
433 });
434 }
435
436 Future<ServiceObject> get(String id) {
437 assert(id.startsWith('/') == false);
438 // Isolates are handled specially, since they can cache sub-objects.
439 if (id.startsWith(_isolatesPrefix)) {
440 String isolateId = _parseIsolateId(id);
441 String objectId = _parseObjectId(id);
442 return _getIsolate(isolateId).then((isolate) {
443 if (isolate == null) {
444 // The isolate does not exist. Return the VM object instead.
445 //
446 // TODO(turnidge): Generate a service error?
447 return this;
448 }
449 if (objectId == null) {
450 return isolate.reload();
451 } else {
452 return isolate.get(objectId);
453 }
454 });
455 }
456
457 var obj = _cache[id];
458 if (obj != null) {
459 return obj.reload();
460 }
461
462 // Cache miss. Get the object from the vm directly.
463 return getAsMap(id).then((ObservableMap map) {
464 var obj = new ServiceObject._fromMap(this, map);
465 if (obj.canCache) {
466 _cache.putIfAbsent(id, () => obj);
467 }
468 return obj;
469 });
470 }
471
472 dynamic _reviver(dynamic key, dynamic value) {
473 return value;
474 }
475
476 ObservableMap _parseJSON(String response) {
477 var map;
478 try {
479 var decoder = new JsonDecoder(_reviver);
480 map = decoder.convert(response);
481 } catch (e, st) {
482 return null;
483 }
484 return toObservable(map);
485 }
486
487 Future<ObservableMap> _processMap(ObservableMap map) {
488 // Verify that the top level response is a service map.
489 if (!_isServiceMap(map)) {
490 return new Future.error(
491 new ServiceObject._fromMap(this, toObservable({
492 'type': 'ServiceException',
493 'id': '',
494 'kind': 'FormatException',
495 'response': map,
496 'message': 'Top level service responses must be service maps.',
497 })));
498 }
499 // Preemptively capture ServiceError and ServiceExceptions.
500 if (map['type'] == 'ServiceError') {
501 return new Future.error(new ServiceObject._fromMap(this, map));
502 } else if (map['type'] == 'ServiceException') {
503 return new Future.error(new ServiceObject._fromMap(this, map));
504 }
505 // map is now guaranteed to be a non-error/exception ServiceObject.
506 return new Future.value(map);
507 }
508
509 Future<ObservableMap> _decodeError(e) {
510 return new Future.error(new ServiceObject._fromMap(this, toObservable({
511 'type': 'ServiceException',
512 'id': '',
513 'kind': 'DecodeException',
514 'response':
515 'This is likely a result of a known V8 bug. Although the '
516 'the bug has been fixed the fix may not be in your Chrome'
517 ' version. For more information see dartbug.com/18385. '
518 'Observatory is still functioning and you should try your'
519 ' action again.',
520 'message': 'Could not decode JSON: $e',
521 })));
522 }
523
524 /// Gets [id] as an [ObservableMap] from the service directly. If
525 /// an error occurs, the future is completed as an error with a
526 /// ServiceError or ServiceException. Therefore any chained then() calls
527 /// will only receive a map encoding a valid ServiceObject.
528 Future<ObservableMap> getAsMap(String id) {
529 return getString(id).then((response) {
530 var map = _parseJSON(response);
531 if (Tracer.current != null) {
532 Tracer.current.trace("Received response for ${id}", map:map);
533 }
534 return _processMap(map);
535 }).catchError((error) {
536 // ServiceError, forward to VM's ServiceError stream.
537 errors.add(error);
538 return new Future.error(error);
539 }, test: (e) => e is ServiceError).catchError((exception) {
540 // ServiceException, forward to VM's ServiceException stream.
541 exceptions.add(exception);
542 return new Future.error(exception);
543 }, test: (e) => e is ServiceException);
544 }
545
546 /// Get [id] as a [String] from the service directly. See [getAsMap].
547 Future<String> getString(String id);
548 /// Force the VM to disconnect.
549 void disconnect();
550 /// Completes when the VM first connects.
551 Future get onConnect;
552 /// Completes when the VM disconnects or there was an error connecting.
553 Future get onDisconnect;
554
555 void _update(ObservableMap map, bool mapIsRef) {
556 if (mapIsRef) {
557 return;
558 }
559 _loaded = true;
560 version = map['version'];
561 targetCPU = map['targetCPU'];
562 architectureBits = map['architectureBits'];
563 uptime = map['uptime'];
564 var dateInMillis = int.parse(map['date']);
565 lastUpdate = new DateTime.fromMillisecondsSinceEpoch(dateInMillis);
566 assertsEnabled = map['assertsEnabled'];
567 pid = map['pid'];
568 typeChecksEnabled = map['typeChecksEnabled'];
569 _updateIsolates(map['isolates']);
570 }
571
572 void _updateIsolates(List newIsolates) {
573 var oldIsolateCache = _isolateCache;
574 var newIsolateCache = new Map<String,Isolate>();
575 for (var isolateMap in newIsolates) {
576 var isolateId = isolateMap['id'];
577 var isolate = oldIsolateCache[isolateId];
578 if (isolate != null) {
579 newIsolateCache[isolateId] = isolate;
580 } else {
581 isolate = new ServiceObject._fromMap(this, isolateMap);
582 newIsolateCache[isolateId] = isolate;
583 Logger.root.info('New isolate \'${isolate.id}\'');
584 }
585 }
586 // Update the individual isolates asynchronously.
587 newIsolateCache.forEach((isolateId, isolate) {
588 isolate.reload();
589 });
590
591 _isolateCache = newIsolateCache;
592 }
593 }
594
595 /// Snapshot in time of tag counters.
596 class TagProfileSnapshot {
597 final double seconds;
598 final List<int> counters;
599 int get sum => _sum;
600 int _sum = 0;
601 TagProfileSnapshot(this.seconds, int countersLength)
602 : counters = new List<int>(countersLength);
603
604 /// Set [counters] and update [sum].
605 void set(List<int> counters) {
606 this.counters.setAll(0, counters);
607 for (var i = 0; i < this.counters.length; i++) {
608 _sum += this.counters[i];
609 }
610 }
611
612 /// Set [counters] with the delta from [counters] to [old_counters]
613 /// and update [sum].
614 void delta(List<int> counters, List<int> old_counters) {
615 for (var i = 0; i < this.counters.length; i++) {
616 this.counters[i] = counters[i] - old_counters[i];
617 _sum += this.counters[i];
618 }
619 }
620
621 /// Update [counters] with new maximum values seen in [counters].
622 void max(List<int> counters) {
623 for (var i = 0; i < counters.length; i++) {
624 var c = counters[i];
625 this.counters[i] = this.counters[i] > c ? this.counters[i] : c;
626 }
627 }
628
629 /// Zero [counters].
630 void zero() {
631 for (var i = 0; i < counters.length; i++) {
632 counters[i] = 0;
633 }
634 }
635 }
636
637 class TagProfile {
638 final List<String> names = new List<String>();
639 final List<TagProfileSnapshot> snapshots = new List<TagProfileSnapshot>();
640 double get updatedAtSeconds => _seconds;
641 double _seconds;
642 TagProfileSnapshot _maxSnapshot;
643 int _historySize;
644 int _countersLength = 0;
645
646 TagProfile(this._historySize);
647
648 void _processTagProfile(double seconds, ObservableMap tagProfile) {
649 _seconds = seconds;
650 var counters = tagProfile['counters'];
651 if (names.length == 0) {
652 // Initialization.
653 names.addAll(tagProfile['names']);
654 _countersLength = tagProfile['counters'].length;
655 for (var i = 0; i < _historySize; i++) {
656 var snapshot = new TagProfileSnapshot(0.0, _countersLength);
657 snapshot.zero();
658 snapshots.add(snapshot);
659 }
660 // The counters monotonically grow, keep track of the maximum value.
661 _maxSnapshot = new TagProfileSnapshot(0.0, _countersLength);
662 _maxSnapshot.set(counters);
663 return;
664 }
665 var snapshot = new TagProfileSnapshot(seconds, _countersLength);
666 // We snapshot the delta from the current counters to the maximum counter
667 // values.
668 snapshot.delta(counters, _maxSnapshot.counters);
669 _maxSnapshot.max(counters);
670 snapshots.add(snapshot);
671 // Only keep _historySize snapshots.
672 if (snapshots.length > _historySize) {
673 snapshots.removeAt(0);
674 }
675 }
676 }
677
678 class HeapSpace extends Observable {
679 @observable int used = 0;
680 @observable int capacity = 0;
681 @observable int external = 0;
682 @observable int collections = 0;
683 @observable double totalCollectionTimeInSeconds = 0.0;
684 @observable double averageCollectionPeriodInMillis = 0.0;
685
686 void update(Map heapMap) {
687 used = heapMap['used'];
688 capacity = heapMap['capacity'];
689 external = heapMap['external'];
690 collections = heapMap['collections'];
691 totalCollectionTimeInSeconds = heapMap['time'];
692 averageCollectionPeriodInMillis = heapMap['avgCollectionPeriodMillis'];
693 }
694 }
695
696 /// State for a running isolate.
697 class Isolate extends ServiceObjectOwner with Coverage {
698 @reflectable VM get vm => owner;
699 @reflectable Isolate get isolate => this;
700 @observable ObservableMap counters = new ObservableMap();
701
702 String get link => '/${_id}';
703
704 @observable ServiceEvent pauseEvent = null;
705 bool get _isPaused => pauseEvent != null;
706
707 @observable bool running = false;
708 @observable bool idle = false;
709 @observable bool loading = true;
710 @observable bool ioEnabled = false;
711
712 Map<String,ServiceObject> _cache = new Map<String,ServiceObject>();
713 final TagProfile tagProfile = new TagProfile(20);
714
715 Isolate._empty(ServiceObjectOwner owner) : super._empty(owner) {
716 assert(owner is VM);
717 }
718
719 /// Creates a link to [id] relative to [this].
720 @reflectable String relativeLink(String id) => '/${this.id}/$id';
721
722 static const TAG_ROOT_ID = 'code/tag-0';
723
724 /// Returns the Code object for the root tag.
725 Code tagRoot() {
726 // TODO(turnidge): Use get() here instead?
727 return _cache[TAG_ROOT_ID];
728 }
729
730 void processProfile(ServiceMap profile) {
731 assert(profile.type == 'Profile');
732 var codeTable = new List<Code>();
733 var codeRegions = profile['codes'];
734 for (var codeRegion in codeRegions) {
735 Code code = codeRegion['code'];
736 assert(code != null);
737 codeTable.add(code);
738 }
739 _resetProfileData();
740 _updateProfileData(profile, codeTable);
741 var exclusiveTrie = profile['exclusive_trie'];
742 if (exclusiveTrie != null) {
743 profileTrieRoot = _processProfileTrie(exclusiveTrie, codeTable);
744 }
745 }
746
747 void _resetProfileData() {
748 _cache.values.forEach((value) {
749 if (value is Code) {
750 Code code = value;
751 code.resetProfileData();
752 }
753 });
754 }
755
756 void _updateProfileData(ServiceMap profile, List<Code> codeTable) {
757 var codeRegions = profile['codes'];
758 var sampleCount = profile['samples'];
759 for (var codeRegion in codeRegions) {
760 Code code = codeRegion['code'];
761 code.updateProfileData(codeRegion, codeTable, sampleCount);
762 }
763 }
764
765 /// Fetches and builds the class hierarchy for this isolate. Returns the
766 /// Object class object.
767 Future<Class> getClassHierarchy() {
768 return get('classes').then(_loadClasses).then(_buildClassHierarchy);
769 }
770
771 /// Given the class list, loads each class.
772 Future<List<Class>> _loadClasses(ServiceMap classList) {
773 assert(classList.type == 'ClassList');
774 var futureClasses = [];
775 for (var cls in classList['members']) {
776 // Skip over non-class classes.
777 if (cls is Class) {
778 futureClasses.add(cls.load());
779 }
780 }
781 return Future.wait(futureClasses);
782 }
783
784 /// Builds the class hierarchy and returns the Object class.
785 Future<Class> _buildClassHierarchy(List<Class> classes) {
786 rootClasses.clear();
787 objectClass = null;
788 for (var cls in classes) {
789 if (cls.superclass == null) {
790 rootClasses.add(cls);
791 }
792 if ((cls.vmName == 'Object') && (cls.isPatch == false)) {
793 objectClass = cls;
794 }
795 }
796 assert(objectClass != null);
797 return new Future.value(objectClass);
798 }
799
800 ServiceObject getFromMap(ObservableMap map) {
801 if (map == null) {
802 return null;
803 }
804 String id = map['id'];
805 var obj = _cache[id];
806 if (obj != null) {
807 // Consider calling update when map is not a reference.
808 return obj;
809 }
810 // Build the object from the map directly.
811 obj = new ServiceObject._fromMap(this, map);
812 if (obj != null && obj.canCache) {
813 _cache[id] = obj;
814 }
815 return obj;
816 }
817
818 Future<ServiceObject> get(String id) {
819 // Do not allow null ids or empty ids.
820 assert(id != null && id != '');
821 var obj = _cache[id];
822 if (obj != null) {
823 return obj.reload();
824 }
825 // Cache miss. Get the object from the vm directly.
826 return vm.getAsMap(relativeLink(id)).then((ObservableMap map) {
827 var obj = new ServiceObject._fromMap(this, map);
828 if (obj.canCache) {
829 _cache.putIfAbsent(id, () => obj);
830 }
831 return obj;
832 });
833 }
834
835 @observable Class objectClass;
836 @observable final rootClasses = new ObservableList<Class>();
837
838 @observable Library rootLib;
839 @observable ObservableList<Library> libraries =
840 new ObservableList<Library>();
841 @observable ObservableMap topFrame;
842
843 @observable String name;
844 @observable String vmName;
845 @observable String mainPort;
846 @observable ServiceFunction entry;
847
848 @observable final Map<String, double> timers =
849 toObservable(new Map<String, double>());
850
851 final HeapSpace newSpace = new HeapSpace();
852 final HeapSpace oldSpace = new HeapSpace();
853
854 @observable String fileAndLine;
855
856 @observable DartError error;
857
858 void updateHeapsFromMap(ObservableMap map) {
859 newSpace.update(map['new']);
860 oldSpace.update(map['old']);
861 }
862
863 void _update(ObservableMap map, bool mapIsRef) {
864 mainPort = map['mainPort'];
865 name = map['name'];
866 vmName = map['name'];
867 if (mapIsRef) {
868 return;
869 }
870 _loaded = true;
871 loading = false;
872
873 reloadBreakpoints();
874 _upgradeCollection(map, isolate);
875 if (map['rootLib'] == null ||
876 map['timers'] == null ||
877 map['heaps'] == null) {
878 Logger.root.severe("Malformed 'Isolate' response: $map");
879 return;
880 }
881 rootLib = map['rootLib'];
882 if (map['entry'] != null) {
883 entry = map['entry'];
884 }
885 if (map['topFrame'] != null) {
886 topFrame = map['topFrame'];
887 } else {
888 topFrame = null ;
889 }
890
891 var countersMap = map['tagCounters'];
892 if (countersMap != null) {
893 var names = countersMap['names'];
894 var counts = countersMap['counters'];
895 assert(names.length == counts.length);
896 var sum = 0;
897 for (var i = 0; i < counts.length; i++) {
898 sum += counts[i];
899 }
900 // TODO: Why does this not work without this?
901 counters = toObservable({});
902 if (sum == 0) {
903 for (var i = 0; i < names.length; i++) {
904 counters[names[i]] = '0.0%';
905 }
906 } else {
907 for (var i = 0; i < names.length; i++) {
908 counters[names[i]] =
909 (counts[i] / sum * 100.0).toStringAsFixed(2) + '%';
910 }
911 }
912 }
913 var timerMap = {};
914 map['timers'].forEach((timer) {
915 timerMap[timer['name']] = timer['time'];
916 });
917 timers['total'] = timerMap['time_total_runtime'];
918 timers['compile'] = timerMap['time_compilation'];
919 timers['gc'] = 0.0; // TODO(turnidge): Export this from VM.
920 timers['init'] = (timerMap['time_script_loading'] +
921 timerMap['time_creating_snapshot'] +
922 timerMap['time_isolate_initialization'] +
923 timerMap['time_bootstrap']);
924 timers['dart'] = timerMap['time_dart_execution'];
925
926 updateHeapsFromMap(map['heaps']);
927
928 List features = map['features'];
929 if (features != null) {
930 for (var feature in features) {
931 if (feature == 'io') {
932 ioEnabled = true;
933 }
934 }
935 }
936 // Isolate status
937 pauseEvent = map['pauseEvent'];
938 running = (!_isPaused && map['topFrame'] != null);
939 idle = (!_isPaused && map['topFrame'] == null);
940 error = map['error'];
941
942 libraries.clear();
943 libraries.addAll(map['libraries']);
944 libraries.sort(ServiceObject.LexicalSortName);
945 }
946
947 Future<TagProfile> updateTagProfile() {
948 return vm.getAsMap(relativeLink('profile/tag')).then((ObservableMap m) {
949 var seconds = new DateTime.now().millisecondsSinceEpoch / 1000.0;
950 tagProfile._processTagProfile(seconds, m);
951 return tagProfile;
952 });
953 }
954
955 @reflectable CodeTrieNode profileTrieRoot;
956 // The profile trie is serialized as a list of integers. Each node
957 // is recreated by consuming some portion of the list. The format is as
958 // follows:
959 // [0] index into codeTable of code object.
960 // [1] tick count (number of times this stack frame occured).
961 // [2] child node count
962 // Reading the trie is done by recursively reading the tree depth-first
963 // pre-order.
964 CodeTrieNode _processProfileTrie(List<int> data, List<Code> codeTable) {
965 // Setup state shared across calls to _readTrieNode.
966 _trieDataCursor = 0;
967 _trieData = data;
968 if (_trieData == null) {
969 return null;
970 }
971 if (_trieData.length < 3) {
972 // Not enough integers for 1 node.
973 return null;
974 }
975 // Read the tree, returns the root node.
976 return _readTrieNode(codeTable);
977 }
978 int _trieDataCursor;
979 List<int> _trieData;
980 CodeTrieNode _readTrieNode(List<Code> codeTable) {
981 // Read index into code table.
982 var index = _trieData[_trieDataCursor++];
983 // Lookup code object.
984 var code = codeTable[index];
985 // Frame counter.
986 var count = _trieData[_trieDataCursor++];
987 // Create node.
988 var node = new CodeTrieNode(code, count);
989 // Number of children.
990 var children = _trieData[_trieDataCursor++];
991 // Recursively read child nodes.
992 for (var i = 0; i < children; i++) {
993 var child = _readTrieNode(codeTable);
994 node.children.add(child);
995 node.summedChildCount += child.count;
996 }
997 return node;
998 }
999
1000 ServiceMap breakpoints;
1001
1002 void _removeBreakpoint(ServiceMap bpt) {
1003 var script = bpt['location']['script'];
1004 var tokenPos = bpt['location']['tokenPos'];
1005 assert(tokenPos != null);
1006 if (script.loaded) {
1007 var line = script.tokenToLine(tokenPos);
1008 assert(line != null);
1009 assert(script.lines[line - 1].bpt == bpt);
1010 script.lines[line - 1].bpt = null;
1011 }
1012 }
1013
1014 void _addBreakpoint(ServiceMap bpt) {
1015 var script = bpt['location']['script'];
1016 var tokenPos = bpt['location']['tokenPos'];
1017 assert(tokenPos != null);
1018 if (script.loaded) {
1019 var line = script.tokenToLine(tokenPos);
1020 assert(line != null);
1021 assert(script.lines[line - 1].bpt == null);
1022 script.lines[line - 1].bpt = bpt;
1023 } else {
1024 // Load the script and then plop in the breakpoint.
1025 script.load().then((_) {
1026 _addBreakpoint(bpt);
1027 });
1028 }
1029 }
1030
1031 void _updateBreakpoints(ServiceMap newBreakpoints) {
1032 // Remove all of the old breakpoints from the Script lines.
1033 if (breakpoints != null) {
1034 for (var bpt in breakpoints['breakpoints']) {
1035 _removeBreakpoint(bpt);
1036 }
1037 }
1038 // Add all of the new breakpoints to the Script lines.
1039 for (var bpt in newBreakpoints['breakpoints']) {
1040 _addBreakpoint(bpt);
1041 }
1042 breakpoints = newBreakpoints;
1043 }
1044
1045 Future<ServiceObject> _inProgressReloadBpts;
1046
1047 Future reloadBreakpoints() {
1048 // TODO(turnidge): Can reusing the Future here ever cause us to
1049 // get stale breakpoints?
1050 if (_inProgressReloadBpts == null) {
1051 _inProgressReloadBpts =
1052 get('debug/breakpoints').then((newBpts) {
1053 _updateBreakpoints(newBpts);
1054 }).whenComplete(() {
1055 _inProgressReloadBpts = null;
1056 });
1057 }
1058 return _inProgressReloadBpts;
1059 }
1060
1061 Future<ServiceObject> setBreakpoint(Script script, int line) {
1062 return get(script.id + "/setBreakpoint?line=${line}").then((result) {
1063 if (result is DartError) {
1064 // Unable to set a breakpoint at desired line.
1065 script.lines[line - 1].possibleBpt = false;
1066 }
1067 return reloadBreakpoints();
1068 });
1069 }
1070
1071 Future clearBreakpoint(ServiceMap bpt) {
1072 return get('${bpt.id}/clear').then((result) {
1073 if (result is DartError) {
1074 // TODO(turnidge): Handle this more gracefully.
1075 Logger.root.severe(result.message);
1076 }
1077 if (pauseEvent != null &&
1078 pauseEvent.breakpoint != null &&
1079 (pauseEvent.breakpoint['id'] == bpt['id'])) {
1080 return isolate.reload();
1081 } else {
1082 return reloadBreakpoints();
1083 }
1084 });
1085 }
1086
1087 Future pause() {
1088 return get("debug/pause").then((result) {
1089 if (result is DartError) {
1090 // TODO(turnidge): Handle this more gracefully.
1091 Logger.root.severe(result.message);
1092 }
1093 return isolate.reload();
1094 });
1095 }
1096
1097 Future resume() {
1098 return get("debug/resume").then((result) {
1099 if (result is DartError) {
1100 // TODO(turnidge): Handle this more gracefully.
1101 Logger.root.severe(result.message);
1102 }
1103 return isolate.reload();
1104 });
1105 }
1106
1107 Future stepInto() {
1108 return get("debug/resume?step=into").then((result) {
1109 if (result is DartError) {
1110 // TODO(turnidge): Handle this more gracefully.
1111 Logger.root.severe(result.message);
1112 }
1113 return isolate.reload();
1114 });
1115 }
1116
1117 Future stepOver() {
1118 return get("debug/resume?step=over").then((result) {
1119 if (result is DartError) {
1120 // TODO(turnidge): Handle this more gracefully.
1121 Logger.root.severe(result.message);
1122 }
1123 return isolate.reload();
1124 });
1125 }
1126
1127 Future stepOut() {
1128 return get("debug/resume?step=out").then((result) {
1129 if (result is DartError) {
1130 // TODO(turnidge): Handle this more gracefully.
1131 Logger.root.severe(result.message);
1132 }
1133 return isolate.reload();
1134 });
1135 }
1136
1137 final ObservableMap<String, ServiceMetric> dartMetrics =
1138 new ObservableMap<String, ServiceMetric>();
1139
1140 final ObservableMap<String, ServiceMetric> vmMetrics =
1141 new ObservableMap<String, ServiceMetric>();
1142
1143 Future<ObservableMap<String, ServiceMetric>> _refreshMetrics(
1144 String id,
1145 ObservableMap<String, ServiceMetric> metricsMap) {
1146 return get(id).then((result) {
1147 if (result is DartError) {
1148 // TODO(turnidge): Handle this more gracefully.
1149 Logger.root.severe(result.message);
1150 return null;
1151 }
1152 // Clear metrics map.
1153 metricsMap.clear();
1154 // Repopulate metrics map.
1155 var members = result['members'];
1156 for (var metric in members) {
1157 metricsMap[metric.id] = metric;
1158 }
1159 return metricsMap;
1160 });
1161 }
1162
1163 Future<ObservableMap<String, ServiceMetric>> refreshDartMetrics() {
1164 return _refreshMetrics('metrics', dartMetrics);
1165 }
1166
1167 Future<ObservableMap<String, ServiceMetric>> refreshVMMetrics() {
1168 return _refreshMetrics('metrics/vm', vmMetrics);
1169 }
1170
1171 Future refreshMetrics() {
1172 return refreshDartMetrics().then((_) => refreshVMMetrics());
1173 }
1174
1175 String toString() => "Isolate($_id)";
1176 }
1177
1178 /// A [ServiceObject] which implements [ObservableMap].
1179 class ServiceMap extends ServiceObject implements ObservableMap {
1180 final ObservableMap _map = new ObservableMap();
1181 static String objectIdRingPrefix = 'objects/';
1182
1183 bool get canCache {
1184 return (_type == 'Class' ||
1185 _type == 'Function' ||
1186 _type == 'Field') &&
1187 !_id.startsWith(objectIdRingPrefix);
1188 }
1189 bool get immutable => false;
1190
1191 ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner);
1192
1193 void _upgradeValues() {
1194 assert(owner != null);
1195 _upgradeCollection(_map, owner);
1196 }
1197
1198 void _update(ObservableMap map, bool mapIsRef) {
1199 _loaded = !mapIsRef;
1200
1201 // TODO(turnidge): Currently _map.clear() prevents us from
1202 // upgrading an already upgraded submap. Is clearing really the
1203 // right thing to do here?
1204 _map.clear();
1205 _map.addAll(map);
1206
1207 name = _map['name'];
1208 vmName = (_map.containsKey('vmName') ? _map['vmName'] : name);
1209 _upgradeValues();
1210 }
1211
1212 // Forward Map interface calls.
1213 void addAll(Map other) => _map.addAll(other);
1214 void clear() => _map.clear();
1215 bool containsValue(v) => _map.containsValue(v);
1216 bool containsKey(k) => _map.containsKey(k);
1217 void forEach(Function f) => _map.forEach(f);
1218 putIfAbsent(key, Function ifAbsent) => _map.putIfAbsent(key, ifAbsent);
1219 void remove(key) => _map.remove(key);
1220 operator [](k) => _map[k];
1221 operator []=(k, v) => _map[k] = v;
1222 bool get isEmpty => _map.isEmpty;
1223 bool get isNotEmpty => _map.isNotEmpty;
1224 Iterable get keys => _map.keys;
1225 Iterable get values => _map.values;
1226 int get length => _map.length;
1227
1228 // Forward ChangeNotifier interface calls.
1229 bool deliverChanges() => _map.deliverChanges();
1230 void notifyChange(ChangeRecord record) => _map.notifyChange(record);
1231 notifyPropertyChange(Symbol field, Object oldValue, Object newValue) =>
1232 _map.notifyPropertyChange(field, oldValue, newValue);
1233 void observed() => _map.observed();
1234 void unobserved() => _map.unobserved();
1235 Stream<List<ChangeRecord>> get changes => _map.changes;
1236 bool get hasObservers => _map.hasObservers;
1237
1238 String toString() => "ServiceMap($_map)";
1239 }
1240
1241 /// A [DartError] is peered to a Dart Error object.
1242 class DartError extends ServiceObject {
1243 DartError._empty(ServiceObject owner) : super._empty(owner);
1244
1245 @observable String kind;
1246 @observable String message;
1247 @observable Instance exception;
1248 @observable Instance stacktrace;
1249
1250 void _update(ObservableMap map, bool mapIsRef) {
1251 kind = map['kind'];
1252 message = map['message'];
1253 exception = new ServiceObject._fromMap(owner, map['exception']);
1254 stacktrace = new ServiceObject._fromMap(owner, map['stacktrace']);
1255 name = 'DartError $kind';
1256 vmName = name;
1257 }
1258
1259 String toString() => 'DartError($message)';
1260 }
1261
1262 /// A [ServiceError] is an error that was triggered in the service
1263 /// server or client. Errors are prorammer mistakes that could have
1264 /// been prevented, for example, requesting a non-existant path over the
1265 /// service.
1266 class ServiceError extends ServiceObject {
1267 ServiceError._empty(ServiceObjectOwner owner) : super._empty(owner);
1268
1269 @observable String kind;
1270 @observable String message;
1271
1272 void _update(ObservableMap map, bool mapIsRef) {
1273 _loaded = true;
1274 kind = map['kind'];
1275 message = map['message'];
1276 name = 'ServiceError $kind';
1277 vmName = name;
1278 }
1279
1280 String toString() => 'ServiceError($message)';
1281 }
1282
1283 /// A [ServiceException] is an exception that was triggered in the service
1284 /// server or client. Exceptions are events that should be handled,
1285 /// for example, an isolate went away or the connection to the VM was lost.
1286 class ServiceException extends ServiceObject {
1287 ServiceException._empty(ServiceObject owner) : super._empty(owner);
1288
1289 @observable String kind;
1290 @observable String message;
1291 @observable dynamic response;
1292
1293 void _update(ObservableMap map, bool mapIsRef) {
1294 kind = map['kind'];
1295 message = map['message'];
1296 response = map['response'];
1297 name = 'ServiceException $kind';
1298 vmName = name;
1299 }
1300
1301 String toString() => 'ServiceException($message)';
1302 }
1303
1304 /// A [ServiceEvent] is an asynchronous event notification from the vm.
1305 class ServiceEvent extends ServiceObject {
1306 ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner);
1307
1308 ServiceEvent.vmDisconencted() : super._empty(null) {
1309 eventType = 'VMDisconnected';
1310 }
1311
1312 @observable String eventType;
1313 @observable ServiceMap breakpoint;
1314 @observable ServiceMap exception;
1315 @observable ByteData data;
1316 @observable int count;
1317
1318 void _update(ObservableMap map, bool mapIsRef) {
1319 _loaded = true;
1320 _upgradeCollection(map, owner);
1321 eventType = map['eventType'];
1322 name = 'ServiceEvent $eventType';
1323 vmName = name;
1324 if (map['breakpoint'] != null) {
1325 breakpoint = map['breakpoint'];
1326 }
1327 if (map['exception'] != null) {
1328 exception = map['exception'];
1329 }
1330 if (map['_data'] != null) {
1331 data = map['_data'];
1332 }
1333 if (map['count'] != null) {
1334 count = map['count'];
1335 }
1336 }
1337
1338 String toString() {
1339 return 'ServiceEvent of type $eventType with '
1340 '${data == null ? 0 : data.lengthInBytes} bytes of binary data';
1341 }
1342 }
1343
1344 class Library extends ServiceObject with Coverage {
1345 @observable String url;
1346 @reflectable final imports = new ObservableList<Library>();
1347 @reflectable final scripts = new ObservableList<Script>();
1348 @reflectable final classes = new ObservableList<Class>();
1349 @reflectable final variables = new ObservableList<Field>();
1350 @reflectable final functions = new ObservableList<ServiceFunction>();
1351
1352 bool get canCache => true;
1353 bool get immutable => false;
1354
1355 Library._empty(ServiceObjectOwner owner) : super._empty(owner);
1356
1357 void _update(ObservableMap map, bool mapIsRef) {
1358 url = map['url'];
1359 var shortUrl = url;
1360 if (url.startsWith('file://') ||
1361 url.startsWith('http://')) {
1362 shortUrl = url.substring(url.lastIndexOf('/') + 1);
1363 }
1364 name = map['name'];
1365 if (name.isEmpty) {
1366 // When there is no name for a library, use the shortUrl.
1367 name = shortUrl;
1368 }
1369 vmName = (map.containsKey('vmName') ? map['vmName'] : name);
1370 if (mapIsRef) {
1371 return;
1372 }
1373 _loaded = true;
1374 _upgradeCollection(map, isolate);
1375 imports.clear();
1376 imports.addAll(removeDuplicatesAndSortLexical(map['imports']));
1377 scripts.clear();
1378 scripts.addAll(removeDuplicatesAndSortLexical(map['scripts']));
1379 classes.clear();
1380 classes.addAll(map['classes']);
1381 classes.sort(ServiceObject.LexicalSortName);
1382 variables.clear();
1383 variables.addAll(map['variables']);
1384 variables.sort(ServiceObject.LexicalSortName);
1385 functions.clear();
1386 functions.addAll(map['functions']);
1387 functions.sort(ServiceObject.LexicalSortName);
1388 }
1389
1390 String toString() => "Library($url)";
1391 }
1392
1393 class AllocationCount extends Observable {
1394 @observable int instances = 0;
1395 @observable int bytes = 0;
1396
1397 void reset() {
1398 instances = 0;
1399 bytes = 0;
1400 }
1401
1402 bool get empty => (instances == 0) && (bytes == 0);
1403 }
1404
1405 class Allocations {
1406 // Indexes into VM provided array. (see vm/class_table.h).
1407 static const ALLOCATED_BEFORE_GC = 0;
1408 static const ALLOCATED_BEFORE_GC_SIZE = 1;
1409 static const LIVE_AFTER_GC = 2;
1410 static const LIVE_AFTER_GC_SIZE = 3;
1411 static const ALLOCATED_SINCE_GC = 4;
1412 static const ALLOCATED_SINCE_GC_SIZE = 5;
1413 static const ACCUMULATED = 6;
1414 static const ACCUMULATED_SIZE = 7;
1415
1416 final AllocationCount accumulated = new AllocationCount();
1417 final AllocationCount current = new AllocationCount();
1418
1419 void update(List stats) {
1420 accumulated.instances = stats[ACCUMULATED];
1421 accumulated.bytes = stats[ACCUMULATED_SIZE];
1422 current.instances = stats[LIVE_AFTER_GC] + stats[ALLOCATED_SINCE_GC];
1423 current.bytes = stats[LIVE_AFTER_GC_SIZE] + stats[ALLOCATED_SINCE_GC_SIZE];
1424 }
1425
1426 bool get empty => accumulated.empty && current.empty;
1427 }
1428
1429 class Class extends ServiceObject with Coverage {
1430 @observable Library library;
1431 @observable Script script;
1432
1433 @observable bool isAbstract;
1434 @observable bool isConst;
1435 @observable bool isFinalized;
1436 @observable bool isPatch;
1437 @observable bool isImplemented;
1438
1439 @observable int tokenPos;
1440 @observable int endTokenPos;
1441
1442 @observable ServiceMap error;
1443 @observable int vmCid;
1444
1445 final Allocations newSpace = new Allocations();
1446 final Allocations oldSpace = new Allocations();
1447 final AllocationCount promotedByLastNewGC = new AllocationCount();
1448
1449 bool get hasNoAllocations => newSpace.empty && oldSpace.empty;
1450
1451 @reflectable final fields = new ObservableList<Field>();
1452 @reflectable final functions = new ObservableList<ServiceFunction>();
1453
1454 @observable Class superclass;
1455 @reflectable final interfaces = new ObservableList<Class>();
1456 @reflectable final subclasses = new ObservableList<Class>();
1457
1458 bool get canCache => true;
1459 bool get immutable => false;
1460
1461 Class._empty(ServiceObjectOwner owner) : super._empty(owner);
1462
1463 void _update(ObservableMap map, bool mapIsRef) {
1464 name = map['name'];
1465 vmName = (map.containsKey('vmName') ? map['vmName'] : name);
1466 var idPrefix = "classes/";
1467 assert(id.startsWith(idPrefix));
1468 vmCid = int.parse(id.substring(idPrefix.length));
1469
1470 if (mapIsRef) {
1471 return;
1472 }
1473
1474 // We are fully loaded.
1475 _loaded = true;
1476
1477 // Extract full properties.
1478 _upgradeCollection(map, isolate);
1479
1480 // Some builtin classes aren't associated with a library.
1481 if (map['library'] is Library) {
1482 library = map['library'];
1483 } else {
1484 library = null;
1485 }
1486
1487 script = map['script'];
1488
1489 isAbstract = map['abstract'];
1490 isConst = map['const'];
1491 isFinalized = map['finalized'];
1492 isPatch = map['patch'];
1493 isImplemented = map['implemented'];
1494
1495 tokenPos = map['tokenPos'];
1496 endTokenPos = map['endTokenPos'];
1497
1498 subclasses.clear();
1499 subclasses.addAll(map['subclasses']);
1500 subclasses.sort(ServiceObject.LexicalSortName);
1501
1502 fields.clear();
1503 fields.addAll(map['fields']);
1504 fields.sort(ServiceObject.LexicalSortName);
1505
1506 functions.clear();
1507 functions.addAll(map['functions']);
1508 functions.sort(ServiceObject.LexicalSortName);
1509
1510 superclass = map['super'];
1511 // Work-around Object not tracking its subclasses in the VM.
1512 if (superclass != null && superclass.name == "Object") {
1513 superclass._addSubclass(this);
1514 }
1515 error = map['error'];
1516
1517 var allocationStats = map['allocationStats'];
1518 if (allocationStats != null) {
1519 newSpace.update(allocationStats['new']);
1520 oldSpace.update(allocationStats['old']);
1521 promotedByLastNewGC.instances = allocationStats['promotedInstances'];
1522 promotedByLastNewGC.bytes = allocationStats['promotedBytes'];
1523 }
1524 }
1525
1526 void _addSubclass(Class subclass) {
1527 if (subclasses.contains(subclass)) {
1528 return;
1529 }
1530 subclasses.add(subclass);
1531 subclasses.sort(ServiceObject.LexicalSortName);
1532 }
1533
1534 Future<ServiceObject> get(String command) {
1535 return isolate.get(id + "/$command");
1536 }
1537
1538 String toString() => 'Class($vmName)';
1539 }
1540
1541 class Instance extends ServiceObject {
1542 @observable Class clazz;
1543 @observable int size;
1544 @observable String valueAsString; // If primitive.
1545 @observable bool valueAsStringIsTruncated;
1546 @observable ServiceFunction closureFunc; // If a closure.
1547 @observable Context closureCtxt; // If a closure.
1548 @observable String name; // If a Type.
1549 @observable int length; // If a List.
1550
1551 @observable var typeClass;
1552 @observable var fields;
1553 @observable var nativeFields;
1554 @observable var elements;
1555 @observable var userName;
1556 @observable var referent; // If a MirrorReference.
1557 @observable Instance key; // If a WeakProperty.
1558 @observable Instance value; // If a WeakProperty.
1559
1560 bool get isClosure => closureFunc != null;
1561
1562 Instance._empty(ServiceObjectOwner owner) : super._empty(owner);
1563
1564 void _update(ObservableMap map, bool mapIsRef) {
1565 // Extract full properties.
1566 _upgradeCollection(map, isolate);
1567
1568 clazz = map['class'];
1569 size = map['size'];
1570 valueAsString = map['valueAsString'];
1571 // Coerce absence to false.
1572 valueAsStringIsTruncated = map['valueAsStringIsTruncated'] == true;
1573 closureFunc = map['closureFunc'];
1574 closureCtxt = map['closureCtxt'];
1575 name = map['name'];
1576 length = map['length'];
1577
1578 if (mapIsRef) {
1579 return;
1580 }
1581
1582 nativeFields = map['nativeFields'];
1583 fields = map['fields'];
1584 elements = map['elements'];
1585 typeClass = map['type_class'];
1586 userName = map['user_name'];
1587 referent = map['referent'];
1588 key = map['key'];
1589 value = map['value'];
1590
1591 // We are fully loaded.
1592 _loaded = true;
1593 }
1594
1595 String get shortName => valueAsString != null ? valueAsString : 'a ${clazz.nam e}';
1596
1597 String toString() => 'Instance($shortName)';
1598 }
1599
1600
1601 class Context extends ServiceObject {
1602 @observable Class clazz;
1603 @observable int size;
1604
1605 @observable var parentContext;
1606 @observable int length;
1607 @observable var variables;
1608
1609 Context._empty(ServiceObjectOwner owner) : super._empty(owner);
1610
1611 void _update(ObservableMap map, bool mapIsRef) {
1612 // Extract full properties.
1613 _upgradeCollection(map, isolate);
1614
1615 size = map['size'];
1616 length = map['length'];
1617 parentContext = map['parent'];
1618
1619 if (mapIsRef) {
1620 return;
1621 }
1622
1623 clazz = map['class'];
1624 variables = map['variables'];
1625
1626 // We are fully loaded.
1627 _loaded = true;
1628 }
1629
1630 String toString() => 'Context($length)';
1631 }
1632
1633
1634 // TODO(koda): Sync this with VM.
1635 class FunctionKind {
1636 final String _strValue;
1637 FunctionKind._internal(this._strValue);
1638 toString() => _strValue;
1639 bool isFake() => [kCollected, kNative, kTag, kReused].contains(this);
1640
1641 static FunctionKind fromJSON(String value) {
1642 switch(value) {
1643 case 'kRegularFunction': return kRegularFunction;
1644 case 'kClosureFunction': return kClosureFunction;
1645 case 'kGetterFunction': return kGetterFunction;
1646 case 'kSetterFunction': return kSetterFunction;
1647 case 'kConstructor': return kConstructor;
1648 case 'kImplicitGetter': return kImplicitGetterFunction;
1649 case 'kImplicitSetter': return kImplicitSetterFunction;
1650 case 'kStaticInitializer': return kStaticInitializer;
1651 case 'kMethodExtractor': return kMethodExtractor;
1652 case 'kNoSuchMethodDispatcher': return kNoSuchMethodDispatcher;
1653 case 'kInvokeFieldDispatcher': return kInvokeFieldDispatcher;
1654 case 'Collected': return kCollected;
1655 case 'Native': return kNative;
1656 case 'Tag': return kTag;
1657 case 'Reused': return kReused;
1658 }
1659 return kUNKNOWN;
1660 }
1661
1662 static FunctionKind kRegularFunction = new FunctionKind._internal('function');
1663 static FunctionKind kClosureFunction = new FunctionKind._internal('closure fun ction');
1664 static FunctionKind kGetterFunction = new FunctionKind._internal('getter funct ion');
1665 static FunctionKind kSetterFunction = new FunctionKind._internal('setter funct ion');
1666 static FunctionKind kConstructor = new FunctionKind._internal('constructor');
1667 static FunctionKind kImplicitGetterFunction = new FunctionKind._internal('impl icit getter function');
1668 static FunctionKind kImplicitSetterFunction = new FunctionKind._internal('impl icit setter function');
1669 static FunctionKind kStaticInitializer = new FunctionKind._internal('static in itializer');
1670 static FunctionKind kMethodExtractor = new FunctionKind._internal('method extr actor');
1671 static FunctionKind kNoSuchMethodDispatcher = new FunctionKind._internal('noSu chMethod dispatcher');
1672 static FunctionKind kInvokeFieldDispatcher = new FunctionKind._internal('invok e field dispatcher');
1673 static FunctionKind kCollected = new FunctionKind._internal('Collected');
1674 static FunctionKind kNative = new FunctionKind._internal('Native');
1675 static FunctionKind kTag = new FunctionKind._internal('Tag');
1676 static FunctionKind kReused = new FunctionKind._internal('Reused');
1677 static FunctionKind kUNKNOWN = new FunctionKind._internal('UNKNOWN');
1678 }
1679
1680 class ServiceFunction extends ServiceObject with Coverage {
1681 @observable Class owningClass;
1682 @observable Library owningLibrary;
1683 @observable bool isStatic;
1684 @observable bool isConst;
1685 @observable ServiceFunction parent;
1686 @observable Script script;
1687 @observable int tokenPos;
1688 @observable int endTokenPos;
1689 @observable Code code;
1690 @observable Code unoptimizedCode;
1691 @observable bool isOptimizable;
1692 @observable bool isInlinable;
1693 @observable FunctionKind kind;
1694 @observable int deoptimizations;
1695 @observable String qualifiedName;
1696 @observable int usageCounter;
1697 @observable bool isDart;
1698
1699 ServiceFunction._empty(ServiceObject owner) : super._empty(owner);
1700
1701 void _update(ObservableMap map, bool mapIsRef) {
1702 name = map['name'];
1703 vmName = (map.containsKey('vmName') ? map['vmName'] : name);
1704
1705 _upgradeCollection(map, isolate);
1706
1707 owningClass = map.containsKey('owningClass') ? map['owningClass'] : null;
1708 owningLibrary = map.containsKey('owningLibrary') ? map['owningLibrary'] : nu ll;
1709 kind = FunctionKind.fromJSON(map['kind']);
1710 isDart = !kind.isFake();
1711
1712 if (mapIsRef) { return; }
1713
1714 isStatic = map['static'];
1715 isConst = map['const'];
1716 parent = map['parent'];
1717 script = map['script'];
1718 tokenPos = map['tokenPos'];
1719 endTokenPos = map['endTokenPos'];
1720 code = _convertNull(map['code']);
1721 unoptimizedCode = _convertNull(map['unoptimizedCode']);
1722 isOptimizable = map['optimizable'];
1723 isInlinable = map['inlinable'];
1724 deoptimizations = map['deoptimizations'];
1725 usageCounter = map['usageCounter'];
1726
1727 if (parent == null) {
1728 qualifiedName = (owningClass != null) ?
1729 "${owningClass.name}.${name}" :
1730 name;
1731 } else {
1732 qualifiedName = "${parent.qualifiedName}.${name}";
1733 }
1734
1735 }
1736 }
1737
1738
1739 class Field extends ServiceObject {
1740 @observable var /* Library or Class */ owner;
1741 @observable Instance declaredType;
1742 @observable bool isStatic;
1743 @observable bool isFinal;
1744 @observable bool isConst;
1745 @observable Instance value;
1746 @observable String name;
1747 @observable String vmName;
1748
1749 @observable bool guardNullable;
1750 @observable String guardClass;
1751 @observable String guardLength;
1752 @observable Script script;
1753 @observable int tokenPos;
1754
1755 Field._empty(ServiceObjectOwner owner) : super._empty(owner);
1756
1757 void _update(ObservableMap map, bool mapIsRef) {
1758 // Extract full properties.
1759 _upgradeCollection(map, isolate);
1760
1761 name = map['name'];
1762 vmName = (map.containsKey('vmName') ? map['vmName'] : name);
1763 owner = map['owner'];
1764 declaredType = map['declaredType'];
1765 isStatic = map['static'];
1766 isFinal = map['final'];
1767 isConst = map['const'];
1768 value = map['value'];
1769
1770 if (mapIsRef) {
1771 return;
1772 }
1773
1774 guardNullable = map['guardNullable'];
1775 guardClass = map['guardClass'];
1776 guardLength = map['guardLength'];
1777 script = map['script'];
1778 tokenPos = map['tokenPos'];
1779
1780 _loaded = true;
1781 }
1782
1783 String toString() => 'Field(${owner.name}.$name)';
1784 }
1785
1786
1787 class ScriptLine extends Observable {
1788 final Script script;
1789 final int line;
1790 final String text;
1791 @observable int hits;
1792 @observable ServiceMap bpt;
1793 @observable bool possibleBpt = true;
1794
1795 static bool _isTrivialToken(String token) {
1796 if (token == 'else') {
1797 return true;
1798 }
1799 for (var c in token.split('')) {
1800 switch (c) {
1801 case '{':
1802 case '}':
1803 case '(':
1804 case ')':
1805 case ';':
1806 break;
1807 default:
1808 return false;
1809 }
1810 }
1811 return true;
1812 }
1813
1814 static bool _isTrivialLine(String text) {
1815 var wsTokens = text.split(new RegExp(r"(\s)+"));
1816 for (var wsToken in wsTokens) {
1817 var tokens = wsToken.split(new RegExp(r"(\b)"));
1818 for (var token in tokens) {
1819 if (!_isTrivialToken(token)) {
1820 return false;
1821 }
1822 }
1823 }
1824 return true;
1825 }
1826
1827 ScriptLine(this.script, this.line, this.text) {
1828 possibleBpt = !_isTrivialLine(text);
1829
1830 // TODO(turnidge): This is not so efficient. Consider improving.
1831 for (var bpt in this.script.isolate.breakpoints['breakpoints']) {
1832 var bptScript = bpt['location']['script'];
1833 var bptTokenPos = bpt['location']['tokenPos'];
1834 if (bptScript == this.script &&
1835 bptScript.tokenToLine(bptTokenPos) == line) {
1836 this.bpt = bpt;
1837 }
1838 }
1839 }
1840 }
1841
1842 class Script extends ServiceObject with Coverage {
1843 final lines = new ObservableList<ScriptLine>();
1844 final _hits = new Map<int, int>();
1845 @observable String kind;
1846 @observable int firstTokenPos;
1847 @observable int lastTokenPos;
1848 @observable Library owningLibrary;
1849 bool get canCache => true;
1850 bool get immutable => true;
1851
1852 String _shortUrl;
1853 String _url;
1854
1855 Script._empty(ServiceObjectOwner owner) : super._empty(owner);
1856
1857 ScriptLine getLine(int line) {
1858 assert(line >= 1);
1859 return lines[line - 1];
1860 }
1861
1862 /// This function maps a token position to a line number.
1863 int tokenToLine(int token) => _tokenToLine[token];
1864 Map _tokenToLine = {};
1865
1866 /// This function maps a token position to a column number.
1867 int tokenToCol(int token) => _tokenToCol[token];
1868 Map _tokenToCol = {};
1869
1870 void _update(ObservableMap map, bool mapIsRef) {
1871 _upgradeCollection(map, isolate);
1872 kind = map['kind'];
1873 _url = map['name'];
1874 _shortUrl = _url.substring(_url.lastIndexOf('/') + 1);
1875 name = _shortUrl;
1876 vmName = _url;
1877 if (mapIsRef) {
1878 return;
1879 }
1880 _processSource(map['source']);
1881 _parseTokenPosTable(map['tokenPosTable']);
1882 owningLibrary = map['owningLibrary'];
1883 }
1884
1885 void _parseTokenPosTable(List<List<int>> table) {
1886 if (table == null) {
1887 return;
1888 }
1889 _tokenToLine.clear();
1890 _tokenToCol.clear();
1891 firstTokenPos = null;
1892 lastTokenPos = null;
1893 var lineSet = new Set();
1894
1895 for (var line in table) {
1896 // Each entry begins with a line number...
1897 var lineNumber = line[0];
1898 lineSet.add(lineNumber);
1899 for (var pos = 1; pos < line.length; pos += 2) {
1900 // ...and is followed by (token offset, col number) pairs.
1901 var tokenOffset = line[pos];
1902 var colNumber = line[pos+1];
1903 if (firstTokenPos == null) {
1904 // Mark first token position.
1905 firstTokenPos = tokenOffset;
1906 lastTokenPos = tokenOffset;
1907 } else {
1908 // Keep track of max and min token positions.
1909 firstTokenPos = (firstTokenPos <= tokenOffset) ?
1910 firstTokenPos : tokenOffset;
1911 lastTokenPos = (lastTokenPos >= tokenOffset) ?
1912 lastTokenPos : tokenOffset;
1913 }
1914 _tokenToLine[tokenOffset] = lineNumber;
1915 _tokenToCol[tokenOffset] = colNumber;
1916 }
1917 }
1918
1919 for (var line in lines) {
1920 // Remove possible breakpoints on lines with no tokens.
1921 if (!lineSet.contains(line.line)) {
1922 line.possibleBpt = false;
1923 }
1924 }
1925 }
1926
1927 void _processHits(List scriptHits) {
1928 // Update hits table.
1929 for (var i = 0; i < scriptHits.length; i += 2) {
1930 var line = scriptHits[i];
1931 var hit = scriptHits[i + 1]; // hit status.
1932 assert(line >= 1); // Lines start at 1.
1933 var oldHits = _hits[line];
1934 if (oldHits != null) {
1935 hit += oldHits;
1936 }
1937 _hits[line] = hit;
1938 }
1939 _applyHitsToLines();
1940 }
1941
1942 void _processSource(String source) {
1943 // Preemptyively mark that this is not loaded.
1944 _loaded = false;
1945 if (source == null) {
1946 return;
1947 }
1948 var sourceLines = source.split('\n');
1949 if (sourceLines.length == 0) {
1950 return;
1951 }
1952 // We have the source to the script. This is now loaded.
1953 _loaded = true;
1954 lines.clear();
1955 Logger.root.info('Adding ${sourceLines.length} source lines for ${_url}');
1956 for (var i = 0; i < sourceLines.length; i++) {
1957 lines.add(new ScriptLine(this, i + 1, sourceLines[i]));
1958 }
1959 _applyHitsToLines();
1960 }
1961
1962 void _applyHitsToLines() {
1963 for (var line in lines) {
1964 var hits = _hits[line.line];
1965 line.hits = hits;
1966 }
1967 }
1968 }
1969
1970 class CodeTick {
1971 final int address;
1972 final int exclusiveTicks;
1973 final int inclusiveTicks;
1974 CodeTick(this.address, this.exclusiveTicks, this.inclusiveTicks);
1975 }
1976
1977 class PcDescriptor extends Observable {
1978 final int address;
1979 @reflectable final int deoptId;
1980 @reflectable final int tokenPos;
1981 @reflectable final int tryIndex;
1982 @reflectable final String kind;
1983 @observable Script script;
1984 @observable String formattedLine;
1985 PcDescriptor(this.address, this.deoptId, this.tokenPos, this.tryIndex,
1986 this.kind);
1987
1988 @reflectable String formattedDeoptId() {
1989 if (deoptId == -1) {
1990 return 'N/A';
1991 }
1992 return deoptId.toString();
1993 }
1994
1995 @reflectable String formattedTokenPos() {
1996 if (tokenPos == -1) {
1997 return '';
1998 }
1999 return tokenPos.toString();
2000 }
2001
2002 void processScript(Script script) {
2003 this.script = null;
2004 if (tokenPos == -1) {
2005 return;
2006 }
2007 var line = script.tokenToLine(tokenPos);
2008 if (line == null) {
2009 return;
2010 }
2011 this.script = script;
2012 var scriptLine = script.getLine(line);
2013 formattedLine = scriptLine.text;
2014 }
2015 }
2016
2017 class PcDescriptors extends ServiceObject {
2018 @observable Class clazz;
2019 @observable int size;
2020 bool get canCache => false;
2021 bool get immutable => true;
2022 @reflectable final List<PcDescriptor> descriptors =
2023 new ObservableList<PcDescriptor>();
2024
2025 PcDescriptors._empty(ServiceObjectOwner owner) : super._empty(owner) {
2026 print('created PcDescriptors.');
2027 }
2028
2029 void _update(ObservableMap m, bool mapIsRef) {
2030 if (mapIsRef) {
2031 return;
2032 }
2033 _upgradeCollection(m, isolate);
2034 clazz = m['class'];
2035 size = m['size'];
2036 descriptors.clear();
2037 for (var descriptor in m['members']) {
2038 var address = int.parse(descriptor['pc'], radix:16);
2039 var deoptId = descriptor['deoptId'];
2040 var tokenPos = descriptor['tokenPos'];
2041 var tryIndex = descriptor['tryIndex'];
2042 var kind = descriptor['kind'].trim();
2043 descriptors.add(
2044 new PcDescriptor(address, deoptId, tokenPos, tryIndex, kind));
2045 }
2046 }
2047 }
2048
2049 class LocalVarDescriptor extends Observable {
2050 @reflectable final String name;
2051 @reflectable final int index;
2052 @reflectable final int beginPos;
2053 @reflectable final int endPos;
2054 @reflectable final int scopeId;
2055 @reflectable final String kind;
2056
2057 LocalVarDescriptor(this.name, this.index, this.beginPos, this.endPos,
2058 this.scopeId, this.kind);
2059 }
2060
2061 class LocalVarDescriptors extends ServiceObject {
2062 @observable Class clazz;
2063 @observable int size;
2064 bool get canCache => false;
2065 bool get immutable => true;
2066 @reflectable final List<LocalVarDescriptor> descriptors =
2067 new ObservableList<LocalVarDescriptor>();
2068 LocalVarDescriptors._empty(ServiceObjectOwner owner) : super._empty(owner);
2069
2070 void _update(ObservableMap m, bool mapIsRef) {
2071 if (mapIsRef) {
2072 return;
2073 }
2074 _upgradeCollection(m, isolate);
2075 clazz = m['class'];
2076 size = m['size'];
2077 descriptors.clear();
2078 for (var descriptor in m['members']) {
2079 var name = descriptor['name'];
2080 var index = descriptor['index'];
2081 var beginPos = descriptor['beginPos'];
2082 var endPos = descriptor['endPos'];
2083 var scopeId = descriptor['scopeId'];
2084 var kind = descriptor['kind'].trim();
2085 descriptors.add(
2086 new LocalVarDescriptor(name, index, beginPos, endPos, scopeId, kind));
2087 }
2088 }
2089 }
2090
2091 class TokenStream extends ServiceObject {
2092 @observable Class clazz;
2093 @observable int size;
2094 bool get canCache => false;
2095 bool get immutable => true;
2096
2097 @observable String privateKey;
2098
2099 TokenStream._empty(ServiceObjectOwner owner) : super._empty(owner);
2100
2101 void _update(ObservableMap m, bool mapIsRef) {
2102 if (mapIsRef) {
2103 return;
2104 }
2105 _upgradeCollection(m, isolate);
2106 clazz = m['class'];
2107 size = m['size'];
2108 privateKey = m['privateKey'];
2109 }
2110 }
2111
2112 class CodeInstruction extends Observable {
2113 @observable final int address;
2114 @observable final String machine;
2115 @observable final String human;
2116 @observable CodeInstruction jumpTarget;
2117 @reflectable List<PcDescriptor> descriptors =
2118 new ObservableList<PcDescriptor>();
2119
2120 static String formatPercent(num a, num total) {
2121 var percent = 100.0 * (a / total);
2122 return '${percent.toStringAsFixed(2)}%';
2123 }
2124
2125 CodeInstruction(this.address, this.machine, this.human);
2126
2127 @reflectable bool get isComment => address == 0;
2128 @reflectable bool get hasDescriptors => descriptors.length > 0;
2129
2130 @reflectable String formattedAddress() {
2131 if (address == 0) {
2132 return '';
2133 }
2134 return '0x${address.toRadixString(16)}';
2135 }
2136
2137 @reflectable String formattedInclusive(Code code) {
2138 if (code == null) {
2139 return '';
2140 }
2141 var tick = code.addressTicks[address];
2142 if (tick == null) {
2143 return '';
2144 }
2145 // Don't show inclusive ticks if they are the same as exclusive ticks.
2146 if (tick.inclusiveTicks == tick.exclusiveTicks) {
2147 return '';
2148 }
2149 var pcent = formatPercent(tick.inclusiveTicks, code.totalSamplesInProfile);
2150 return '$pcent (${tick.inclusiveTicks})';
2151 }
2152
2153 @reflectable String formattedExclusive(Code code) {
2154 if (code == null) {
2155 return '';
2156 }
2157 var tick = code.addressTicks[address];
2158 if (tick == null) {
2159 return '';
2160 }
2161 var pcent = formatPercent(tick.exclusiveTicks, code.totalSamplesInProfile);
2162 return '$pcent (${tick.exclusiveTicks})';
2163 }
2164
2165 bool _isJumpInstruction() {
2166 return human.startsWith('j');
2167 }
2168
2169 int _getJumpAddress() {
2170 assert(_isJumpInstruction());
2171 var chunks = human.split(' ');
2172 if (chunks.length != 2) {
2173 // We expect jump instructions to be of the form 'j.. address'.
2174 return 0;
2175 }
2176 var address = chunks[1];
2177 if (address.startsWith('0x')) {
2178 // Chop off the 0x.
2179 address = address.substring(2);
2180 }
2181 try {
2182 return int.parse(address, radix:16);
2183 } catch (_) {
2184 return 0;
2185 }
2186 }
2187
2188 void _resolveJumpTarget(List<CodeInstruction> instructions) {
2189 if (!_isJumpInstruction()) {
2190 return;
2191 }
2192 int address = _getJumpAddress();
2193 if (address == 0) {
2194 // Could not determine jump address.
2195 Logger.root.severe('Could not determine jump address for $human');
2196 return;
2197 }
2198 for (var i = 0; i < instructions.length; i++) {
2199 var instruction = instructions[i];
2200 if (instruction.address == address) {
2201 jumpTarget = instruction;
2202 return;
2203 }
2204 }
2205 Logger.root.severe(
2206 'Could not find instruction at ${address.toRadixString(16)}');
2207 }
2208 }
2209
2210 class CodeKind {
2211 final _value;
2212 const CodeKind._internal(this._value);
2213 String toString() => '$_value';
2214
2215 static CodeKind fromString(String s) {
2216 if (s == 'Native') {
2217 return Native;
2218 } else if (s == 'Dart') {
2219 return Dart;
2220 } else if (s == 'Collected') {
2221 return Collected;
2222 } else if (s == 'Reused') {
2223 return Reused;
2224 } else if (s == 'Tag') {
2225 return Tag;
2226 }
2227 Logger.root.warning('Unknown code kind $s');
2228 throw new FallThroughError();
2229 }
2230 static const Native = const CodeKind._internal('Native');
2231 static const Dart = const CodeKind._internal('Dart');
2232 static const Collected = const CodeKind._internal('Collected');
2233 static const Reused = const CodeKind._internal('Reused');
2234 static const Tag = const CodeKind._internal('Tag');
2235 }
2236
2237 class CodeCallCount {
2238 final Code code;
2239 final int count;
2240 CodeCallCount(this.code, this.count);
2241 }
2242
2243 class CodeTrieNode {
2244 final Code code;
2245 final int count;
2246 final children = new List<CodeTrieNode>();
2247 int summedChildCount = 0;
2248 CodeTrieNode(this.code, this.count);
2249 }
2250
2251 class Code extends ServiceObject {
2252 @observable CodeKind kind;
2253 @observable int totalSamplesInProfile = 0;
2254 @reflectable int exclusiveTicks = 0;
2255 @reflectable int inclusiveTicks = 0;
2256 @reflectable int startAddress = 0;
2257 @reflectable int endAddress = 0;
2258 @reflectable final callers = new List<CodeCallCount>();
2259 @reflectable final callees = new List<CodeCallCount>();
2260 @reflectable final instructions = new ObservableList<CodeInstruction>();
2261 @reflectable final addressTicks = new ObservableMap<int, CodeTick>();
2262 @observable String formattedInclusiveTicks = '';
2263 @observable String formattedExclusiveTicks = '';
2264 @observable Instance objectPool;
2265 @observable ServiceFunction function;
2266 @observable Script script;
2267 @observable bool isOptimized = false;
2268
2269 bool get canCache => true;
2270 bool get immutable => true;
2271
2272 Code._empty(ServiceObjectOwner owner) : super._empty(owner);
2273
2274 // Reset all data associated with a profile.
2275 void resetProfileData() {
2276 totalSamplesInProfile = 0;
2277 exclusiveTicks = 0;
2278 inclusiveTicks = 0;
2279 formattedInclusiveTicks = '';
2280 formattedExclusiveTicks = '';
2281 callers.clear();
2282 callees.clear();
2283 addressTicks.clear();
2284 }
2285
2286 void _updateDescriptors(Script script) {
2287 this.script = script;
2288 for (var instruction in instructions) {
2289 for (var descriptor in instruction.descriptors) {
2290 descriptor.processScript(script);
2291 }
2292 }
2293 }
2294
2295 void loadScript() {
2296 if (script != null) {
2297 // Already done.
2298 return;
2299 }
2300 if (kind != CodeKind.Dart){
2301 return;
2302 }
2303 if (function == null) {
2304 return;
2305 }
2306 if (function.script == null) {
2307 // Attempt to load the function.
2308 function.load().then((func) {
2309 var script = function.script;
2310 if (script == null) {
2311 // Function doesn't have an associated script.
2312 return;
2313 }
2314 // Load the script and then update descriptors.
2315 script.load().then(_updateDescriptors);
2316 });
2317 return;
2318 }
2319 // Load the script and then update descriptors.
2320 function.script.load().then(_updateDescriptors);
2321 }
2322
2323 /// Reload [this]. Returns a future which completes to [this] or
2324 /// a [ServiceError].
2325 Future<ServiceObject> reload() {
2326 assert(kind != null);
2327 if (kind == CodeKind.Dart) {
2328 // We only reload Dart code.
2329 return super.reload();
2330 }
2331 return new Future.value(this);
2332 }
2333
2334 void _resolveCalls(List<CodeCallCount> calls, List data, List<Code> codes) {
2335 // Assert that this has been cleared.
2336 assert(calls.length == 0);
2337 // Resolve.
2338 for (var i = 0; i < data.length; i += 2) {
2339 var index = int.parse(data[i]);
2340 var count = int.parse(data[i + 1]);
2341 assert(index >= 0);
2342 assert(index < codes.length);
2343 calls.add(new CodeCallCount(codes[index], count));
2344 }
2345 // Sort to descending count order.
2346 calls.sort((a, b) => b.count - a.count);
2347 }
2348
2349
2350 static String formatPercent(num a, num total) {
2351 var percent = 100.0 * (a / total);
2352 return '${percent.toStringAsFixed(2)}%';
2353 }
2354
2355 void updateProfileData(Map profileData,
2356 List<Code> codeTable,
2357 int sampleCount) {
2358 // Assert we have a CodeRegion entry.
2359 assert(profileData['type'] == 'CodeRegion');
2360 // Assert we are handed profile data for this code object.
2361 assert(profileData['code'] == this);
2362 totalSamplesInProfile = sampleCount;
2363 inclusiveTicks = int.parse(profileData['inclusive_ticks']);
2364 exclusiveTicks = int.parse(profileData['exclusive_ticks']);
2365 _resolveCalls(callers, profileData['callers'], codeTable);
2366 _resolveCalls(callees, profileData['callees'], codeTable);
2367 var ticks = profileData['ticks'];
2368 if (ticks != null) {
2369 _processTicks(ticks);
2370 }
2371 formattedInclusiveTicks =
2372 '${formatPercent(inclusiveTicks, totalSamplesInProfile)} '
2373 '($inclusiveTicks)';
2374 formattedExclusiveTicks =
2375 '${formatPercent(exclusiveTicks, totalSamplesInProfile)} '
2376 '($exclusiveTicks)';
2377 }
2378
2379 void _update(ObservableMap m, bool mapIsRef) {
2380 name = m['name'];
2381 vmName = (m.containsKey('vmName') ? m['vmName'] : name);
2382 isOptimized = m['optimized'] != null ? m['optimized'] : false;
2383 kind = CodeKind.fromString(m['kind']);
2384 startAddress = int.parse(m['start'], radix:16);
2385 endAddress = int.parse(m['end'], radix:16);
2386 function = isolate.getFromMap(m['function']);
2387 objectPool = isolate.getFromMap(m['objectPool']);
2388 var disassembly = m['disassembly'];
2389 if (disassembly != null) {
2390 _processDisassembly(disassembly);
2391 }
2392 var descriptors = m['descriptors'];
2393 if (descriptors != null) {
2394 descriptors = descriptors['members'];
2395 _processDescriptors(descriptors);
2396 }
2397 // We are loaded if we have instructions or are not Dart code.
2398 _loaded = (instructions.length != 0) || (kind != CodeKind.Dart);
2399 hasDisassembly = (instructions.length != 0) && (kind == CodeKind.Dart);
2400 }
2401
2402 @observable bool hasDisassembly = false;
2403
2404 void _processDisassembly(List<String> disassembly){
2405 assert(disassembly != null);
2406 instructions.clear();
2407 assert((disassembly.length % 3) == 0);
2408 for (var i = 0; i < disassembly.length; i += 3) {
2409 var address = 0; // Assume code comment.
2410 var machine = disassembly[i + 1];
2411 var human = disassembly[i + 2];
2412 if (disassembly[i] != '') {
2413 // Not a code comment, extract address.
2414 address = int.parse(disassembly[i]);
2415 }
2416 var instruction = new CodeInstruction(address, machine, human);
2417 instructions.add(instruction);
2418 }
2419 for (var instruction in instructions) {
2420 instruction._resolveJumpTarget(instructions);
2421 }
2422 }
2423
2424 void _processDescriptor(Map d) {
2425 var address = int.parse(d['pc'], radix:16);
2426 var deoptId = d['deoptId'];
2427 var tokenPos = d['tokenPos'];
2428 var tryIndex = d['tryIndex'];
2429 var kind = d['kind'].trim();
2430 for (var instruction in instructions) {
2431 if (instruction.address == address) {
2432 instruction.descriptors.add(new PcDescriptor(address,
2433 deoptId,
2434 tokenPos,
2435 tryIndex,
2436 kind));
2437 return;
2438 }
2439 }
2440 Logger.root.warning(
2441 'Could not find instruction with pc descriptor address: $address');
2442 }
2443
2444 void _processDescriptors(List<Map> descriptors) {
2445 for (Map descriptor in descriptors) {
2446 _processDescriptor(descriptor);
2447 }
2448 }
2449
2450 void _processTicks(List<String> profileTicks) {
2451 assert(profileTicks != null);
2452 assert((profileTicks.length % 3) == 0);
2453 for (var i = 0; i < profileTicks.length; i += 3) {
2454 var address = int.parse(profileTicks[i], radix:16);
2455 var exclusive = int.parse(profileTicks[i + 1]);
2456 var inclusive = int.parse(profileTicks[i + 2]);
2457 var tick = new CodeTick(address, exclusive, inclusive);
2458 addressTicks[address] = tick;
2459 }
2460 }
2461
2462 /// Returns true if [address] is contained inside [this].
2463 bool contains(int address) {
2464 return (address >= startAddress) && (address < endAddress);
2465 }
2466
2467 /// Sum all caller counts.
2468 int sumCallersCount() => _sumCallCount(callers);
2469 /// Specific caller count.
2470 int callersCount(Code code) => _callCount(callers, code);
2471 /// Sum of callees count.
2472 int sumCalleesCount() => _sumCallCount(callees);
2473 /// Specific callee count.
2474 int calleesCount(Code code) => _callCount(callees, code);
2475
2476 int _sumCallCount(List<CodeCallCount> calls) {
2477 var sum = 0;
2478 for (CodeCallCount caller in calls) {
2479 sum += caller.count;
2480 }
2481 return sum;
2482 }
2483
2484 int _callCount(List<CodeCallCount> calls, Code code) {
2485 for (CodeCallCount caller in calls) {
2486 if (caller.code == code) {
2487 return caller.count;
2488 }
2489 }
2490 return 0;
2491 }
2492
2493 @reflectable bool get isDartCode => kind == CodeKind.Dart;
2494 }
2495
2496
2497 class SocketKind {
2498 final _value;
2499 const SocketKind._internal(this._value);
2500 String toString() => '$_value';
2501
2502 static SocketKind fromString(String s) {
2503 if (s == 'Listening') {
2504 return Listening;
2505 } else if (s == 'Normal') {
2506 return Normal;
2507 } else if (s == 'Pipe') {
2508 return Pipe;
2509 } else if (s == 'Internal') {
2510 return Internal;
2511 }
2512 Logger.root.warning('Unknown socket kind $s');
2513 throw new FallThroughError();
2514 }
2515 static const Listening = const SocketKind._internal('Listening');
2516 static const Normal = const SocketKind._internal('Normal');
2517 static const Pipe = const SocketKind._internal('Pipe');
2518 static const Internal = const SocketKind._internal('Internal');
2519 }
2520
2521 /// A snapshot of statistics associated with a [Socket].
2522 class SocketStats {
2523 @reflectable final int bytesRead;
2524 @reflectable final int bytesWritten;
2525 @reflectable final int readCalls;
2526 @reflectable final int writeCalls;
2527 @reflectable final int available;
2528
2529 SocketStats(this.bytesRead, this.bytesWritten,
2530 this.readCalls, this.writeCalls,
2531 this.available);
2532 }
2533
2534 /// A peer to a Socket in dart:io. Sockets can represent network sockets or
2535 /// OS pipes. Each socket is owned by another ServceObject, for example,
2536 /// a process or an HTTP server.
2537 class Socket extends ServiceObject {
2538 Socket._empty(ServiceObjectOwner owner) : super._empty(owner);
2539
2540 bool get canCache => true;
2541
2542 ServiceObject socketOwner;
2543
2544 @reflectable bool get isPipe => (kind == SocketKind.Pipe);
2545
2546 @observable SocketStats latest;
2547 @observable SocketStats previous;
2548
2549 @observable SocketKind kind;
2550
2551 @observable String protocol = '';
2552
2553 @observable bool readClosed = false;
2554 @observable bool writeClosed = false;
2555 @observable bool closing = false;
2556
2557 /// Listening for connections.
2558 @observable bool listening = false;
2559
2560 @observable int fd;
2561
2562 @observable String localAddress;
2563 @observable int localPort;
2564 @observable String remoteAddress;
2565 @observable int remotePort;
2566
2567 // Updates internal state from [map]. [map] can be a reference.
2568 void _update(ObservableMap map, bool mapIsRef) {
2569 name = map['name'];
2570 vmName = map['name'];
2571
2572 kind = SocketKind.fromString(map['kind']);
2573
2574 if (mapIsRef) {
2575 return;
2576 }
2577
2578 _loaded = true;
2579
2580 _upgradeCollection(map, isolate);
2581
2582 readClosed = map['readClosed'];
2583 writeClosed = map['writeClosed'];
2584 closing = map['closing'];
2585 listening = map['listening'];
2586
2587 protocol = map['protocol'];
2588
2589 localAddress = map['localAddress'];
2590 localPort = map['localPort'];
2591 remoteAddress = map['remoteAddress'];
2592 remotePort = map['remotePort'];
2593
2594 fd = map['fd'];
2595 socketOwner = map['owner'];
2596 }
2597 }
2598
2599 class MetricSample {
2600 final double value;
2601 final DateTime time;
2602 MetricSample(this.value) : time = new DateTime.now();
2603 }
2604
2605 class ServiceMetric extends ServiceObject {
2606 ServiceMetric._empty(ServiceObjectOwner owner) : super._empty(owner) {
2607 }
2608
2609 bool get canCache => true;
2610 bool get immutable => false;
2611
2612 @observable bool recording = false;
2613 MetricPoller poller;
2614
2615 final ObservableList<MetricSample> samples =
2616 new ObservableList<MetricSample>();
2617 int _sampleBufferSize = 100;
2618 int get sampleBufferSize => _sampleBufferSize;
2619 set sampleBufferSize(int size) {
2620 _sampleBufferSize = size;
2621 _removeOld();
2622 }
2623
2624 void addSample(MetricSample sample) {
2625 samples.add(sample);
2626 _removeOld();
2627 }
2628
2629 void _removeOld() {
2630 // TODO(johnmccutchan): If this becomes hot, consider using a circular
2631 // buffer.
2632 if (samples.length > _sampleBufferSize) {
2633 int count = samples.length - _sampleBufferSize;
2634 samples.removeRange(0, count);
2635 }
2636 }
2637
2638 @observable String description;
2639 @observable double value = 0.0;
2640 // Only a guage has a non-null min and max.
2641 @observable double min;
2642 @observable double max;
2643
2644 bool get isGauge => (min != null) && (max != null);
2645
2646 void _update(ObservableMap map, bool mapIsRef) {
2647 name = map['name'];
2648 description = map['description'];
2649 vmName = map['name'];
2650 value = map['value'];
2651 min = map['min'];
2652 max = map['max'];
2653 }
2654
2655 String toString() => "ServiceMetric($_id)";
2656 }
2657
2658 class MetricPoller {
2659 // Metrics to be polled.
2660 final List<ServiceMetric> metrics = new List<ServiceMetric>();
2661 final Duration pollPeriod;
2662 Timer _pollTimer;
2663
2664 MetricPoller(int milliseconds) :
2665 pollPeriod = new Duration(milliseconds: milliseconds) {
2666 start();
2667 }
2668
2669 void start() {
2670 _pollTimer = new Timer.periodic(pollPeriod, _onPoll);
2671 }
2672
2673 void cancel() {
2674 if (_pollTimer != null) {
2675 _pollTimer.cancel();
2676 }
2677 _pollTimer = null;
2678 }
2679
2680 void _onPoll(_) {
2681 // Reload metrics and add a sample to each.
2682 for (var metric in metrics) {
2683 metric.reload().then((m) {
2684 m.addSample(new MetricSample(m.value));
2685 });
2686 }
2687 }
2688 }
2689
2690 // Convert any ServiceMaps representing a null instance into an actual null.
2691 _convertNull(obj) {
2692 if (obj.isNull) {
2693 return null;
2694 }
2695 return obj;
2696 }
2697
2698 // Returns true if [map] is a service map. i.e. it has the following keys:
2699 // 'id' and a 'type'.
2700 bool _isServiceMap(ObservableMap m) {
2701 return (m != null) && (m['id'] != null) && (m['type'] != null);
2702 }
2703
2704 bool _hasRef(String type) => type.startsWith('@');
2705 String _stripRef(String type) => (_hasRef(type) ? type.substring(1) : type);
2706
2707 /// Recursively upgrades all [ServiceObject]s inside [collection] which must
2708 /// be an [ObservableMap] or an [ObservableList]. Upgraded elements will be
2709 /// associated with [vm] and [isolate].
2710 void _upgradeCollection(collection, ServiceObjectOwner owner) {
2711 if (collection is ServiceMap) {
2712 return;
2713 }
2714 if (collection is ObservableMap) {
2715 _upgradeObservableMap(collection, owner);
2716 } else if (collection is ObservableList) {
2717 _upgradeObservableList(collection, owner);
2718 }
2719 }
2720
2721 void _upgradeObservableMap(ObservableMap map, ServiceObjectOwner owner) {
2722 map.forEach((k, v) {
2723 if ((v is ObservableMap) && _isServiceMap(v)) {
2724 map[k] = owner.getFromMap(v);
2725 } else if (v is ObservableList) {
2726 _upgradeObservableList(v, owner);
2727 } else if (v is ObservableMap) {
2728 _upgradeObservableMap(v, owner);
2729 }
2730 });
2731 }
2732
2733 void _upgradeObservableList(ObservableList list, ServiceObjectOwner owner) {
2734 for (var i = 0; i < list.length; i++) {
2735 var v = list[i];
2736 if ((v is ObservableMap) && _isServiceMap(v)) {
2737 list[i] = owner.getFromMap(v);
2738 } else if (v is ObservableList) {
2739 _upgradeObservableList(v, owner);
2740 } else if (v is ObservableMap) {
2741 _upgradeObservableMap(v, owner);
2742 }
2743 }
2744 }
OLDNEW
« no previous file with comments | « runtime/observatory/lib/src/elements/vm_view.html ('k') | runtime/observatory/lib/tracer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698