1#!/usr/bin/python2 2# 3# Copyright 2015 The ANGLE Project Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6# 7# perf_test_runner.py: 8# Helper script for running and analyzing perftest results. Runs the 9# tests in an infinite batch, printing out the mean and coefficient of 10# variation of the population continuously. 11# 12 13import glob 14import subprocess 15import sys 16import os 17import re 18 19base_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) 20 21# Look for a [Rr]elease build. 22perftests_paths = glob.glob('out/*elease*') 23metric = 'wall_time' 24max_experiments = 10 25 26binary_name = 'angle_perftests' 27if sys.platform == 'win32': 28 binary_name += '.exe' 29 30scores = [] 31 32 33# Danke to http://stackoverflow.com/a/27758326 34def mean(data): 35 """Return the sample arithmetic mean of data.""" 36 n = len(data) 37 if n < 1: 38 raise ValueError('mean requires at least one data point') 39 return float(sum(data)) / float(n) # in Python 2 use sum(data)/float(n) 40 41 42def sum_of_square_deviations(data, c): 43 """Return sum of square deviations of sequence data.""" 44 ss = sum((float(x) - c)**2 for x in data) 45 return ss 46 47 48def coefficient_of_variation(data): 49 """Calculates the population coefficient of variation.""" 50 n = len(data) 51 if n < 2: 52 raise ValueError('variance requires at least two data points') 53 c = mean(data) 54 ss = sum_of_square_deviations(data, c) 55 pvar = ss / n # the population variance 56 stddev = (pvar**0.5) # population standard deviation 57 return stddev / c 58 59 60def truncated_list(data, n): 61 """Compute a truncated list, n is truncation size""" 62 if len(data) < n * 2: 63 raise ValueError('list not large enough to truncate') 64 return sorted(data)[n:-n] 65 66 67def truncated_mean(data, n): 68 """Compute a truncated mean, n is truncation size""" 69 return mean(truncated_list(data, n)) 70 71 72def truncated_cov(data, n): 73 """Compute a truncated coefficient of variation, n is truncation size""" 74 return coefficient_of_variation(truncated_list(data, n)) 75 76 77# Find most recent binary 78newest_binary = None 79newest_mtime = None 80 81for path in perftests_paths: 82 binary_path = os.path.join(base_path, path, binary_name) 83 if os.path.exists(binary_path): 84 binary_mtime = os.path.getmtime(binary_path) 85 if (newest_binary is None) or (binary_mtime > newest_mtime): 86 newest_binary = binary_path 87 newest_mtime = binary_mtime 88 89perftests_path = newest_binary 90 91if perftests_path == None or not os.path.exists(perftests_path): 92 print('Cannot find Release %s!' % binary_name) 93 sys.exit(1) 94 95if sys.platform == 'win32': 96 test_name = 'DrawCallPerfBenchmark.Run/d3d11_null' 97else: 98 test_name = 'DrawCallPerfBenchmark.Run/gl' 99 100if len(sys.argv) >= 2: 101 test_name = sys.argv[1] 102 103print('Using test executable: ' + perftests_path) 104print('Test name: ' + test_name) 105 106 107def get_results(metric, extra_args=[]): 108 process = subprocess.Popen( 109 [perftests_path, '--gtest_filter=' + test_name] + extra_args, 110 stdout=subprocess.PIPE, 111 stderr=subprocess.PIPE) 112 output, err = process.communicate() 113 114 m = re.search(r'Running (\d+) tests', output) 115 if m and int(m.group(1)) > 1: 116 print("Found more than one test result in output:") 117 print(output) 118 sys.exit(3) 119 120 # Results are reported in the format: 121 # name_backend.metric: story= value units. 122 pattern = r'\.' + metric + r':.*= ([0-9.]+)' 123 m = re.findall(pattern, output) 124 if not m: 125 print("Did not find the metric '%s' in the test output:" % metric) 126 print(output) 127 sys.exit(1) 128 129 return [float(value) for value in m] 130 131 132# Calibrate the number of steps 133steps = get_results("steps", ["--calibration"])[0] 134print("running with %d steps." % steps) 135 136# Loop 'max_experiments' times, running the tests. 137for experiment in range(max_experiments): 138 experiment_scores = get_results(metric, ["--steps", str(steps)]) 139 140 for score in experiment_scores: 141 sys.stdout.write("%s: %.2f" % (metric, score)) 142 scores.append(score) 143 144 if (len(scores) > 1): 145 sys.stdout.write(", mean: %.2f" % mean(scores)) 146 sys.stdout.write(", variation: %.2f%%" % (coefficient_of_variation(scores) * 100.0)) 147 148 if (len(scores) > 7): 149 truncation_n = len(scores) >> 3 150 sys.stdout.write(", truncated mean: %.2f" % truncated_mean(scores, truncation_n)) 151 sys.stdout.write(", variation: %.2f%%" % (truncated_cov(scores, truncation_n) * 100.0)) 152 153 print("") 154