blob: 50906e4271669fd7342c9ed9f033998a2f4c4ecc [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
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +08005import json
Simran Basi833814b2013-01-29 13:13:43 -08006import logging
7import os
Simran Basi833814b2013-01-29 13:13:43 -08008import StringIO
9
Simran Basi833814b2013-01-29 13:13:43 -080010from autotest_lib.client.common_lib import error, utils
11from autotest_lib.client.common_lib.cros import dev_server
12
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'
Gurchetan Singhfaf75e92017-04-17 18:09:44 -070016TELEMETRY_RUN_GPU_TESTS_SCRIPT = 'content/test/gpu/run_gpu_integration_test.py'
Mao Huangc9642d72017-09-28 16:50:02 +080017TELEMETRY_TIMEOUT_MINS = 150
Simran Basi833814b2013-01-29 13:13:43 -080018
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080019DUT_CHROME_ROOT = '/usr/local/telemetry/src'
20
Simran Basi833814b2013-01-29 13:13:43 -080021# Result Statuses
22SUCCESS_STATUS = 'SUCCESS'
23WARNING_STATUS = 'WARNING'
24FAILED_STATUS = 'FAILED'
25
Kuo-Hsin Yang07da7b62018-08-08 16:56:06 +080026# A list of telemetry tests that cannot run on dut.
27ON_DUT_BLACKLIST = ['system_health.memory_desktop']
Simran Basi833814b2013-01-29 13:13:43 -080028
29class TelemetryResult(object):
30 """Class to represent the results of a telemetry run.
31
32 This class represents the results of a telemetry run, whether it ran
33 successful, failed or had warnings.
34 """
35
36
37 def __init__(self, exit_code=0, stdout='', stderr=''):
38 """Initializes this TelemetryResultObject instance.
39
40 @param status: Status of the telemtry run.
41 @param stdout: Stdout of the telemetry run.
42 @param stderr: Stderr of the telemetry run.
43 """
44 if exit_code == 0:
45 self.status = SUCCESS_STATUS
46 else:
47 self.status = FAILED_STATUS
48
Simran Basi833814b2013-01-29 13:13:43 -080049 self._stdout = stdout
50 self._stderr = stderr
51 self.output = '\n'.join([stdout, stderr])
52
53
Simran Basi833814b2013-01-29 13:13:43 -080054class TelemetryRunner(object):
55 """Class responsible for telemetry for a given build.
56
57 This class will extract and install telemetry on the devserver and is
58 responsible for executing the telemetry benchmarks and returning their
59 output to the caller.
60 """
61
Ting-Yuan Huang85dcde82016-04-08 17:41:32 +080062 def __init__(self, host, local=False, telemetry_on_dut=True):
Simran Basi833814b2013-01-29 13:13:43 -080063 """Initializes this telemetry runner instance.
64
65 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -080066
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080067 Basically, the following commands on the local pc on which test_that
68 will be executed, depending on the 4 possible combinations of
69 local x telemetry_on_dut:
70
71 local=True, telemetry_on_dut=False:
72 run_benchmark --browser=cros-chrome --remote=[dut] [test]
73
74 local=True, telemetry_on_dut=True:
75 ssh [dut] run_benchmark --browser=system [test]
76
77 local=False, telemetry_on_dut=False:
78 ssh [devserver] run_benchmark --browser=cros-chrome --remote=[dut] [test]
79
80 local=False, telemetry_on_dut=True:
81 ssh [devserver] ssh [dut] run_benchmark --browser=system [test]
82
Luis Lozano23ae3192013-11-08 16:22:46 -080083 @param host: Host where the test will be run.
84 @param local: If set, no devserver will be used, test will be run
85 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080086 If not set, "ssh [devserver] " will be appended to test
87 commands.
88 @param telemetry_on_dut: If set, telemetry itself (the test harness)
89 will run on dut.
90 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -080091 """
92 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -070093 self._devserver = None
94 self._telemetry_path = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080095 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -080096 # TODO (llozano crbug.com/324964). Remove conditional code.
97 # Use a class hierarchy instead.
98 if local:
99 self._setup_local_telemetry()
100 else:
101 self._setup_devserver_telemetry()
102
103 logging.debug('Telemetry Path: %s', self._telemetry_path)
104
105
106 def _setup_devserver_telemetry(self):
107 """Setup Telemetry to use the devserver."""
108 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800109 logging.debug('Grabbing build from AFE.')
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800110 info = self._host.host_info_store.get()
111 if not info.build:
Simran Basi833814b2013-01-29 13:13:43 -0800112 logging.error('Unable to locate build label for host: %s.',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800113 self._host.host_port)
Simran Basi833814b2013-01-29 13:13:43 -0800114 raise error.AutotestError('Failed to grab build for host %s.' %
Dean Liaoe3e75f62017-11-14 10:36:43 +0800115 self._host.host_port)
Simran Basi833814b2013-01-29 13:13:43 -0800116
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800117 logging.debug('Setting up telemetry for build: %s', info.build)
Simran Basi833814b2013-01-29 13:13:43 -0800118
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800119 self._devserver = dev_server.ImageServer.resolve(
120 info.build, hostname=self._host.hostname)
121 self._devserver.stage_artifacts(info.build, ['autotest_packages'])
122 self._telemetry_path = self._devserver.setup_telemetry(build=info.build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800123
124
125 def _setup_local_telemetry(self):
126 """Setup Telemetry to use local path to its sources.
127
128 First look for chrome source root, either externally mounted, or inside
129 the chroot. Prefer chrome-src-internal source tree to chrome-src.
130 """
131 TELEMETRY_DIR = 'src'
132 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700133 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800134
135 logging.debug('Setting up telemetry for local testing')
136
137 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700138 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800139 dir_list.extend(
140 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
141 if 'CHROME_ROOT' in os.environ:
142 dir_list.insert(0, os.environ['CHROME_ROOT'])
143
144 telemetry_src = ''
145 for dir in dir_list:
146 if os.path.exists(dir):
147 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
148 break
149 else:
150 raise error.TestError('Telemetry source directory not found.')
151
152 self._devserver = None
153 self._telemetry_path = telemetry_src
154
155
Luis Lozano814c7182015-09-08 11:20:47 -0700156 def _get_telemetry_cmd(self, script, test_or_benchmark, *args):
Luis Lozano23ae3192013-11-08 16:22:46 -0800157 """Build command to execute telemetry based on script and benchmark.
158
159 @param script: Telemetry script we want to run. For example:
160 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
161 @param test_or_benchmark: Name of the test or benchmark we want to run,
162 with the page_set (if required) as part of
163 the string.
Luis Lozano814c7182015-09-08 11:20:47 -0700164 @param args: additional list of arguments to pass to the script.
165
Luis Lozano23ae3192013-11-08 16:22:46 -0800166 @returns Full telemetry command to execute the script.
167 """
168 telemetry_cmd = []
169 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800170 devserver_hostname = self._devserver.hostname
Luis Lozano23ae3192013-11-08 16:22:46 -0800171 telemetry_cmd.extend(['ssh', devserver_hostname])
172
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800173 if self._telemetry_on_dut:
174 telemetry_cmd.extend(
Dean Liaoe3e75f62017-11-14 10:36:43 +0800175 [self._host.ssh_command(alive_interval=900,
176 connection_attempts=4),
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800177 'python',
178 script,
179 '--verbose',
180 '--output-format=chartjson',
181 '--output-dir=%s' % DUT_CHROME_ROOT,
182 '--browser=system'])
183 else:
184 telemetry_cmd.extend(
185 ['python',
186 script,
187 '--verbose',
188 '--browser=cros-chrome',
189 '--output-format=chartjson',
190 '--output-dir=%s' % self._telemetry_path,
Dean Liaoe3e75f62017-11-14 10:36:43 +0800191 '--remote=%s' % self._host.host_port])
Luis Lozano814c7182015-09-08 11:20:47 -0700192 telemetry_cmd.extend(args)
193 telemetry_cmd.append(test_or_benchmark)
194
Keith Haddow1e5c7012016-03-09 16:05:37 -0800195 return ' '.join(telemetry_cmd)
196
197
198 def _scp_telemetry_results_cmd(self, perf_results_dir):
199 """Build command to copy the telemetry results from the devserver.
200
201 @param perf_results_dir: directory path where test output is to be
202 collected.
203 @returns SCP command to copy the results json to the specified directory.
204 """
Dean Liaoe3e75f62017-11-14 10:36:43 +0800205 if not perf_results_dir:
206 return ''
207
208 scp_cmd = ['scp']
209 if self._telemetry_on_dut:
210 scp_cmd.append(self._host.make_ssh_options(alive_interval=900,
211 connection_attempts=4))
212 if not self._host.is_default_port:
213 scp_cmd.append('-P %d' % self._host.port)
214 src = 'root@%s:%s/results-chart.json' % (self._host.hostname,
215 DUT_CHROME_ROOT)
216 else:
217 devserver_hostname = ''
Ricky Liangd186f3e2016-03-15 16:50:55 +0800218 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800219 devserver_hostname = self._devserver.hostname + ':'
Dean Liaoe3e75f62017-11-14 10:36:43 +0800220 src = '%s%s/results-chart.json' % (devserver_hostname,
221 self._telemetry_path)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800222
Dean Liaoe3e75f62017-11-14 10:36:43 +0800223 scp_cmd.extend([src, perf_results_dir])
Keith Haddow1e5c7012016-03-09 16:05:37 -0800224 return ' '.join(scp_cmd)
225
226
227 def _run_cmd(self, cmd):
228 """Execute an command in a external shell and capture the output.
229
230 @param cmd: String of is a valid shell command.
231
232 @returns The standard out, standard error and the integer exit code of
233 the executed command.
234 """
235 logging.debug('Running: %s', cmd)
236
237 output = StringIO.StringIO()
238 error_output = StringIO.StringIO()
239 exit_code = 0
240 try:
241 result = utils.run(cmd, stdout_tee=output,
242 stderr_tee=error_output,
243 timeout=TELEMETRY_TIMEOUT_MINS*60)
244 exit_code = result.exit_status
245 except error.CmdError as e:
246 logging.debug('Error occurred executing.')
247 exit_code = e.result_obj.exit_status
248
249 stdout = output.getvalue()
250 stderr = error_output.getvalue()
251 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
252 'stderr:%s', exit_code, stdout, stderr)
253 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800254
255
Luis Lozano814c7182015-09-08 11:20:47 -0700256 def _run_telemetry(self, script, test_or_benchmark, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800257 """Runs telemetry on a dut.
258
259 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800260 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800261 @param test_or_benchmark: Name of the test or benchmark we want to run,
262 with the page_set (if required) as part of the
263 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700264 @param args: additional list of arguments to pass to the script.
Simran Basi833814b2013-01-29 13:13:43 -0800265
266 @returns A TelemetryResult Instance with the results of this telemetry
267 execution.
268 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700269 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800270
Luis Lozano814c7182015-09-08 11:20:47 -0700271 telemetry_cmd = self._get_telemetry_cmd(script,
272 test_or_benchmark,
273 *args)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800274 logging.debug('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800275
Keith Haddow1e5c7012016-03-09 16:05:37 -0800276 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800277
278 return TelemetryResult(exit_code=exit_code, stdout=stdout,
279 stderr=stderr)
280
281
Keith Haddow1e5c7012016-03-09 16:05:37 -0800282 def _run_scp(self, perf_results_dir):
283 """Runs telemetry on a dut.
284
285 @param perf_results_dir: The local directory that results are being
286 collected.
287 """
288 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir)
289 logging.debug('Retrieving Results: %s', scp_cmd)
Dean Liaoe4773c72017-11-09 16:15:38 +0800290 _, _, exit_code = self._run_cmd(scp_cmd)
291 if exit_code != 0:
292 raise error.TestFail('Unable to retrieve results.')
Keith Haddow1e5c7012016-03-09 16:05:37 -0800293
294
Luis Lozano814c7182015-09-08 11:20:47 -0700295 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700296 """Runs a telemetry test on a dut.
297
298 @param script: Which telemetry test script we want to run. Can be
299 telemetry's base test script or the Chrome OS specific
300 test script.
301 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700302 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700303
304 @returns A TelemetryResult Instance with the results of this telemetry
305 execution.
306 """
307 logging.debug('Running telemetry test: %s', test)
308 telemetry_script = os.path.join(self._telemetry_path, script)
Luis Lozano814c7182015-09-08 11:20:47 -0700309 result = self._run_telemetry(telemetry_script, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700310 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700311 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700312 return result
313
314
Luis Lozano814c7182015-09-08 11:20:47 -0700315 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800316 """Runs a telemetry test on a dut.
317
318 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700319 @param args: additional list of arguments to pass to the telemetry
320 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800321
322 @returns A TelemetryResult Instance with the results of this telemetry
323 execution.
324 """
Luis Lozano814c7182015-09-08 11:20:47 -0700325 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700326
327
Luis Lozano814c7182015-09-08 11:20:47 -0700328 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None,
329 *args):
Simran Basi833814b2013-01-29 13:13:43 -0800330 """Runs a telemetry benchmark on a dut.
331
332 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800333 @param perf_value_writer: Should be an instance with the function
334 output_perf_value(), if None, no perf value
335 will be written. Typically this will be the
336 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700337 @param args: additional list of arguments to pass to the telemetry
338 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800339
340 @returns A TelemetryResult Instance with the results of this telemetry
341 execution.
342 """
Dave Tu6a404e62013-11-05 15:54:48 -0800343 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800344
Kuo-Hsin Yang07da7b62018-08-08 16:56:06 +0800345 if benchmark in ON_DUT_BLACKLIST:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800346 self._telemetry_on_dut = False
347
348 if self._telemetry_on_dut:
349 telemetry_script = os.path.join(DUT_CHROME_ROOT,
350 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
351 self._ensure_deps(self._host, benchmark)
352 else:
353 telemetry_script = os.path.join(self._telemetry_path,
354 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
355
Luis Lozano814c7182015-09-08 11:20:47 -0700356 result = self._run_telemetry(telemetry_script, benchmark, *args)
Simran Basi833814b2013-01-29 13:13:43 -0800357
358 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800359 raise error.TestWarn('Telemetry Benchmark: %s'
360 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800361 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800362 raise error.TestFail('Telemetry Benchmark: %s'
363 ' failed to run.' % benchmark)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800364 if perf_value_writer:
365 self._run_scp(perf_value_writer.resultsdir)
Simran Basi833814b2013-01-29 13:13:43 -0800366 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800367
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700368
369 def run_gpu_integration_test(self, test, *args):
370 """Runs a gpu test on a dut.
371
372 @param test: Gpu test we want to run.
373 @param args: additional list of arguments to pass to the telemetry
374 execution script.
375
376 @returns A TelemetryResult instance with the results of this telemetry
377 execution.
378 """
379 script = os.path.join(DUT_CHROME_ROOT,
380 TELEMETRY_RUN_GPU_TESTS_SCRIPT)
381 cmd = []
382 if self._devserver:
383 devserver_hostname = self._devserver.hostname
384 cmd.extend(['ssh', devserver_hostname])
385
386 cmd.extend(
Dean Liaoe3e75f62017-11-14 10:36:43 +0800387 [self._host.ssh_command(alive_interval=900, connection_attempts=4),
388 'python', script])
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700389 cmd.extend(args)
390 cmd.append(test)
391 cmd = ' '.join(cmd)
392 stdout, stderr, exit_code = self._run_cmd(cmd)
393
394 return TelemetryResult(exit_code=exit_code, stdout=stdout,
395 stderr=stderr)
396
397
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800398 def _ensure_deps(self, dut, test_name):
399 """
400 Ensure the dependencies are locally available on DUT.
401
402 @param dut: The autotest host object representing DUT.
403 @param test_name: Name of the telemetry test.
404 """
405 # Get DEPs using host's telemetry.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800406 # Example output, fetch_benchmark_deps.py --output-deps=deps octane:
407 # {'octane': ['tools/perf/page_sets/data/octane_002.wprgo']}
408 perf_path = os.path.join(self._telemetry_path, 'tools', 'perf')
409 deps_path = os.path.join(perf_path, 'fetch_benchmark_deps_result.json')
410 fetch_path = os.path.join(perf_path, 'fetch_benchmark_deps.py')
411 format_fetch = ('python %s --output-deps=%s %s')
412 command_fetch = format_fetch % (fetch_path, deps_path, test_name)
413 command_get = 'cat %s' % deps_path
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800414
415 if self._devserver:
416 devserver_hostname = self._devserver.url().split(
417 'http://')[1].split(':')[0]
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800418 command_fetch = 'ssh %s %s' % (devserver_hostname, command_fetch)
419 command_get = 'ssh %s %s' % (devserver_hostname, command_get)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800420
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800421 logging.info('Getting DEPs: %s', command_fetch)
422 _, _, exit_code = self._run_cmd(command_fetch)
423 if exit_code != 0:
424 raise error.TestFail('Error occurred while fetching DEPs.')
425 stdout, _, exit_code = self._run_cmd(command_get)
426 if exit_code != 0:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800427 raise error.TestFail('Error occurred while getting DEPs.')
428
429 # Download DEPs to DUT.
430 # send_file() relies on rsync over ssh. Couldn't be better.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800431 deps = json.loads(stdout)
432 for dep in deps[test_name]:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800433 src = os.path.join(self._telemetry_path, dep)
434 dst = os.path.join(DUT_CHROME_ROOT, dep)
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800435 if self._devserver:
436 logging.info('Copying: %s -> %s', src, dst)
Chung-yih Wangfd8eb242017-12-09 19:23:04 +0800437 rsync_cmd = utils.sh_escape('rsync %s %s %s:%s' %
438 (self._host.rsync_options(), src,
439 self._host.hostname, dst))
440 utils.run('ssh %s "%s"' % (devserver_hostname, rsync_cmd))
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800441 else:
442 if not os.path.isfile(src):
443 raise error.TestFail('Error occurred while saving DEPs.')
444 logging.info('Copying: %s -> %s', src, dst)
445 dut.send_file(src, dst)