blob: e4f339e4ea9dc78ae69b64e1dead7ecec341a6ce [file] [log] [blame]
#!/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()