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