blob: bc5f37cce8ecd9be617c21567bbd81dbb39d77b4 [file] [log] [blame]
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
################################################################################
import argparse
import json
import urllib
import urllib2
from regression_report import loadBenchmarkNames
"""
The regression detection algorithm calculates the regression ratio as the ratio of change between the current
throughput and the maximum throughput observed in the most recent numBaselineSamples samples. A regression alert is
triggered if the regression ratio exceeds max(minRegressionRatio, minInstabilityMultiplier * lastStandardDeviation)
Please refer to https://docs.google.com/document/d/1Bvzvq79Ll5yxd1UtC0YzczgFbZPAgPcN3cI0MjVkIag for more detail.
"""
ENVIRONMENT = 2
MIN_SAMPLE_SIZE_LIMIT = 5
"""
Returns a list of benchmark results
"""
def loadHistoryData(codespeedUrl, exe, benchmark, baselineSize):
url = codespeedUrl + 'timeline/json/?' + urllib.urlencode(
{'exe': exe, 'ben': benchmark, 'env': ENVIRONMENT, 'revs': baselineSize})
f = urllib2.urlopen(url)
response = f.read()
f.close()
timelines = json.loads(response)['timelines'][0]
result = timelines['branches']['master'][exe]
lessIsbBetter = (timelines['lessisbetter'] == " (less is better)")
return result, lessIsbBetter
def detectRegression(urlToBenchmark, stds, scores, baselineSize, minRegressionRatio, minInstabilityMultiplier,
direction):
sustainable_x = [min(scores[i - 3: i]) for i in range(3, baselineSize)]
baseline_throughput = max(sustainable_x)
current_throughput = max(scores[-3:])
current_instability = stds[-1] / current_throughput
if direction * (1 - current_throughput / baseline_throughput) > max(minRegressionRatio, direction * minInstabilityMultiplier * current_instability):
print "<%s|%s> baseline=%s current_value=%s" % (urlToBenchmark, benchmark, direction * baseline_throughput, direction * current_throughput)
def checkBenchmark(args, exe, benchmark):
results, lessIsbBetter = loadHistoryData(args.codespeedUrl, exe, benchmark, args.numBaselineSamples + 3)
results = list(reversed(results))
scores = [score for (date, score, deviation, commit, branch) in results]
stds = [deviation for (date, score, deviation, commit, branch) in results]
urlToBenchmark = args.codespeedUrl + 'timeline/#/?' + urllib.urlencode({
'ben': benchmark,
'exe': exe,
'env': ENVIRONMENT,
'revs': args.numDisplaySamples,
'equid': 'off',
'quarts': 'on',
'extr': 'on'})
if len(results) < MIN_SAMPLE_SIZE_LIMIT:
return
direction = 1
if lessIsbBetter:
scores = [-1 * score for score in scores]
direction = -1
detectRegression(urlToBenchmark, stds, scores, args.numBaselineSamples, args.minRegressionRatio,
args.minInstabilityMultiplier, direction)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Regression report based on Max/Min value')
parser.add_argument('--num-baseline-samples', dest='numBaselineSamples', required=False, default=30, type=int,
help='The maximum number of recent samples across which the maximum achieved throughput would be '
'used as the baseline for regression detection.')
parser.add_argument('--num-display-samples', dest='numDisplaySamples', required=False, default=200,
type=int,
help='Number of samples to display in regression report for human inspection. Not all values '
'are working.')
parser.add_argument('--min-regression-ratio', dest='minRegressionRatio', required=False,
default=0.04, type=float,
help='A regression should be alerted only if the ratio of change between the baseline '
'throughput and the current throughput exceeds the configured value.')
parser.add_argument('--min-instability-multiplier', dest='minInstabilityMultiplier', required=False,
default=2, type=float,
help="Min instability multiplier to measure deviation.")
parser.add_argument('--codespeed-url', dest='codespeedUrl', default="http://codespeed.dak8s.net:8000/",
help='The codespeed url.')
args = parser.parse_args()
execToBenchmarks = loadBenchmarkNames(args.codespeedUrl)
for exe, benchmarks in execToBenchmarks.items():
for benchmark in benchmarks:
checkBenchmark(args, exe, benchmark)