blob: 2adb15b9d71aac079bebaba2ad9082e95fba9e5a [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
18# Result Statuses
19SUCCESS_STATUS = 'SUCCESS'
20WARNING_STATUS = 'WARNING'
21FAILED_STATUS = 'FAILED'
22
23
24class TelemetryResult(object):
25 """Class to represent the results of a telemetry run.
26
27 This class represents the results of a telemetry run, whether it ran
28 successful, failed or had warnings.
29 """
30
31
32 def __init__(self, exit_code=0, stdout='', stderr=''):
33 """Initializes this TelemetryResultObject instance.
34
35 @param status: Status of the telemtry run.
36 @param stdout: Stdout of the telemetry run.
37 @param stderr: Stderr of the telemetry run.
38 """
39 if exit_code == 0:
40 self.status = SUCCESS_STATUS
41 else:
42 self.status = FAILED_STATUS
43
Simran Basi833814b2013-01-29 13:13:43 -080044 self._stdout = stdout
45 self._stderr = stderr
46 self.output = '\n'.join([stdout, stderr])
47
48
Simran Basi833814b2013-01-29 13:13:43 -080049class TelemetryRunner(object):
50 """Class responsible for telemetry for a given build.
51
52 This class will extract and install telemetry on the devserver and is
53 responsible for executing the telemetry benchmarks and returning their
54 output to the caller.
55 """
56
Luis Lozano23ae3192013-11-08 16:22:46 -080057 def __init__(self, host, local=False):
Simran Basi833814b2013-01-29 13:13:43 -080058 """Initializes this telemetry runner instance.
59
60 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -080061
62 @param host: Host where the test will be run.
63 @param local: If set, no devserver will be used, test will be run
64 locally.
Simran Basi833814b2013-01-29 13:13:43 -080065 """
66 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -070067 self._devserver = None
68 self._telemetry_path = None
Luis Lozano23ae3192013-11-08 16:22:46 -080069 # TODO (llozano crbug.com/324964). Remove conditional code.
70 # Use a class hierarchy instead.
71 if local:
72 self._setup_local_telemetry()
73 else:
74 self._setup_devserver_telemetry()
75
76 logging.debug('Telemetry Path: %s', self._telemetry_path)
77
78
79 def _setup_devserver_telemetry(self):
80 """Setup Telemetry to use the devserver."""
81 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -080082 logging.debug('Grabbing build from AFE.')
83
Simran Basi5ace6f22016-01-06 17:30:44 -080084 build = afe_utils.get_build(self._host)
Simran Basi833814b2013-01-29 13:13:43 -080085 if not build:
86 logging.error('Unable to locate build label for host: %s.',
87 self._host.hostname)
88 raise error.AutotestError('Failed to grab build for host %s.' %
89 self._host.hostname)
90
91 logging.debug('Setting up telemetry for build: %s', build)
92
93 self._devserver = dev_server.ImageServer.resolve(build)
Simran Basicf81f682014-12-03 16:31:39 -080094 self._devserver.stage_artifacts(build, ['autotest_packages'])
Simran Basi833814b2013-01-29 13:13:43 -080095 self._telemetry_path = self._devserver.setup_telemetry(build=build)
Luis Lozano23ae3192013-11-08 16:22:46 -080096
97
98 def _setup_local_telemetry(self):
99 """Setup Telemetry to use local path to its sources.
100
101 First look for chrome source root, either externally mounted, or inside
102 the chroot. Prefer chrome-src-internal source tree to chrome-src.
103 """
104 TELEMETRY_DIR = 'src'
105 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700106 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800107
108 logging.debug('Setting up telemetry for local testing')
109
110 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700111 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800112 dir_list.extend(
113 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
114 if 'CHROME_ROOT' in os.environ:
115 dir_list.insert(0, os.environ['CHROME_ROOT'])
116
117 telemetry_src = ''
118 for dir in dir_list:
119 if os.path.exists(dir):
120 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
121 break
122 else:
123 raise error.TestError('Telemetry source directory not found.')
124
125 self._devserver = None
126 self._telemetry_path = telemetry_src
127
128
Luis Lozano814c7182015-09-08 11:20:47 -0700129 def _get_telemetry_cmd(self, script, test_or_benchmark, *args):
Luis Lozano23ae3192013-11-08 16:22:46 -0800130 """Build command to execute telemetry based on script and benchmark.
131
132 @param script: Telemetry script we want to run. For example:
133 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
134 @param test_or_benchmark: Name of the test or benchmark we want to run,
135 with the page_set (if required) as part of
136 the string.
Luis Lozano814c7182015-09-08 11:20:47 -0700137 @param args: additional list of arguments to pass to the script.
138
Luis Lozano23ae3192013-11-08 16:22:46 -0800139 @returns Full telemetry command to execute the script.
140 """
141 telemetry_cmd = []
142 if self._devserver:
Keith Haddow1e5c7012016-03-09 16:05:37 -0800143 devserver_hostname = dev_server.DevServer.get_server_name(
144 self._devserver.url())
Luis Lozano23ae3192013-11-08 16:22:46 -0800145 telemetry_cmd.extend(['ssh', devserver_hostname])
146
147 telemetry_cmd.extend(
148 ['python',
149 script,
Ilja H. Friedel6965bd82014-05-20 18:29:15 -0700150 '--verbose',
Luis Lozano23ae3192013-11-08 16:22:46 -0800151 '--browser=cros-chrome',
Keith Haddow1e5c7012016-03-09 16:05:37 -0800152 '--output-format=chartjson',
153 '--output-dir=%s' % self._telemetry_path,
Luis Lozano814c7182015-09-08 11:20:47 -0700154 '--remote=%s' % self._host.hostname])
155 telemetry_cmd.extend(args)
156 telemetry_cmd.append(test_or_benchmark)
157
Keith Haddow1e5c7012016-03-09 16:05:37 -0800158 return ' '.join(telemetry_cmd)
159
160
161 def _scp_telemetry_results_cmd(self, perf_results_dir):
162 """Build command to copy the telemetry results from the devserver.
163
164 @param perf_results_dir: directory path where test output is to be
165 collected.
166 @returns SCP command to copy the results json to the specified directory.
167 """
168 scp_cmd = []
169 if self._devserver and perf_results_dir:
170 devserver_hostname = dev_server.DevServer.get_server_name(
171 self._devserver.url())
172 scp_cmd.extend(['scp',
173 '%s:%s/results-chart.json' % (
174 devserver_hostname, self._telemetry_path),
175 perf_results_dir])
176
177 return ' '.join(scp_cmd)
178
179
180 def _run_cmd(self, cmd):
181 """Execute an command in a external shell and capture the output.
182
183 @param cmd: String of is a valid shell command.
184
185 @returns The standard out, standard error and the integer exit code of
186 the executed command.
187 """
188 logging.debug('Running: %s', cmd)
189
190 output = StringIO.StringIO()
191 error_output = StringIO.StringIO()
192 exit_code = 0
193 try:
194 result = utils.run(cmd, stdout_tee=output,
195 stderr_tee=error_output,
196 timeout=TELEMETRY_TIMEOUT_MINS*60)
197 exit_code = result.exit_status
198 except error.CmdError as e:
199 logging.debug('Error occurred executing.')
200 exit_code = e.result_obj.exit_status
201
202 stdout = output.getvalue()
203 stderr = error_output.getvalue()
204 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
205 'stderr:%s', exit_code, stdout, stderr)
206 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800207
208
Luis Lozano814c7182015-09-08 11:20:47 -0700209 def _run_telemetry(self, script, test_or_benchmark, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800210 """Runs telemetry on a dut.
211
212 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800213 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800214 @param test_or_benchmark: Name of the test or benchmark we want to run,
215 with the page_set (if required) as part of the
216 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700217 @param args: additional list of arguments to pass to the script.
Simran Basi833814b2013-01-29 13:13:43 -0800218
219 @returns A TelemetryResult Instance with the results of this telemetry
220 execution.
221 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700222 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800223
Luis Lozano814c7182015-09-08 11:20:47 -0700224 telemetry_cmd = self._get_telemetry_cmd(script,
225 test_or_benchmark,
226 *args)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800227 logging.debug('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800228
Keith Haddow1e5c7012016-03-09 16:05:37 -0800229 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800230
231 return TelemetryResult(exit_code=exit_code, stdout=stdout,
232 stderr=stderr)
233
234
Keith Haddow1e5c7012016-03-09 16:05:37 -0800235 def _run_scp(self, perf_results_dir):
236 """Runs telemetry on a dut.
237
238 @param perf_results_dir: The local directory that results are being
239 collected.
240 """
241 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir)
242 logging.debug('Retrieving Results: %s', scp_cmd)
243
244 self._run_cmd(scp_cmd)
245
246
Luis Lozano814c7182015-09-08 11:20:47 -0700247 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700248 """Runs a telemetry test on a dut.
249
250 @param script: Which telemetry test script we want to run. Can be
251 telemetry's base test script or the Chrome OS specific
252 test script.
253 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700254 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700255
256 @returns A TelemetryResult Instance with the results of this telemetry
257 execution.
258 """
259 logging.debug('Running telemetry test: %s', test)
260 telemetry_script = os.path.join(self._telemetry_path, script)
Luis Lozano814c7182015-09-08 11:20:47 -0700261 result = self._run_telemetry(telemetry_script, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700262 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700263 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700264 return result
265
266
Luis Lozano814c7182015-09-08 11:20:47 -0700267 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800268 """Runs a telemetry test on a dut.
269
270 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700271 @param args: additional list of arguments to pass to the telemetry
272 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800273
274 @returns A TelemetryResult Instance with the results of this telemetry
275 execution.
276 """
Luis Lozano814c7182015-09-08 11:20:47 -0700277 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700278
279
Luis Lozano814c7182015-09-08 11:20:47 -0700280 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None,
281 *args):
Simran Basi833814b2013-01-29 13:13:43 -0800282 """Runs a telemetry benchmark on a dut.
283
284 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800285 @param perf_value_writer: Should be an instance with the function
286 output_perf_value(), if None, no perf value
287 will be written. Typically this will be the
288 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700289 @param args: additional list of arguments to pass to the telemetry
290 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800291
292 @returns A TelemetryResult Instance with the results of this telemetry
293 execution.
294 """
Dave Tu6a404e62013-11-05 15:54:48 -0800295 logging.debug('Running telemetry benchmark: %s', benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800296 telemetry_script = os.path.join(self._telemetry_path,
297 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
Luis Lozano814c7182015-09-08 11:20:47 -0700298 result = self._run_telemetry(telemetry_script, benchmark, *args)
Simran Basi833814b2013-01-29 13:13:43 -0800299
300 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800301 raise error.TestWarn('Telemetry Benchmark: %s'
302 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800303 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800304 raise error.TestFail('Telemetry Benchmark: %s'
305 ' failed to run.' % benchmark)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800306 if perf_value_writer:
307 self._run_scp(perf_value_writer.resultsdir)
Simran Basi833814b2013-01-29 13:13:43 -0800308 return result