| // 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. | 
 | // | 
 | // NOTE: See also wrapper script sdk/runtime/tools/bmu_benchmark_gallery.sh | 
 | // | 
 | // 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 { | 
 |   int begin; | 
 |   int end; | 
 |   Interval(this.begin, this.end); | 
 |   int get length => max(0, end - begin); | 
 |   Interval overlap(Interval 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 pause) { | 
 |     var last = _pauses.isEmpty ? new Interval(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 iv) { | 
 |     if (_run.begin > iv.begin || iv.end > _run.end || iv.length == 0) { | 
 |       return 1.0; | 
 |     } | 
 |     int paused = 0; | 
 |     for (Interval p in _pauses) { | 
 |       paused += p.overlap(iv).length; | 
 |     } | 
 |     return 1.0 - (paused / iv.length); | 
 |   } | 
 |  | 
 |   final Interval _run = new Interval(0, 0); | 
 |   final List<Interval> _pauses = []; | 
 | } | 
 |  | 
 | // Returns a GC pause as an interval in microseconds since program start, or | 
 | // the interval [0, 0) on parse error. | 
 | Interval parseVerboseGCLine(String line) { | 
 |   var fields = line.split(','); | 
 |   // Update this (and indices below, if needed) when logging format changes. | 
 |   if (fields.length < 10) { | 
 |     // Ignore the lines that just specify column names, separated by '|'. | 
 |     // We assume these have very few commas in them, so that fields.length | 
 |     // is < 10. | 
 |     assert(line.contains("|")); | 
 |     return new Interval(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(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'); | 
 |   } | 
 | } |