blob: 08bc6a50fc052dcaa5789a761c26ad772d03ad72 [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'
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +080019DUT_COMMON_SSH_OPTIONS = ['-o StrictHostKeyChecking=no',
20 '-o UserKnownHostsFile=/dev/null',
21 '-o BatchMode=yes',
22 '-o ConnectTimeout=30',
23 '-o ServerAliveInterval=900',
24 '-o ServerAliveCountMax=3',
25 '-o ConnectionAttempts=4',
26 '-o Protocol=2']
27DUT_SSH_OPTIONS = ' '.join(DUT_COMMON_SSH_OPTIONS + ['-x', '-a', '-l root'])
28DUT_SCP_OPTIONS = ' '.join(DUT_COMMON_SSH_OPTIONS)
29DUT_RSYNC_OPTIONS = ' '.join(['--rsh="/usr/bin/ssh %s"' % DUT_SSH_OPTIONS,
30 '-L', '--timeout=1800', '-az',
31 '--no-o', '--no-g'])
Ting-Yuan Huang74036ae2016-04-08 17:03:34 +080032# Prevent double quotes from being unfolded.
33DUT_RSYNC_OPTIONS = utils.sh_escape(DUT_RSYNC_OPTIONS)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080034
Simran Basi833814b2013-01-29 13:13:43 -080035# Result Statuses
36SUCCESS_STATUS = 'SUCCESS'
37WARNING_STATUS = 'WARNING'
38FAILED_STATUS = 'FAILED'
39
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080040# A list of benchmarks with that the telemetry test harness can run on dut.
41ON_DUT_WHITE_LIST = ['dromaeo.domcoreattr',
42 'dromaeo.domcoremodify',
43 'dromaeo.domcorequery',
44 'dromaeo.domcoretraverse',
45 'image_decoding.image_decoding_measurement',
46 'jetstream',
47 'kraken',
48 'memory.top_7_stress',
49 'octane',
50 'page_cycler.typical_25',
Chung-yih Wang80ba5812017-01-20 17:22:11 +080051 'page_cycler_v2.typical_25',
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080052 'robohornet_pro',
53 'smoothness.top_25_smooth',
54 'smoothness.tough_animation_cases',
55 'smoothness.tough_canvas_cases',
56 'smoothness.tough_filters_cases',
57 'smoothness.tough_pinch_zoom_cases',
58 'smoothness.tough_scrolling_cases',
59 'smoothness.tough_webgl_cases',
60 'speedometer',
61 'startup.cold.blank_page',
62 'sunspider',
63 'tab_switching.top_10',
Chung-yih Wang80ba5812017-01-20 17:22:11 +080064 'tab_switching.typical_25',
Patrik Höglund2ef2e172016-11-16 10:07:29 +010065 'webrtc.peerconnection',
66 'webrtc.stress']
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080067
68# BLACK LIST
69# 'session_restore.cold.typical_25', # profile generator not implemented on
70 # CrOS.
Simran Basi833814b2013-01-29 13:13:43 -080071
72class TelemetryResult(object):
73 """Class to represent the results of a telemetry run.
74
75 This class represents the results of a telemetry run, whether it ran
76 successful, failed or had warnings.
77 """
78
79
80 def __init__(self, exit_code=0, stdout='', stderr=''):
81 """Initializes this TelemetryResultObject instance.
82
83 @param status: Status of the telemtry run.
84 @param stdout: Stdout of the telemetry run.
85 @param stderr: Stderr of the telemetry run.
86 """
87 if exit_code == 0:
88 self.status = SUCCESS_STATUS
89 else:
90 self.status = FAILED_STATUS
91
Simran Basi833814b2013-01-29 13:13:43 -080092 self._stdout = stdout
93 self._stderr = stderr
94 self.output = '\n'.join([stdout, stderr])
95
96
Simran Basi833814b2013-01-29 13:13:43 -080097class TelemetryRunner(object):
98 """Class responsible for telemetry for a given build.
99
100 This class will extract and install telemetry on the devserver and is
101 responsible for executing the telemetry benchmarks and returning their
102 output to the caller.
103 """
104
Ting-Yuan Huang85dcde82016-04-08 17:41:32 +0800105 def __init__(self, host, local=False, telemetry_on_dut=True):
Simran Basi833814b2013-01-29 13:13:43 -0800106 """Initializes this telemetry runner instance.
107
108 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -0800109
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800110 Basically, the following commands on the local pc on which test_that
111 will be executed, depending on the 4 possible combinations of
112 local x telemetry_on_dut:
113
114 local=True, telemetry_on_dut=False:
115 run_benchmark --browser=cros-chrome --remote=[dut] [test]
116
117 local=True, telemetry_on_dut=True:
118 ssh [dut] run_benchmark --browser=system [test]
119
120 local=False, telemetry_on_dut=False:
121 ssh [devserver] run_benchmark --browser=cros-chrome --remote=[dut] [test]
122
123 local=False, telemetry_on_dut=True:
124 ssh [devserver] ssh [dut] run_benchmark --browser=system [test]
125
Luis Lozano23ae3192013-11-08 16:22:46 -0800126 @param host: Host where the test will be run.
127 @param local: If set, no devserver will be used, test will be run
128 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800129 If not set, "ssh [devserver] " will be appended to test
130 commands.
131 @param telemetry_on_dut: If set, telemetry itself (the test harness)
132 will run on dut.
133 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -0800134 """
135 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700136 self._devserver = None
137 self._telemetry_path = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800138 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -0800139 # TODO (llozano crbug.com/324964). Remove conditional code.
140 # Use a class hierarchy instead.
141 if local:
142 self._setup_local_telemetry()
143 else:
144 self._setup_devserver_telemetry()
145
146 logging.debug('Telemetry Path: %s', self._telemetry_path)
147
148
149 def _setup_devserver_telemetry(self):
150 """Setup Telemetry to use the devserver."""
151 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800152 logging.debug('Grabbing build from AFE.')
153
Simran Basi5ace6f22016-01-06 17:30:44 -0800154 build = afe_utils.get_build(self._host)
Simran Basi833814b2013-01-29 13:13:43 -0800155 if not build:
156 logging.error('Unable to locate build label for host: %s.',
157 self._host.hostname)
158 raise error.AutotestError('Failed to grab build for host %s.' %
159 self._host.hostname)
160
161 logging.debug('Setting up telemetry for build: %s', build)
162
Ting-Yuan Huang5d459a22016-08-22 17:11:07 -0700163 self._devserver = dev_server.ImageServer.resolve(build,
164 hostname=self._host.hostname)
Simran Basicf81f682014-12-03 16:31:39 -0800165 self._devserver.stage_artifacts(build, ['autotest_packages'])
Simran Basi833814b2013-01-29 13:13:43 -0800166 self._telemetry_path = self._devserver.setup_telemetry(build=build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800167
168
169 def _setup_local_telemetry(self):
170 """Setup Telemetry to use local path to its sources.
171
172 First look for chrome source root, either externally mounted, or inside
173 the chroot. Prefer chrome-src-internal source tree to chrome-src.
174 """
175 TELEMETRY_DIR = 'src'
176 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700177 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800178
179 logging.debug('Setting up telemetry for local testing')
180
181 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700182 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800183 dir_list.extend(
184 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
185 if 'CHROME_ROOT' in os.environ:
186 dir_list.insert(0, os.environ['CHROME_ROOT'])
187
188 telemetry_src = ''
189 for dir in dir_list:
190 if os.path.exists(dir):
191 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
192 break
193 else:
194 raise error.TestError('Telemetry source directory not found.')
195
196 self._devserver = None
197 self._telemetry_path = telemetry_src
198
199
Luis Lozano814c7182015-09-08 11:20:47 -0700200 def _get_telemetry_cmd(self, script, test_or_benchmark, *args):
Luis Lozano23ae3192013-11-08 16:22:46 -0800201 """Build command to execute telemetry based on script and benchmark.
202
203 @param script: Telemetry script we want to run. For example:
204 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
205 @param test_or_benchmark: Name of the test or benchmark we want to run,
206 with the page_set (if required) as part of
207 the string.
Luis Lozano814c7182015-09-08 11:20:47 -0700208 @param args: additional list of arguments to pass to the script.
209
Luis Lozano23ae3192013-11-08 16:22:46 -0800210 @returns Full telemetry command to execute the script.
211 """
212 telemetry_cmd = []
213 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800214 devserver_hostname = self._devserver.hostname
Luis Lozano23ae3192013-11-08 16:22:46 -0800215 telemetry_cmd.extend(['ssh', devserver_hostname])
216
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800217 if self._telemetry_on_dut:
218 telemetry_cmd.extend(
219 ['ssh',
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800220 DUT_SSH_OPTIONS,
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800221 self._host.hostname,
222 'python',
223 script,
224 '--verbose',
225 '--output-format=chartjson',
226 '--output-dir=%s' % DUT_CHROME_ROOT,
227 '--browser=system'])
228 else:
229 telemetry_cmd.extend(
230 ['python',
231 script,
232 '--verbose',
233 '--browser=cros-chrome',
234 '--output-format=chartjson',
235 '--output-dir=%s' % self._telemetry_path,
236 '--remote=%s' % self._host.hostname])
Luis Lozano814c7182015-09-08 11:20:47 -0700237 telemetry_cmd.extend(args)
238 telemetry_cmd.append(test_or_benchmark)
239
Keith Haddow1e5c7012016-03-09 16:05:37 -0800240 return ' '.join(telemetry_cmd)
241
242
243 def _scp_telemetry_results_cmd(self, perf_results_dir):
244 """Build command to copy the telemetry results from the devserver.
245
246 @param perf_results_dir: directory path where test output is to be
247 collected.
248 @returns SCP command to copy the results json to the specified directory.
249 """
250 scp_cmd = []
Ricky Liangd186f3e2016-03-15 16:50:55 +0800251 devserver_hostname = ''
252 if perf_results_dir:
253 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800254 devserver_hostname = self._devserver.hostname + ':'
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800255 if self._telemetry_on_dut:
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800256 src = ('root@%s:%s/results-chart.json' %
257 (self._host.hostname, DUT_CHROME_ROOT))
258 scp_cmd.extend(['scp', DUT_SCP_OPTIONS, src, perf_results_dir])
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800259 else:
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800260 src = ('%s%s/results-chart.json' %
261 (devserver_hostname, self._telemetry_path))
262 scp_cmd.extend(['scp', src, perf_results_dir])
Keith Haddow1e5c7012016-03-09 16:05:37 -0800263
264 return ' '.join(scp_cmd)
265
266
267 def _run_cmd(self, cmd):
268 """Execute an command in a external shell and capture the output.
269
270 @param cmd: String of is a valid shell command.
271
272 @returns The standard out, standard error and the integer exit code of
273 the executed command.
274 """
275 logging.debug('Running: %s', cmd)
276
277 output = StringIO.StringIO()
278 error_output = StringIO.StringIO()
279 exit_code = 0
280 try:
281 result = utils.run(cmd, stdout_tee=output,
282 stderr_tee=error_output,
283 timeout=TELEMETRY_TIMEOUT_MINS*60)
284 exit_code = result.exit_status
285 except error.CmdError as e:
286 logging.debug('Error occurred executing.')
287 exit_code = e.result_obj.exit_status
288
289 stdout = output.getvalue()
290 stderr = error_output.getvalue()
291 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
292 'stderr:%s', exit_code, stdout, stderr)
293 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800294
295
Luis Lozano814c7182015-09-08 11:20:47 -0700296 def _run_telemetry(self, script, test_or_benchmark, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800297 """Runs telemetry on a dut.
298
299 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800300 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800301 @param test_or_benchmark: Name of the test or benchmark we want to run,
302 with the page_set (if required) as part of the
303 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700304 @param args: additional list of arguments to pass to the script.
Simran Basi833814b2013-01-29 13:13:43 -0800305
306 @returns A TelemetryResult Instance with the results of this telemetry
307 execution.
308 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700309 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800310
Luis Lozano814c7182015-09-08 11:20:47 -0700311 telemetry_cmd = self._get_telemetry_cmd(script,
312 test_or_benchmark,
313 *args)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800314 logging.debug('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800315
Keith Haddow1e5c7012016-03-09 16:05:37 -0800316 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800317
318 return TelemetryResult(exit_code=exit_code, stdout=stdout,
319 stderr=stderr)
320
321
Keith Haddow1e5c7012016-03-09 16:05:37 -0800322 def _run_scp(self, perf_results_dir):
323 """Runs telemetry on a dut.
324
325 @param perf_results_dir: The local directory that results are being
326 collected.
327 """
328 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir)
329 logging.debug('Retrieving Results: %s', scp_cmd)
330
331 self._run_cmd(scp_cmd)
332
333
Luis Lozano814c7182015-09-08 11:20:47 -0700334 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700335 """Runs a telemetry test on a dut.
336
337 @param script: Which telemetry test script we want to run. Can be
338 telemetry's base test script or the Chrome OS specific
339 test script.
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 script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700342
343 @returns A TelemetryResult Instance with the results of this telemetry
344 execution.
345 """
346 logging.debug('Running telemetry test: %s', test)
347 telemetry_script = os.path.join(self._telemetry_path, script)
Luis Lozano814c7182015-09-08 11:20:47 -0700348 result = self._run_telemetry(telemetry_script, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700349 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700350 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700351 return result
352
353
Luis Lozano814c7182015-09-08 11:20:47 -0700354 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800355 """Runs a telemetry test on a dut.
356
357 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700358 @param args: additional list of arguments to pass to the telemetry
359 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800360
361 @returns A TelemetryResult Instance with the results of this telemetry
362 execution.
363 """
Luis Lozano814c7182015-09-08 11:20:47 -0700364 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700365
366
Luis Lozano814c7182015-09-08 11:20:47 -0700367 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None,
368 *args):
Simran Basi833814b2013-01-29 13:13:43 -0800369 """Runs a telemetry benchmark on a dut.
370
371 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800372 @param perf_value_writer: Should be an instance with the function
373 output_perf_value(), if None, no perf value
374 will be written. Typically this will be the
375 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700376 @param args: additional list of arguments to pass to the telemetry
377 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800378
379 @returns A TelemetryResult Instance with the results of this telemetry
380 execution.
381 """
Dave Tu6a404e62013-11-05 15:54:48 -0800382 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800383
384 if benchmark not in ON_DUT_WHITE_LIST:
385 self._telemetry_on_dut = False
386
387 if self._telemetry_on_dut:
388 telemetry_script = os.path.join(DUT_CHROME_ROOT,
389 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
390 self._ensure_deps(self._host, benchmark)
391 else:
392 telemetry_script = os.path.join(self._telemetry_path,
393 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
394
Luis Lozano814c7182015-09-08 11:20:47 -0700395 result = self._run_telemetry(telemetry_script, benchmark, *args)
Simran Basi833814b2013-01-29 13:13:43 -0800396
397 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800398 raise error.TestWarn('Telemetry Benchmark: %s'
399 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800400 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800401 raise error.TestFail('Telemetry Benchmark: %s'
402 ' failed to run.' % benchmark)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800403 if perf_value_writer:
404 self._run_scp(perf_value_writer.resultsdir)
Simran Basi833814b2013-01-29 13:13:43 -0800405 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800406
407 def _ensure_deps(self, dut, test_name):
408 """
409 Ensure the dependencies are locally available on DUT.
410
411 @param dut: The autotest host object representing DUT.
412 @param test_name: Name of the telemetry test.
413 """
414 # Get DEPs using host's telemetry.
415 format_string = ('python %s/tools/perf/fetch_benchmark_deps.py %s')
416 command = format_string % (self._telemetry_path, test_name)
417 stdout = StringIO.StringIO()
418 stderr = StringIO.StringIO()
419
420 if self._devserver:
421 devserver_hostname = self._devserver.url().split(
422 'http://')[1].split(':')[0]
423 command = 'ssh %s %s' % (devserver_hostname, command)
424
425 logging.info('Getting DEPs: %s', command)
426 try:
427 result = utils.run(command, stdout_tee=stdout,
428 stderr_tee=stderr)
429 except error.CmdError as e:
430 logging.debug('Error occurred getting DEPs: %s\n %s\n',
431 stdout.getvalue(), stderr.getvalue())
432 raise error.TestFail('Error occurred while getting DEPs.')
433
434 # Download DEPs to DUT.
435 # send_file() relies on rsync over ssh. Couldn't be better.
436 stdout_str = stdout.getvalue()
437 stdout.close()
438 stderr.close()
439 for dep in stdout_str.split():
440 src = os.path.join(self._telemetry_path, dep)
441 dst = os.path.join(DUT_CHROME_ROOT, dep)
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800442 if self._devserver:
443 logging.info('Copying: %s -> %s', src, dst)
444 utils.run('ssh %s rsync %s %s %s:%s' %
445 (devserver_hostname, DUT_RSYNC_OPTIONS, src,
446 self._host.hostname, dst))
447 else:
448 if not os.path.isfile(src):
449 raise error.TestFail('Error occurred while saving DEPs.')
450 logging.info('Copying: %s -> %s', src, dst)
451 dut.send_file(src, dst)