blob: 845b5fcabe8fa1661b2ebd5f0af3488e746735d9 [file] [log] [blame]
Simran Basi833814b2013-01-29 13:13:43 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Ryo Hashimoto66ead5c2014-12-11 20:19:35 +09005import json
Simran Basi833814b2013-01-29 13:13:43 -08006import logging
Ryo Hashimoto66ead5c2014-12-11 20:19:35 +09007import math
Simran Basi833814b2013-01-29 13:13:43 -08008import os
Dennis Jeffreyc42fd302013-04-17 11:57:51 -07009import pprint
Simran Basi833814b2013-01-29 13:13:43 -080010import re
11import StringIO
12
Simran Basi833814b2013-01-29 13:13:43 -080013from autotest_lib.client.common_lib import error, utils
14from autotest_lib.client.common_lib.cros import dev_server
15
16
Dave Tu6a404e62013-11-05 15:54:48 -080017TELEMETRY_RUN_BENCHMARKS_SCRIPT = 'tools/perf/run_benchmark'
Simran Basi1dbfc132013-05-02 10:11:02 -070018TELEMETRY_RUN_CROS_TESTS_SCRIPT = 'chrome/test/telemetry/run_cros_tests'
Ilja Friedelf2473802014-03-28 17:54:34 -070019TELEMETRY_RUN_GPU_TESTS_SCRIPT = 'content/test/gpu/run_gpu_test.py'
Ilja H. Friedel086bc3f2014-02-27 22:17:55 -080020TELEMETRY_RUN_TESTS_SCRIPT = 'tools/telemetry/run_tests'
Achuith Bhandarkar124e4732014-01-21 15:27:54 -080021TELEMETRY_TIMEOUT_MINS = 120
Simran Basi833814b2013-01-29 13:13:43 -080022
23# Result Statuses
24SUCCESS_STATUS = 'SUCCESS'
25WARNING_STATUS = 'WARNING'
26FAILED_STATUS = 'FAILED'
27
Dennis Jeffreyc42fd302013-04-17 11:57:51 -070028# Regex for the RESULT output lines understood by chrome buildbot.
Ryo Hashimoto66ead5c2014-12-11 20:19:35 +090029# Keep in sync with
30# chromium/tools/build/scripts/slave/performance_log_processor.py.
Dennis Jeffreyc42fd302013-04-17 11:57:51 -070031RESULTS_REGEX = re.compile(r'(?P<IMPORTANT>\*)?RESULT '
Ryo Hashimoto66ead5c2014-12-11 20:19:35 +090032 r'(?P<GRAPH>[^:]*): (?P<TRACE>[^=]*)= '
33 r'(?P<VALUE>[\{\[]?[-\d\., ]+[\}\]]?)('
34 r' ?(?P<UNITS>.+))?')
35HISTOGRAM_REGEX = re.compile(r'(?P<IMPORTANT>\*)?HISTOGRAM '
36 r'(?P<GRAPH>[^:]*): (?P<TRACE>[^=]*)= '
37 r'(?P<VALUE_JSON>{.*})(?P<UNITS>.+)?')
Dennis Jeffreyc42fd302013-04-17 11:57:51 -070038
Simran Basi833814b2013-01-29 13:13:43 -080039
40class TelemetryResult(object):
41 """Class to represent the results of a telemetry run.
42
43 This class represents the results of a telemetry run, whether it ran
44 successful, failed or had warnings.
45 """
46
47
48 def __init__(self, exit_code=0, stdout='', stderr=''):
49 """Initializes this TelemetryResultObject instance.
50
51 @param status: Status of the telemtry run.
52 @param stdout: Stdout of the telemetry run.
53 @param stderr: Stderr of the telemetry run.
54 """
55 if exit_code == 0:
56 self.status = SUCCESS_STATUS
57 else:
58 self.status = FAILED_STATUS
59
Fang Denge689e712013-11-13 18:27:06 -080060 # A list of perf values, e.g.
61 # [{'graph': 'graphA', 'trace': 'page_load_time',
62 # 'units': 'secs', 'value':0.5}, ...]
63 self.perf_data = []
Simran Basi833814b2013-01-29 13:13:43 -080064 self._stdout = stdout
65 self._stderr = stderr
66 self.output = '\n'.join([stdout, stderr])
67
68
Dennis Jeffreyc42fd302013-04-17 11:57:51 -070069 def _cleanup_perf_string(self, str):
70 """Clean up a perf-related string by removing illegal characters.
Simran Basi833814b2013-01-29 13:13:43 -080071
Dennis Jeffreyc42fd302013-04-17 11:57:51 -070072 Perf keys stored in the chromeOS database may contain only letters,
73 numbers, underscores, periods, and dashes. Transform an inputted
74 string so that any illegal characters are replaced by underscores.
75
76 @param str: The perf string to clean up.
77
78 @return The cleaned-up perf string.
79 """
80 return re.sub(r'[^\w.-]', '_', str)
81
82
83 def _cleanup_units_string(self, units):
84 """Cleanup a units string.
85
86 Given a string representing units for a perf measurement, clean it up
87 by replacing certain illegal characters with meaningful alternatives.
88 Any other illegal characters should then be replaced with underscores.
Simran Basi833814b2013-01-29 13:13:43 -080089
90 Examples:
Dennis Jeffreyc42fd302013-04-17 11:57:51 -070091 count/time -> count_per_time
92 % -> percent
93 units! --> units_
94 score (bigger is better) -> score__bigger_is_better_
95 score (runs/s) -> score__runs_per_s_
Simran Basi833814b2013-01-29 13:13:43 -080096
Dennis Jeffreyc42fd302013-04-17 11:57:51 -070097 @param units: The units string to clean up.
Simran Basi833814b2013-01-29 13:13:43 -080098
Dennis Jeffreyc42fd302013-04-17 11:57:51 -070099 @return The cleaned-up units string.
Simran Basi833814b2013-01-29 13:13:43 -0800100 """
Dennis Jeffreyc42fd302013-04-17 11:57:51 -0700101 if '%' in units:
102 units = units.replace('%', 'percent')
Simran Basi833814b2013-01-29 13:13:43 -0800103 if '/' in units:
104 units = units.replace('/','_per_')
Dennis Jeffreyc42fd302013-04-17 11:57:51 -0700105 return self._cleanup_perf_string(units)
Simran Basi833814b2013-01-29 13:13:43 -0800106
107
108 def parse_benchmark_results(self):
109 """Parse the results of a telemetry benchmark run.
110
Dave Tu6a404e62013-11-05 15:54:48 -0800111 Stdout has the output in RESULT block format below.
Simran Basi833814b2013-01-29 13:13:43 -0800112
Dennis Jeffreyc42fd302013-04-17 11:57:51 -0700113 The lines of interest start with the substring "RESULT". These are
114 specially-formatted perf data lines that are interpreted by chrome
115 builbot (when the Telemetry tests run for chrome desktop) and are
116 parsed to extract perf data that can then be displayed on a perf
117 dashboard. This format is documented in the docstring of class
118 GraphingLogProcessor in this file in the chrome tree:
Simran Basi833814b2013-01-29 13:13:43 -0800119
Dennis Jeffreyc42fd302013-04-17 11:57:51 -0700120 chromium/tools/build/scripts/slave/process_log_utils.py
Simran Basi833814b2013-01-29 13:13:43 -0800121
Dennis Jeffreyc42fd302013-04-17 11:57:51 -0700122 Example RESULT output lines:
123 RESULT average_commit_time_by_url: http___www.ebay.com= 8.86528 ms
124 RESULT CodeLoad: CodeLoad= 6343 score (bigger is better)
125 RESULT ai-astar: ai-astar= [614,527,523,471,530,523,577,625,614,538] ms
126
127 Currently for chromeOS, we can only associate a single perf key (string)
128 with a perf value. That string can only contain letters, numbers,
129 dashes, periods, and underscores, as defined by write_keyval() in:
130
131 chromeos/src/third_party/autotest/files/client/common_lib/
132 base_utils.py
133
134 We therefore parse each RESULT line, clean up the strings to remove any
135 illegal characters not accepted by chromeOS, and construct a perf key
136 string based on the parsed components of the RESULT line (with each
137 component separated by a special delimiter). We prefix the perf key
138 with the substring "TELEMETRY" to identify it as a telemetry-formatted
139 perf key.
Simran Basi833814b2013-01-29 13:13:43 -0800140
141 Stderr has the format of Warnings/Tracebacks. There is always a default
Dennis Jeffreyc42fd302013-04-17 11:57:51 -0700142 warning of the display enviornment setting, followed by warnings of
Simran Basi833814b2013-01-29 13:13:43 -0800143 page timeouts or a traceback.
144
145 If there are any other warnings we flag the test as warning. If there
146 is a traceback we consider this test a failure.
Simran Basi833814b2013-01-29 13:13:43 -0800147 """
Simran Basi833814b2013-01-29 13:13:43 -0800148 if not self._stdout:
149 # Nothing in stdout implies a test failure.
150 logging.error('No stdout, test failed.')
151 self.status = FAILED_STATUS
152 return
153
154 stdout_lines = self._stdout.splitlines()
Simran Basi833814b2013-01-29 13:13:43 -0800155 for line in stdout_lines:
Dennis Jeffreyc42fd302013-04-17 11:57:51 -0700156 results_match = RESULTS_REGEX.search(line)
Ryo Hashimoto66ead5c2014-12-11 20:19:35 +0900157 histogram_match = HISTOGRAM_REGEX.search(line)
158 if results_match:
159 self._process_results_line(results_match)
160 elif histogram_match:
161 self._process_histogram_line(histogram_match)
Dennis Jeffreyc42fd302013-04-17 11:57:51 -0700162
163 pp = pprint.PrettyPrinter(indent=2)
Fang Denge689e712013-11-13 18:27:06 -0800164 logging.debug('Perf values: %s', pp.pformat(self.perf_data))
Simran Basi833814b2013-01-29 13:13:43 -0800165
166 if self.status is SUCCESS_STATUS:
167 return
168
169 # Otherwise check if simply a Warning occurred or a Failure,
170 # i.e. a Traceback is listed.
171 self.status = WARNING_STATUS
172 for line in self._stderr.splitlines():
173 if line.startswith('Traceback'):
174 self.status = FAILED_STATUS
175
Ryo Hashimoto66ead5c2014-12-11 20:19:35 +0900176 def _process_results_line(self, line_match):
177 """Processes a line that matches the standard RESULT line format.
178
179 Args:
180 line_match: A MatchObject as returned by re.search.
181 """
182 match_dict = line_match.groupdict()
183 graph_name = self._cleanup_perf_string(match_dict['GRAPH'].strip())
184 trace_name = self._cleanup_perf_string(match_dict['TRACE'].strip())
185 units = self._cleanup_units_string(
186 (match_dict['UNITS'] or 'units').strip())
187 value = match_dict['VALUE'].strip()
188 unused_important = match_dict['IMPORTANT'] or False # Unused now.
189
190 if value.startswith('['):
191 # A list of values, e.g., "[12,15,8,7,16]". Extract just the
192 # numbers, compute the average and use that. In this example,
193 # we'd get 12+15+8+7+16 / 5 --> 11.6.
194 value_list = [float(x) for x in value.strip('[],').split(',')]
195 value = float(sum(value_list)) / len(value_list)
196 elif value.startswith('{'):
197 # A single value along with a standard deviation, e.g.,
198 # "{34.2,2.15}". Extract just the value itself and use that.
199 # In this example, we'd get 34.2.
200 value_list = [float(x) for x in value.strip('{},').split(',')]
201 value = value_list[0] # Position 0 is the value.
202 elif re.search('^\d+$', value):
203 value = int(value)
204 else:
205 value = float(value)
206
207 self.perf_data.append({'graph':graph_name, 'trace': trace_name,
208 'units': units, 'value': value})
209
210 def _process_histogram_line(self, line_match):
211 """Processes a line that matches the HISTOGRAM line format.
212
213 Args:
214 line_match: A MatchObject as returned by re.search.
215 """
216 match_dict = line_match.groupdict()
217 graph_name = self._cleanup_perf_string(match_dict['GRAPH'].strip())
218 trace_name = self._cleanup_perf_string(match_dict['TRACE'].strip())
219 units = self._cleanup_units_string(
220 (match_dict['UNITS'] or 'units').strip())
221 histogram_json = match_dict['VALUE_JSON'].strip()
222 unused_important = match_dict['IMPORTANT'] or False # Unused now.
223 histogram_data = json.loads(histogram_json)
224
225 # Compute geometric mean
226 count = 0
227 sum_of_logs = 0
228 for bucket in histogram_data['buckets']:
229 mean = (bucket['low'] + bucket['high']) / 2.0
230 if mean > 0:
231 sum_of_logs += math.log(mean) * bucket['count']
232 count += bucket['count']
233
234 value = math.exp(sum_of_logs / count) if count > 0 else 0.0
235
236 self.perf_data.append({'graph':graph_name, 'trace': trace_name,
237 'units': units, 'value': value})
Simran Basi833814b2013-01-29 13:13:43 -0800238
239class TelemetryRunner(object):
240 """Class responsible for telemetry for a given build.
241
242 This class will extract and install telemetry on the devserver and is
243 responsible for executing the telemetry benchmarks and returning their
244 output to the caller.
245 """
246
Luis Lozano23ae3192013-11-08 16:22:46 -0800247 def __init__(self, host, local=False):
Simran Basi833814b2013-01-29 13:13:43 -0800248 """Initializes this telemetry runner instance.
249
250 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -0800251
252 @param host: Host where the test will be run.
253 @param local: If set, no devserver will be used, test will be run
254 locally.
Simran Basi833814b2013-01-29 13:13:43 -0800255 """
256 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700257 self._devserver = None
258 self._telemetry_path = None
Luis Lozano23ae3192013-11-08 16:22:46 -0800259 # TODO (llozano crbug.com/324964). Remove conditional code.
260 # Use a class hierarchy instead.
261 if local:
262 self._setup_local_telemetry()
263 else:
264 self._setup_devserver_telemetry()
265
266 logging.debug('Telemetry Path: %s', self._telemetry_path)
267
268
269 def _setup_devserver_telemetry(self):
270 """Setup Telemetry to use the devserver."""
271 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800272 logging.debug('Grabbing build from AFE.')
273
Luis Lozano23ae3192013-11-08 16:22:46 -0800274 build = self._host.get_build()
Simran Basi833814b2013-01-29 13:13:43 -0800275 if not build:
276 logging.error('Unable to locate build label for host: %s.',
277 self._host.hostname)
278 raise error.AutotestError('Failed to grab build for host %s.' %
279 self._host.hostname)
280
281 logging.debug('Setting up telemetry for build: %s', build)
282
283 self._devserver = dev_server.ImageServer.resolve(build)
Simran Basicf81f682014-12-03 16:31:39 -0800284 self._devserver.stage_artifacts(build, ['autotest_packages'])
Simran Basi833814b2013-01-29 13:13:43 -0800285 self._telemetry_path = self._devserver.setup_telemetry(build=build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800286
287
288 def _setup_local_telemetry(self):
289 """Setup Telemetry to use local path to its sources.
290
291 First look for chrome source root, either externally mounted, or inside
292 the chroot. Prefer chrome-src-internal source tree to chrome-src.
293 """
294 TELEMETRY_DIR = 'src'
295 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700296 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800297
298 logging.debug('Setting up telemetry for local testing')
299
300 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700301 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800302 dir_list.extend(
303 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
304 if 'CHROME_ROOT' in os.environ:
305 dir_list.insert(0, os.environ['CHROME_ROOT'])
306
307 telemetry_src = ''
308 for dir in dir_list:
309 if os.path.exists(dir):
310 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
311 break
312 else:
313 raise error.TestError('Telemetry source directory not found.')
314
315 self._devserver = None
316 self._telemetry_path = telemetry_src
317
318
319 def _get_telemetry_cmd(self, script, test_or_benchmark):
320 """Build command to execute telemetry based on script and benchmark.
321
322 @param script: Telemetry script we want to run. For example:
323 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
324 @param test_or_benchmark: Name of the test or benchmark we want to run,
325 with the page_set (if required) as part of
326 the string.
327 @returns Full telemetry command to execute the script.
328 """
329 telemetry_cmd = []
330 if self._devserver:
331 devserver_hostname = self._devserver.url().split(
332 'http://')[1].split(':')[0]
333 telemetry_cmd.extend(['ssh', devserver_hostname])
334
335 telemetry_cmd.extend(
336 ['python',
337 script,
Ilja H. Friedel6965bd82014-05-20 18:29:15 -0700338 '--verbose',
Luis Lozano23ae3192013-11-08 16:22:46 -0800339 '--browser=cros-chrome',
340 '--remote=%s' % self._host.hostname,
341 test_or_benchmark])
342 return telemetry_cmd
Simran Basi833814b2013-01-29 13:13:43 -0800343
344
345 def _run_telemetry(self, script, test_or_benchmark):
346 """Runs telemetry on a dut.
347
348 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800349 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800350 @param test_or_benchmark: Name of the test or benchmark we want to run,
351 with the page_set (if required) as part of the
352 string.
353
354 @returns A TelemetryResult Instance with the results of this telemetry
355 execution.
356 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700357 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800358
Luis Lozano23ae3192013-11-08 16:22:46 -0800359 telemetry_cmd = self._get_telemetry_cmd(script, test_or_benchmark)
360 logging.debug('Running Telemetry: %s', ' '.join(telemetry_cmd))
361
Simran Basi833814b2013-01-29 13:13:43 -0800362 output = StringIO.StringIO()
363 error_output = StringIO.StringIO()
364 exit_code = 0
365 try:
Luis Lozano23ae3192013-11-08 16:22:46 -0800366 result = utils.run(' '.join(telemetry_cmd), stdout_tee=output,
Simran Basi833814b2013-01-29 13:13:43 -0800367 stderr_tee=error_output,
368 timeout=TELEMETRY_TIMEOUT_MINS*60)
369 exit_code = result.exit_status
370 except error.CmdError as e:
371 # Telemetry returned a return code of not 0; for benchmarks this
372 # can be due to a timeout on one of the pages of the page set and
373 # we may still have data on the rest. For a test however this
374 # indicates failure.
375 logging.debug('Error occurred executing telemetry.')
376 exit_code = e.result_obj.exit_status
377
378 stdout = output.getvalue()
379 stderr = error_output.getvalue()
380 logging.debug('Telemetry completed with exit code: %d.\nstdout:%s\n'
381 'stderr:%s', exit_code, stdout, stderr)
382
383 return TelemetryResult(exit_code=exit_code, stdout=stdout,
384 stderr=stderr)
385
386
Simran Basi1dbfc132013-05-02 10:11:02 -0700387 def _run_test(self, script, test):
388 """Runs a telemetry test on a dut.
389
390 @param script: Which telemetry test script we want to run. Can be
391 telemetry's base test script or the Chrome OS specific
392 test script.
393 @param test: Telemetry test we want to run.
394
395 @returns A TelemetryResult Instance with the results of this telemetry
396 execution.
397 """
398 logging.debug('Running telemetry test: %s', test)
399 telemetry_script = os.path.join(self._telemetry_path, script)
400 result = self._run_telemetry(telemetry_script, test)
401 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700402 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700403 return result
404
405
Simran Basi833814b2013-01-29 13:13:43 -0800406 def run_telemetry_test(self, test):
407 """Runs a telemetry test on a dut.
408
409 @param test: Telemetry test we want to run.
410
411 @returns A TelemetryResult Instance with the results of this telemetry
412 execution.
413 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700414 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test)
415
416
417 def run_cros_telemetry_test(self, test):
418 """Runs a cros specific telemetry test on a dut.
419
420 @param test: Telemetry test we want to run.
421
422 @returns A TelemetryResult instance with the results of this telemetry
423 execution.
424 """
425 return self._run_test(TELEMETRY_RUN_CROS_TESTS_SCRIPT, test)
Simran Basi833814b2013-01-29 13:13:43 -0800426
427
Ilja H. Friedel086bc3f2014-02-27 22:17:55 -0800428 def run_gpu_test(self, test):
429 """Runs a gpu test on a dut.
430
431 @param test: Gpu test we want to run.
432
433 @returns A TelemetryResult instance with the results of this telemetry
434 execution.
435 """
436 return self._run_test(TELEMETRY_RUN_GPU_TESTS_SCRIPT, test)
437
438
Fang Denge689e712013-11-13 18:27:06 -0800439 @staticmethod
440 def _output_perf_value(perf_value_writer, perf_data):
441 """Output perf values to result dir.
442
443 The perf values will be output to the result dir and
444 be subsequently uploaded to perf dashboard.
445
446 @param perf_value_writer: Should be an instance with the function
447 output_perf_value(), if None, no perf value
448 will be written. Typically this will be the
449 job object from an autotest test.
450 @param perf_data: A list of perf values, each value is
451 a dictionary that looks like
452 {'graph':'GraphA', 'trace':'metric1',
453 'units':'secs', 'value':0.5}
454 """
455 for perf_value in perf_data:
456 perf_value_writer.output_perf_value(
457 description=perf_value['trace'],
458 value=perf_value['value'],
459 units=perf_value['units'],
460 graph=perf_value['graph'])
461
462
463 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None):
Simran Basi833814b2013-01-29 13:13:43 -0800464 """Runs a telemetry benchmark on a dut.
465
466 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800467 @param perf_value_writer: Should be an instance with the function
468 output_perf_value(), if None, no perf value
469 will be written. Typically this will be the
470 job object from an autotest test.
Simran Basi833814b2013-01-29 13:13:43 -0800471
472 @returns A TelemetryResult Instance with the results of this telemetry
473 execution.
474 """
Dave Tu6a404e62013-11-05 15:54:48 -0800475 logging.debug('Running telemetry benchmark: %s', benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800476 telemetry_script = os.path.join(self._telemetry_path,
477 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
Dave Tu6a404e62013-11-05 15:54:48 -0800478 result = self._run_telemetry(telemetry_script, benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800479 result.parse_benchmark_results()
480
Fang Denge689e712013-11-13 18:27:06 -0800481 if perf_value_writer:
482 self._output_perf_value(perf_value_writer, result.perf_data)
Simran Basi833814b2013-01-29 13:13:43 -0800483
484 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800485 raise error.TestWarn('Telemetry Benchmark: %s'
486 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800487 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800488 raise error.TestFail('Telemetry Benchmark: %s'
489 ' failed to run.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800490
491 return result