普通文本  |  137行  |  4.14 KB

#!/usr/bin/env python
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Script to plot the results of a bisect run."""

import argparse
import json
import math
import re
import urllib2

from matplotlib import cm  # pylint: disable=import-error
from matplotlib import pyplot  # pylint: disable=import-error
import numpy


_PLOT_WIDTH_INCHES = 8
_PLOT_HEIGHT_INCHES = 6
_PERCENTILES = (0, 0.05, 0.25, 0.5, 0.75, 0.95, 1)


def main():
  parser = argparse.ArgumentParser()
  parser.add_argument('bisect_url_or_debug_info_file',
                      help='The Buildbot URL of a bisect run, or a file '
                      'containing the output from the Debug Info step.')
  parser.add_argument('output', nargs='?', help='File path to save a PNG to.')
  args = parser.parse_args()

  url = (args.bisect_url_or_debug_info_file +
         '/steps/Debug%20Info/logs/Debug%20Info/text')
  try:
    f = urllib2.urlopen(url)
  except ValueError:  # Not a valid URL.
    f = open(args.bisect_url_or_debug_info_file, 'r')

  results = []
  for line in f.readlines():
    regex = (r'(?:(?:[a-z0-9-]+@)?[a-z0-9]+,)*'
             r'(?:[a-z0-9-]+@)?(?P<commit>[a-z0-9]+)\s*'
             r'(?P<values>\[(?:-?[0-9.]+, )*-?[0-9.]*\])')
    match = re.match(regex, line)
    if not match:
      continue

    commit = match.group('commit')
    values = json.loads(match.group('values'))
    if not values:
      continue

    print commit, values
    results.append((commit, values))

  _SavePlots(results, args.output)


def _SavePlots(results, file_path=None):
  """Saves histograms and empirial distribution plots showing the diff.

  Args:
    file_path: The location to save the plots go.
  """
  figsize = (_PLOT_WIDTH_INCHES * 2, _PLOT_HEIGHT_INCHES)
  _, (axis0, axis1) = pyplot.subplots(nrows=1, ncols=2, figsize=figsize)

  _DrawHistogram(axis0, results)
  _DrawEmpiricalCdf(axis1, results)

  if file_path:
    pyplot.savefig(file_path)
  pyplot.show()
  pyplot.close()


def _DrawHistogram(axis, results):
  values_per_commit = [values for _, values in results]

  # Calculate bounds and bins.
  combined_values = sum(values_per_commit, [])
  lower_bound = min(combined_values)
  upper_bound = max(combined_values)
  if lower_bound == upper_bound:
    lower_bound -= 0.5
    upper_bound += 0.5
  bins = numpy.linspace(lower_bound, upper_bound,
                        math.log(len(combined_values)) * 4)

  # Histograms.
  colors = cm.rainbow(numpy.linspace(  # pylint: disable=no-member
      1, 0, len(results) + 1))
  for (commit, values), color in zip(results, colors):
    axis.hist(values, bins, alpha=0.5, normed=True, histtype='stepfilled',
              label='%s (n=%d)' % (commit, len(values)), color=color)

  # Vertical lines denoting the medians.
  medians = tuple(numpy.percentile(values, 50) for values in values_per_commit)
  axis.set_xticks(medians, minor=True)
  axis.grid(which='minor', axis='x', linestyle='--')

  # Axis labels and legend.
  #axis.set_xlabel(step.metric_name)
  axis.set_ylabel('Relative probability')
  axis.legend(loc='upper right')


def _DrawEmpiricalCdf(axis, results):
  colors = cm.rainbow(numpy.linspace(  # pylint: disable=no-member
      1, 0, len(results) + 1))
  for (commit, values), color in zip(results, colors):
    # Empirical distribution function.
    levels = numpy.linspace(0, 1, len(values) + 1)
    axis.step(sorted(values) + [max(values)], levels,
              label='%s (n=%d)' % (commit, len(values)), color=color)

    # Dots denoting the percentiles.
    axis.plot(numpy.percentile(values, tuple(p * 100 for p in _PERCENTILES)),
              _PERCENTILES, '.', color=color)

  axis.set_yticks(_PERCENTILES)

  # Vertical lines denoting the medians.
  values_per_commit = [values for _, values in results]
  medians = tuple(numpy.percentile(values, 50) for values in values_per_commit)
  axis.set_xticks(medians, minor=True)
  axis.grid(which='minor', axis='x', linestyle='--')

  # Axis labels and legend.
  #axis.set_xlabel(step.metric_name)
  axis.set_ylabel('Cumulative probability')
  axis.legend(loc='lower right')


if __name__ == '__main__':
  main()