| 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 /// State for a running isolate. |
| 8 class Isolate extends ServiceObject { |
| 9 final VM vm; |
| 10 String get link => _id; |
| 11 String get hashLink => '#/$_id'; |
| 12 |
| 13 ScriptCache _scripts; |
| 14 /// Script cache. |
| 15 ScriptCache get scripts => _scripts; |
| 16 CodeCache _codes; |
| 17 /// Code cache. |
| 18 CodeCache get codes => _codes; |
| 19 /// Class cache. |
| 20 ClassCache _classes; |
| 21 ClassCache get classes => _classes; |
| 22 |
| 23 void _initOnce() { |
| 24 // Only called once. |
| 25 assert(_isolate == null); |
| 26 _isolate = this; |
| 27 _scripts = new ScriptCache(this); |
| 28 _codes = new CodeCache(this); |
| 29 _classes = new ClassCache(this); |
| 30 } |
| 31 |
| 32 Isolate.fromId(this.vm, String id) : super(null, id, '@Isolate') { |
| 33 _initOnce(); |
| 34 } |
| 35 |
| 36 Isolate.fromMap(this.vm, Map map) : super.fromMap(null, map) { |
| 37 _initOnce(); |
| 38 } |
| 39 |
| 40 /// Creates a link to [id] relative to [this]. |
| 41 @reflectable String relativeLink(String id) => '${this.id}/$id'; |
| 42 /// Creates a relative link to [id] with a '#/' prefix. |
| 43 @reflectable String relativeHashLink(String id) => '#/${relativeLink(id)}'; |
| 44 |
| 45 Future<ScriptCache> refreshCoverage() { |
| 46 return get('coverage').then(_scripts._processCoverage); |
| 47 } |
| 48 |
| 49 void processProfile(ServiceMap profile) { |
| 50 assert(profile.serviceType == 'Profile'); |
| 51 var codeTable = new List<Code>(); |
| 52 var profileCodes = profile['codes']; |
| 53 for (var profileCode in profileCodes) { |
| 54 Code code = profileCode['code']; |
| 55 codeTable.add(code); |
| 56 } |
| 57 _codes._resetProfileData(); |
| 58 _codes._updateProfileData(profile, codeTable); |
| 59 } |
| 60 |
| 61 /// Requests [serviceId] from [this]. Completes to a [ServiceObject]. |
| 62 /// Can return pre-existing, cached, [ServiceObject]s. |
| 63 Future<ServiceObject> get(String serviceId) { |
| 64 if (_scripts.cachesId(serviceId)) { |
| 65 return _scripts.get(serviceId); |
| 66 } |
| 67 if (_codes.cachesId(serviceId)) { |
| 68 return _codes.get(serviceId); |
| 69 } |
| 70 if (_classes.cachesId(serviceId)) { |
| 71 return _classes.get(serviceId); |
| 72 } |
| 73 return vm.getAsMap(relativeLink(serviceId)).then((ObservableMap m) { |
| 74 return _upgradeToServiceObject(vm, this, m); |
| 75 }); |
| 76 } |
| 77 |
| 78 @observable ServiceMap rootLib; |
| 79 @observable ObservableMap topFrame; |
| 80 |
| 81 @observable String name; |
| 82 @observable String vmName; |
| 83 @observable Map entry; |
| 84 |
| 85 @observable final Map<String, double> timers = |
| 86 toObservable(new Map<String, double>()); |
| 87 |
| 88 @observable int newHeapUsed = 0; |
| 89 @observable int oldHeapUsed = 0; |
| 90 |
| 91 @observable String fileAndLine; |
| 92 |
| 93 void _update(ObservableMap map) { |
| 94 upgradeCollection(map, vm, this); |
| 95 _ref = false; |
| 96 if (map['rootLib'] == null || |
| 97 map['timers'] == null || |
| 98 map['heap'] == null) { |
| 99 Logger.root.severe("Malformed 'Isolate' response: $map"); |
| 100 return; |
| 101 } |
| 102 rootLib = map['rootLib']; |
| 103 vmName = map['name']; |
| 104 if (map['entry'] != null) { |
| 105 entry = map['entry']; |
| 106 name = entry['name']; |
| 107 } else { |
| 108 // fred |
| 109 name = 'root isolate'; |
| 110 } |
| 111 if (map['topFrame'] != null) { |
| 112 topFrame = map['topFrame']; |
| 113 } else { |
| 114 topFrame = null ; |
| 115 } |
| 116 |
| 117 var timerMap = {}; |
| 118 map['timers'].forEach((timer) { |
| 119 timerMap[timer['name']] = timer['time']; |
| 120 }); |
| 121 timers['total'] = timerMap['time_total_runtime']; |
| 122 timers['compile'] = timerMap['time_compilation']; |
| 123 timers['gc'] = 0.0; // TODO(turnidge): Export this from VM. |
| 124 timers['init'] = (timerMap['time_script_loading'] + |
| 125 timerMap['time_creating_snapshot'] + |
| 126 timerMap['time_isolate_initialization'] + |
| 127 timerMap['time_bootstrap']); |
| 128 timers['dart'] = timerMap['time_dart_execution']; |
| 129 |
| 130 newHeapUsed = map['heap']['usedNew']; |
| 131 oldHeapUsed = map['heap']['usedOld']; |
| 132 } |
| 133 } |
| 134 |
| 135 // TODO(johnmccutchan): Make this into an IsolateCache. |
| 136 class IsolateList extends ServiceObject { |
| 137 final VM _vm; |
| 138 VM get vm => _vm; |
| 139 @observable final isolates = new ObservableMap<String, Isolate>(); |
| 140 IsolateList(this._vm) : super(null, 'isolates', 'IsolateList') { |
| 141 name = 'IsolateList'; |
| 142 vmName = name; |
| 143 } |
| 144 IsolateList.fromMap(this._vm, Map m) : super.fromMap(null, m) { |
| 145 name = 'IsolateList'; |
| 146 vmName = name; |
| 147 } |
| 148 |
| 149 Future<ServiceObject> reload() { |
| 150 return vm.getAsMap(id).then(update); |
| 151 } |
| 152 |
| 153 void _update(ObservableMap map) { |
| 154 _updateIsolates(map['members']); |
| 155 } |
| 156 |
| 157 void _updateIsolates(List<Map> members) { |
| 158 // Find dead isolates. |
| 159 var deadIsolates = []; |
| 160 isolates.forEach((k, v) { |
| 161 if (!_foundIsolateInMembers(k, members)) { |
| 162 deadIsolates.add(k); |
| 163 } |
| 164 }); |
| 165 // Remove them. |
| 166 deadIsolates.forEach((id) { |
| 167 isolates.remove(id); |
| 168 Logger.root.info('Isolate \'$id\' has gone away.'); |
| 169 }); |
| 170 |
| 171 // Add new isolates. |
| 172 members.forEach((map) { |
| 173 var id = map['id']; |
| 174 var isolate = isolates[id]; |
| 175 if (isolate == null) { |
| 176 isolate = new Isolate.fromMap(vm, map); |
| 177 Logger.root.info('Created ServiceObject for \'${isolate.id}\' with ' |
| 178 'type \'${isolate.serviceType}\''); |
| 179 isolates[id] = isolate; |
| 180 } |
| 181 }); |
| 182 |
| 183 // After updating the isolate list, refresh each isolate. |
| 184 _refreshIsolates(); |
| 185 } |
| 186 |
| 187 void _refreshIsolates() { |
| 188 // This is technically asynchronous but we don't need to wait for |
| 189 // the result. |
| 190 isolates.forEach((k, Isolate isolate) { |
| 191 isolate.reload(); |
| 192 }); |
| 193 } |
| 194 |
| 195 Isolate getIsolate(String id) { |
| 196 assert(id.startsWith('isolates/')); |
| 197 var isolate = isolates[id]; |
| 198 if (isolate != null) { |
| 199 return isolate; |
| 200 } |
| 201 isolate = new Isolate.fromId(vm, id); |
| 202 isolates[id] = isolate; |
| 203 isolate.load(); |
| 204 return isolate; |
| 205 } |
| 206 |
| 207 Isolate getIsolateFromMap(ObservableMap m) { |
| 208 assert(ServiceObject.isServiceMap(m)); |
| 209 String id = m['id']; |
| 210 assert(id.startsWith('isolates/')); |
| 211 var isolate = isolates[id]; |
| 212 if (isolate != null) { |
| 213 isolate.update(m); |
| 214 return isolate; |
| 215 } |
| 216 isolate = new Isolate.fromMap(vm, m); |
| 217 isolates[id] = isolate; |
| 218 isolate.load(); |
| 219 return isolate; |
| 220 } |
| 221 |
| 222 static bool _foundIsolateInMembers(String id, List<Map> members) { |
| 223 return members.any((E) => E['id'] == id); |
| 224 } |
| 225 } |
| 226 |
| 227 |
| 228 /// A [ServiceObject] which implements [ObservableMap]. |
| 229 class ServiceMap extends ServiceObject implements ObservableMap { |
| 230 final ObservableMap _map = new ObservableMap(); |
| 231 ServiceMap(Isolate isolate, String id, String serviceType) : |
| 232 super(isolate, id, serviceType) { |
| 233 } |
| 234 |
| 235 ServiceMap.fromMap(Isolate isolate, ObservableMap m) : |
| 236 super.fromMap(isolate, m); |
| 237 |
| 238 String toString() => _map.toString(); |
| 239 |
| 240 void _upgradeValues() { |
| 241 assert(isolate != null); |
| 242 upgradeCollection(_map, vm, isolate); |
| 243 } |
| 244 |
| 245 void _update(ObservableMap m) { |
| 246 _map.clear(); |
| 247 _map.addAll(m); |
| 248 name = _map['user_name']; |
| 249 vmName = _map['name']; |
| 250 _upgradeValues(); |
| 251 } |
| 252 |
| 253 // Forward Map interface calls. |
| 254 void addAll(Map other) => _map.addAll(other); |
| 255 void clear() => _map.clear(); |
| 256 bool containsValue(v) => _map.containsValue(v); |
| 257 bool containsKey(k) => _map.containsKey(k); |
| 258 void forEach(Function f) => _map.forEach(f); |
| 259 putIfAbsent(key, Function ifAbsent) => _map.putIfAbsent(key, ifAbsent); |
| 260 void remove(key) => _map.remove(key); |
| 261 operator [](k) => _map[k]; |
| 262 operator []=(k, v) => _map[k] = v; |
| 263 bool get isEmpty => _map.isEmpty; |
| 264 bool get isNotEmpty => _map.isNotEmpty; |
| 265 Iterable get keys => _map.keys; |
| 266 Iterable get values => _map.values; |
| 267 int get length => _map.length; |
| 268 |
| 269 // Forward ChangeNotifier interface calls. |
| 270 bool deliverChanges() => _map.deliverChanges(); |
| 271 void notifyChange(ChangeRecord record) => _map.notifyChange(record); |
| 272 notifyPropertyChange(Symbol field, Object oldValue, Object newValue) => |
| 273 _map.notifyPropertyChange(field, oldValue, newValue); |
| 274 void observed() => _map.observed(); |
| 275 void unobserved() => _map.unobserved(); |
| 276 Stream<List<ChangeRecord>> get changes => _map.changes; |
| 277 bool get hasObservers => _map.hasObservers; |
| 278 } |
| 279 |
| 280 class ServiceError extends ServiceObject { |
| 281 ServiceError.fromMap(Isolate isolate, Map m) : super.fromMap(isolate, m); |
| 282 |
| 283 @observable String kind; |
| 284 @observable String message; |
| 285 |
| 286 void _update(ObservableMap map) { |
| 287 kind = map['kind']; |
| 288 message = map['message']; |
| 289 name = 'ServiceError $kind'; |
| 290 vmName = name; |
| 291 } |
| 292 |
| 293 // TODO: stackTrace? |
| 294 } |
| 295 |
| 296 class ScriptLine { |
| 297 @reflectable final int line; |
| 298 @reflectable final String text; |
| 299 ScriptLine(this.line, this.text); |
| 300 } |
| 301 |
| 302 class Script extends ServiceObject { |
| 303 @reflectable final lines = new ObservableList<ScriptLine>(); |
| 304 @reflectable final hits = new ObservableMap<int, int>(); |
| 305 @observable ServiceObject library; |
| 306 @observable String kind; |
| 307 |
| 308 String _shortUrl; |
| 309 String _url; |
| 310 |
| 311 Script.fromMap(Isolate isolate, Map m) : super.fromMap(isolate, m); |
| 312 |
| 313 void _update(ObservableMap m) { |
| 314 // Assert that m is a service map. |
| 315 assert(ServiceObject.isServiceMap(m)); |
| 316 if ((m['type'] == 'Error') && (m['kind'] == 'NotFoundError')) { |
| 317 // TODO(johnmccutchan): Find out why dart:core/identical.dart can't |
| 318 // be found but shows up in coverage. i.e. a function has reference |
| 319 // to script that no library does. |
| 320 Logger.root.info(m['message']); |
| 321 return; |
| 322 } |
| 323 // Assert that the id hasn't changed. |
| 324 assert(m['id'] == _id); |
| 325 // Assert that the type hasn't changed. |
| 326 assert(ServiceObject.stripRef(m['type']) == _serviceType); |
| 327 _url = m['name']; |
| 328 _shortUrl = _url.substring(_url.lastIndexOf('/') + 1); |
| 329 name = _shortUrl; |
| 330 vmName = _url; |
| 331 kind = m['kind']; |
| 332 _processSource(m['source']); |
| 333 } |
| 334 |
| 335 void _processHits(List scriptHits) { |
| 336 if (_ref) { |
| 337 // Eagerly grab script source. |
| 338 load(); |
| 339 } |
| 340 // Update hits table. |
| 341 for (var i = 0; i < scriptHits.length; i += 2) { |
| 342 var line = scriptHits[i]; |
| 343 var hit = scriptHits[i + 1]; // hit status. |
| 344 assert(line >= 1); // Lines start at 1. |
| 345 hits[line] = hit; |
| 346 } |
| 347 } |
| 348 |
| 349 void _processSource(String source) { |
| 350 // Preemptyively mark that this is a reference. |
| 351 _ref = true; |
| 352 if (source == null) { |
| 353 return; |
| 354 } |
| 355 var sourceLines = source.split('\n'); |
| 356 if (sourceLines.length == 0) { |
| 357 return; |
| 358 } |
| 359 // We have the source to the script. This is no longer a reference. |
| 360 _ref = false; |
| 361 lines.clear(); |
| 362 Logger.root.info('Adding ${sourceLines.length} source lines for ${_url}'); |
| 363 for (var i = 0; i < sourceLines.length; i++) { |
| 364 lines.add(new ScriptLine(i + 1, sourceLines[i])); |
| 365 } |
| 366 } |
| 367 |
| 368 |
| 369 } |
| 370 |
| 371 class CodeTick { |
| 372 final int address; |
| 373 final int exclusiveTicks; |
| 374 final int inclusiveTicks; |
| 375 CodeTick(this.address, this.exclusiveTicks, this.inclusiveTicks); |
| 376 } |
| 377 |
| 378 |
| 379 class CodeInstruction extends Observable { |
| 380 @observable final int address; |
| 381 @observable final String machine; |
| 382 @observable final String human; |
| 383 |
| 384 static String formatPercent(num a, num total) { |
| 385 var percent = 100.0 * (a / total); |
| 386 return '${percent.toStringAsFixed(2)}%'; |
| 387 } |
| 388 |
| 389 CodeInstruction(this.address, this.machine, this.human); |
| 390 |
| 391 @reflectable String formattedAddress() { |
| 392 if (address == 0) { |
| 393 return ''; |
| 394 } |
| 395 return '0x${address.toRadixString(16)}'; |
| 396 } |
| 397 |
| 398 @reflectable String formattedInclusive(Code code) { |
| 399 if (code == null) { |
| 400 return ''; |
| 401 } |
| 402 var tick = code.addressTicks[address]; |
| 403 if (tick == null) { |
| 404 return ''; |
| 405 } |
| 406 var pcent = formatPercent(tick.inclusiveTicks, code.totalSamplesInProfile); |
| 407 return '${tick.inclusiveTicks} ($pcent)'; |
| 408 } |
| 409 |
| 410 @reflectable String formattedExclusive(Code code) { |
| 411 if (code == null) { |
| 412 return ''; |
| 413 } |
| 414 var tick = code.addressTicks[address]; |
| 415 if (tick == null) { |
| 416 return ''; |
| 417 } |
| 418 var pcent = formatPercent(tick.exclusiveTicks, code.totalSamplesInProfile); |
| 419 return '${tick.exclusiveTicks} ($pcent)'; |
| 420 } |
| 421 } |
| 422 |
| 423 class CodeKind { |
| 424 final _value; |
| 425 const CodeKind._internal(this._value); |
| 426 String toString() => 'CodeKind.$_value'; |
| 427 |
| 428 static CodeKind fromString(String s) { |
| 429 if (s == 'Native') { |
| 430 return Native; |
| 431 } else if (s == 'Dart') { |
| 432 return Dart; |
| 433 } else if (s == 'Collected') { |
| 434 return Collected; |
| 435 } |
| 436 throw new FallThroughError(); |
| 437 } |
| 438 static const Native = const CodeKind._internal('Native'); |
| 439 static const Dart = const CodeKind._internal('Dart'); |
| 440 static const Collected = const CodeKind._internal('Collected'); |
| 441 } |
| 442 |
| 443 class CodeCallCount { |
| 444 final Code code; |
| 445 final int count; |
| 446 CodeCallCount(this.code, this.count); |
| 447 } |
| 448 |
| 449 class Code extends ServiceObject { |
| 450 @observable CodeKind kind; |
| 451 @observable int totalSamplesInProfile = 0; |
| 452 @reflectable int exclusiveTicks = 0; |
| 453 @reflectable int inclusiveTicks = 0; |
| 454 @reflectable int startAddress = 0; |
| 455 @reflectable int endAddress = 0; |
| 456 @reflectable final callers = new List<CodeCallCount>(); |
| 457 @reflectable final callees = new List<CodeCallCount>(); |
| 458 @reflectable final instructions = new ObservableList<CodeInstruction>(); |
| 459 @reflectable final addressTicks = new ObservableMap<int, CodeTick>(); |
| 460 |
| 461 @observable ServiceMap function; |
| 462 String name; |
| 463 String vmName; |
| 464 |
| 465 Code.fromMap(Isolate isolate, Map map) : super.fromMap(isolate, map); |
| 466 |
| 467 // Reset all data associated with a profile. |
| 468 void resetProfileData() { |
| 469 totalSamplesInProfile = 0; |
| 470 exclusiveTicks = 0; |
| 471 inclusiveTicks = 0; |
| 472 callers.clear(); |
| 473 callees.clear(); |
| 474 addressTicks.clear(); |
| 475 } |
| 476 |
| 477 void _resolveCalls(List<CodeCallCount> calls, List data, List<Code> codes) { |
| 478 // Assert that this has been cleared. |
| 479 assert(calls.length == 0); |
| 480 // Resolve. |
| 481 for (var i = 0; i < data.length; i += 2) { |
| 482 var index = int.parse(data[i]); |
| 483 var count = int.parse(data[i + 1]); |
| 484 assert(index >= 0); |
| 485 assert(index < codes.length); |
| 486 calls.add(new CodeCallCount(codes[index], count)); |
| 487 } |
| 488 // Sort to descending count order. |
| 489 calls.sort((a, b) => b.count - a.count); |
| 490 } |
| 491 |
| 492 |
| 493 void updateProfileData(Map profileData, |
| 494 List<Code> codeTable, |
| 495 int sampleCount) { |
| 496 // Assert we have a ProfileCode entry. |
| 497 assert(profileData['type'] == 'ProfileCode'); |
| 498 // Assert we are handed profile data for this code object. |
| 499 assert(profileData['code'] == this); |
| 500 totalSamplesInProfile = sampleCount; |
| 501 inclusiveTicks = int.parse(profileData['inclusive_ticks']); |
| 502 exclusiveTicks = int.parse(profileData['exclusive_ticks']); |
| 503 _resolveCalls(callers, profileData['callers'], codeTable); |
| 504 _resolveCalls(callees, profileData['callees'], codeTable); |
| 505 var ticks = profileData['ticks']; |
| 506 if (ticks != null) { |
| 507 _processTicks(ticks); |
| 508 } |
| 509 } |
| 510 |
| 511 void _update(ObservableMap m) { |
| 512 assert(ServiceObject.isServiceMap(m)); |
| 513 assert(m['id'] == _id); |
| 514 assert(ServiceObject.stripRef(m['type']) == _serviceType); |
| 515 name = m['user_name']; |
| 516 vmName = m['name']; |
| 517 startAddress = int.parse(m['start'], radix:16); |
| 518 endAddress = int.parse(m['end'], radix:16); |
| 519 // Upgrade the function. |
| 520 function = _upgradeToServiceObject(isolate.vm, isolate, m['function']); |
| 521 var disassembly = m['disassembly']; |
| 522 if (disassembly != null) { |
| 523 _processDisassembly(disassembly); |
| 524 } |
| 525 // We are a reference if we don't have instructions. |
| 526 _ref = (instructions.length == 0); |
| 527 } |
| 528 |
| 529 void _processDisassembly(List<String> disassembly){ |
| 530 assert(disassembly != null); |
| 531 instructions.clear(); |
| 532 assert((disassembly.length % 3) == 0); |
| 533 for (var i = 0; i < disassembly.length; i += 3) { |
| 534 var address = 0; // Assume code comment. |
| 535 var machine = disassembly[i + 1]; |
| 536 var human = disassembly[i + 2]; |
| 537 if (disassembly[i] != '') { |
| 538 // Not a code comment, extract address. |
| 539 address = int.parse(disassembly[i]); |
| 540 } |
| 541 var instruction = new CodeInstruction(address, machine, human); |
| 542 instructions.add(instruction); |
| 543 } |
| 544 } |
| 545 |
| 546 void _processTicks(List<String> profileTicks) { |
| 547 assert(profileTicks != null); |
| 548 assert((profileTicks.length % 3) == 0); |
| 549 for (var i = 0; i < profileTicks.length; i += 3) { |
| 550 var address = int.parse(profileTicks[i], radix:16); |
| 551 var exclusive = int.parse(profileTicks[i + 1]); |
| 552 var inclusive = int.parse(profileTicks[i + 2]); |
| 553 var tick = new CodeTick(address, exclusive, inclusive); |
| 554 addressTicks[address] = tick; |
| 555 } |
| 556 } |
| 557 |
| 558 /// Returns true if [address] is contained inside [this]. |
| 559 bool contains(int address) { |
| 560 return (address >= startAddress) && (address < endAddress); |
| 561 } |
| 562 |
| 563 /// Sum all caller counts. |
| 564 int sumCallersCount() => _sumCallCount(callers); |
| 565 /// Specific caller count. |
| 566 int callersCount(Code code) => _callCount(callers, code); |
| 567 /// Sum of callees count. |
| 568 int sumCalleesCount() => _sumCallCount(callees); |
| 569 /// Specific callee count. |
| 570 int calleesCount(Code code) => _callCount(callees, code); |
| 571 |
| 572 int _sumCallCount(List<CodeCallCount> calls) { |
| 573 var sum = 0; |
| 574 for (CodeCallCount caller in calls) { |
| 575 sum += caller.count; |
| 576 } |
| 577 return sum; |
| 578 } |
| 579 |
| 580 int _callCount(List<CodeCallCount> calls, Code code) { |
| 581 for (CodeCallCount caller in calls) { |
| 582 if (caller.code == code) { |
| 583 return caller.count; |
| 584 } |
| 585 } |
| 586 return 0; |
| 587 } |
| 588 } |
| OLD | NEW |