blob: 95ad5b5035ddce98cb742fdea925bf62c484ef1d [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',
51 'robohornet_pro',
52 'smoothness.top_25_smooth',
53 'smoothness.tough_animation_cases',
54 'smoothness.tough_canvas_cases',
55 'smoothness.tough_filters_cases',
56 'smoothness.tough_pinch_zoom_cases',
57 'smoothness.tough_scrolling_cases',
58 'smoothness.tough_webgl_cases',
59 'speedometer',
60 'startup.cold.blank_page',
61 'sunspider',
62 'tab_switching.top_10',
Patrik Höglund36439d62016-11-14 13:15:36 +010063 'webrtc.peerconnection']
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080064
65# BLACK LIST
66# 'session_restore.cold.typical_25', # profile generator not implemented on
67 # CrOS.
Simran Basi833814b2013-01-29 13:13:43 -080068
69class TelemetryResult(object):
70 """Class to represent the results of a telemetry run.
71
72 This class represents the results of a telemetry run, whether it ran
73 successful, failed or had warnings.
74 """
75
76
77 def __init__(self, exit_code=0, stdout='', stderr=''):
78 """Initializes this TelemetryResultObject instance.
79
80 @param status: Status of the telemtry run.
81 @param stdout: Stdout of the telemetry run.
82 @param stderr: Stderr of the telemetry run.
83 """
84 if exit_code == 0:
85 self.status = SUCCESS_STATUS
86 else:
87 self.status = FAILED_STATUS
88
Simran Basi833814b2013-01-29 13:13:43 -080089 self._stdout = stdout
90 self._stderr = stderr
91 self.output = '\n'.join([stdout, stderr])
92
93
Simran Basi833814b2013-01-29 13:13:43 -080094class TelemetryRunner(object):
95 """Class responsible for telemetry for a given build.
96
97 This class will extract and install telemetry on the devserver and is
98 responsible for executing the telemetry benchmarks and returning their
99 output to the caller.
100 """
101
Ting-Yuan Huang85dcde82016-04-08 17:41:32 +0800102 def __init__(self, host, local=False, telemetry_on_dut=True):
Simran Basi833814b2013-01-29 13:13:43 -0800103 """Initializes this telemetry runner instance.
104
105 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -0800106
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800107 Basically, the following commands on the local pc on which test_that
108 will be executed, depending on the 4 possible combinations of
109 local x telemetry_on_dut:
110
111 local=True, telemetry_on_dut=False:
112 run_benchmark --browser=cros-chrome --remote=[dut] [test]
113
114 local=True, telemetry_on_dut=True:
115 ssh [dut] run_benchmark --browser=system [test]
116
117 local=False, telemetry_on_dut=False:
118 ssh [devserver] run_benchmark --browser=cros-chrome --remote=[dut] [test]
119
120 local=False, telemetry_on_dut=True:
121 ssh [devserver] ssh [dut] run_benchmark --browser=system [test]
122
Luis Lozano23ae3192013-11-08 16:22:46 -0800123 @param host: Host where the test will be run.
124 @param local: If set, no devserver will be used, test will be run
125 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800126 If not set, "ssh [devserver] " will be appended to test
127 commands.
128 @param telemetry_on_dut: If set, telemetry itself (the test harness)
129 will run on dut.
130 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -0800131 """
132 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700133 self._devserver = None
134 self._telemetry_path = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800135 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -0800136 # TODO (llozano crbug.com/324964). Remove conditional code.
137 # Use a class hierarchy instead.
138 if local:
139 self._setup_local_telemetry()
140 else:
141 self._setup_devserver_telemetry()
142
143 logging.debug('Telemetry Path: %s', self._telemetry_path)
144
145
146 def _setup_devserver_telemetry(self):
147 """Setup Telemetry to use the devserver."""
148 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800149 logging.debug('Grabbing build from AFE.')
150
Simran Basi5ace6f22016-01-06 17:30:44 -0800151 build = afe_utils.get_build(self._host)
Simran Basi833814b2013-01-29 13:13:43 -0800152 if not build:
153 logging.error('Unable to locate build label for host: %s.',
154 self._host.hostname)
155 raise error.AutotestError('Failed to grab build for host %s.' %
156 self._host.hostname)
157
158 logging.debug('Setting up telemetry for build: %s', build)
159
Ting-Yuan Huang5d459a22016-08-22 17:11:07 -0700160 self._devserver = dev_server.ImageServer.resolve(build,
161 hostname=self._host.hostname)
Simran Basicf81f682014-12-03 16:31:39 -0800162 self._devserver.stage_artifacts(build, ['autotest_packages'])
Simran Basi833814b2013-01-29 13:13:43 -0800163 self._telemetry_path = self._devserver.setup_telemetry(build=build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800164
165
166 def _setup_local_telemetry(self):
167 """Setup Telemetry to use local path to its sources.
168
169 First look for chrome source root, either externally mounted, or inside
170 the chroot. Prefer chrome-src-internal source tree to chrome-src.
171 """
172 TELEMETRY_DIR = 'src'
173 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700174 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800175
176 logging.debug('Setting up telemetry for local testing')
177
178 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700179 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800180 dir_list.extend(
181 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
182 if 'CHROME_ROOT' in os.environ:
183 dir_list.insert(0, os.environ['CHROME_ROOT'])
184
185 telemetry_src = ''
186 for dir in dir_list:
187 if os.path.exists(dir):
188 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
189 break
190 else:
191 raise error.TestError('Telemetry source directory not found.')
192
193 self._devserver = None
194 self._telemetry_path = telemetry_src
195
196
Luis Lozano814c7182015-09-08 11:20:47 -0700197 def _get_telemetry_cmd(self, script, test_or_benchmark, *args):
Luis Lozano23ae3192013-11-08 16:22:46 -0800198 """Build command to execute telemetry based on script and benchmark.
199
200 @param script: Telemetry script we want to run. For example:
201 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
202 @param test_or_benchmark: Name of the test or benchmark we want to run,
203 with the page_set (if required) as part of
204 the string.
Luis Lozano814c7182015-09-08 11:20:47 -0700205 @param args: additional list of arguments to pass to the script.
206
Luis Lozano23ae3192013-11-08 16:22:46 -0800207 @returns Full telemetry command to execute the script.
208 """
209 telemetry_cmd = []
210 if self._devserver:
Keith Haddow1e5c7012016-03-09 16:05:37 -0800211 devserver_hostname = dev_server.DevServer.get_server_name(
212 self._devserver.url())
Luis Lozano23ae3192013-11-08 16:22:46 -0800213 telemetry_cmd.extend(['ssh', devserver_hostname])
214
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800215 if self._telemetry_on_dut:
216 telemetry_cmd.extend(
217 ['ssh',
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800218 DUT_SSH_OPTIONS,
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800219 self._host.hostname,
220 'python',
221 script,
222 '--verbose',
223 '--output-format=chartjson',
224 '--output-dir=%s' % DUT_CHROME_ROOT,
225 '--browser=system'])
226 else:
227 telemetry_cmd.extend(
228 ['python',
229 script,
230 '--verbose',
231 '--browser=cros-chrome',
232 '--output-format=chartjson',
233 '--output-dir=%s' % self._telemetry_path,
234 '--remote=%s' % self._host.hostname])
Luis Lozano814c7182015-09-08 11:20:47 -0700235 telemetry_cmd.extend(args)
236 telemetry_cmd.append(test_or_benchmark)
237
Keith Haddow1e5c7012016-03-09 16:05:37 -0800238 return ' '.join(telemetry_cmd)
239
240
241 def _scp_telemetry_results_cmd(self, perf_results_dir):
242 """Build command to copy the telemetry results from the devserver.
243
244 @param perf_results_dir: directory path where test output is to be
245 collected.
246 @returns SCP command to copy the results json to the specified directory.
247 """
248 scp_cmd = []
Ricky Liangd186f3e2016-03-15 16:50:55 +0800249 devserver_hostname = ''
250 if perf_results_dir:
251 if self._devserver:
252 devserver_hostname = dev_server.DevServer.get_server_name(
253 self._devserver.url()) + ':'
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800254 if self._telemetry_on_dut:
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800255 src = ('root@%s:%s/results-chart.json' %
256 (self._host.hostname, DUT_CHROME_ROOT))
257 scp_cmd.extend(['scp', DUT_SCP_OPTIONS, src, perf_results_dir])
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800258 else:
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800259 src = ('%s%s/results-chart.json' %
260 (devserver_hostname, self._telemetry_path))
261 scp_cmd.extend(['scp', src, perf_results_dir])
Keith Haddow1e5c7012016-03-09 16:05:37 -0800262
263 return ' '.join(scp_cmd)
264
265
266 def _run_cmd(self, cmd):
267 """Execute an command in a external shell and capture the output.
268
269 @param cmd: String of is a valid shell command.
270
271 @returns The standard out, standard error and the integer exit code of
272 the executed command.
273 """
274 logging.debug('Running: %s', cmd)
275
276 output = StringIO.StringIO()
277 error_output = StringIO.StringIO()
278 exit_code = 0
279 try:
280 result = utils.run(cmd, stdout_tee=output,
281 stderr_tee=error_output,
282 timeout=TELEMETRY_TIMEOUT_MINS*60)
283 exit_code = result.exit_status
284 except error.CmdError as e:
285 logging.debug('Error occurred executing.')
286 exit_code = e.result_obj.exit_status
287
288 stdout = output.getvalue()
289 stderr = error_output.getvalue()
290 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
291 'stderr:%s', exit_code, stdout, stderr)
292 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800293
294
Luis Lozano814c7182015-09-08 11:20:47 -0700295 def _run_telemetry(self, script, test_or_benchmark, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800296 """Runs telemetry on a dut.
297
298 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800299 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800300 @param test_or_benchmark: Name of the test or benchmark we want to run,
301 with the page_set (if required) as part of the
302 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700303 @param args: additional list of arguments to pass to the script.
Simran Basi833814b2013-01-29 13:13:43 -0800304
305 @returns A TelemetryResult Instance with the results of this telemetry
306 execution.
307 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700308 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800309
Luis Lozano814c7182015-09-08 11:20:47 -0700310 telemetry_cmd = self._get_telemetry_cmd(script,
311 test_or_benchmark,
312 *args)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800313 logging.debug('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800314
Keith Haddow1e5c7012016-03-09 16:05:37 -0800315 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800316
317 return TelemetryResult(exit_code=exit_code, stdout=stdout,
318 stderr=stderr)
319
320
Keith Haddow1e5c7012016-03-09 16:05:37 -0800321 def _run_scp(self, perf_results_dir):
322 """Runs telemetry on a dut.
323
324 @param perf_results_dir: The local directory that results are being
325 collected.
326 """
327 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir)
328 logging.debug('Retrieving Results: %s', scp_cmd)
329
330 self._run_cmd(scp_cmd)
331
332
Luis Lozano814c7182015-09-08 11:20:47 -0700333 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700334 """Runs a telemetry test on a dut.
335
336 @param script: Which telemetry test script we want to run. Can be
337 telemetry's base test script or the Chrome OS specific
338 test script.
339 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700340 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700341
342 @returns A TelemetryResult Instance with the results of this telemetry
343 execution.
344 """
345 logging.debug('Running telemetry test: %s', test)
346 telemetry_script = os.path.join(self._telemetry_path, script)
Luis Lozano814c7182015-09-08 11:20:47 -0700347 result = self._run_telemetry(telemetry_script, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700348 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700349 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700350 return result
351
352
Luis Lozano814c7182015-09-08 11:20:47 -0700353 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800354 """Runs a telemetry test on a dut.
355
356 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700357 @param args: additional list of arguments to pass to the telemetry
358 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800359
360 @returns A TelemetryResult Instance with the results of this telemetry
361 execution.
362 """
Luis Lozano814c7182015-09-08 11:20:47 -0700363 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700364
365
Luis Lozano814c7182015-09-08 11:20:47 -0700366 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None,
367 *args):
Simran Basi833814b2013-01-29 13:13:43 -0800368 """Runs a telemetry benchmark on a dut.
369
370 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800371 @param perf_value_writer: Should be an instance with the function
372 output_perf_value(), if None, no perf value
373 will be written. Typically this will be the
374 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700375 @param args: additional list of arguments to pass to the telemetry
376 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800377
378 @returns A TelemetryResult Instance with the results of this telemetry
379 execution.
380 """
Dave Tu6a404e62013-11-05 15:54:48 -0800381 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800382
383 if benchmark not in ON_DUT_WHITE_LIST:
384 self._telemetry_on_dut = False
385
386 if self._telemetry_on_dut:
387 telemetry_script = os.path.join(DUT_CHROME_ROOT,
388 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
389 self._ensure_deps(self._host, benchmark)
390 else:
391 telemetry_script = os.path.join(self._telemetry_path,
392 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
393
Luis Lozano814c7182015-09-08 11:20:47 -0700394 result = self._run_telemetry(telemetry_script, benchmark, *args)
Simran Basi833814b2013-01-29 13:13:43 -0800395
396 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800397 raise error.TestWarn('Telemetry Benchmark: %s'
398 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800399 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800400 raise error.TestFail('Telemetry Benchmark: %s'
401 ' failed to run.' % benchmark)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800402 if perf_value_writer:
403 self._run_scp(perf_value_writer.resultsdir)
Simran Basi833814b2013-01-29 13:13:43 -0800404 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800405
406 def _ensure_deps(self, dut, test_name):
407 """
408 Ensure the dependencies are locally available on DUT.
409
410 @param dut: The autotest host object representing DUT.
411 @param test_name: Name of the telemetry test.
412 """
413 # Get DEPs using host's telemetry.
414 format_string = ('python %s/tools/perf/fetch_benchmark_deps.py %s')
415 command = format_string % (self._telemetry_path, test_name)
416 stdout = StringIO.StringIO()
417 stderr = StringIO.StringIO()
418
419 if self._devserver:
420 devserver_hostname = self._devserver.url().split(
421 'http://')[1].split(':')[0]
422 command = 'ssh %s %s' % (devserver_hostname, command)
423
424 logging.info('Getting DEPs: %s', command)
425 try:
426 result = utils.run(command, stdout_tee=stdout,
427 stderr_tee=stderr)
428 except error.CmdError as e:
429 logging.debug('Error occurred getting DEPs: %s\n %s\n',
430 stdout.getvalue(), stderr.getvalue())
431 raise error.TestFail('Error occurred while getting DEPs.')
432
433 # Download DEPs to DUT.
434 # send_file() relies on rsync over ssh. Couldn't be better.
435 stdout_str = stdout.getvalue()
436 stdout.close()
437 stderr.close()
438 for dep in stdout_str.split():
439 src = os.path.join(self._telemetry_path, dep)
440 dst = os.path.join(DUT_CHROME_ROOT, dep)
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800441 if self._devserver:
442 logging.info('Copying: %s -> %s', src, dst)
443 utils.run('ssh %s rsync %s %s %s:%s' %
444 (devserver_hostname, DUT_RSYNC_OPTIONS, src,
445 self._host.hostname, dst))
446 else:
447 if not os.path.isfile(src):
448 raise error.TestFail('Error occurred while saving DEPs.')
449 logging.info('Copying: %s -> %s', src, dst)
450 dut.send_file(src, dst)