Index: runtime/tools/verbose_gc_to_bmu.dart |
=================================================================== |
--- runtime/tools/verbose_gc_to_bmu.dart (revision 0) |
+++ runtime/tools/verbose_gc_to_bmu.dart (working copy) |
@@ -0,0 +1,105 @@ |
+// 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. |
+ |
+// Tool to compute bounded mutator utilization (BMU) from a --verbose_gc log. |
+// Outputs CSV suitable for, e.g., gnuplot: |
+// |
+// dart --verbose_gc foo.dart 2> foo.gclog |
+// dart verbose_gc_to_bmu.dart < foo.gclog > foo.bmu |
+// gnuplot -p -e "set yr [0:1]; set logscale x; plot 'foo.bmu' with linespoints" |
+ |
+import 'dart:io'; |
+import 'dart:math'; |
+ |
+const WINDOW_STEP_FACTOR = 0.9; |
+const MINIMUM_WINDOW_SIZE_MS = 1; |
+ |
+class Interval<T> { |
+ T begin; |
+ T end; |
+ Interval(this.begin, this.end); |
+ T get length => max(0, end - begin); |
+ Interval<T> overlap(Interval<T> other) => |
+ new Interval(max(this.begin, other.begin), min(this.end, other.end)); |
+} |
+ |
+class Timeline { |
+ // Pauses must be added in non-decreasing order of 'begin'. |
+ void addPause(Interval<int> pause) { |
+ var last = _pauses.isEmpty ? new Interval<int>(0, 0) : _pauses.last; |
+ assert(last.begin <= pause.begin); |
+ // Trim any initial overlap. |
+ _pauses.add(new Interval(max(pause.begin, last.end), pause.end)); |
+ // TODO(koda): Make VM log actual end time, rather than just last GC end. |
+ _run.end = max(_run.end, pause.end); |
+ } |
+ |
+ int get maxWindowSize => _run.length; |
+ |
+ // The windowSize must be no larger than the entire run. |
+ double minUtilization(int windowSize) { |
+ assert(windowSize <= _run.length); |
+ // The minimum utilization can always be found in a window that has one of |
+ // its endpoints at the beginning or end of a pause or the entire timeline. |
+ List<int> interesting = [_run.begin, _run.end]; |
+ for (Interval p in _pauses) { |
+ interesting.add(p.begin); |
+ interesting.add(p.end); |
+ } |
+ double result = 1.0; |
+ for (int i in interesting) { |
+ result = min(result, _utilization(new Interval(i, i + windowSize))); |
+ result = min(result, _utilization(new Interval(i - windowSize, i))); |
+ } |
+ return result; |
+ } |
+ |
+ // Returns the fraction of non-pause time, or 1.0 for an invalid interval. |
+ double _utilization(Interval<int> iv) { |
+ if (_run.begin > iv.begin || iv.end > _run.end || iv.length == 0) { |
+ return 1.0; |
+ } |
+ int paused = 0; |
+ for (Interval<int> p in _pauses) { |
+ paused += p.overlap(iv).length; |
+ } |
+ return 1.0 - (paused / iv.length); |
+ } |
+ |
+ final Interval<int> _run = new Interval<int>(0, 0); |
+ final List<Interval<int>> _pauses = []; |
+} |
+ |
+// Returns a GC pause as an interval in microseconds since program start, or |
+// the interval [0, 0) on parse error. |
+Interval<int> parseVerboseGCLine(String line) { |
+ var fields = line.split(','); |
+ // Update this (and indices below, if needed) when logging format changes. |
+ if (fields.length != 25) { |
+ assert(line.startsWith('[ GC | space | count | start | gc time') || |
+ line.startsWith('[ (isolate)| (reason)| | (s) | (ms) ')); |
+ return new Interval<int>(0, 0); |
+ } |
+ var begin = (1e6 * double.parse(fields[2])).floor(); |
+ var duration = (1000 * double.parse(fields[3])).floor(); |
+ var end = begin + duration; |
+ return new Interval<int>(begin, end); |
+} |
+ |
+void main() { |
+ Timeline t = new Timeline(); |
+ for (String line = stdin.readLineSync(); |
+ line != null; |
+ line = stdin.readLineSync()) { |
+ t.addPause(parseVerboseGCLine(line)); |
+ } |
+ print('# window_size_ms, bounded_mutator_utilization'); |
+ var minimumSeen = 1.0; |
+ for (int w = t._run.length; |
+ w > 1000 * MINIMUM_WINDOW_SIZE_MS; |
+ w = (w * WINDOW_STEP_FACTOR).floor()) { |
+ minimumSeen = min(minimumSeen, t.minUtilization(w)); |
+ print('${w / 1000}, $minimumSeen'); |
+ } |
+} |