blob: 8c437219c33acbb521a9b026a34988740fcf9754 [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 Huange5b19132016-03-22 13:02:41 +080032
Simran Basi833814b2013-01-29 13:13:43 -080033# Result Statuses
34SUCCESS_STATUS = 'SUCCESS'
35WARNING_STATUS = 'WARNING'
36FAILED_STATUS = 'FAILED'
37
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080038# A list of benchmarks with that the telemetry test harness can run on dut.
39ON_DUT_WHITE_LIST = ['dromaeo.domcoreattr',
40 'dromaeo.domcoremodify',
41 'dromaeo.domcorequery',
42 'dromaeo.domcoretraverse',
43 'image_decoding.image_decoding_measurement',
44 'jetstream',
45 'kraken',
46 'memory.top_7_stress',
47 'octane',
48 'page_cycler.typical_25',
49 'robohornet_pro',
50 'smoothness.top_25_smooth',
51 'smoothness.tough_animation_cases',
52 'smoothness.tough_canvas_cases',
53 'smoothness.tough_filters_cases',
54 'smoothness.tough_pinch_zoom_cases',
55 'smoothness.tough_scrolling_cases',
56 'smoothness.tough_webgl_cases',
57 'speedometer',
58 'startup.cold.blank_page',
59 'sunspider',
60 'tab_switching.top_10',
61 'webrtc.webrtc_cases']
62
63# BLACK LIST
64# 'session_restore.cold.typical_25', # profile generator not implemented on
65 # CrOS.
Simran Basi833814b2013-01-29 13:13:43 -080066
67class TelemetryResult(object):
68 """Class to represent the results of a telemetry run.
69
70 This class represents the results of a telemetry run, whether it ran
71 successful, failed or had warnings.
72 """
73
74
75 def __init__(self, exit_code=0, stdout='', stderr=''):
76 """Initializes this TelemetryResultObject instance.
77
78 @param status: Status of the telemtry run.
79 @param stdout: Stdout of the telemetry run.
80 @param stderr: Stderr of the telemetry run.
81 """
82 if exit_code == 0:
83 self.status = SUCCESS_STATUS
84 else:
85 self.status = FAILED_STATUS
86
Simran Basi833814b2013-01-29 13:13:43 -080087 self._stdout = stdout
88 self._stderr = stderr
89 self.output = '\n'.join([stdout, stderr])
90
91
Simran Basi833814b2013-01-29 13:13:43 -080092class TelemetryRunner(object):
93 """Class responsible for telemetry for a given build.
94
95 This class will extract and install telemetry on the devserver and is
96 responsible for executing the telemetry benchmarks and returning their
97 output to the caller.
98 """
99
Ting-Yuan Huanga6898c62016-03-28 18:13:01 +0800100 def __init__(self, host, local=False, telemetry_on_dut=False):
Simran Basi833814b2013-01-29 13:13:43 -0800101 """Initializes this telemetry runner instance.
102
103 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -0800104
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800105 Basically, the following commands on the local pc on which test_that
106 will be executed, depending on the 4 possible combinations of
107 local x telemetry_on_dut:
108
109 local=True, telemetry_on_dut=False:
110 run_benchmark --browser=cros-chrome --remote=[dut] [test]
111
112 local=True, telemetry_on_dut=True:
113 ssh [dut] run_benchmark --browser=system [test]
114
115 local=False, telemetry_on_dut=False:
116 ssh [devserver] run_benchmark --browser=cros-chrome --remote=[dut] [test]
117
118 local=False, telemetry_on_dut=True:
119 ssh [devserver] ssh [dut] run_benchmark --browser=system [test]
120
Luis Lozano23ae3192013-11-08 16:22:46 -0800121 @param host: Host where the test will be run.
122 @param local: If set, no devserver will be used, test will be run
123 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800124 If not set, "ssh [devserver] " will be appended to test
125 commands.
126 @param telemetry_on_dut: If set, telemetry itself (the test harness)
127 will run on dut.
128 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -0800129 """
130 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700131 self._devserver = None
132 self._telemetry_path = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800133 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -0800134 # TODO (llozano crbug.com/324964). Remove conditional code.
135 # Use a class hierarchy instead.
136 if local:
137 self._setup_local_telemetry()
138 else:
139 self._setup_devserver_telemetry()
140
141 logging.debug('Telemetry Path: %s', self._telemetry_path)
142
143
144 def _setup_devserver_telemetry(self):
145 """Setup Telemetry to use the devserver."""
146 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800147 logging.debug('Grabbing build from AFE.')
148
Simran Basi5ace6f22016-01-06 17:30:44 -0800149 build = afe_utils.get_build(self._host)
Simran Basi833814b2013-01-29 13:13:43 -0800150 if not build:
151 logging.error('Unable to locate build label for host: %s.',
152 self._host.hostname)
153 raise error.AutotestError('Failed to grab build for host %s.' %
154 self._host.hostname)
155
156 logging.debug('Setting up telemetry for build: %s', build)
157
158 self._devserver = dev_server.ImageServer.resolve(build)
Simran Basicf81f682014-12-03 16:31:39 -0800159 self._devserver.stage_artifacts(build, ['autotest_packages'])
Simran Basi833814b2013-01-29 13:13:43 -0800160 self._telemetry_path = self._devserver.setup_telemetry(build=build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800161
162
163 def _setup_local_telemetry(self):
164 """Setup Telemetry to use local path to its sources.
165
166 First look for chrome source root, either externally mounted, or inside
167 the chroot. Prefer chrome-src-internal source tree to chrome-src.
168 """
169 TELEMETRY_DIR = 'src'
170 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700171 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800172
173 logging.debug('Setting up telemetry for local testing')
174
175 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700176 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800177 dir_list.extend(
178 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
179 if 'CHROME_ROOT' in os.environ:
180 dir_list.insert(0, os.environ['CHROME_ROOT'])
181
182 telemetry_src = ''
183 for dir in dir_list:
184 if os.path.exists(dir):
185 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
186 break
187 else:
188 raise error.TestError('Telemetry source directory not found.')
189
190 self._devserver = None
191 self._telemetry_path = telemetry_src
192
193
Luis Lozano814c7182015-09-08 11:20:47 -0700194 def _get_telemetry_cmd(self, script, test_or_benchmark, *args):
Luis Lozano23ae3192013-11-08 16:22:46 -0800195 """Build command to execute telemetry based on script and benchmark.
196
197 @param script: Telemetry script we want to run. For example:
198 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
199 @param test_or_benchmark: Name of the test or benchmark we want to run,
200 with the page_set (if required) as part of
201 the string.
Luis Lozano814c7182015-09-08 11:20:47 -0700202 @param args: additional list of arguments to pass to the script.
203
Luis Lozano23ae3192013-11-08 16:22:46 -0800204 @returns Full telemetry command to execute the script.
205 """
206 telemetry_cmd = []
207 if self._devserver:
Keith Haddow1e5c7012016-03-09 16:05:37 -0800208 devserver_hostname = dev_server.DevServer.get_server_name(
209 self._devserver.url())
Luis Lozano23ae3192013-11-08 16:22:46 -0800210 telemetry_cmd.extend(['ssh', devserver_hostname])
211
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800212 if self._telemetry_on_dut:
213 telemetry_cmd.extend(
214 ['ssh',
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800215 DUT_SSH_OPTIONS,
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800216 self._host.hostname,
217 'python',
218 script,
219 '--verbose',
220 '--output-format=chartjson',
221 '--output-dir=%s' % DUT_CHROME_ROOT,
222 '--browser=system'])
223 else:
224 telemetry_cmd.extend(
225 ['python',
226 script,
227 '--verbose',
228 '--browser=cros-chrome',
229 '--output-format=chartjson',
230 '--output-dir=%s' % self._telemetry_path,
231 '--remote=%s' % self._host.hostname])
Luis Lozano814c7182015-09-08 11:20:47 -0700232 telemetry_cmd.extend(args)
233 telemetry_cmd.append(test_or_benchmark)
234
Keith Haddow1e5c7012016-03-09 16:05:37 -0800235 return ' '.join(telemetry_cmd)
236
237
238 def _scp_telemetry_results_cmd(self, perf_results_dir):
239 """Build command to copy the telemetry results from the devserver.
240
241 @param perf_results_dir: directory path where test output is to be
242 collected.
243 @returns SCP command to copy the results json to the specified directory.
244 """
245 scp_cmd = []
Ricky Liangd186f3e2016-03-15 16:50:55 +0800246 devserver_hostname = ''
247 if perf_results_dir:
248 if self._devserver:
249 devserver_hostname = dev_server.DevServer.get_server_name(
250 self._devserver.url()) + ':'
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800251 if self._telemetry_on_dut:
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800252 src = ('root@%s:%s/results-chart.json' %
253 (self._host.hostname, DUT_CHROME_ROOT))
254 scp_cmd.extend(['scp', DUT_SCP_OPTIONS, src, perf_results_dir])
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800255 else:
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800256 src = ('%s%s/results-chart.json' %
257 (devserver_hostname, self._telemetry_path))
258 scp_cmd.extend(['scp', src, perf_results_dir])
Keith Haddow1e5c7012016-03-09 16:05:37 -0800259
260 return ' '.join(scp_cmd)
261
262
263 def _run_cmd(self, cmd):
264 """Execute an command in a external shell and capture the output.
265
266 @param cmd: String of is a valid shell command.
267
268 @returns The standard out, standard error and the integer exit code of
269 the executed command.
270 """
271 logging.debug('Running: %s', cmd)
272
273 output = StringIO.StringIO()
274 error_output = StringIO.StringIO()
275 exit_code = 0
276 try:
277 result = utils.run(cmd, stdout_tee=output,
278 stderr_tee=error_output,
279 timeout=TELEMETRY_TIMEOUT_MINS*60)
280 exit_code = result.exit_status
281 except error.CmdError as e:
282 logging.debug('Error occurred executing.')
283 exit_code = e.result_obj.exit_status
284
285 stdout = output.getvalue()
286 stderr = error_output.getvalue()
287 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
288 'stderr:%s', exit_code, stdout, stderr)
289 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800290
291
Luis Lozano814c7182015-09-08 11:20:47 -0700292 def _run_telemetry(self, script, test_or_benchmark, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800293 """Runs telemetry on a dut.
294
295 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800296 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800297 @param test_or_benchmark: Name of the test or benchmark we want to run,
298 with the page_set (if required) as part of the
299 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700300 @param args: additional list of arguments to pass to the script.
Simran Basi833814b2013-01-29 13:13:43 -0800301
302 @returns A TelemetryResult Instance with the results of this telemetry
303 execution.
304 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700305 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800306
Luis Lozano814c7182015-09-08 11:20:47 -0700307 telemetry_cmd = self._get_telemetry_cmd(script,
308 test_or_benchmark,
309 *args)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800310 logging.debug('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800311
Keith Haddow1e5c7012016-03-09 16:05:37 -0800312 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800313
314 return TelemetryResult(exit_code=exit_code, stdout=stdout,
315 stderr=stderr)
316
317
Keith Haddow1e5c7012016-03-09 16:05:37 -0800318 def _run_scp(self, perf_results_dir):
319 """Runs telemetry on a dut.
320
321 @param perf_results_dir: The local directory that results are being
322 collected.
323 """
324 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir)
325 logging.debug('Retrieving Results: %s', scp_cmd)
326
327 self._run_cmd(scp_cmd)
328
329
Luis Lozano814c7182015-09-08 11:20:47 -0700330 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700331 """Runs a telemetry test on a dut.
332
333 @param script: Which telemetry test script we want to run. Can be
334 telemetry's base test script or the Chrome OS specific
335 test script.
336 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700337 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700338
339 @returns A TelemetryResult Instance with the results of this telemetry
340 execution.
341 """
342 logging.debug('Running telemetry test: %s', test)
343 telemetry_script = os.path.join(self._telemetry_path, script)
Luis Lozano814c7182015-09-08 11:20:47 -0700344 result = self._run_telemetry(telemetry_script, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700345 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700346 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700347 return result
348
349
Luis Lozano814c7182015-09-08 11:20:47 -0700350 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800351 """Runs a telemetry test on a dut.
352
353 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700354 @param args: additional list of arguments to pass to the telemetry
355 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800356
357 @returns A TelemetryResult Instance with the results of this telemetry
358 execution.
359 """
Luis Lozano814c7182015-09-08 11:20:47 -0700360 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700361
362
Luis Lozano814c7182015-09-08 11:20:47 -0700363 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None,
364 *args):
Simran Basi833814b2013-01-29 13:13:43 -0800365 """Runs a telemetry benchmark on a dut.
366
367 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800368 @param perf_value_writer: Should be an instance with the function
369 output_perf_value(), if None, no perf value
370 will be written. Typically this will be the
371 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700372 @param args: additional list of arguments to pass to the telemetry
373 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800374
375 @returns A TelemetryResult Instance with the results of this telemetry
376 execution.
377 """
Dave Tu6a404e62013-11-05 15:54:48 -0800378 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800379
380 if benchmark not in ON_DUT_WHITE_LIST:
381 self._telemetry_on_dut = False
382
383 if self._telemetry_on_dut:
384 telemetry_script = os.path.join(DUT_CHROME_ROOT,
385 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
386 self._ensure_deps(self._host, benchmark)
387 else:
388 telemetry_script = os.path.join(self._telemetry_path,
389 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
390
Luis Lozano814c7182015-09-08 11:20:47 -0700391 result = self._run_telemetry(telemetry_script, benchmark, *args)
Simran Basi833814b2013-01-29 13:13:43 -0800392
393 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800394 raise error.TestWarn('Telemetry Benchmark: %s'
395 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800396 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800397 raise error.TestFail('Telemetry Benchmark: %s'
398 ' failed to run.' % benchmark)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800399 if perf_value_writer:
400 self._run_scp(perf_value_writer.resultsdir)
Simran Basi833814b2013-01-29 13:13:43 -0800401 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800402
403 def _ensure_deps(self, dut, test_name):
404 """
405 Ensure the dependencies are locally available on DUT.
406
407 @param dut: The autotest host object representing DUT.
408 @param test_name: Name of the telemetry test.
409 """
410 # Get DEPs using host's telemetry.
411 format_string = ('python %s/tools/perf/fetch_benchmark_deps.py %s')
412 command = format_string % (self._telemetry_path, test_name)
413 stdout = StringIO.StringIO()
414 stderr = StringIO.StringIO()
415
416 if self._devserver:
417 devserver_hostname = self._devserver.url().split(
418 'http://')[1].split(':')[0]
419 command = 'ssh %s %s' % (devserver_hostname, command)
420
421 logging.info('Getting DEPs: %s', command)
422 try:
423 result = utils.run(command, stdout_tee=stdout,
424 stderr_tee=stderr)
425 except error.CmdError as e:
426 logging.debug('Error occurred getting DEPs: %s\n %s\n',
427 stdout.getvalue(), stderr.getvalue())
428 raise error.TestFail('Error occurred while getting DEPs.')
429
430 # Download DEPs to DUT.
431 # send_file() relies on rsync over ssh. Couldn't be better.
432 stdout_str = stdout.getvalue()
433 stdout.close()
434 stderr.close()
435 for dep in stdout_str.split():
436 src = os.path.join(self._telemetry_path, dep)
437 dst = os.path.join(DUT_CHROME_ROOT, dep)
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800438 if self._devserver:
439 logging.info('Copying: %s -> %s', src, dst)
440 utils.run('ssh %s rsync %s %s %s:%s' %
441 (devserver_hostname, DUT_RSYNC_OPTIONS, src,
442 self._host.hostname, dst))
443 else:
444 if not os.path.isfile(src):
445 raise error.TestFail('Error occurred while saving DEPs.')
446 logging.info('Copying: %s -> %s', src, dst)
447 dut.send_file(src, dst)