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 |