| 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 app; | |
| 6 | |
| 7 class CodeInstruction extends Observable { | |
| 8 @observable final int address; | |
| 9 @observable final String machine; | |
| 10 @observable final String human; | |
| 11 @observable int ticks = 0; | |
| 12 @observable double percent; | |
| 13 @observable String formattedTicks() { | |
| 14 if (percent == null || percent <= 0.0) { | |
| 15 return ''; | |
| 16 } | |
| 17 return '${percent.toStringAsFixed(2)}% (${ticks})'; | |
| 18 } | |
| 19 @observable String formattedAddress() { | |
| 20 return '0x${address.toRadixString(16)}'; | |
| 21 } | |
| 22 CodeInstruction(this.address, this.machine, this.human); | |
| 23 void updateTickString(Code code) { | |
| 24 if ((code == null) || (code.inclusiveTicks == 0)) { | |
| 25 percent = null; | |
| 26 return; | |
| 27 } | |
| 28 percent = (ticks / code.inclusiveTicks) * 100.0; | |
| 29 if (percent <= 0.00) { | |
| 30 percent = null; | |
| 31 return; | |
| 32 } | |
| 33 } | |
| 34 } | |
| 35 | |
| 36 class CodeKind { | |
| 37 final _value; | |
| 38 const CodeKind._internal(this._value); | |
| 39 String toString() => 'CodeKind.$_value'; | |
| 40 | |
| 41 static CodeKind fromString(String s) { | |
| 42 if (s == 'Native') { | |
| 43 return Native; | |
| 44 } else if (s == 'Dart') { | |
| 45 return Dart; | |
| 46 } else if (s == 'Collected') { | |
| 47 return Collected; | |
| 48 } | |
| 49 throw new FallThroughError(); | |
| 50 } | |
| 51 static const Native = const CodeKind._internal('Native'); | |
| 52 static const Dart = const CodeKind._internal('Dart'); | |
| 53 static const Collected = const CodeKind._internal('Collected'); | |
| 54 } | |
| 55 | |
| 56 class CodeTick { | |
| 57 final int address; | |
| 58 final int exclusive_ticks; | |
| 59 final int inclusive_ticks; | |
| 60 CodeTick(this.address, this.exclusive_ticks, this.inclusive_ticks); | |
| 61 } | |
| 62 | |
| 63 class CodeCallCount { | |
| 64 final Code code; | |
| 65 final int count; | |
| 66 CodeCallCount(this.code, this.count); | |
| 67 } | |
| 68 | |
| 69 class Code extends Observable { | |
| 70 final CodeKind kind; | |
| 71 final int startAddress; | |
| 72 final int endAddress; | |
| 73 final List<CodeTick> ticks = []; | |
| 74 final List<CodeCallCount> callers = []; | |
| 75 final List<CodeCallCount> callees = []; | |
| 76 int inclusiveTicks = 0; | |
| 77 int exclusiveTicks = 0; | |
| 78 @observable final List<CodeInstruction> instructions = toObservable([]); | |
| 79 @observable Map functionRef = toObservable({}); | |
| 80 @observable Map codeRef = toObservable({}); | |
| 81 @observable String name; | |
| 82 @observable String userName; | |
| 83 | |
| 84 Code(this.kind, this.name, this.startAddress, this.endAddress); | |
| 85 | |
| 86 Code.fromMap(Map map) : | |
| 87 kind = CodeKind.Dart, | |
| 88 startAddress = int.parse(map['start'], radix: 16), | |
| 89 endAddress = int.parse(map['end'], radix: 16) { | |
| 90 functionRef = toObservable(map['function']); | |
| 91 codeRef = toObservable(map); | |
| 92 name = map['name']; | |
| 93 userName = map['user_name']; | |
| 94 if (map['disassembly'] != null) { | |
| 95 _loadInstructions(map['disassembly']); | |
| 96 } | |
| 97 } | |
| 98 | |
| 99 factory Code.fromProfileMap(Map map) { | |
| 100 var kind = CodeKind.fromString(map['kind']); | |
| 101 var startAddress; | |
| 102 var endAddress; | |
| 103 var name; | |
| 104 var userName; | |
| 105 var codeRef = map['code']; | |
| 106 assert(codeRef != null); | |
| 107 startAddress = int.parse(codeRef['start'], radix:16); | |
| 108 endAddress = int.parse(codeRef['end'], radix:16); | |
| 109 name = codeRef['name']; | |
| 110 userName = codeRef['user_name']; | |
| 111 var code = new Code(kind, name, startAddress, endAddress); | |
| 112 code.codeRef = codeRef; | |
| 113 code.functionRef = toObservable(codeRef['function']);; | |
| 114 code.userName = userName; | |
| 115 if (codeRef['disassembly'] != null) { | |
| 116 code._loadInstructions(codeRef['disassembly']); | |
| 117 // Throw the JSON version away after loading the disassembly. | |
| 118 codeRef['disassembly'] = null; | |
| 119 } | |
| 120 return code; | |
| 121 } | |
| 122 | |
| 123 // Refresh tick counts, etc for a code object. | |
| 124 void _refresh(Map map) { | |
| 125 inclusiveTicks = int.parse(map['inclusive_ticks']); | |
| 126 exclusiveTicks = int.parse(map['exclusive_ticks']); | |
| 127 // Load address ticks. | |
| 128 var ticksList = map['ticks']; | |
| 129 if ((ticksList != null) && (ticksList.length > 0)) { | |
| 130 assert((ticks.length % 3) == 0); | |
| 131 for (var i = 0; i < ticksList.length; i += 3) { | |
| 132 var address = int.parse(ticksList[i], radix:16); | |
| 133 var inclusive_ticks = int.parse(ticksList[i + 1]); | |
| 134 var exclusive_ticks = int.parse(ticksList[i + 2]); | |
| 135 var codeTick = new CodeTick(address, exclusive_ticks, inclusive_ticks); | |
| 136 ticks.add(codeTick); | |
| 137 } | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 /// Sum all caller counts. | |
| 142 int sumCallersCount() => _sumCallCount(callers); | |
| 143 /// Specific caller count. | |
| 144 int callersCount(Code code) => _callCount(callers, code); | |
| 145 /// Sum of callees count. | |
| 146 int sumCalleesCount() => _sumCallCount(callees); | |
| 147 /// Specific callee count. | |
| 148 int calleesCount(Code code) => _callCount(callees, code); | |
| 149 | |
| 150 int _sumCallCount(List<CodeCallCount> calls) { | |
| 151 var sum = 0; | |
| 152 for (CodeCallCount caller in calls) { | |
| 153 sum += caller.count; | |
| 154 } | |
| 155 return sum; | |
| 156 } | |
| 157 | |
| 158 int _callCount(List<CodeCallCount> calls, Code code) { | |
| 159 for (CodeCallCount caller in calls) { | |
| 160 if (caller.code == code) { | |
| 161 return caller.count; | |
| 162 } | |
| 163 } | |
| 164 return 0; | |
| 165 } | |
| 166 | |
| 167 void resolveCalls(Map code, List<Code> codes) { | |
| 168 _resolveCalls(callers, code['callers'], codes); | |
| 169 _resolveCalls(callees, code['callees'], codes); | |
| 170 } | |
| 171 | |
| 172 void _resolveCalls(List<CodeCallCount> calls, List data, List<Code> codes) { | |
| 173 // Clear. | |
| 174 calls.clear(); | |
| 175 // Resolve. | |
| 176 for (var i = 0; i < data.length; i += 2) { | |
| 177 var index = int.parse(data[i]); | |
| 178 var count = int.parse(data[i + 1]); | |
| 179 assert(index >= 0); | |
| 180 assert(index < codes.length); | |
| 181 calls.add(new CodeCallCount(codes[index], count)); | |
| 182 } | |
| 183 // Sort to descending count order. | |
| 184 calls.sort((a, b) => b.count - a.count); | |
| 185 } | |
| 186 | |
| 187 /// Resets all tick counts to 0. | |
| 188 void resetTicks() { | |
| 189 inclusiveTicks = 0; | |
| 190 exclusiveTicks = 0; | |
| 191 ticks.clear(); | |
| 192 for (var instruction in instructions) { | |
| 193 instruction.ticks = 0; | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 /// Adds [count] to the tick count for the instruction at [address]. | |
| 198 void tick(int address, int count) { | |
| 199 for (var instruction in instructions) { | |
| 200 if (instruction.address == address) { | |
| 201 instruction.ticks += count; | |
| 202 return; | |
| 203 } | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 /// Clears [instructions] and then adds all instructions from | |
| 208 /// [instructionList]. | |
| 209 void _loadInstructions(List instructionList) { | |
| 210 instructions.clear(); | |
| 211 // Load disassembly into code object. | |
| 212 for (int i = 0; i < instructionList.length; i += 3) { | |
| 213 if (instructionList[i] == '') { | |
| 214 // Code comment. | |
| 215 // TODO(johnmccutchan): Insert code comments into instructions. | |
| 216 continue; | |
| 217 } | |
| 218 var address = int.parse(instructionList[i]); | |
| 219 var machine = instructionList[i + 1]; | |
| 220 var human = instructionList[i + 2]; | |
| 221 instructions.add(new CodeInstruction(address, machine, human)); | |
| 222 } | |
| 223 } | |
| 224 | |
| 225 /// returns true if [address] is inside the address range. | |
| 226 bool contains(int address) { | |
| 227 return (address >= startAddress) && (address < endAddress); | |
| 228 } | |
| 229 } | |
| 230 | |
| 231 class Profile { | |
| 232 final Isolate isolate; | |
| 233 final List<Code> _codeObjectsInImportOrder = new List<Code>(); | |
| 234 int totalSamples = 0; | |
| 235 | |
| 236 Profile.fromMap(this.isolate, Map m) { | |
| 237 var codes = m['codes']; | |
| 238 totalSamples = m['samples']; | |
| 239 Logger.root.info('Creating profile from ${totalSamples} samples ' | |
| 240 'and ${codes.length} code objects.'); | |
| 241 isolate.resetCodeTicks(); | |
| 242 _codeObjectsInImportOrder.clear(); | |
| 243 codes.forEach((code) { | |
| 244 try { | |
| 245 _processCode(code); | |
| 246 } catch (e, st) { | |
| 247 Logger.root.warning('Error processing code object. $e $st', e, st); | |
| 248 } | |
| 249 }); | |
| 250 // Now that code objects have been loaded, post-process them | |
| 251 // and resolve callers and callees. | |
| 252 assert(_codeObjectsInImportOrder.length == codes.length); | |
| 253 for (var i = 0; i < codes.length; i++) { | |
| 254 Code code = _codeObjectsInImportOrder[i]; | |
| 255 code.resolveCalls(codes[i], _codeObjectsInImportOrder); | |
| 256 } | |
| 257 _codeObjectsInImportOrder.clear(); | |
| 258 } | |
| 259 | |
| 260 int _extractCodeStartAddress(Map code) { | |
| 261 return int.parse(code['code']['start'], radix:16); | |
| 262 } | |
| 263 | |
| 264 void _processCode(Map profileCode) { | |
| 265 if (profileCode['type'] != 'ProfileCode') { | |
| 266 return; | |
| 267 } | |
| 268 int address = _extractCodeStartAddress(profileCode); | |
| 269 var code = isolate.findCodeByAddress(address); | |
| 270 if (code == null) { | |
| 271 // Never seen a code object at this address before, create a new one. | |
| 272 code = new Code.fromProfileMap(profileCode); | |
| 273 isolate.codes.add(code); | |
| 274 } | |
| 275 code._refresh(profileCode); | |
| 276 _codeObjectsInImportOrder.add(code); | |
| 277 } | |
| 278 | |
| 279 List<Code> topExclusive(int count) { | |
| 280 List<Code> exclusive = isolate.codes; | |
| 281 exclusive.sort((Code a, Code b) { | |
| 282 return b.exclusiveTicks - a.exclusiveTicks; | |
| 283 }); | |
| 284 if ((exclusive.length < count) || (count == 0)) { | |
| 285 return exclusive; | |
| 286 } | |
| 287 return exclusive.sublist(0, count); | |
| 288 } | |
| 289 } | |
| 290 | |
| 291 class ScriptLine extends Observable { | |
| 292 @observable final int line; | |
| 293 @observable int hits = -1; | |
| 294 @observable String text = ''; | |
| 295 /// Is this a line of executable code? | |
| 296 bool get executable => hits >= 0; | |
| 297 /// Has this line executed before? | |
| 298 bool get covered => hits > 0; | |
| 299 ScriptLine(this.line); | |
| 300 } | |
| 301 | |
| 302 class Script extends Observable { | |
| 303 @observable String kind = null; | |
| 304 @observable Map scriptRef = toObservable({}); | |
| 305 @published String shortName; | |
| 306 @observable Map libraryRef = toObservable({}); | |
| 307 @observable final List<ScriptLine> lines = | |
| 308 toObservable(new List<ScriptLine>()); | |
| 309 bool _needsSource = true; | |
| 310 bool get needsSource => _needsSource; | |
| 311 Script.fromMap(Map map) { | |
| 312 scriptRef = toObservable({ | |
| 313 'id': map['id'], | |
| 314 'name': map['name'], | |
| 315 'user_name': map['user_name'] | |
| 316 }); | |
| 317 shortName = map['name'].substring(map['name'].lastIndexOf('/') + 1); | |
| 318 libraryRef = toObservable(map['library']); | |
| 319 kind = map['kind']; | |
| 320 _processSource(map['source']); | |
| 321 } | |
| 322 | |
| 323 // Iterable of lines for display. Skips line '0'. | |
| 324 @observable Iterable get linesForDisplay { | |
| 325 return lines.skip(1); | |
| 326 } | |
| 327 | |
| 328 // Fetch (possibly create) the ScriptLine for [lineNumber]. | |
| 329 ScriptLine _getLine(int lineNumber) { | |
| 330 assert(lineNumber != 0); | |
| 331 if (lineNumber >= lines.length) { | |
| 332 // Grow lines list. | |
| 333 lines.length = lineNumber + 1; | |
| 334 } | |
| 335 var line = lines[lineNumber]; | |
| 336 if (line == null) { | |
| 337 // Create this line. | |
| 338 line = new ScriptLine(lineNumber); | |
| 339 lines[lineNumber] = line; | |
| 340 } | |
| 341 return line; | |
| 342 } | |
| 343 | |
| 344 void _processSource(String source) { | |
| 345 if (source == null) { | |
| 346 return; | |
| 347 } | |
| 348 Logger.root.info('Loading source for ${scriptRef['name']}'); | |
| 349 var sourceLines = source.split('\n'); | |
| 350 _needsSource = sourceLines.length == 0; | |
| 351 for (var i = 0; i < sourceLines.length; i++) { | |
| 352 var line = _getLine(i + 1); | |
| 353 line.text = sourceLines[i]; | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 void _processCoverageHits(List hits) { | |
| 358 for (var i = 0; i < hits.length; i += 2) { | |
| 359 var line = _getLine(hits[i]); | |
| 360 line.hits = hits[i + 1]; | |
| 361 } | |
| 362 notifyPropertyChange(#coveredPercentageFormatted, '', | |
| 363 coveredPercentageFormatted()); | |
| 364 } | |
| 365 | |
| 366 /// What percentage of lines in this script have been covered? | |
| 367 @observable double coveredPercentage() { | |
| 368 int coveredLines = 0; | |
| 369 int executableLines = 0; | |
| 370 for (var line in lines) { | |
| 371 if (line == null) { | |
| 372 continue; | |
| 373 } | |
| 374 if (!line.executable) { | |
| 375 continue; | |
| 376 } | |
| 377 executableLines++; | |
| 378 if (!line.covered) { | |
| 379 continue; | |
| 380 } | |
| 381 coveredLines++; | |
| 382 } | |
| 383 if (executableLines == 0) { | |
| 384 return 0.0; | |
| 385 } | |
| 386 return (coveredLines / executableLines) * 100.0; | |
| 387 } | |
| 388 | |
| 389 @observable String coveredPercentageFormatted() { | |
| 390 return '(' + coveredPercentage().toStringAsFixed(1) + '% covered)'; | |
| 391 } | |
| 392 } | |
| OLD | NEW |