blob: 16ef4bfaa403f1a1bdc1994dc702e9bc266ea385 [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
5import logging
6import os
Simran Basi833814b2013-01-29 13:13:43 -08007import StringIO
8
Simran Basi833814b2013-01-29 13:13:43 -08009from autotest_lib.client.common_lib import error, utils
10from autotest_lib.client.common_lib.cros import dev_server
Simran Basi5ace6f22016-01-06 17:30:44 -080011from autotest_lib.server import afe_utils
Simran Basi833814b2013-01-29 13:13:43 -080012
13
Dave Tu6a404e62013-11-05 15:54:48 -080014TELEMETRY_RUN_BENCHMARKS_SCRIPT = 'tools/perf/run_benchmark'
Ilja H. Friedel086bc3f2014-02-27 22:17:55 -080015TELEMETRY_RUN_TESTS_SCRIPT = 'tools/telemetry/run_tests'
Achuith Bhandarkar124e4732014-01-21 15:27:54 -080016TELEMETRY_TIMEOUT_MINS = 120
Simran Basi833814b2013-01-29 13:13:43 -080017
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080018DUT_CHROME_ROOT = '/usr/local/telemetry/src'
19
Simran Basi833814b2013-01-29 13:13:43 -080020# Result Statuses
21SUCCESS_STATUS = 'SUCCESS'
22WARNING_STATUS = 'WARNING'
23FAILED_STATUS = 'FAILED'
24
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080025# A list of benchmarks with that the telemetry test harness can run on dut.
26ON_DUT_WHITE_LIST = ['dromaeo.domcoreattr',
27 'dromaeo.domcoremodify',
28 'dromaeo.domcorequery',
29 'dromaeo.domcoretraverse',
30 'image_decoding.image_decoding_measurement',
31 'jetstream',
32 'kraken',
33 'memory.top_7_stress',
34 'octane',
35 'page_cycler.typical_25',
36 'robohornet_pro',
37 'smoothness.top_25_smooth',
38 'smoothness.tough_animation_cases',
39 'smoothness.tough_canvas_cases',
40 'smoothness.tough_filters_cases',
41 'smoothness.tough_pinch_zoom_cases',
42 'smoothness.tough_scrolling_cases',
43 'smoothness.tough_webgl_cases',
44 'speedometer',
45 'startup.cold.blank_page',
46 'sunspider',
47 'tab_switching.top_10',
48 'webrtc.webrtc_cases']
49
50# BLACK LIST
51# 'session_restore.cold.typical_25', # profile generator not implemented on
52 # CrOS.
Simran Basi833814b2013-01-29 13:13:43 -080053
54class TelemetryResult(object):
55 """Class to represent the results of a telemetry run.
56
57 This class represents the results of a telemetry run, whether it ran
58 successful, failed or had warnings.
59 """
60
61
62 def __init__(self, exit_code=0, stdout='', stderr=''):
63 """Initializes this TelemetryResultObject instance.
64
65 @param status: Status of the telemtry run.
66 @param stdout: Stdout of the telemetry run.
67 @param stderr: Stderr of the telemetry run.
68 """
69 if exit_code == 0:
70 self.status = SUCCESS_STATUS
71 else:
72 self.status = FAILED_STATUS
73
Simran Basi833814b2013-01-29 13:13:43 -080074 self._stdout = stdout
75 self._stderr = stderr
76 self.output = '\n'.join([stdout, stderr])
77
78
Simran Basi833814b2013-01-29 13:13:43 -080079class TelemetryRunner(object):
80 """Class responsible for telemetry for a given build.
81
82 This class will extract and install telemetry on the devserver and is
83 responsible for executing the telemetry benchmarks and returning their
84 output to the caller.
85 """
86
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080087 def __init__(self, host, local=False, telemetry_on_dut=True):
Simran Basi833814b2013-01-29 13:13:43 -080088 """Initializes this telemetry runner instance.
89
90 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -080091
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080092 Basically, the following commands on the local pc on which test_that
93 will be executed, depending on the 4 possible combinations of
94 local x telemetry_on_dut:
95
96 local=True, telemetry_on_dut=False:
97 run_benchmark --browser=cros-chrome --remote=[dut] [test]
98
99 local=True, telemetry_on_dut=True:
100 ssh [dut] run_benchmark --browser=system [test]
101
102 local=False, telemetry_on_dut=False:
103 ssh [devserver] run_benchmark --browser=cros-chrome --remote=[dut] [test]
104
105 local=False, telemetry_on_dut=True:
106 ssh [devserver] ssh [dut] run_benchmark --browser=system [test]
107
Luis Lozano23ae3192013-11-08 16:22:46 -0800108 @param host: Host where the test will be run.
109 @param local: If set, no devserver will be used, test will be run
110 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800111 If not set, "ssh [devserver] " will be appended to test
112 commands.
113 @param telemetry_on_dut: If set, telemetry itself (the test harness)
114 will run on dut.
115 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -0800116 """
117 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700118 self._devserver = None
119 self._telemetry_path = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800120 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -0800121 # TODO (llozano crbug.com/324964). Remove conditional code.
122 # Use a class hierarchy instead.
123 if local:
124 self._setup_local_telemetry()
125 else:
126 self._setup_devserver_telemetry()
127
128 logging.debug('Telemetry Path: %s', self._telemetry_path)
129
130
131 def _setup_devserver_telemetry(self):
132 """Setup Telemetry to use the devserver."""
133 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800134 logging.debug('Grabbing build from AFE.')
135
Simran Basi5ace6f22016-01-06 17:30:44 -0800136 build = afe_utils.get_build(self._host)
Simran Basi833814b2013-01-29 13:13:43 -0800137 if not build:
138 logging.error('Unable to locate build label for host: %s.',
139 self._host.hostname)
140 raise error.AutotestError('Failed to grab build for host %s.' %
141 self._host.hostname)
142
143 logging.debug('Setting up telemetry for build: %s', build)
144
145 self._devserver = dev_server.ImageServer.resolve(build)
Simran Basicf81f682014-12-03 16:31:39 -0800146 self._devserver.stage_artifacts(build, ['autotest_packages'])
Simran Basi833814b2013-01-29 13:13:43 -0800147 self._telemetry_path = self._devserver.setup_telemetry(build=build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800148
149
150 def _setup_local_telemetry(self):
151 """Setup Telemetry to use local path to its sources.
152
153 First look for chrome source root, either externally mounted, or inside
154 the chroot. Prefer chrome-src-internal source tree to chrome-src.
155 """
156 TELEMETRY_DIR = 'src'
157 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700158 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800159
160 logging.debug('Setting up telemetry for local testing')
161
162 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700163 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800164 dir_list.extend(
165 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
166 if 'CHROME_ROOT' in os.environ:
167 dir_list.insert(0, os.environ['CHROME_ROOT'])
168
169 telemetry_src = ''
170 for dir in dir_list:
171 if os.path.exists(dir):
172 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
173 break
174 else:
175 raise error.TestError('Telemetry source directory not found.')
176
177 self._devserver = None
178 self._telemetry_path = telemetry_src
179
180
Luis Lozano814c7182015-09-08 11:20:47 -0700181 def _get_telemetry_cmd(self, script, test_or_benchmark, *args):
Luis Lozano23ae3192013-11-08 16:22:46 -0800182 """Build command to execute telemetry based on script and benchmark.
183
184 @param script: Telemetry script we want to run. For example:
185 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
186 @param test_or_benchmark: Name of the test or benchmark we want to run,
187 with the page_set (if required) as part of
188 the string.
Luis Lozano814c7182015-09-08 11:20:47 -0700189 @param args: additional list of arguments to pass to the script.
190
Luis Lozano23ae3192013-11-08 16:22:46 -0800191 @returns Full telemetry command to execute the script.
192 """
193 telemetry_cmd = []
194 if self._devserver:
Keith Haddow1e5c7012016-03-09 16:05:37 -0800195 devserver_hostname = dev_server.DevServer.get_server_name(
196 self._devserver.url())
Luis Lozano23ae3192013-11-08 16:22:46 -0800197 telemetry_cmd.extend(['ssh', devserver_hostname])
198
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800199 if self._telemetry_on_dut:
200 telemetry_cmd.extend(
201 ['ssh',
202 self._host.hostname,
203 'python',
204 script,
205 '--verbose',
206 '--output-format=chartjson',
207 '--output-dir=%s' % DUT_CHROME_ROOT,
208 '--browser=system'])
209 else:
210 telemetry_cmd.extend(
211 ['python',
212 script,
213 '--verbose',
214 '--browser=cros-chrome',
215 '--output-format=chartjson',
216 '--output-dir=%s' % self._telemetry_path,
217 '--remote=%s' % self._host.hostname])
Luis Lozano814c7182015-09-08 11:20:47 -0700218 telemetry_cmd.extend(args)
219 telemetry_cmd.append(test_or_benchmark)
220
Keith Haddow1e5c7012016-03-09 16:05:37 -0800221 return ' '.join(telemetry_cmd)
222
223
224 def _scp_telemetry_results_cmd(self, perf_results_dir):
225 """Build command to copy the telemetry results from the devserver.
226
227 @param perf_results_dir: directory path where test output is to be
228 collected.
229 @returns SCP command to copy the results json to the specified directory.
230 """
231 scp_cmd = []
Ricky Liangd186f3e2016-03-15 16:50:55 +0800232 devserver_hostname = ''
233 if perf_results_dir:
234 if self._devserver:
235 devserver_hostname = dev_server.DevServer.get_server_name(
236 self._devserver.url()) + ':'
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800237 if self._telemetry_on_dut:
238 scp_cmd.extend(
239 ['scp', '%s:%s/results-chart.json' % (
240 self._host.hostname, DUT_CHROME_ROOT),
241 perf_results_dir])
242 else:
243 scp_cmd.extend(['scp', '%s%s/results-chart.json' % (
Keith Haddow1e5c7012016-03-09 16:05:37 -0800244 devserver_hostname, self._telemetry_path),
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800245 perf_results_dir])
Keith Haddow1e5c7012016-03-09 16:05:37 -0800246
247 return ' '.join(scp_cmd)
248
249
250 def _run_cmd(self, cmd):
251 """Execute an command in a external shell and capture the output.
252
253 @param cmd: String of is a valid shell command.
254
255 @returns The standard out, standard error and the integer exit code of
256 the executed command.
257 """
258 logging.debug('Running: %s', cmd)
259
260 output = StringIO.StringIO()
261 error_output = StringIO.StringIO()
262 exit_code = 0
263 try:
264 result = utils.run(cmd, stdout_tee=output,
265 stderr_tee=error_output,
266 timeout=TELEMETRY_TIMEOUT_MINS*60)
267 exit_code = result.exit_status
268 except error.CmdError as e:
269 logging.debug('Error occurred executing.')
270 exit_code = e.result_obj.exit_status
271
272 stdout = output.getvalue()
273 stderr = error_output.getvalue()
274 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
275 'stderr:%s', exit_code, stdout, stderr)
276 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800277
278
Luis Lozano814c7182015-09-08 11:20:47 -0700279 def _run_telemetry(self, script, test_or_benchmark, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800280 """Runs telemetry on a dut.
281
282 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800283 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800284 @param test_or_benchmark: Name of the test or benchmark we want to run,
285 with the page_set (if required) as part of the
286 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700287 @param args: additional list of arguments to pass to the script.
Simran Basi833814b2013-01-29 13:13:43 -0800288
289 @returns A TelemetryResult Instance with the results of this telemetry
290 execution.
291 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700292 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800293
Luis Lozano814c7182015-09-08 11:20:47 -0700294 telemetry_cmd = self._get_telemetry_cmd(script,
295 test_or_benchmark,
296 *args)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800297 logging.debug('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800298
Keith Haddow1e5c7012016-03-09 16:05:37 -0800299 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800300
301 return TelemetryResult(exit_code=exit_code, stdout=stdout,
302 stderr=stderr)
303
304
Keith Haddow1e5c7012016-03-09 16:05:37 -0800305 def _run_scp(self, perf_results_dir):
306 """Runs telemetry on a dut.
307
308 @param perf_results_dir: The local directory that results are being
309 collected.
310 """
311 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir)
312 logging.debug('Retrieving Results: %s', scp_cmd)
313
314 self._run_cmd(scp_cmd)
315
316
Luis Lozano814c7182015-09-08 11:20:47 -0700317 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700318 """Runs a telemetry test on a dut.
319
320 @param script: Which telemetry test script we want to run. Can be
321 telemetry's base test script or the Chrome OS specific
322 test script.
323 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700324 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700325
326 @returns A TelemetryResult Instance with the results of this telemetry
327 execution.
328 """
329 logging.debug('Running telemetry test: %s', test)
330 telemetry_script = os.path.join(self._telemetry_path, script)
Luis Lozano814c7182015-09-08 11:20:47 -0700331 result = self._run_telemetry(telemetry_script, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700332 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700333 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700334 return result
335
336
Luis Lozano814c7182015-09-08 11:20:47 -0700337 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800338 """Runs a telemetry test on a dut.
339
340 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700341 @param args: additional list of arguments to pass to the telemetry
342 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800343
344 @returns A TelemetryResult Instance with the results of this telemetry
345 execution.
346 """
Luis Lozano814c7182015-09-08 11:20:47 -0700347 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700348
349
Luis Lozano814c7182015-09-08 11:20:47 -0700350 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None,
351 *args):
Simran Basi833814b2013-01-29 13:13:43 -0800352 """Runs a telemetry benchmark on a dut.
353
354 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800355 @param perf_value_writer: Should be an instance with the function
356 output_perf_value(), if None, no perf value
357 will be written. Typically this will be the
358 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700359 @param args: additional list of arguments to pass to the telemetry
360 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800361
362 @returns A TelemetryResult Instance with the results of this telemetry
363 execution.
364 """
Dave Tu6a404e62013-11-05 15:54:48 -0800365 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800366
367 if benchmark not in ON_DUT_WHITE_LIST:
368 self._telemetry_on_dut = False
369
370 if self._telemetry_on_dut:
371 telemetry_script = os.path.join(DUT_CHROME_ROOT,
372 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
373 self._ensure_deps(self._host, benchmark)
374 else:
375 telemetry_script = os.path.join(self._telemetry_path,
376 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
377
Luis Lozano814c7182015-09-08 11:20:47 -0700378 result = self._run_telemetry(telemetry_script, benchmark, *args)
Simran Basi833814b2013-01-29 13:13:43 -0800379
380 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800381 raise error.TestWarn('Telemetry Benchmark: %s'
382 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800383 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800384 raise error.TestFail('Telemetry Benchmark: %s'
385 ' failed to run.' % benchmark)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800386 if perf_value_writer:
387 self._run_scp(perf_value_writer.resultsdir)
Simran Basi833814b2013-01-29 13:13:43 -0800388 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800389
390 def _ensure_deps(self, dut, test_name):
391 """
392 Ensure the dependencies are locally available on DUT.
393
394 @param dut: The autotest host object representing DUT.
395 @param test_name: Name of the telemetry test.
396 """
397 # Get DEPs using host's telemetry.
398 format_string = ('python %s/tools/perf/fetch_benchmark_deps.py %s')
399 command = format_string % (self._telemetry_path, test_name)
400 stdout = StringIO.StringIO()
401 stderr = StringIO.StringIO()
402
403 if self._devserver:
404 devserver_hostname = self._devserver.url().split(
405 'http://')[1].split(':')[0]
406 command = 'ssh %s %s' % (devserver_hostname, command)
407
408 logging.info('Getting DEPs: %s', command)
409 try:
410 result = utils.run(command, stdout_tee=stdout,
411 stderr_tee=stderr)
412 except error.CmdError as e:
413 logging.debug('Error occurred getting DEPs: %s\n %s\n',
414 stdout.getvalue(), stderr.getvalue())
415 raise error.TestFail('Error occurred while getting DEPs.')
416
417 # Download DEPs to DUT.
418 # send_file() relies on rsync over ssh. Couldn't be better.
419 stdout_str = stdout.getvalue()
420 stdout.close()
421 stderr.close()
422 for dep in stdout_str.split():
423 src = os.path.join(self._telemetry_path, dep)
424 dst = os.path.join(DUT_CHROME_ROOT, dep)
425 try:
426 if self._devserver:
427 logging.info('Copying: %s -> %s', src, dst)
428 utils.run('ssh %s rsync %s %s:%s' % (devserver_hostname, src, self._host.hostname, dst))
429 else:
430 if not os.path.isfile(src):
431 raise error.TestFail('Error occurred while saving DEPs.')
432 logging.info('Copying: %s -> %s', src, dst)
433 dut.send_file(src, dst)
434 except:
435 raise error.TestFail('Error occurred while sending DEPs to dut.\n')