// 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');
  }
}
