| 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 dart2js.helpers; | |
| 6 | |
| 7 // Helper methods for statistics. | |
| 8 | |
| 9 /// Current stats collector. Use [enableStatsOutput] to enable recording of | |
| 10 /// stats. | |
| 11 Stats get stats { | |
| 12 enableDebugMode(); | |
| 13 if (_stats == null) { | |
| 14 _stats = const Stats(); | |
| 15 } | |
| 16 return _stats; | |
| 17 } | |
| 18 | |
| 19 Stats _stats; | |
| 20 | |
| 21 /// Enable recording of stats. Use [Stats.dumpStats] to output the record stats. | |
| 22 /// | |
| 23 /// Pass the [outputProvider] of [Compiler] to generate stats into a separate | |
| 24 /// file using [name] and [extension] for the filename. If omitted, stats are | |
| 25 /// printed on standard out. | |
| 26 /// | |
| 27 /// If [xml] is `true`, stats output is formatted as XML with a default | |
| 28 /// extension of 'xml', otherwise the output is indented text with a default | |
| 29 /// extension of 'log'. | |
| 30 void enableStatsOutput({CompilerOutputProvider outputProvider, | |
| 31 bool xml: true, | |
| 32 String name: 'stats', | |
| 33 String extension, | |
| 34 int examples: 10}) { | |
| 35 if (_stats != null) { | |
| 36 throw new StateError('Stats have already been initialized.'); | |
| 37 } | |
| 38 enableDebugMode(); | |
| 39 | |
| 40 StatsOutput output; | |
| 41 if (outputProvider != null) { | |
| 42 if (extension == null) { | |
| 43 extension = xml ? 'xml' : 'log'; | |
| 44 } | |
| 45 output = new SinkOutput(outputProvider(name, extension)); | |
| 46 } else { | |
| 47 output = const DebugOutput(); | |
| 48 } | |
| 49 StatsPrinter printer; | |
| 50 if (xml) { | |
| 51 printer = new XMLPrinter(output: output, examples: examples); | |
| 52 } else { | |
| 53 printer = new ConsolePrinter(output: output, examples: examples); | |
| 54 } | |
| 55 _stats = new ActiveStats(printer); | |
| 56 } | |
| 57 | |
| 58 /// Interface for gathering and display of statistical information. | |
| 59 /// This class serves as the noop collector. | |
| 60 class Stats { | |
| 61 const Stats(); | |
| 62 | |
| 63 /// Registers [key], [value] pair in the map [id]. If [fromExisting] is | |
| 64 /// non-null and [key] already exists, the value associated with [key] will | |
| 65 /// be the return value of [fromExisting] when called with the existing value. | |
| 66 /// | |
| 67 /// The recorded information is not dumped automatically. | |
| 68 void recordMap(id, key, value, {fromExisting(value)}) {} | |
| 69 | |
| 70 /// Returns the map [id] recorded with [recordMap]. | |
| 71 Map getMap(id) => const {}; | |
| 72 | |
| 73 /// Registers [element] as an element of the list [id]. If provided, [data] | |
| 74 /// provides additional data for [element]. | |
| 75 /// | |
| 76 /// The recorded information is dumped automatically on call to [dumpStats]. | |
| 77 /// | |
| 78 /// Example: | |
| 79 /// Calling [recordElement] like this: | |
| 80 /// recordElement('foo', 'a', data: 'first-a-data'); | |
| 81 /// recordElement('foo', 'a', data: 'second-a-data'); | |
| 82 /// recordElement('foo', 'b'); | |
| 83 /// recordElement('bar', 'a', data: 'third-a-data'); | |
| 84 /// recordElement('bar', 'c'); | |
| 85 /// will result in a dump like this: | |
| 86 /// foo: 2 | |
| 87 /// value=a data=second-a-data | |
| 88 /// b | |
| 89 /// bar: 2 | |
| 90 /// value=a data=third-a-data | |
| 91 /// c | |
| 92 /// | |
| 93 void recordElement(id, element, {data}) {} | |
| 94 | |
| 95 /// Returns the list [id] recorded with [recordElement]. | |
| 96 Iterable getList(String id) => const []; | |
| 97 | |
| 98 /// Registers [value] as an occurrence of [id]. If passed, [example] provides | |
| 99 /// an example data of the occurrence of [value]. | |
| 100 /// | |
| 101 /// The recorded information is dumped automatically on call to [dumpStats]. | |
| 102 /// | |
| 103 /// Example: | |
| 104 /// Calling [recordFrequency] like this: | |
| 105 /// recordFrequency('foo', 'a', 'first-a-data'); | |
| 106 /// recordFrequency('foo', 'a', 'second-a-data'); | |
| 107 /// recordFrequency('bar', 'b', 'first-b-data'); | |
| 108 /// recordFrequency('foo', 'c'); | |
| 109 /// recordFrequency('bar', 'b'); | |
| 110 /// will result in a dump like this: | |
| 111 /// foo: | |
| 112 /// a: 2 | |
| 113 /// first-a-data | |
| 114 /// second-a-data | |
| 115 /// c: 1 | |
| 116 /// bar: | |
| 117 /// b: 2 | |
| 118 /// first-b-data | |
| 119 /// | |
| 120 void recordFrequency(id, value, [example]) {} | |
| 121 | |
| 122 /// For each key/value pair in [map] the elements in the value are registered | |
| 123 /// as examples of occurrences of the key in [id]. | |
| 124 void recordFrequencies(id, Map<dynamic, Iterable> map) {} | |
| 125 | |
| 126 /// Returns the examples given for the occurrence of [value] for [id]. | |
| 127 Iterable recordedFrequencies(id, value) => const []; | |
| 128 | |
| 129 /// Increases the counter [id] by 1. If provided, [example] is used as an | |
| 130 /// example of the count and [data] provides additional information for | |
| 131 /// [example]. | |
| 132 /// | |
| 133 /// The recorded information is dumped automatically on call to [dumpStats]. | |
| 134 /// | |
| 135 /// Example: | |
| 136 /// Calling [recordCounter] like this: | |
| 137 /// recordCounter('foo', 'a'); | |
| 138 /// recordCounter('foo', 'a'); | |
| 139 /// recordCounter('foo', 'b'); | |
| 140 /// recordCounter('bar', 'c', 'first-c-data'); | |
| 141 /// recordCounter('bar', 'c', 'second-c-data'); | |
| 142 /// recordCounter('bar', 'd'); | |
| 143 /// recordCounter('bar', 'd'); | |
| 144 /// recordCounter('baz'); | |
| 145 /// recordCounter('baz'); | |
| 146 /// will result in a dump like this: | |
| 147 /// foo: 3 | |
| 148 /// count=2 example=a | |
| 149 /// count=1 example=b | |
| 150 /// bar: 4 | |
| 151 /// count=2 examples=2 | |
| 152 /// c: | |
| 153 /// first-c-data | |
| 154 /// second-c-data | |
| 155 /// d | |
| 156 /// baz: 2 | |
| 157 /// | |
| 158 void recordCounter(id, [example, data]) {} | |
| 159 | |
| 160 /// Records the current stack trace under the key [id]. Only every | |
| 161 /// [sampleFrequency] call with the same id is recorded, and if omitted | |
| 162 /// [stackTraceSampleFrequency] is used. | |
| 163 void recordTrace(id, {int sampleFrequency}) {} | |
| 164 | |
| 165 /// The default sample frequency used for recording stack traces. | |
| 166 int get stackTraceSampleFrequency => 0; | |
| 167 | |
| 168 /// Set the default sample frequency used for recording stack traces. | |
| 169 void set stackTraceSampleFrequency(int value) {} | |
| 170 | |
| 171 /// Dumps the stats for the recorded frequencies, sets, and counters. If | |
| 172 /// provided [beforeClose] is called before closing the dump output. This | |
| 173 /// can be used to include correlations on the collected data through | |
| 174 /// [dumpCorrelation]. | |
| 175 void dumpStats({void beforeClose()}) {} | |
| 176 | |
| 177 /// Prints the correlation between the elements of [a] and [b]. | |
| 178 /// | |
| 179 /// Three sets are output using [idA] and [idB] as labels for the elements | |
| 180 /// [a] and [b]: | |
| 181 /// | |
| 182 /// 'idA && idB' lists the elements both in [a] and [b], | |
| 183 /// '!idA && idB' lists the elements not in [a] but in [b], and | |
| 184 /// 'idA && !idB' lists the elements in [a] but not in [b]. | |
| 185 /// | |
| 186 /// If [dataA] and/or [dataB] are provided, additional information on the | |
| 187 /// elements are looked up in [dataA] or [dataB] using [dataA] as the primary | |
| 188 /// source. | |
| 189 void dumpCorrelation(idA, Iterable a, idB, Iterable b, | |
| 190 {Map dataA, Map dataB}) {} | |
| 191 } | |
| 192 | |
| 193 /// Interface for printing output data. | |
| 194 /// | |
| 195 /// This class serves as the disabled output. | |
| 196 class StatsOutput { | |
| 197 const StatsOutput(); | |
| 198 | |
| 199 /// Print [text] as on a separate line. | |
| 200 void println(String text) {} | |
| 201 } | |
| 202 | |
| 203 /// Output to the [debugPrint] method. | |
| 204 class DebugOutput implements StatsOutput { | |
| 205 const DebugOutput(); | |
| 206 | |
| 207 void println(String text) => debugPrint(text); | |
| 208 } | |
| 209 | |
| 210 /// Output to an [EventSink]. Used to output to a file through the | |
| 211 /// [CompilerOutputProvider]. | |
| 212 class SinkOutput implements StatsOutput { | |
| 213 EventSink<String> sink; | |
| 214 | |
| 215 SinkOutput(this.sink); | |
| 216 | |
| 217 void println(String text) { | |
| 218 sink.add(text); | |
| 219 sink.add('\n'); | |
| 220 } | |
| 221 } | |
| 222 | |
| 223 /// Interface for printing stats collected in [Stats]. | |
| 224 abstract class StatsPrinter { | |
| 225 /// The number of examples printer. If `null` all examples are printed. | |
| 226 int get examples => 0; | |
| 227 | |
| 228 /// Start a group [id]. | |
| 229 void start(String id) {} | |
| 230 | |
| 231 /// Create a group [id] with content created by [createGroupContent]. | |
| 232 void group(String id, void createGroupContent()) { | |
| 233 start(id); | |
| 234 createGroupContent(); | |
| 235 end(id); | |
| 236 } | |
| 237 | |
| 238 /// End a group [id]. | |
| 239 void end(String id) {} | |
| 240 | |
| 241 /// Start a stat entry for [id] with additional [data]. | |
| 242 void open(String id, | |
| 243 [Map<String, dynamic> data = const <String, dynamic>{}]) {} | |
| 244 | |
| 245 /// Create a stat entry for [id] with additional [data] and content created by | |
| 246 /// [createChildContent]. | |
| 247 void child(String id, | |
| 248 [Map<String, dynamic> data = const <String, dynamic>{}, | |
| 249 void createChildContent()]) { | |
| 250 open(id, data); | |
| 251 if (createChildContent != null) createChildContent(); | |
| 252 close(id); | |
| 253 } | |
| 254 | |
| 255 /// End a stat entry for [id]. | |
| 256 void close(String id) {} | |
| 257 | |
| 258 /// Starts a group of additional information. | |
| 259 void beginExtra() {} | |
| 260 | |
| 261 /// Starts a group of additional information. | |
| 262 void endExtra() {} | |
| 263 } | |
| 264 | |
| 265 /// Abstract base class for [ConsolePrinter] and [XMLPrinter]. | |
| 266 abstract class BasePrinter extends StatsPrinter with Indentation { | |
| 267 final int examples; | |
| 268 final StatsOutput output; | |
| 269 | |
| 270 BasePrinter({this.output: const DebugOutput(), | |
| 271 this.examples: 10}) { | |
| 272 indentationUnit = " "; | |
| 273 } | |
| 274 } | |
| 275 | |
| 276 /// [StatsPrinter] that displays stats in console lines. | |
| 277 class ConsolePrinter extends BasePrinter { | |
| 278 int extraLevel = 0; | |
| 279 | |
| 280 ConsolePrinter({StatsOutput output: const DebugOutput(), | |
| 281 int examples: 10}) | |
| 282 : super(output: output, examples: examples); | |
| 283 | |
| 284 void open(String id, | |
| 285 [Map<String, dynamic> data = const <String, dynamic>{}]) { | |
| 286 if (extraLevel > 0) return; | |
| 287 | |
| 288 StringBuffer sb = new StringBuffer(); | |
| 289 sb.write(indentation); | |
| 290 String space = ''; | |
| 291 if (data['title'] != null) { | |
| 292 sb.write('${data['title']}:'); | |
| 293 space = ' '; | |
| 294 data.remove('title'); | |
| 295 } else if (data['name'] != null) { | |
| 296 sb.write('${data['name']}'); | |
| 297 space = ' '; | |
| 298 data.remove('name'); | |
| 299 } | |
| 300 Iterable nonNullValues = data.values.where((v) => v != null); | |
| 301 if (nonNullValues.length == 1) { | |
| 302 sb.write('$space${nonNullValues.first}'); | |
| 303 } else { | |
| 304 data.forEach((key, value) { | |
| 305 sb.write('$space$key=$value'); | |
| 306 space = ' '; | |
| 307 }); | |
| 308 } | |
| 309 output.println(sb.toString()); | |
| 310 indentMore(); | |
| 311 } | |
| 312 | |
| 313 void close(String id) { | |
| 314 if (extraLevel > 0) return; | |
| 315 | |
| 316 indentLess(); | |
| 317 } | |
| 318 | |
| 319 void beginExtra() { | |
| 320 if (extraLevel == 0) output.println('$indentation...'); | |
| 321 extraLevel++; | |
| 322 } | |
| 323 | |
| 324 void endExtra() { | |
| 325 extraLevel--; | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 /// [StatsPrinter] that displays stats in XML format. | |
| 330 class XMLPrinter extends BasePrinter { | |
| 331 static const HtmlEscape escape = const HtmlEscape(); | |
| 332 bool opened = false; | |
| 333 | |
| 334 XMLPrinter({output: const DebugOutput(), | |
| 335 int examples: 10}) | |
| 336 : super(output: output, examples: examples); | |
| 337 | |
| 338 void start(String id) { | |
| 339 if (!opened) { | |
| 340 output.println('<?xml version="1.0" encoding="UTF-8"?>'); | |
| 341 opened = true; | |
| 342 } | |
| 343 open(id); | |
| 344 } | |
| 345 | |
| 346 void end(String id) { | |
| 347 close(id); | |
| 348 } | |
| 349 | |
| 350 void open(String id, | |
| 351 [Map<String, dynamic> data = const <String, dynamic>{}]) { | |
| 352 StringBuffer sb = new StringBuffer(); | |
| 353 sb.write(indentation); | |
| 354 sb.write('<$id'); | |
| 355 data.forEach((key, value) { | |
| 356 if (value != null) { | |
| 357 sb.write(' $key="${escape.convert('$value')}"'); | |
| 358 } | |
| 359 }); | |
| 360 sb.write('>'); | |
| 361 output.println(sb.toString()); | |
| 362 indentMore(); | |
| 363 } | |
| 364 | |
| 365 void close(String id) { | |
| 366 indentLess(); | |
| 367 output.println('${indentation}</$id>'); | |
| 368 } | |
| 369 | |
| 370 void beginExtra() { | |
| 371 open('extra'); | |
| 372 } | |
| 373 | |
| 374 void endExtra() { | |
| 375 close('extra'); | |
| 376 } | |
| 377 } | |
| 378 | |
| 379 /// A node in a stack trace tree used to store and organize stack traces by | |
| 380 /// common prefixes. | |
| 381 class _StackTraceNode implements Comparable<_StackTraceNode> { | |
| 382 int count; | |
| 383 List<StackTraceLine> commonPrefix; | |
| 384 List<_StackTraceNode> subtraces; | |
| 385 | |
| 386 _StackTraceNode(this.commonPrefix, this.count, this.subtraces); | |
| 387 | |
| 388 _StackTraceNode.root() : this([], 0, []); | |
| 389 | |
| 390 _StackTraceNode.leaf(StackTraceLines stackTrace) | |
| 391 : this(stackTrace.lines, 1, const []); | |
| 392 | |
| 393 _StackTraceNode.node(List<StackTraceLine> commonPrefix, | |
| 394 _StackTraceNode first, | |
| 395 _StackTraceNode second) | |
| 396 : this(commonPrefix, first.count + second.count, [first, second]); | |
| 397 | |
| 398 void add(StackTraceLines stackTrace) { | |
| 399 count++; | |
| 400 if (!stackTrace.lines.isEmpty) { | |
| 401 addSubtrace(stackTrace); | |
| 402 } | |
| 403 } | |
| 404 | |
| 405 void addSubtrace(StackTraceLines stackTrace) { | |
| 406 List<StackTraceLine> lines = stackTrace.lines; | |
| 407 for (_StackTraceNode subtrace in subtraces) { | |
| 408 int commonPrefixLength = | |
| 409 longestCommonPrefixLength(subtrace.commonPrefix, lines); | |
| 410 if (commonPrefixLength > 0) { | |
| 411 stackTrace = stackTrace.subtrace(commonPrefixLength); | |
| 412 if (commonPrefixLength == subtrace.commonPrefix.length) { | |
| 413 subtrace.add(stackTrace); | |
| 414 } else { | |
| 415 subtrace.commonPrefix = | |
| 416 subtrace.commonPrefix.sublist(commonPrefixLength); | |
| 417 subtraces.remove(subtrace); | |
| 418 subtraces.add(new _StackTraceNode.node( | |
| 419 lines.sublist(0, commonPrefixLength), | |
| 420 subtrace, | |
| 421 new _StackTraceNode.leaf(stackTrace))); | |
| 422 } | |
| 423 return; | |
| 424 } | |
| 425 } | |
| 426 subtraces.add(new _StackTraceNode.leaf(stackTrace)); | |
| 427 } | |
| 428 | |
| 429 void dumpTraces(StatsPrinter printer) { | |
| 430 printer.open('trace', {'count': count, 'line': commonPrefix.first}); | |
| 431 if (commonPrefix.length > 1) { | |
| 432 for (StackTraceLine line in commonPrefix.skip(1)) { | |
| 433 printer.child('trace', {'line': line}); | |
| 434 } | |
| 435 } | |
| 436 dumpSubtraces(printer); | |
| 437 printer.close('trace'); | |
| 438 } | |
| 439 | |
| 440 void dumpSubtraces(StatsPrinter printer) { | |
| 441 if (!subtraces.isEmpty) { | |
| 442 subtraces.sort(); | |
| 443 for (_StackTraceNode step in subtraces) { | |
| 444 step.dumpTraces(printer); | |
| 445 } | |
| 446 } | |
| 447 } | |
| 448 | |
| 449 int compareTo(_StackTraceNode other) { | |
| 450 // Sorts in decreasing count order. | |
| 451 return other.count - count; | |
| 452 } | |
| 453 | |
| 454 void printOn(StringBuffer sb, String indentation) { | |
| 455 String countText = '$indentation$count '; | |
| 456 sb.write(countText); | |
| 457 sb.write('\n'); | |
| 458 indentation = ''.padLeft(countText.length, ' '); | |
| 459 if (commonPrefix != null) { | |
| 460 int index = 0; | |
| 461 for (String line in commonPrefix) { | |
| 462 sb.write(indentation); | |
| 463 if (index > 1) { | |
| 464 sb.write('...\n'); | |
| 465 break; | |
| 466 } | |
| 467 sb.write(line); | |
| 468 sb.write('\n'); | |
| 469 index++; | |
| 470 } | |
| 471 } | |
| 472 subtraces.sort(); | |
| 473 for (_StackTraceNode subtrace in subtraces) { | |
| 474 subtrace.printOn(sb, indentation); | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 String toString() { | |
| 479 StringBuffer sb = new StringBuffer(); | |
| 480 printOn(sb, ''); | |
| 481 return sb.toString(); | |
| 482 } | |
| 483 } | |
| 484 | |
| 485 class _StackTraceTree extends _StackTraceNode { | |
| 486 final id; | |
| 487 int totalCount = 0; | |
| 488 final int sampleFrequency; | |
| 489 | |
| 490 _StackTraceTree(this.id, this.sampleFrequency) : super.root(); | |
| 491 | |
| 492 void dumpTraces(StatsPrinter printer) { | |
| 493 printer.open('trace', { | |
| 494 'id': id, | |
| 495 'totalCount': totalCount, | |
| 496 'sampleFrequency': sampleFrequency}); | |
| 497 dumpSubtraces(printer); | |
| 498 printer.close('trace'); | |
| 499 } | |
| 500 | |
| 501 void sample() { | |
| 502 if (totalCount++ % sampleFrequency == 0) { | |
| 503 add(stackTrace(offset: 3)); | |
| 504 } | |
| 505 } | |
| 506 } | |
| 507 | |
| 508 /// Actual implementation of [Stats]. | |
| 509 class ActiveStats implements Stats { | |
| 510 final StatsPrinter printer; | |
| 511 Map<dynamic, Map> maps = {}; | |
| 512 Map<dynamic, Map<dynamic, List>> frequencyMaps = {}; | |
| 513 Map<dynamic, Map> setsMap = {}; | |
| 514 Map<dynamic, Map<dynamic, List>> countersMap = | |
| 515 <dynamic, Map<dynamic, List>>{}; | |
| 516 Map<dynamic, _StackTraceTree> traceMap = {}; | |
| 517 int stackTraceSampleFrequency = 1; | |
| 518 | |
| 519 ActiveStats(StatsPrinter this.printer); | |
| 520 | |
| 521 void recordMap(id, key, value, {fromExisting(value)}) { | |
| 522 Map map = maps.putIfAbsent(id, () => {}); | |
| 523 if (fromExisting != null && map.containsKey(key)) { | |
| 524 map[key] = fromExisting(map[key]); | |
| 525 } else { | |
| 526 map[key] = value; | |
| 527 } | |
| 528 } | |
| 529 | |
| 530 Map getMap(key) { | |
| 531 return maps[key]; | |
| 532 } | |
| 533 | |
| 534 void recordFrequency(id, value, [example]) { | |
| 535 Map<int, List> map = frequencyMaps.putIfAbsent(id, () => {}); | |
| 536 map.putIfAbsent(value, () => []); | |
| 537 map[value].add(example); | |
| 538 } | |
| 539 | |
| 540 void recordFrequencies(id, Map<dynamic, Iterable> frequencyMap) { | |
| 541 Map<int, List> map = frequencyMaps.putIfAbsent(id, () => {}); | |
| 542 frequencyMap.forEach((value, examples) { | |
| 543 map.putIfAbsent(value, () => []); | |
| 544 map[value].addAll(examples); | |
| 545 }); | |
| 546 } | |
| 547 | |
| 548 Iterable recordedFrequencies(id, value) { | |
| 549 Map<dynamic, List> map = frequencyMaps[id]; | |
| 550 if (map == null) return const []; | |
| 551 List list = map[value]; | |
| 552 if (list == null) return const []; | |
| 553 return list; | |
| 554 } | |
| 555 | |
| 556 void recordCounter(id, [reason, example]) { | |
| 557 Map<dynamic, List> map = countersMap.putIfAbsent(id, () => {}); | |
| 558 map.putIfAbsent(reason, () => []).add(example); | |
| 559 } | |
| 560 | |
| 561 void recordElement(key, element, {data}) { | |
| 562 setsMap.putIfAbsent(key, () => new Map())[element] = data; | |
| 563 } | |
| 564 | |
| 565 void recordTrace(key, {int sampleFrequency}) { | |
| 566 if (sampleFrequency == null) { | |
| 567 sampleFrequency = stackTraceSampleFrequency; | |
| 568 } | |
| 569 traceMap.putIfAbsent(key, | |
| 570 () => new _StackTraceTree(key, sampleFrequency)).sample(); | |
| 571 | |
| 572 } | |
| 573 | |
| 574 Iterable getList(String key) { | |
| 575 Map map = setsMap[key]; | |
| 576 if (map == null) return const []; | |
| 577 return map.keys; | |
| 578 } | |
| 579 | |
| 580 void dumpStats({void beforeClose()}) { | |
| 581 printer.start('stats'); | |
| 582 dumpFrequencies(); | |
| 583 dumpSets(); | |
| 584 dumpCounters(); | |
| 585 dumpTraces(); | |
| 586 if (beforeClose != null) { | |
| 587 beforeClose(); | |
| 588 } | |
| 589 printer.end('stats'); | |
| 590 } | |
| 591 | |
| 592 void dumpSets() { | |
| 593 printer.group('sets', () { | |
| 594 setsMap.forEach((k, set) { | |
| 595 dumpIterable('examples', '$k', set.keys, | |
| 596 limit: printer.examples, dataMap: set); | |
| 597 }); | |
| 598 }); | |
| 599 | |
| 600 } | |
| 601 | |
| 602 void dumpFrequencies() { | |
| 603 printer.group('frequencies', () { | |
| 604 frequencyMaps.forEach((key, Map<dynamic, List> map) { | |
| 605 printer.child('frequency', {'title': '$key'}, () { | |
| 606 dumpFrequency(map); | |
| 607 }); | |
| 608 }); | |
| 609 }); | |
| 610 } | |
| 611 | |
| 612 void dumpFrequency(Map<dynamic, Iterable> map) { | |
| 613 Map sortedMap = trySortMap(map); | |
| 614 sortedMap.forEach((k, list) { | |
| 615 dumpIterable('examples', '$k', list, limit: printer.examples); | |
| 616 }); | |
| 617 } | |
| 618 | |
| 619 void dumpCounters() { | |
| 620 printer.group('counters', () { | |
| 621 countersMap.keys.forEach(dumpCounter); | |
| 622 }); | |
| 623 } | |
| 624 | |
| 625 void dumpCounter(id) { | |
| 626 Map<dynamic, List> map = countersMap[id]; | |
| 627 bool hasData(example) { | |
| 628 if (map == null) return false; | |
| 629 List list = map[example]; | |
| 630 if (list == null) return false; | |
| 631 return list.any((data) => data != null); | |
| 632 } | |
| 633 | |
| 634 int count = 0; | |
| 635 Map<dynamic, int> frequencyMap = {}; | |
| 636 map.forEach((var category, List examples) { | |
| 637 if (category != null) { | |
| 638 frequencyMap.putIfAbsent(category, () => 0); | |
| 639 frequencyMap[category] += examples.length; | |
| 640 } | |
| 641 count += examples.length; | |
| 642 }); | |
| 643 Map<int, Set> result = sortMap(inverseMap(frequencyMap), (a, b) => b - a); | |
| 644 int examplesLimit = null; | |
| 645 if (printer.examples != null && result.length >= printer.examples) { | |
| 646 examplesLimit = 0; | |
| 647 } | |
| 648 int counter = 0; | |
| 649 bool hasMore = false; | |
| 650 printer.open('counter', {'title': '$id', 'count': count}); | |
| 651 result.forEach((int count, Set examples) { | |
| 652 if (counter == printer.examples) { | |
| 653 printer.beginExtra(); | |
| 654 hasMore = true; | |
| 655 } | |
| 656 if (examples.length == 1 && | |
| 657 (examplesLimit == 0 || !hasData(examples.first))) { | |
| 658 printer.child('examples', {'count': count, 'example': examples.first}); | |
| 659 } else { | |
| 660 printer.child('examples', | |
| 661 {'count': count, 'examples': examples.length}, | |
| 662 () { | |
| 663 examples.forEach((example) { | |
| 664 dumpIterable( | |
| 665 'examples', '$example', map[example], | |
| 666 limit: examplesLimit, | |
| 667 includeCount: false); | |
| 668 }); | |
| 669 }); | |
| 670 } | |
| 671 counter++; | |
| 672 }); | |
| 673 if (hasMore) { | |
| 674 printer.endExtra(); | |
| 675 } | |
| 676 printer.close('counter'); | |
| 677 } | |
| 678 | |
| 679 void dumpTraces() { | |
| 680 printer.group('traces', () { | |
| 681 traceMap.keys.forEach(dumpTrace); | |
| 682 }); | |
| 683 } | |
| 684 | |
| 685 void dumpTrace(key) { | |
| 686 _StackTraceTree tree = traceMap[key]; | |
| 687 tree.dumpTraces(printer); | |
| 688 } | |
| 689 | |
| 690 void dumpCorrelation(keyA, Iterable a, keyB, Iterable b, | |
| 691 {Map dataA, Map dataB}) { | |
| 692 printer.child('correlations', {'title': '$keyA vs $keyB'}, () { | |
| 693 List aAndB = a.where((e) => e != null && b.contains(e)).toList(); | |
| 694 List aAndNotB = a.where((e) => e != null && !b.contains(e)).toList(); | |
| 695 List notAandB = b.where((e) => e != null && !a.contains(e)).toList(); | |
| 696 dumpIterable('correlation', '$keyA && $keyB', aAndB, dataMap: dataA, | |
| 697 limit: printer.examples); | |
| 698 dumpIterable('correlation', '$keyA && !$keyB', aAndNotB, dataMap: dataA, | |
| 699 limit: printer.examples); | |
| 700 dumpIterable('correlation', '!$keyA && $keyB', notAandB, dataMap: dataB, | |
| 701 limit: printer.examples); | |
| 702 }); | |
| 703 } | |
| 704 | |
| 705 void dumpIterable(String tag, String title, Iterable iterable, | |
| 706 {int limit, Map dataMap, bool includeCount: true}) { | |
| 707 if (limit == 0) return; | |
| 708 | |
| 709 Map childData = {}; | |
| 710 Iterable nonNullIterable = iterable.where((e) => e != null); | |
| 711 if (nonNullIterable.isEmpty && !includeCount) { | |
| 712 childData['name'] = title; | |
| 713 } else { | |
| 714 childData['title'] = title; | |
| 715 } | |
| 716 if (includeCount) { | |
| 717 childData['count'] = iterable.length; | |
| 718 } | |
| 719 printer.child(tag, childData, () { | |
| 720 bool hasMore = false; | |
| 721 int counter = 0; | |
| 722 nonNullIterable.forEach((element) { | |
| 723 if (counter == limit) { | |
| 724 printer.beginExtra(); | |
| 725 hasMore = true; | |
| 726 } | |
| 727 var data = dataMap != null ? dataMap[element] : null; | |
| 728 if (data != null) { | |
| 729 printer.child('example', {'value': element, 'data': data}); | |
| 730 } else { | |
| 731 printer.child('example', {'value': element}); | |
| 732 } | |
| 733 counter++; | |
| 734 }); | |
| 735 if (hasMore) { | |
| 736 printer.endExtra(); | |
| 737 } | |
| 738 }); | |
| 739 } | |
| 740 } | |
| 741 | |
| 742 /// Returns a map that is an inversion of [map], where the keys are the values | |
| 743 /// of [map] and the values are the set of keys in [map] that share values. | |
| 744 /// | |
| 745 /// If [equals] and [hashCode] are provided, these are used to determine | |
| 746 /// equality among the values of [map]. | |
| 747 /// | |
| 748 /// If [isValidKey] is provided, this is used to determine with a value of [map] | |
| 749 /// is a potential key of the inversion map. | |
| 750 Map<dynamic, Set> inverseMap(Map map, | |
| 751 {bool equals(key1, key2), | |
| 752 int hashCode(key), | |
| 753 bool isValidKey(potentialKey)}) { | |
| 754 Map<dynamic, Set> result = new LinkedHashMap<dynamic, Set>( | |
| 755 equals: equals, hashCode: hashCode, isValidKey: isValidKey); | |
| 756 map.forEach((k, v) { | |
| 757 if (isValidKey == null || isValidKey(v)) { | |
| 758 result.putIfAbsent(v, () => new Set()).add(k); | |
| 759 } | |
| 760 }); | |
| 761 return result; | |
| 762 } | |
| 763 | |
| 764 /// Return a new map heuristically sorted by the keys of [map]. If the first | |
| 765 /// key of [map] is [Comparable], the keys are sorted using [sortMap] under | |
| 766 /// the assumption that all keys are [Comparable]. | |
| 767 /// Otherwise, the keys are sorted as string using their `toString` | |
| 768 /// representation. | |
| 769 Map trySortMap(Map map) { | |
| 770 Iterable iterable = map.keys.where((k) => k != null); | |
| 771 if (iterable.isEmpty) return map; | |
| 772 var key = iterable.first; | |
| 773 if (key is Comparable) { | |
| 774 return sortMap(map); | |
| 775 } | |
| 776 return sortMap(map, (a, b) => '$a'.compareTo('$b')); | |
| 777 } | |
| 778 | |
| 779 /// Returns a new map in which the keys of [map] are sorted using [compare]. | |
| 780 /// If [compare] is null, the keys must be [Comparable]. | |
| 781 Map sortMap(Map map, [int compare(a,b)]) { | |
| 782 List keys = map.keys.toList(); | |
| 783 keys.sort(compare); | |
| 784 Map sortedMap = new Map(); | |
| 785 keys.forEach((k) => sortedMap[k] = map[k]); | |
| 786 return sortedMap; | |
| 787 } | |
| 788 | |
| OLD | NEW |