Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(20)

Side by Side Diff: scripts/slave/recipe_modules/bisect_tester/perf_test.py

Issue 2247373002: Refactor stages 1, 2 and test_api overhaul. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/build.git@master
Patch Set: Addressing all early feedback. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import os 5 import os
6 import re 6 import re
7 import time 7 import time
8 8
9 from . import parse_metric
10
11 9
12 class Metric(object): # pragma: no cover 10 class Metric(object): # pragma: no cover
13 OLD_STYLE_DELIMITER = '-' 11 OLD_STYLE_DELIMITER = '-'
14 NEW_STYLE_DELIMITER = '@@' 12 NEW_STYLE_DELIMITER = '@@'
15 13
16 def __init__(self, metric_string): 14 def __init__(self, metric_string):
17 parts = metric_string.split('/') 15 parts = metric_string.split('/')
18 self.chart_name = None 16 self.chart_name = None
19 self.interaction_record_name = None 17 self.interaction_record_name = None
20 self.trace_name = None 18 self.trace_name = None
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
65 # processed -- that is, \t is converted to a tab character, etc. Hence we 63 # processed -- that is, \t is converted to a tab character, etc. Hence we
66 # use a placeholder with no backslashes and later replace with str.replace. 64 # use a placeholder with no backslashes and later replace with str.replace.
67 command = out_dir_regex.sub(placeholder, command) 65 command = out_dir_regex.sub(placeholder, command)
68 return command.replace(placeholder, new_arg) 66 return command.replace(placeholder, new_arg)
69 67
70 68
71 def _is_telemetry_command(command): 69 def _is_telemetry_command(command):
72 """Attempts to discern whether or not a given command is running telemetry.""" 70 """Attempts to discern whether or not a given command is running telemetry."""
73 return 'run_benchmark' in command 71 return 'run_benchmark' in command
74 72
75
76 def run_perf_test(api, test_config, **kwargs): 73 def run_perf_test(api, test_config, **kwargs):
77 """Runs the command N times and parses a metric from the output.""" 74 """Runs the command N times and parses a metric from the output."""
78 # TODO(prasadv): Consider extracting out the body of the for loop into 75 # TODO(prasadv): Consider extracting out the body of the for loop into
79 # a helper method, or extract the metric-extraction to make this more 76 # a helper method, or extract the metric-extraction to make this more
80 # cleaner. 77 # cleaner.
81 limit = test_config['max_time_minutes'] * kwargs.get('time_multiplier', 1) 78 limit = test_config['max_time_minutes'] * kwargs.get('time_multiplier', 1)
82 run_results = {'measured_values': [], 'errors': set()} 79 results = {'valueset_paths': [], 'chartjson_paths': [], 'errors': set(),
83 values = run_results['measured_values'] 80 'retcodes': [], 'stdout_paths': [], 'output': []}
84 metric = test_config.get('metric') 81 metric = test_config.get('metric')
85 retcodes = []
86 output_for_all_runs = []
87 temp_dir = None 82 temp_dir = None
88 repeat_cnt = test_config['repeat_count'] 83 repeat_count = test_config['repeat_count']
89 84
90 command = test_config['command'] 85 command = test_config['command']
91 use_chartjson = bool('chartjson' in command) 86 use_chartjson = bool('chartjson' in command)
87 use_valueset = bool('valueset' in command)
88 use_buildbot = bool('buildbot' in command)
92 is_telemetry = _is_telemetry_command(command) 89 is_telemetry = _is_telemetry_command(command)
93 start_time = time.time() 90 start_time = time.time()
94 91
95 if api.m.chromium.c.TARGET_PLATFORM == 'android' and is_telemetry: 92 if api.m.chromium.c.TARGET_PLATFORM == 'android' and is_telemetry:
96 device_serial_number = api.device_to_test; 93 device_serial_number = api.device_to_test;
97 if device_serial_number: 94 if device_serial_number:
98 command += ' --device ' + device_serial_number # pragma: no cover 95 command += ' --device ' + device_serial_number # pragma: no cover
99 96
100 for i in range(repeat_cnt): 97 for i in range(repeat_count):
101 elapsed_minutes = (time.time() - start_time) / 60.0 98 elapsed_minutes = (time.time() - start_time) / 60.0
102 # A limit of 0 means 'no timeout set'. 99 # A limit of 0 means 'no timeout set'.
103 if limit and elapsed_minutes >= limit: # pragma: no cover 100 if limit and elapsed_minutes >= limit: # pragma: no cover
104 break 101 break
105 if is_telemetry: 102 if is_telemetry:
106 if i == 0 and kwargs.get('reset_on_first_run'): 103 if i == 0 and kwargs.get('reset_on_first_run'):
107 command += ' --reset-results' 104 command += ' --reset-results'
108 if i == repeat_cnt - 1 and kwargs.get('upload_on_last_run'): 105 if i == repeat_count - 1 and kwargs.get('upload_on_last_run'):
109 command += ' --upload-results' 106 command += ' --upload-results'
110 if kwargs.get('results_label'): 107 if kwargs.get('results_label'):
111 command += ' --results-label=%s' % kwargs.get('results_label') 108 command += ' --results-label=%s' % kwargs.get('results_label')
112 if use_chartjson: # pragma: no cover 109 temp_dir = api.m.path.mkdtemp('perf-test-output')
113 temp_dir = api.m.path.mkdtemp('perf-test-output') 110 if use_chartjson or use_valueset: # pragma: no cover
114 command = _set_output_dir(command, str(temp_dir)) 111 command = _set_output_dir(command, str(temp_dir))
115 results_path = temp_dir.join('results-chart.json') 112 chartjson_path = temp_dir.join('results-chart.json')
113 valueset_path = temp_dir.join('results-valueset.json')
116 114
117 step_name = "Performance Test%s %d of %d" % ( 115 step_name = "Performance Test%s %d of %d" % (
118 ' (%s)' % kwargs['name'] if 'name' in kwargs else '', i + 1, repeat_cnt) 116 ' (%s)' % kwargs['name'] if 'name' in kwargs else '', i + 1, repeat_coun t)
119 if api.m.platform.is_linux: 117 if api.m.platform.is_linux:
120 os.environ['CHROME_DEVEL_SANDBOX'] = api.m.path.join( 118 os.environ['CHROME_DEVEL_SANDBOX'] = api.m.path.join(
121 '/opt', 'chromium', 'chrome_sandbox') 119 '/opt', 'chromium', 'chrome_sandbox')
122 out, err, retcode = _run_command(api, command, step_name) 120 out, err, retcode = _run_command(api, command, step_name, **kwargs)
121 results['output'].append(out or '')
122 if out:
123 # Write stdout to a local temp location for possible buildbot parsing
124 stdout_path = temp_dir.join('results.txt')
125 api.m.file.write('write buildbot output to disk', stdout_path, out)
126 results['stdout_paths'].append(stdout_path)
123 127
124 if out is None and err is None: 128 if metric:
125 # dummy value when running test TODO: replace with a mock
126 values.append(0)
127 elif metric: # pragma: no cover
128 if use_chartjson: 129 if use_chartjson:
129 step_result = api.m.json.read( 130 try:
130 'Reading chartjson results', results_path) 131 step_result = api.m.json.read(
131 has_valid_value, value = find_values( 132 'Reading chartjson results', chartjson_path)
132 step_result.json.output, Metric(metric)) 133 except api.m.step.StepFailure: # pragma: no cover
133 else: 134 pass
134 has_valid_value, value = parse_metric.parse_metric( 135 else:
135 out, err, metric.split('/')) 136 if step_result.json.output: # pragma: no cover
136 output_for_all_runs.append(out) 137 results['chartjson_paths'].append(chartjson_path)
137 if has_valid_value: 138 if use_valueset:
138 values.extend(value) 139 try:
139 else: 140 step_result = api.m.json.read(
140 # This means the metric was not found in the output. 141 'Reading valueset results', valueset_path,
141 if not retcode: 142 step_test_data=lambda: api.m.json.test_api.output(
142 # If all tests passed, but the metric was not found, this means that 143 {'dummy':'dict'}))
143 # something changed on the test, or the given metric name was 144 except api.m.step.StepFailure: # pragma: no cover
144 # incorrect, we need to surface this on the bisector. 145 pass
145 run_results['errors'].add('MISSING_METRIC') 146 else:
146 else: 147 if step_result.json.output:
147 output_for_all_runs.append(out) 148 results['valueset_paths'].append(valueset_path)
148 retcodes.append(retcode) 149 results['retcodes'].append(retcode)
149 150 return results
150 return run_results, output_for_all_runs, retcodes
151
152
153 def find_values(results, metric): # pragma: no cover
154 """Tries to extract the given metric from the given results.
155
156 This method tries several different possible chart names depending
157 on the given metric.
158
159 Args:
160 results: The chartjson dict.
161 metric: A Metric instance.
162
163 Returns:
164 A pair (has_valid_value, value), where has_valid_value is a boolean,
165 and value is the value(s) extracted from the results.
166 """
167 has_valid_value, value, _ = parse_metric.parse_chartjson_metric(
168 results, metric.as_pair())
169 if has_valid_value:
170 return True, value
171
172 # TODO(eakuefner): Get rid of this fallback when bisect uses ToT Telemetry.
173 has_valid_value, value, _ = parse_metric.parse_chartjson_metric(
174 results, metric.as_pair(Metric.OLD_STYLE_DELIMITER))
175 if has_valid_value:
176 return True, value
177
178 # If we still haven't found a valid value, it's possible that the metric was
179 # specified as interaction-chart/trace or interaction-chart/interaction-chart,
180 # and the chartjson chart names use @@ as the separator between interaction
181 # and chart names.
182 if Metric.OLD_STYLE_DELIMITER not in metric.chart_name:
183 return False, [] # Give up; no results found.
184 interaction, chart = metric.chart_name.split(Metric.OLD_STYLE_DELIMITER, 1)
185 metric.interaction_record_name = interaction
186 metric.chart_name = chart
187 has_valid_value, value, _ = parse_metric.parse_chartjson_metric(
188 results, metric.as_pair())
189 return has_valid_value, value
190 151
191 def _rebase_path(api, file_path): 152 def _rebase_path(api, file_path):
192 """Attempts to make an absolute path for the command. 153 """Attempts to make an absolute path for the command.
193 154
194 We want to pass to runtest.py an absolute path if possible. 155 We want to pass to runtest.py an absolute path if possible.
195 """ 156 """
196 if (file_path.startswith('src/') or file_path.startswith('./src/')): 157 if (file_path.startswith('src/') or file_path.startswith('./src/')):
197 return api.m.path['checkout'].join( 158 return api.m.path['checkout'].join(
198 *file_path.split('src', 1)[1].split('/')[1:]) 159 *file_path.split('src', 1)[1].split('/')[1:])
199 elif (file_path.startswith('src\\') or 160 elif (file_path.startswith('src\\') or
200 file_path.startswith('.\\src\\')): # pragma: no cover 161 file_path.startswith('.\\src\\')): # pragma: no cover
201 return api.m.path['checkout'].join( 162 return api.m.path['checkout'].join(
202 *file_path.split('src', 1)[1].split('\\')[1:]) 163 *file_path.split('src', 1)[1].split('\\')[1:])
203 return file_path 164 return file_path
204 165
205 def _run_command(api, command, step_name): 166 def _run_command(api, command, step_name, **kwargs):
206 command_parts = command.split() 167 command_parts = command.split()
207 stdout = api.m.raw_io.output() 168 stdout = api.m.raw_io.output()
208 stderr = api.m.raw_io.output() 169 stderr = api.m.raw_io.output()
209 170
171 inner_kwargs = {}
172 if 'step_test_data' in kwargs:
173 inner_kwargs['step_test_data'] = kwargs['step_test_data']
210 # TODO(prasadv): Remove this once bisect runs are no longer running 174 # TODO(prasadv): Remove this once bisect runs are no longer running
211 # against revisions from February 2016 or earlier. 175 # against revisions from February 2016 or earlier.
212 kwargs = {}
213 if 'android-chrome' in command: # pragma: no cover 176 if 'android-chrome' in command: # pragma: no cover
214 kwargs['env'] = {'CHROMIUM_OUTPUT_DIR': api.m.chromium.output_dir} 177 inner_kwargs['env'] = {'CHROMIUM_OUTPUT_DIR': api.m.chromium.output_dir}
215 178
216 # By default, we assume that the test to run is an executable binary. In the 179 # By default, we assume that the test to run is an executable binary. In the
217 # case of python scripts, runtest.py will guess based on the extension. 180 # case of python scripts, runtest.py will guess based on the extension.
218 python_mode = False 181 python_mode = False
219 if command_parts[0] == 'python': # pragma: no cover 182 if command_parts[0] == 'python': # pragma: no cover
220 # Dashboard prepends the command with 'python' when on windows, however, it 183 # Dashboard prepends the command with 'python' when on windows, however, it
221 # is not necessary to pass this along to the runtest.py invocation. 184 # is not necessary to pass this along to the runtest.py invocation.
222 # TODO(robertocn): Remove this clause when dashboard stops sending python as 185 # TODO(robertocn): Remove this clause when dashboard stops sending python as
223 # part of the command. 186 # part of the command.
224 # https://github.com/catapult-project/catapult/issues/2283 187 # https://github.com/catapult-project/catapult/issues/2283
225 command_parts = command_parts[1:] 188 command_parts = command_parts[1:]
226 python_mode = True 189 python_mode = True
227 elif _is_telemetry_command(command): 190 elif _is_telemetry_command(command):
228 # run_benchmark is a python script without an extension, hence we force 191 # run_benchmark is a python script without an extension, hence we force
229 # python mode. 192 # python mode.
230 python_mode = True 193 python_mode = True
231 try: 194 try:
232 step_result = api.m.chromium.runtest( 195 step_result = api.m.chromium.runtest(
233 test=_rebase_path(api, command_parts[0]), 196 test=_rebase_path(api, command_parts[0]),
234 args=command_parts[1:], 197 args=command_parts[1:],
235 xvfb=True, 198 xvfb=True,
236 name=step_name, 199 name=step_name,
237 python_mode=python_mode, 200 python_mode=python_mode,
238 stdout=stdout, 201 stdout=stdout,
239 stderr=stderr, 202 stderr=stderr,
240 **kwargs) 203 **inner_kwargs)
241 step_result.presentation.logs['Captured Output'] = ( 204 step_result.presentation.logs['Captured Output'] = (
242 step_result.stdout or '').splitlines() 205 step_result.stdout or '').splitlines()
243 except api.m.step.StepFailure as sf: 206 except api.m.step.StepFailure as sf:
244 sf.result.presentation.logs['Failure Output'] = ( 207 sf.result.presentation.logs['Failure Output'] = (
245 sf.result.stdout or '').splitlines() 208 sf.result.stdout or '').splitlines()
246 if sf.result.stderr: # pragma: no cover 209 if sf.result.stderr: # pragma: no cover
247 sf.result.presentation.logs['stderr'] = ( 210 sf.result.presentation.logs['stderr'] = (
248 sf.result.stderr).splitlines() 211 sf.result.stderr).splitlines()
249 return sf.result.stdout, sf.result.stderr, sf.result.retcode 212 return sf.result.stdout, sf.result.stderr, sf.result.retcode
250 return step_result.stdout, step_result.stderr, step_result.retcode 213 return step_result.stdout, step_result.stderr, step_result.retcode
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698