|  | // Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | import 'dart:async' show EventSink; | 
|  | import 'dart:collection'; | 
|  | import 'dart:convert'; | 
|  |  | 
|  | import 'package:kernel/text/indentation.dart' show Indentation; | 
|  |  | 
|  | import '../../compiler.dart'; | 
|  | import '../common.dart'; | 
|  | import '../compiler.dart' show Compiler; | 
|  | import '../util/util.dart'; | 
|  |  | 
|  | // Helper methods for statistics. | 
|  |  | 
|  | /// Current stats collector. Use [enableStatsOutput] to enable recording of | 
|  | /// stats. | 
|  | Stats get stats { | 
|  | enableDebugMode(); | 
|  | if (_stats == null) { | 
|  | _stats = const Stats(); | 
|  | } | 
|  | return _stats; | 
|  | } | 
|  |  | 
|  | Stats _stats; | 
|  |  | 
|  | /// Enable recording of stats. Use [Stats.dumpStats] to output the record stats. | 
|  | /// | 
|  | /// Pass the [outputProvider] of [Compiler] to generate stats into a separate | 
|  | /// file using [name] and [extension] for the filename. If omitted, stats are | 
|  | /// printed on standard out. | 
|  | /// | 
|  | /// If [xml] is `true`, stats output is formatted as XML with a default | 
|  | /// extension of 'xml', otherwise the output is indented text with a default | 
|  | /// extension of 'log'. | 
|  | void enableStatsOutput( | 
|  | {CompilerOutputProvider outputProvider, | 
|  | bool xml = true, | 
|  | String name = 'stats', | 
|  | String extension, | 
|  | int examples = 10}) { | 
|  | if (_stats != null) { | 
|  | throw StateError('Stats have already been initialized.'); | 
|  | } | 
|  | enableDebugMode(); | 
|  |  | 
|  | StatsOutput output; | 
|  | if (outputProvider != null) { | 
|  | if (extension == null) { | 
|  | extension = xml ? 'xml' : 'log'; | 
|  | } | 
|  | output = SinkOutput(outputProvider(name, extension)); | 
|  | } else { | 
|  | output = const DebugOutput(); | 
|  | } | 
|  | StatsPrinter printer; | 
|  | if (xml) { | 
|  | printer = XMLPrinter(output: output, examples: examples); | 
|  | } else { | 
|  | printer = ConsolePrinter(output: output, examples: examples); | 
|  | } | 
|  | _stats = ActiveStats(printer); | 
|  | } | 
|  |  | 
|  | /// Interface for gathering and display of statistical information. | 
|  | /// This class serves as the noop collector. | 
|  | class Stats { | 
|  | const Stats(); | 
|  |  | 
|  | /// Registers [key], [value] pair in the map [id]. If [fromExisting] is | 
|  | /// non-null and [key] already exists, the value associated with [key] will | 
|  | /// be the return value of [fromExisting] when called with the existing value. | 
|  | /// | 
|  | /// The recorded information is not dumped automatically. | 
|  | void recordMap(id, key, value, {fromExisting(value)}) {} | 
|  |  | 
|  | /// Returns the map [id] recorded with [recordMap]. | 
|  | Map getMap(id) => const {}; | 
|  |  | 
|  | /// Registers [element] as an element of the list [id]. If provided, [data] | 
|  | /// provides additional data for [element]. | 
|  | /// | 
|  | /// The recorded information is dumped automatically on call to [dumpStats]. | 
|  | /// | 
|  | /// Example: | 
|  | ///   Calling [recordElement] like this: | 
|  | ///     recordElement('foo', 'a', data: 'first-a-data'); | 
|  | ///     recordElement('foo', 'a', data: 'second-a-data'); | 
|  | ///     recordElement('foo', 'b'); | 
|  | ///     recordElement('bar', 'a', data: 'third-a-data'); | 
|  | ///     recordElement('bar', 'c'); | 
|  | ///   will result in a dump like this: | 
|  | ///     foo: 2 | 
|  | ///      value=a data=second-a-data | 
|  | ///      b | 
|  | ///     bar: 2 | 
|  | ///      value=a data=third-a-data | 
|  | ///      c | 
|  | /// | 
|  | void recordElement(id, element, {data}) {} | 
|  |  | 
|  | /// Returns the list [id] recorded with [recordElement]. | 
|  | Iterable getList(String id) => const []; | 
|  |  | 
|  | /// Registers [value] as an occurrence of [id]. If passed, [example] provides | 
|  | /// an example data of the occurrence of [value]. | 
|  | /// | 
|  | /// The recorded information is dumped automatically on call to [dumpStats]. | 
|  | /// | 
|  | /// Example: | 
|  | ///   Calling [recordFrequency] like this: | 
|  | ///     recordFrequency('foo', 'a', 'first-a-data'); | 
|  | ///     recordFrequency('foo', 'a', 'second-a-data'); | 
|  | ///     recordFrequency('bar', 'b', 'first-b-data'); | 
|  | ///     recordFrequency('foo', 'c'); | 
|  | ///     recordFrequency('bar', 'b'); | 
|  | ///   will result in a dump like this: | 
|  | ///     foo: | 
|  | ///      a: 2 | 
|  | ///       first-a-data | 
|  | ///       second-a-data | 
|  | ///      c: 1 | 
|  | ///     bar: | 
|  | ///      b: 2 | 
|  | ///       first-b-data | 
|  | /// | 
|  | void recordFrequency(id, value, [example]) {} | 
|  |  | 
|  | /// For each key/value pair in [map] the elements in the value are registered | 
|  | /// as examples of occurrences of the key in [id]. | 
|  | void recordFrequencies(id, Map<dynamic, Iterable> map) {} | 
|  |  | 
|  | /// Returns the examples given for the occurrence of [value] for [id]. | 
|  | Iterable recordedFrequencies(id, value) => const []; | 
|  |  | 
|  | /// Increases the counter [id] by 1. If provided, [example] is used as an | 
|  | /// example of the count and [data] provides additional information for | 
|  | /// [example]. | 
|  | /// | 
|  | /// The recorded information is dumped automatically on call to [dumpStats]. | 
|  | /// | 
|  | /// Example: | 
|  | ///   Calling [recordCounter] like this: | 
|  | ///     recordCounter('foo', 'a'); | 
|  | ///     recordCounter('foo', 'a'); | 
|  | ///     recordCounter('foo', 'b'); | 
|  | ///     recordCounter('bar', 'c', 'first-c-data'); | 
|  | ///     recordCounter('bar', 'c', 'second-c-data'); | 
|  | ///     recordCounter('bar', 'd'); | 
|  | ///     recordCounter('bar', 'd'); | 
|  | ///     recordCounter('baz'); | 
|  | ///     recordCounter('baz'); | 
|  | ///   will result in a dump like this: | 
|  | ///     foo: 3 | 
|  | ///      count=2 example=a | 
|  | ///      count=1 example=b | 
|  | ///     bar: 4 | 
|  | ///      count=2 examples=2 | 
|  | ///       c: | 
|  | ///        first-c-data | 
|  | ///        second-c-data | 
|  | ///       d | 
|  | ///     baz: 2 | 
|  | /// | 
|  | void recordCounter(id, [example, data]) {} | 
|  |  | 
|  | /// Records the current stack trace under the key [id]. Only every | 
|  | /// [sampleFrequency] call with the same id is recorded, and if omitted | 
|  | /// [stackTraceSampleFrequency] is used. | 
|  | void recordTrace(id, {int sampleFrequency}) {} | 
|  |  | 
|  | /// The default sample frequency used for recording stack traces. | 
|  | int get stackTraceSampleFrequency => 0; | 
|  |  | 
|  | /// Set the default sample frequency used for recording stack traces. | 
|  | void set stackTraceSampleFrequency(int value) {} | 
|  |  | 
|  | /// Dumps the stats for the recorded frequencies, sets, and counters. If | 
|  | /// provided [beforeClose] is called before closing the dump output. This | 
|  | /// can be used to include correlations on the collected data through | 
|  | /// [dumpCorrelation]. | 
|  | void dumpStats({void beforeClose()}) {} | 
|  |  | 
|  | /// Prints the correlation between the elements of [a] and [b]. | 
|  | /// | 
|  | /// Three sets are output using [idA] and [idB] as labels for the elements | 
|  | /// [a] and [b]: | 
|  | /// | 
|  | ///   'idA && idB' lists the elements both in [a] and [b], | 
|  | ///   '!idA && idB' lists the elements not in [a] but in [b], and | 
|  | ///   'idA && !idB' lists the elements in [a] but not in [b]. | 
|  | /// | 
|  | /// If [dataA] and/or [dataB] are provided, additional information on the | 
|  | /// elements are looked up in [dataA] or [dataB] using [dataA] as the primary | 
|  | /// source. | 
|  | void dumpCorrelation(idA, Iterable a, idB, Iterable b, | 
|  | {Map dataA, Map dataB}) {} | 
|  | } | 
|  |  | 
|  | /// Interface for printing output data. | 
|  | /// | 
|  | /// This class serves as the disabled output. | 
|  | class StatsOutput { | 
|  | const StatsOutput(); | 
|  |  | 
|  | /// Print [text] as on a separate line. | 
|  | void println(String text) {} | 
|  | } | 
|  |  | 
|  | /// Output to the [debugPrint] method. | 
|  | class DebugOutput implements StatsOutput { | 
|  | const DebugOutput(); | 
|  |  | 
|  | @override | 
|  | void println(String text) => debugPrint(text); | 
|  | } | 
|  |  | 
|  | /// Output to an [EventSink]. Used to output to a file through the | 
|  | /// [CompilerOutputProvider]. | 
|  | class SinkOutput implements StatsOutput { | 
|  | EventSink<String> sink; | 
|  |  | 
|  | SinkOutput(this.sink); | 
|  |  | 
|  | @override | 
|  | void println(String text) { | 
|  | sink.add(text); | 
|  | sink.add('\n'); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Interface for printing stats collected in [Stats]. | 
|  | abstract class StatsPrinter { | 
|  | /// The number of examples printer. If `null` all examples are printed. | 
|  | int get examples => 0; | 
|  |  | 
|  | /// Start a group [id]. | 
|  | void start(String id) {} | 
|  |  | 
|  | /// Create a group [id] with content created by [createGroupContent]. | 
|  | void group(String id, void createGroupContent()) { | 
|  | start(id); | 
|  | createGroupContent(); | 
|  | end(id); | 
|  | } | 
|  |  | 
|  | /// End a group [id]. | 
|  | void end(String id) {} | 
|  |  | 
|  | /// Start a stat entry for [id] with additional [data]. | 
|  | void open(String id, [Map<String, dynamic> data = const {}]) {} | 
|  |  | 
|  | /// Create a stat entry for [id] with additional [data] and content created by | 
|  | /// [createChildContent]. | 
|  | void child(String id, | 
|  | [Map<String, dynamic> data = const {}, void createChildContent()]) { | 
|  | open(id, data); | 
|  | if (createChildContent != null) createChildContent(); | 
|  | close(id); | 
|  | } | 
|  |  | 
|  | /// End a stat entry for [id]. | 
|  | void close(String id) {} | 
|  |  | 
|  | /// Starts a group of additional information. | 
|  | void beginExtra() {} | 
|  |  | 
|  | /// Starts a group of additional information. | 
|  | void endExtra() {} | 
|  | } | 
|  |  | 
|  | /// Abstract base class for [ConsolePrinter] and [XMLPrinter]. | 
|  | abstract class BasePrinter extends StatsPrinter with Indentation { | 
|  | @override | 
|  | final int examples; | 
|  | final StatsOutput output; | 
|  |  | 
|  | BasePrinter({this.output = const DebugOutput(), this.examples = 10}) { | 
|  | indentationUnit = " "; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// [StatsPrinter] that displays stats in console lines. | 
|  | class ConsolePrinter extends BasePrinter { | 
|  | int extraLevel = 0; | 
|  |  | 
|  | ConsolePrinter({StatsOutput output = const DebugOutput(), int examples = 10}) | 
|  | : super(output: output, examples: examples); | 
|  |  | 
|  | @override | 
|  | void open(String id, [Map<String, dynamic> data = const {}]) { | 
|  | if (extraLevel > 0) return; | 
|  |  | 
|  | StringBuffer sb = StringBuffer(); | 
|  | sb.write(indentation); | 
|  | String space = ''; | 
|  | if (data['title'] != null) { | 
|  | sb.write('${data['title']}:'); | 
|  | space = ' '; | 
|  | data.remove('title'); | 
|  | } else if (data['name'] != null) { | 
|  | sb.write('${data['name']}'); | 
|  | space = ' '; | 
|  | data.remove('name'); | 
|  | } | 
|  | Iterable nonNullValues = data.values.where((v) => v != null); | 
|  | if (nonNullValues.length == 1) { | 
|  | sb.write('$space${nonNullValues.first}'); | 
|  | } else { | 
|  | data.forEach((key, value) { | 
|  | sb.write('$space$key=$value'); | 
|  | space = ' '; | 
|  | }); | 
|  | } | 
|  | output.println(sb.toString()); | 
|  | indentMore(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void close(String id) { | 
|  | if (extraLevel > 0) return; | 
|  |  | 
|  | indentLess(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void beginExtra() { | 
|  | if (extraLevel == 0) output.println('$indentation...'); | 
|  | extraLevel++; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void endExtra() { | 
|  | extraLevel--; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// [StatsPrinter] that displays stats in XML format. | 
|  | class XMLPrinter extends BasePrinter { | 
|  | static const HtmlEscape escape = HtmlEscape(); | 
|  | bool opened = false; | 
|  |  | 
|  | XMLPrinter({output = const DebugOutput(), int examples = 10}) | 
|  | : super(output: output, examples: examples); | 
|  |  | 
|  | @override | 
|  | void start(String id) { | 
|  | if (!opened) { | 
|  | output.println('<?xml version="1.0" encoding="UTF-8"?>'); | 
|  | opened = true; | 
|  | } | 
|  | open(id); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void end(String id) { | 
|  | close(id); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void open(String id, [Map<String, dynamic> data = const {}]) { | 
|  | StringBuffer sb = StringBuffer(); | 
|  | sb.write(indentation); | 
|  | sb.write('<$id'); | 
|  | data.forEach((key, value) { | 
|  | if (value != null) { | 
|  | sb.write(' $key="${escape.convert('$value')}"'); | 
|  | } | 
|  | }); | 
|  | sb.write('>'); | 
|  | output.println(sb.toString()); | 
|  | indentMore(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void close(String id) { | 
|  | indentLess(); | 
|  | output.println('${indentation}</$id>'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void beginExtra() { | 
|  | open('extra'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void endExtra() { | 
|  | close('extra'); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// A node in a stack trace tree used to store and organize stack traces by | 
|  | /// common prefixes. | 
|  | class _StackTraceNode implements Comparable<_StackTraceNode> { | 
|  | int count; | 
|  | List<StackTraceLine> commonPrefix; | 
|  | List<_StackTraceNode> subtraces; | 
|  |  | 
|  | _StackTraceNode(this.commonPrefix, this.count, this.subtraces); | 
|  |  | 
|  | _StackTraceNode.root() : this([], 0, []); | 
|  |  | 
|  | _StackTraceNode.leaf(StackTraceLines stackTrace) | 
|  | : this(stackTrace.lines, 1, const []); | 
|  |  | 
|  | _StackTraceNode.node(List<StackTraceLine> commonPrefix, _StackTraceNode first, | 
|  | _StackTraceNode second) | 
|  | : this(commonPrefix, first.count + second.count, [first, second]); | 
|  |  | 
|  | void add(StackTraceLines stackTrace) { | 
|  | count++; | 
|  | if (!stackTrace.lines.isEmpty) { | 
|  | addSubtrace(stackTrace); | 
|  | } | 
|  | } | 
|  |  | 
|  | void addSubtrace(StackTraceLines stackTrace) { | 
|  | List<StackTraceLine> lines = stackTrace.lines; | 
|  | for (_StackTraceNode subtrace in subtraces) { | 
|  | int commonPrefixLength = | 
|  | longestCommonPrefixLength(subtrace.commonPrefix, lines); | 
|  | if (commonPrefixLength > 0) { | 
|  | stackTrace = stackTrace.subtrace(commonPrefixLength); | 
|  | if (commonPrefixLength == subtrace.commonPrefix.length) { | 
|  | subtrace.add(stackTrace); | 
|  | } else { | 
|  | subtrace.commonPrefix = | 
|  | subtrace.commonPrefix.sublist(commonPrefixLength); | 
|  | subtraces.remove(subtrace); | 
|  | subtraces.add(_StackTraceNode.node( | 
|  | lines.sublist(0, commonPrefixLength), | 
|  | subtrace, | 
|  | _StackTraceNode.leaf(stackTrace))); | 
|  | } | 
|  | return; | 
|  | } | 
|  | } | 
|  | subtraces.add(_StackTraceNode.leaf(stackTrace)); | 
|  | } | 
|  |  | 
|  | void dumpTraces(StatsPrinter printer) { | 
|  | printer.open('trace', {'count': count, 'line': commonPrefix.first}); | 
|  | if (commonPrefix.length > 1) { | 
|  | for (StackTraceLine line in commonPrefix.skip(1)) { | 
|  | printer.child('trace', {'line': line}); | 
|  | } | 
|  | } | 
|  | dumpSubtraces(printer); | 
|  | printer.close('trace'); | 
|  | } | 
|  |  | 
|  | void dumpSubtraces(StatsPrinter printer) { | 
|  | if (!subtraces.isEmpty) { | 
|  | subtraces.sort(); | 
|  | for (_StackTraceNode step in subtraces) { | 
|  | step.dumpTraces(printer); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | int compareTo(_StackTraceNode other) { | 
|  | // Sorts in decreasing count order. | 
|  | return other.count - count; | 
|  | } | 
|  |  | 
|  | void printOn(StringBuffer sb, String indentation) { | 
|  | String countText = '$indentation$count  '; | 
|  | sb.write(countText); | 
|  | sb.write('\n'); | 
|  | indentation = ''.padLeft(countText.length, ' '); | 
|  | if (commonPrefix != null) { | 
|  | int index = 0; | 
|  | for (StackTraceLine line in commonPrefix) { | 
|  | sb.write(indentation); | 
|  | if (index > 1) { | 
|  | sb.write('...\n'); | 
|  | break; | 
|  | } | 
|  | sb.write(line); | 
|  | sb.write('\n'); | 
|  | index++; | 
|  | } | 
|  | } | 
|  | subtraces.sort(); | 
|  | for (_StackTraceNode subtrace in subtraces) { | 
|  | subtrace.printOn(sb, indentation); | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | String toString() { | 
|  | StringBuffer sb = StringBuffer(); | 
|  | printOn(sb, ''); | 
|  | return sb.toString(); | 
|  | } | 
|  | } | 
|  |  | 
|  | class _StackTraceTree extends _StackTraceNode { | 
|  | final id; | 
|  | int totalCount = 0; | 
|  | final int sampleFrequency; | 
|  |  | 
|  | _StackTraceTree(this.id, this.sampleFrequency) : super.root(); | 
|  |  | 
|  | @override | 
|  | void dumpTraces(StatsPrinter printer) { | 
|  | printer.open('trace', { | 
|  | 'id': id, | 
|  | 'totalCount': totalCount, | 
|  | 'sampleFrequency': sampleFrequency | 
|  | }); | 
|  | dumpSubtraces(printer); | 
|  | printer.close('trace'); | 
|  | } | 
|  |  | 
|  | void sample() { | 
|  | if (totalCount++ % sampleFrequency == 0) { | 
|  | add(stackTrace(offset: 3)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Actual implementation of [Stats]. | 
|  | class ActiveStats implements Stats { | 
|  | final StatsPrinter printer; | 
|  | Map<dynamic, Map> maps = {}; | 
|  | Map<dynamic, Map<dynamic, List>> frequencyMaps = {}; | 
|  | Map<dynamic, Map> setsMap = {}; | 
|  | Map<dynamic, Map<dynamic, List>> countersMap = {}; | 
|  | Map<dynamic, _StackTraceTree> traceMap = {}; | 
|  | @override | 
|  | int stackTraceSampleFrequency = 1; | 
|  |  | 
|  | ActiveStats(StatsPrinter this.printer); | 
|  |  | 
|  | @override | 
|  | void recordMap(id, key, value, {fromExisting(value)}) { | 
|  | Map map = maps.putIfAbsent(id, () => {}); | 
|  | if (fromExisting != null && map.containsKey(key)) { | 
|  | map[key] = fromExisting(map[key]); | 
|  | } else { | 
|  | map[key] = value; | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | Map getMap(key) { | 
|  | return maps[key]; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void recordFrequency(id, value, [example]) { | 
|  | Map<dynamic, List> map = frequencyMaps.putIfAbsent(id, () => {}); | 
|  | map.putIfAbsent(value, () => []); | 
|  | map[value].add(example); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void recordFrequencies(id, Map<dynamic, Iterable> frequencyMap) { | 
|  | Map<dynamic, List> map = frequencyMaps.putIfAbsent(id, () => {}); | 
|  | frequencyMap.forEach((value, examples) { | 
|  | map.putIfAbsent(value, () => []); | 
|  | map[value].addAll(examples); | 
|  | }); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Iterable recordedFrequencies(id, value) { | 
|  | Map<dynamic, List> map = frequencyMaps[id]; | 
|  | if (map == null) return const []; | 
|  | List list = map[value]; | 
|  | if (list == null) return const []; | 
|  | return list; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void recordCounter(id, [reason, example]) { | 
|  | Map<dynamic, List> map = countersMap.putIfAbsent(id, () => {}); | 
|  | map.putIfAbsent(reason, () => []).add(example); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void recordElement(key, element, {data}) { | 
|  | setsMap.putIfAbsent(key, () => Map())[element] = data; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void recordTrace(key, {int sampleFrequency}) { | 
|  | if (sampleFrequency == null) { | 
|  | sampleFrequency = stackTraceSampleFrequency; | 
|  | } | 
|  | traceMap | 
|  | .putIfAbsent(key, () => _StackTraceTree(key, sampleFrequency)) | 
|  | .sample(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Iterable getList(String key) { | 
|  | Map map = setsMap[key]; | 
|  | if (map == null) return const []; | 
|  | return map.keys; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void dumpStats({void beforeClose()}) { | 
|  | printer.start('stats'); | 
|  | dumpFrequencies(); | 
|  | dumpSets(); | 
|  | dumpCounters(); | 
|  | dumpTraces(); | 
|  | if (beforeClose != null) { | 
|  | beforeClose(); | 
|  | } | 
|  | printer.end('stats'); | 
|  | } | 
|  |  | 
|  | void dumpSets() { | 
|  | printer.group('sets', () { | 
|  | setsMap.forEach((k, set) { | 
|  | dumpIterable('examples', '$k', set.keys, | 
|  | limit: printer.examples, dataMap: set); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void dumpFrequencies() { | 
|  | printer.group('frequencies', () { | 
|  | frequencyMaps.forEach((key, Map<dynamic, List> map) { | 
|  | printer.child('frequency', {'title': '$key'}, () { | 
|  | dumpFrequency(map); | 
|  | }); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void dumpFrequency(Map<dynamic, Iterable> map) { | 
|  | Map sortedMap = trySortMap(map); | 
|  | sortedMap.forEach((k, list) { | 
|  | dumpIterable('examples', '$k', list, limit: printer.examples); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void dumpCounters() { | 
|  | printer.group('counters', () { | 
|  | countersMap.keys.forEach(dumpCounter); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void dumpCounter(id) { | 
|  | Map<dynamic, List> map = countersMap[id]; | 
|  | bool hasData(example) { | 
|  | if (map == null) return false; | 
|  | List list = map[example]; | 
|  | if (list == null) return false; | 
|  | return list.any((data) => data != null); | 
|  | } | 
|  |  | 
|  | int count = 0; | 
|  | Map<dynamic, int> frequencyMap = {}; | 
|  | map.forEach((var category, List examples) { | 
|  | if (category != null) { | 
|  | frequencyMap.putIfAbsent(category, () => 0); | 
|  | frequencyMap[category] += examples.length; | 
|  | } | 
|  | count += examples.length; | 
|  | }); | 
|  | Map<int, Set> result = sortMap(inverseMap(frequencyMap), (a, b) => b - a); | 
|  | int examplesLimit = null; | 
|  | if (printer.examples != null && result.length >= printer.examples) { | 
|  | examplesLimit = 0; | 
|  | } | 
|  | int counter = 0; | 
|  | bool hasMore = false; | 
|  | printer.open('counter', {'title': '$id', 'count': count}); | 
|  | result.forEach((int count, Set examples) { | 
|  | if (counter == printer.examples) { | 
|  | printer.beginExtra(); | 
|  | hasMore = true; | 
|  | } | 
|  | if (examples.length == 1 && | 
|  | (examplesLimit == 0 || !hasData(examples.first))) { | 
|  | printer.child('examples', {'count': count, 'example': examples.first}); | 
|  | } else { | 
|  | printer.child('examples', {'count': count, 'examples': examples.length}, | 
|  | () { | 
|  | examples.forEach((example) { | 
|  | dumpIterable('examples', '$example', map[example], | 
|  | limit: examplesLimit, includeCount: false); | 
|  | }); | 
|  | }); | 
|  | } | 
|  | counter++; | 
|  | }); | 
|  | if (hasMore) { | 
|  | printer.endExtra(); | 
|  | } | 
|  | printer.close('counter'); | 
|  | } | 
|  |  | 
|  | void dumpTraces() { | 
|  | printer.group('traces', () { | 
|  | traceMap.keys.forEach(dumpTrace); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void dumpTrace(key) { | 
|  | _StackTraceTree tree = traceMap[key]; | 
|  | tree.dumpTraces(printer); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void dumpCorrelation(keyA, Iterable a, keyB, Iterable b, | 
|  | {Map dataA, Map dataB}) { | 
|  | printer.child('correlations', {'title': '$keyA vs $keyB'}, () { | 
|  | List aAndB = a.where((e) => e != null && b.contains(e)).toList(); | 
|  | List aAndNotB = a.where((e) => e != null && !b.contains(e)).toList(); | 
|  | List notAandB = b.where((e) => e != null && !a.contains(e)).toList(); | 
|  | dumpIterable('correlation', '$keyA && $keyB', aAndB, | 
|  | dataMap: dataA, limit: printer.examples); | 
|  | dumpIterable('correlation', '$keyA && !$keyB', aAndNotB, | 
|  | dataMap: dataA, limit: printer.examples); | 
|  | dumpIterable('correlation', '!$keyA && $keyB', notAandB, | 
|  | dataMap: dataB, limit: printer.examples); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void dumpIterable(String tag, String title, Iterable iterable, | 
|  | {int limit, Map dataMap, bool includeCount = true}) { | 
|  | if (limit == 0) return; | 
|  |  | 
|  | Map<String, dynamic> childData = {}; | 
|  | Iterable nonNullIterable = iterable.where((e) => e != null); | 
|  | if (nonNullIterable.isEmpty && !includeCount) { | 
|  | childData['name'] = title; | 
|  | } else { | 
|  | childData['title'] = title; | 
|  | } | 
|  | if (includeCount) { | 
|  | childData['count'] = iterable.length; | 
|  | } | 
|  | printer.child(tag, childData, () { | 
|  | bool hasMore = false; | 
|  | int counter = 0; | 
|  | nonNullIterable.forEach((element) { | 
|  | if (counter == limit) { | 
|  | printer.beginExtra(); | 
|  | hasMore = true; | 
|  | } | 
|  | var data = dataMap != null ? dataMap[element] : null; | 
|  | if (data != null) { | 
|  | printer.child('example', {'value': element, 'data': data}); | 
|  | } else { | 
|  | printer.child('example', {'value': element}); | 
|  | } | 
|  | counter++; | 
|  | }); | 
|  | if (hasMore) { | 
|  | printer.endExtra(); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns a map that is an inversion of [map], where the keys are the values | 
|  | /// of [map] and the values are the set of keys in [map] that share values. | 
|  | /// | 
|  | /// If [equals] and [hashCode] are provided, these are used to determine | 
|  | /// equality among the values of [map]. | 
|  | /// | 
|  | /// If [isValidKey] is provided, this is used to determine with a value of [map] | 
|  | /// is a potential key of the inversion map. | 
|  | Map<V, Set<K>> inverseMap<K, V>(Map<K, V> map, | 
|  | {bool equals(V key1, V key2), | 
|  | int hashCode(V key), | 
|  | bool isValidKey(V potentialKey)}) { | 
|  | Map<V, Set<K>> result = LinkedHashMap<V, Set<K>>( | 
|  | equals: equals, hashCode: hashCode, isValidKey: isValidKey); | 
|  | map.forEach((k, v) { | 
|  | if (isValidKey == null || isValidKey(v)) { | 
|  | result.putIfAbsent(v, () => Set()).add(k); | 
|  | } | 
|  | }); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Return a new map heuristically sorted by the keys of [map]. If the first | 
|  | /// key of [map] is [Comparable], the keys are sorted using [sortMap] under | 
|  | /// the assumption that all keys are [Comparable]. | 
|  | /// Otherwise, the keys are sorted as string using their `toString` | 
|  | /// representation. | 
|  | Map<K, V> trySortMap<K, V>(Map<K, V> map) { | 
|  | Iterable<K> iterable = map.keys.where((K k) => k != null); | 
|  | if (iterable.isEmpty) return map; | 
|  | var key = iterable.first; | 
|  | if (key is Comparable<K>) { | 
|  | return sortMap(map); | 
|  | } | 
|  | return sortMap(map, (a, b) => '$a'.compareTo('$b')); | 
|  | } | 
|  |  | 
|  | /// Returns a new map in which the keys of [map] are sorted using [compare]. | 
|  | /// If [compare] is null, the keys must be [Comparable]. | 
|  | Map<K, V> sortMap<K, V>(Map<K, V> map, [int compare(K a, K b)]) { | 
|  | List<K> keys = map.keys.toList(); | 
|  | keys.sort(compare); | 
|  | Map<K, V> sortedMap = {}; | 
|  | keys.forEach((K k) => sortedMap[k] = map[k]); | 
|  | return sortedMap; | 
|  | } |