OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 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 } | |
OLD | NEW |