| 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 |