blob: 263d6962b2675336a5358f056dd3660c861106d5 [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 = []
Ricky Liangd186f3e2016-03-15 16:50:55 +0800169 devserver_hostname = ''
170 if perf_results_dir:
171 if self._devserver:
172 devserver_hostname = dev_server.DevServer.get_server_name(
173 self._devserver.url()) + ':'
Keith Haddow1e5c7012016-03-09 16:05:37 -0800174 scp_cmd.extend(['scp',
Ricky Liangd186f3e2016-03-15 16:50:55 +0800175 '%s%s/results-chart.json' % (
Keith Haddow1e5c7012016-03-09 16:05:37 -0800176 devserver_hostname, self._telemetry_path),
177 perf_results_dir])
178
179 return ' '.join(scp_cmd)
180
181
182 def _run_cmd(self, cmd):
183 """Execute an command in a external shell and capture the output.
184
185 @param cmd: String of is a valid shell command.
186
187 @returns The standard out, standard error and the integer exit code of
188 the executed command.
189 """
190 logging.debug('Running: %s', cmd)
191
192 output = StringIO.StringIO()
193 error_output = StringIO.StringIO()
194 exit_code = 0
195 try:
196 result = utils.run(cmd, stdout_tee=output,
197 stderr_tee=error_output,
198 timeout=TELEMETRY_TIMEOUT_MINS*60)
199 exit_code = result.exit_status
200 except error.CmdError as e:
201 logging.debug('Error occurred executing.')
202 exit_code = e.result_obj.exit_status
203
204 stdout = output.getvalue()
205 stderr = error_output.getvalue()
206 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
207 'stderr:%s', exit_code, stdout, stderr)
208 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800209
210
Luis Lozano814c7182015-09-08 11:20:47 -0700211 def _run_telemetry(self, script, test_or_benchmark, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800212 """Runs telemetry on a dut.
213
214 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800215 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800216 @param test_or_benchmark: Name of the test or benchmark we want to run,
217 with the page_set (if required) as part of the
218 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700219 @param args: additional list of arguments to pass to the script.
Simran Basi833814b2013-01-29 13:13:43 -0800220
221 @returns A TelemetryResult Instance with the results of this telemetry
222 execution.
223 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700224 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800225
Luis Lozano814c7182015-09-08 11:20:47 -0700226 telemetry_cmd = self._get_telemetry_cmd(script,
227 test_or_benchmark,
228 *args)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800229 logging.debug('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800230
Keith Haddow1e5c7012016-03-09 16:05:37 -0800231 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800232
233 return TelemetryResult(exit_code=exit_code, stdout=stdout,
234 stderr=stderr)
235
236
Keith Haddow1e5c7012016-03-09 16:05:37 -0800237 def _run_scp(self, perf_results_dir):
238 """Runs telemetry on a dut.
239
240 @param perf_results_dir: The local directory that results are being
241 collected.
242 """
243 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir)
244 logging.debug('Retrieving Results: %s', scp_cmd)
245
246 self._run_cmd(scp_cmd)
247
248
Luis Lozano814c7182015-09-08 11:20:47 -0700249 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700250 """Runs a telemetry test on a dut.
251
252 @param script: Which telemetry test script we want to run. Can be
253 telemetry's base test script or the Chrome OS specific
254 test script.
255 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700256 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700257
258 @returns A TelemetryResult Instance with the results of this telemetry
259 execution.
260 """
261 logging.debug('Running telemetry test: %s', test)
262 telemetry_script = os.path.join(self._telemetry_path, script)
Luis Lozano814c7182015-09-08 11:20:47 -0700263 result = self._run_telemetry(telemetry_script, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700264 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700265 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700266 return result
267
268
Luis Lozano814c7182015-09-08 11:20:47 -0700269 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800270 """Runs a telemetry test on a dut.
271
272 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700273 @param args: additional list of arguments to pass to the telemetry
274 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800275
276 @returns A TelemetryResult Instance with the results of this telemetry
277 execution.
278 """
Luis Lozano814c7182015-09-08 11:20:47 -0700279 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700280
281
Luis Lozano814c7182015-09-08 11:20:47 -0700282 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None,
283 *args):
Simran Basi833814b2013-01-29 13:13:43 -0800284 """Runs a telemetry benchmark on a dut.
285
286 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800287 @param perf_value_writer: Should be an instance with the function
288 output_perf_value(), if None, no perf value
289 will be written. Typically this will be the
290 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700291 @param args: additional list of arguments to pass to the telemetry
292 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800293
294 @returns A TelemetryResult Instance with the results of this telemetry
295 execution.
296 """
Dave Tu6a404e62013-11-05 15:54:48 -0800297 logging.debug('Running telemetry benchmark: %s', benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800298 telemetry_script = os.path.join(self._telemetry_path,
299 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
Luis Lozano814c7182015-09-08 11:20:47 -0700300 result = self._run_telemetry(telemetry_script, benchmark, *args)
Simran Basi833814b2013-01-29 13:13:43 -0800301
302 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800303 raise error.TestWarn('Telemetry Benchmark: %s'
304 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800305 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800306 raise error.TestFail('Telemetry Benchmark: %s'
307 ' failed to run.' % benchmark)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800308 if perf_value_writer:
309 self._run_scp(perf_value_writer.resultsdir)
Simran Basi833814b2013-01-29 13:13:43 -0800310 return result