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

Side by Side Diff: runtime/bin/vmservice/client/lib/src/service/object.dart

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

Powered by Google App Engine
This is Rietveld 408576698