Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(30)

Side by Side Diff: sdk/lib/_internal/compiler/implementation/helpers/stats.dart

Issue 694353007: Move dart2js from sdk/lib/_internal/compiler to pkg/compiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698