blob: b8d4e0ed011960c0d35fcc5c1fd4892d1bfa5e10 [file] [log] [blame]
Derek Beckett5fb683c2020-08-19 15:24:13 -07001# Lint as: python2, python3
Simran Basi833814b2013-01-29 13:13:43 -08002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Derek Beckett5fb683c2020-08-19 15:24:13 -07006from __future__ import absolute_import
7from __future__ import division
8from __future__ import print_function
9
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -080010import abc
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +080011import json
Simran Basi833814b2013-01-29 13:13:43 -080012import logging
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +080013import numbers
Simran Basi833814b2013-01-29 13:13:43 -080014import os
Chinglin Yu0adaf112020-01-20 14:48:20 +080015import tempfile
Derek Beckett5fb683c2020-08-19 15:24:13 -070016import six
Simran Basi833814b2013-01-29 13:13:43 -080017
Zhizhou Yang65d37042020-01-08 17:44:12 -080018import numpy
19
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -080020import common
Simran Basi833814b2013-01-29 13:13:43 -080021from autotest_lib.client.common_lib import error, utils
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -080022from autotest_lib.server.cros import telemetry_setup
Simran Basi833814b2013-01-29 13:13:43 -080023
Dave Tu6a404e62013-11-05 15:54:48 -080024TELEMETRY_RUN_BENCHMARKS_SCRIPT = 'tools/perf/run_benchmark'
Ilja H. Friedel086bc3f2014-02-27 22:17:55 -080025TELEMETRY_RUN_TESTS_SCRIPT = 'tools/telemetry/run_tests'
Gurchetan Singhfaf75e92017-04-17 18:09:44 -070026TELEMETRY_RUN_GPU_TESTS_SCRIPT = 'content/test/gpu/run_gpu_integration_test.py'
Mao Huangc9642d72017-09-28 16:50:02 +080027TELEMETRY_TIMEOUT_MINS = 150
Simran Basi833814b2013-01-29 13:13:43 -080028
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080029DUT_CHROME_ROOT = '/usr/local/telemetry/src'
30
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +080031CHART_JSON_RESULT = 'results-chart.json'
32HISTOGRAM_SET_RESULT = 'histograms.json'
Zhizhou Yang65d37042020-01-08 17:44:12 -080033PROFILE_ARTIFACTS = 'artifacts'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +080034
Simran Basi833814b2013-01-29 13:13:43 -080035# Result Statuses
36SUCCESS_STATUS = 'SUCCESS'
37WARNING_STATUS = 'WARNING'
38FAILED_STATUS = 'FAILED'
39
Kuo-Hsin Yang07da7b62018-08-08 16:56:06 +080040# A list of telemetry tests that cannot run on dut.
Derek Beckett6b7cc0f2020-11-30 13:15:26 -080041ON_DUT_BLOCKLIST = [
Zhizhou Yang65d37042020-01-08 17:44:12 -080042 'loading.desktop', # crbug/882299
43 'rendering.desktop', # crbug/882291
Kuo-Hsin Yange0915472018-09-10 10:36:16 +080044]
Simran Basi833814b2013-01-29 13:13:43 -080045
Zhizhou Yang65d37042020-01-08 17:44:12 -080046
Simran Basi833814b2013-01-29 13:13:43 -080047class TelemetryResult(object):
48 """Class to represent the results of a telemetry run.
49
50 This class represents the results of a telemetry run, whether it ran
51 successful, failed or had warnings.
52 """
53
Simran Basi833814b2013-01-29 13:13:43 -080054 def __init__(self, exit_code=0, stdout='', stderr=''):
55 """Initializes this TelemetryResultObject instance.
56
57 @param status: Status of the telemtry run.
58 @param stdout: Stdout of the telemetry run.
59 @param stderr: Stderr of the telemetry run.
60 """
61 if exit_code == 0:
62 self.status = SUCCESS_STATUS
63 else:
64 self.status = FAILED_STATUS
65
Simran Basi833814b2013-01-29 13:13:43 -080066 self._stdout = stdout
67 self._stderr = stderr
68 self.output = '\n'.join([stdout, stderr])
69
70
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -080071class TelemetryRunnerFactory(object):
72 """A factory class to determine TelemetryRunner subclass to be used.
73
74 The TelemetryRunner class, today, has various ways to execute the telemetry
75 test. The test can be executed locally (using a tool like test_that) or can
76 be executed in the Lab environment - for this usecase, either the drone OR
77 the devserver can be used.
78
79 A Factory class offloads this determination overhead from the clients. Users
80 of the TelemetryRunner class are highly encouraged to go through this
81 Factory class while determining the correct TelemetryRunner subclass.
82 """
83
84 def get_runner(self, host, local=False, telemetry_on_dut=True):
85 """Method to determine which TelemetryRunner subclass to use."""
86 if local:
87 return LocalTelemetryRunner(host, telemetry_on_dut)
88 else:
Sanika Kulkarni7ef23fe2021-02-10 15:34:07 -080089 return DroneTelemetryRunner(host, telemetry_on_dut)
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -080090
91
92class TelemetryRunner(six.with_metaclass(abc.ABCMeta, object)):
Simran Basi833814b2013-01-29 13:13:43 -080093 """Class responsible for telemetry for a given build.
94
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -080095 This class will extract and install telemetry environment and is
Simran Basi833814b2013-01-29 13:13:43 -080096 responsible for executing the telemetry benchmarks and returning their
97 output to the caller.
98 """
99
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -0800100 def __init__(self, host, telemetry_on_dut=True):
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
105 @param host: Host where the test will be run.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800106 @param telemetry_on_dut: If set, telemetry itself (the test harness)
107 will run on dut.
108 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -0800109 """
110 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700111 self._telemetry_path = None
Zhizhou Yang65d37042020-01-08 17:44:12 -0800112 self._perf_value_writer = None
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -0800113 self._setup_telemetry()
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800114 self._telemetry_on_dut = telemetry_on_dut
Chinglin Yu0adaf112020-01-20 14:48:20 +0800115 self._benchmark_deps = None
Luis Lozano23ae3192013-11-08 16:22:46 -0800116 logging.debug('Telemetry Path: %s', self._telemetry_path)
117
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -0800118 def __enter__(self):
119 """Called while entering context manager; does nothing."""
120 return self
Simran Basi833814b2013-01-29 13:13:43 -0800121
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -0800122 def __exit__(self, exc_type, exc_value, traceback):
123 """Called while exiting context manager."""
Simran Basi833814b2013-01-29 13:13:43 -0800124
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -0800125 @abc.abstractmethod
126 def _setup_telemetry(self):
127 """Set up telemetry environment."""
Luis Lozano23ae3192013-11-08 16:22:46 -0800128
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800129 def _get_telemetry_cmd(self, script, test_or_benchmark, output_format,
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700130 *args, **kwargs):
Luis Lozano23ae3192013-11-08 16:22:46 -0800131 """Build command to execute telemetry based on script and benchmark.
132
133 @param script: Telemetry script we want to run. For example:
134 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
135 @param test_or_benchmark: Name of the test or benchmark we want to run,
136 with the page_set (if required) as part of
137 the string.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800138 @param output_format: Format of the json result file: histogram or
139 chart-json.
Luis Lozano814c7182015-09-08 11:20:47 -0700140 @param args: additional list of arguments to pass to the script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700141 @param kwargs: additional list of keyword arguments to pass to the
142 script.
Luis Lozano814c7182015-09-08 11:20:47 -0700143
Luis Lozano23ae3192013-11-08 16:22:46 -0800144 @returns Full telemetry command to execute the script.
145 """
146 telemetry_cmd = []
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700147 no_verbose = kwargs.get('no_verbose', False)
148
Zhizhou Yang65d37042020-01-08 17:44:12 -0800149 output_dir = (DUT_CHROME_ROOT
150 if self._telemetry_on_dut else self._telemetry_path)
151 # Create a temp directory to hold single test run.
152 if self._perf_value_writer:
153 output_dir = os.path.join(
154 output_dir, self._perf_value_writer.tmpdir.strip('/'))
155
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800156 if self._telemetry_on_dut:
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800157 telemetry_cmd.extend([
Zhizhou Yang65d37042020-01-08 17:44:12 -0800158 self._host.ssh_command(
159 alive_interval=900, connection_attempts=4),
160 'python2',
161 script,
162 '--output-format=%s' % output_format,
163 '--output-dir=%s' % output_dir,
164 '--browser=system',
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800165 ])
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800166 else:
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800167 telemetry_cmd.extend([
Zhizhou Yang65d37042020-01-08 17:44:12 -0800168 'python2',
169 script,
170 '--browser=cros-chrome',
171 '--output-format=%s' % output_format,
172 '--output-dir=%s' % output_dir,
Tiancong Wangbb13ed32020-03-25 16:50:28 -0700173 '--remote=%s' % self._host.hostname,
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800174 ])
Tiancong Wangbb13ed32020-03-25 16:50:28 -0700175 if self._host.host_port != self._host.hostname:
176 # If the user specify a different port for the DUT, we should
177 # use different telemetry argument to set it up.
178 #
179 # e.g. When user is running experiments with ssh port
180 # forwarding, they specify remote as 127.0.0.1:2222. Now
181 # host_port is 127.0.0.1:2222 and hostname is 127.0.0.1
182 # port is 2222
183 telemetry_cmd.append('--remote-ssh-port=%s' % self._host.port)
184
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700185 if not no_verbose:
186 telemetry_cmd.append('--verbose')
Luis Lozano814c7182015-09-08 11:20:47 -0700187 telemetry_cmd.extend(args)
188 telemetry_cmd.append(test_or_benchmark)
189
Keith Haddow1e5c7012016-03-09 16:05:37 -0800190 return ' '.join(telemetry_cmd)
191
Zhizhou Yang65d37042020-01-08 17:44:12 -0800192 def _scp_telemetry_results_cmd(self, perf_results_dir, output_format,
193 artifacts):
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -0800194 """Build command to copy the telemetry results from the work directory.
Keith Haddow1e5c7012016-03-09 16:05:37 -0800195
196 @param perf_results_dir: directory path where test output is to be
197 collected.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800198 @param output_format: Format of the json result file: histogram or
199 chart-json.
200 @param artifacts: Whether we want to copy artifacts directory.
201
202 @returns SCP command to copy the results json to the specified
203 directory.
Keith Haddow1e5c7012016-03-09 16:05:37 -0800204 """
Dean Liaoe3e75f62017-11-14 10:36:43 +0800205 if not perf_results_dir:
206 return ''
207
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800208 output_filename = CHART_JSON_RESULT
209 if output_format == 'histograms':
210 output_filename = HISTOGRAM_SET_RESULT
Zhizhou Yang65d37042020-01-08 17:44:12 -0800211 scp_cmd = []
Dean Liaoe3e75f62017-11-14 10:36:43 +0800212 if self._telemetry_on_dut:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800213 scp_cmd.extend(['scp', '-r'])
214 scp_cmd.append(
215 self._host.make_ssh_options(
216 alive_interval=900, connection_attempts=4))
Dean Liaoe3e75f62017-11-14 10:36:43 +0800217 if not self._host.is_default_port:
218 scp_cmd.append('-P %d' % self._host.port)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800219 src = 'root@%s:%s' % (self._host.hostname, DUT_CHROME_ROOT)
Dean Liaoe3e75f62017-11-14 10:36:43 +0800220 else:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800221 # Use rsync --remove-source-file to move rather than copy from
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -0800222 # work dir. This is because each run will generate certain artifacts
Zhizhou Yang65d37042020-01-08 17:44:12 -0800223 # and will not be removed after, making result size getting larger.
224 # We don't do this for results on DUT because 1) rsync doesn't work
225 # 2) DUT will be reflashed frequently and no need to worry about
226 # result size.
227 scp_cmd.extend(['rsync', '-avz', '--remove-source-files'])
Sanika Kulkarni7ef23fe2021-02-10 15:34:07 -0800228 src = self._telemetry_path
Keith Haddow1e5c7012016-03-09 16:05:37 -0800229
Zhizhou Yang65d37042020-01-08 17:44:12 -0800230 if self._perf_value_writer:
231 src = os.path.join(src, self._perf_value_writer.tmpdir.strip('/'))
232
233 scp_cmd.append(os.path.join(src, output_filename))
234
235 # Copy artifacts back to result directory if needed.
236 if artifacts:
237 scp_cmd.append(os.path.join(src, PROFILE_ARTIFACTS))
238
239 scp_cmd.append(perf_results_dir)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800240 return ' '.join(scp_cmd)
241
Keith Haddow1e5c7012016-03-09 16:05:37 -0800242 def _run_cmd(self, cmd):
243 """Execute an command in a external shell and capture the output.
244
245 @param cmd: String of is a valid shell command.
246
247 @returns The standard out, standard error and the integer exit code of
248 the executed command.
249 """
250 logging.debug('Running: %s', cmd)
251
Derek Beckett5fb683c2020-08-19 15:24:13 -0700252 output = six.StringIO()
253 error_output = six.StringIO()
Keith Haddow1e5c7012016-03-09 16:05:37 -0800254 exit_code = 0
255 try:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800256 result = utils.run(
257 cmd,
258 stdout_tee=output,
259 stderr_tee=error_output,
260 timeout=TELEMETRY_TIMEOUT_MINS * 60)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800261 exit_code = result.exit_status
262 except error.CmdError as e:
263 logging.debug('Error occurred executing.')
264 exit_code = e.result_obj.exit_status
265
266 stdout = output.getvalue()
267 stderr = error_output.getvalue()
268 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
269 'stderr:%s', exit_code, stdout, stderr)
270 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800271
Zhizhou Yang65d37042020-01-08 17:44:12 -0800272 def _run_telemetry(self, script, test_or_benchmark, output_format, *args,
273 **kwargs):
Simran Basi833814b2013-01-29 13:13:43 -0800274 """Runs telemetry on a dut.
275
276 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800277 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800278 @param test_or_benchmark: Name of the test or benchmark we want to run,
279 with the page_set (if required) as part of the
280 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700281 @param args: additional list of arguments to pass to the script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700282 @param kwargs: additional list of keyword arguments to pass to the
283 script.
Simran Basi833814b2013-01-29 13:13:43 -0800284
285 @returns A TelemetryResult Instance with the results of this telemetry
286 execution.
287 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700288 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800289
Zhizhou Yang65d37042020-01-08 17:44:12 -0800290 telemetry_cmd = self._get_telemetry_cmd(script, test_or_benchmark,
291 output_format, *args, **kwargs)
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700292 logging.info('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800293
Keith Haddow1e5c7012016-03-09 16:05:37 -0800294 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800295
Zhizhou Yang65d37042020-01-08 17:44:12 -0800296 return TelemetryResult(
297 exit_code=exit_code, stdout=stdout, stderr=stderr)
Simran Basi833814b2013-01-29 13:13:43 -0800298
Zhizhou Yang65d37042020-01-08 17:44:12 -0800299 def _run_scp(self, perf_results_dir, output_format, artifacts=False):
Keith Haddow1e5c7012016-03-09 16:05:37 -0800300 """Runs telemetry on a dut.
301
302 @param perf_results_dir: The local directory that results are being
303 collected.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800304 @param output_format: Format of the json result file.
305 @param artifacts: Whether we want to copy artifacts directory.
Keith Haddow1e5c7012016-03-09 16:05:37 -0800306 """
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800307 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir,
Zhizhou Yang65d37042020-01-08 17:44:12 -0800308 output_format, artifacts)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800309 logging.debug('Retrieving Results: %s', scp_cmd)
Dean Liaoe4773c72017-11-09 16:15:38 +0800310 _, _, exit_code = self._run_cmd(scp_cmd)
311 if exit_code != 0:
312 raise error.TestFail('Unable to retrieve results.')
Keith Haddow1e5c7012016-03-09 16:05:37 -0800313
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800314 if output_format == 'histograms':
315 # Converts to chart json format.
316 input_filename = os.path.join(perf_results_dir,
317 HISTOGRAM_SET_RESULT)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800318 output_filename = os.path.join(perf_results_dir, CHART_JSON_RESULT)
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800319 histograms = json.loads(open(input_filename).read())
320 chartjson = TelemetryRunner.convert_chart_json(histograms)
321 with open(output_filename, 'w') as fout:
322 fout.write(json.dumps(chartjson, indent=2))
Keith Haddow1e5c7012016-03-09 16:05:37 -0800323
Luis Lozano814c7182015-09-08 11:20:47 -0700324 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700325 """Runs a telemetry test on a dut.
326
327 @param script: Which telemetry test script we want to run. Can be
328 telemetry's base test script or the Chrome OS specific
329 test script.
330 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700331 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700332
333 @returns A TelemetryResult Instance with the results of this telemetry
334 execution.
335 """
336 logging.debug('Running telemetry test: %s', test)
337 telemetry_script = os.path.join(self._telemetry_path, script)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800338 result = self._run_telemetry(telemetry_script, test, 'chartjson',
339 *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700340 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700341 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700342 return result
343
Luis Lozano814c7182015-09-08 11:20:47 -0700344 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800345 """Runs a telemetry test on a dut.
346
347 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700348 @param args: additional list of arguments to pass to the telemetry
349 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800350
351 @returns A TelemetryResult Instance with the results of this telemetry
352 execution.
353 """
Luis Lozano814c7182015-09-08 11:20:47 -0700354 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700355
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700356 def run_telemetry_benchmark(self,
357 benchmark,
358 perf_value_writer=None,
359 *args,
360 **kwargs):
Simran Basi833814b2013-01-29 13:13:43 -0800361 """Runs a telemetry benchmark on a dut.
362
363 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800364 @param perf_value_writer: Should be an instance with the function
365 output_perf_value(), if None, no perf value
366 will be written. Typically this will be the
367 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700368 @param args: additional list of arguments to pass to the telemetry
369 execution script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700370 @param kwargs: additional list of keyword arguments to pass to the
371 telemetry execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800372
373 @returns A TelemetryResult Instance with the results of this telemetry
374 execution.
375 """
Dave Tu6a404e62013-11-05 15:54:48 -0800376 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800377
Zhizhou Yang65d37042020-01-08 17:44:12 -0800378 self._perf_value_writer = perf_value_writer
379
Derek Beckett6b7cc0f2020-11-30 13:15:26 -0800380 if benchmark in ON_DUT_BLOCKLIST:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800381 self._telemetry_on_dut = False
382
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700383 output_format = kwargs.get('ex_output_format', '')
384
385 if not output_format:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800386 output_format = 'histograms'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800387
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800388 if self._telemetry_on_dut:
389 telemetry_script = os.path.join(DUT_CHROME_ROOT,
390 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
391 self._ensure_deps(self._host, benchmark)
392 else:
393 telemetry_script = os.path.join(self._telemetry_path,
394 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
395
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800396 result = self._run_telemetry(telemetry_script, benchmark,
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700397 output_format, *args, **kwargs)
Simran Basi833814b2013-01-29 13:13:43 -0800398
399 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800400 raise error.TestWarn('Telemetry Benchmark: %s'
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700401 ' exited with Warnings.\nOutput:\n%s\n' %
402 (benchmark, result.output))
403 elif result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800404 raise error.TestFail('Telemetry Benchmark: %s'
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700405 ' failed to run.\nOutput:\n%s\n' %
406 (benchmark, result.output))
407 elif '[ PASSED ] 0 tests.' in result.output:
408 raise error.TestWarn('Telemetry Benchmark: %s exited successfully,'
409 ' but no test actually passed.\nOutput\n%s\n'
410 % (benchmark, result.output))
Keith Haddow1e5c7012016-03-09 16:05:37 -0800411 if perf_value_writer:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800412 artifacts = kwargs.get('artifacts', False)
413 self._run_scp(perf_value_writer.resultsdir, output_format,
414 artifacts)
Simran Basi833814b2013-01-29 13:13:43 -0800415 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800416
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700417 def run_gpu_integration_test(self, test, *args):
418 """Runs a gpu test on a dut.
419
420 @param test: Gpu test we want to run.
421 @param args: additional list of arguments to pass to the telemetry
422 execution script.
423
Drew Davenport84395922018-09-10 10:42:37 -0600424 @returns A TelemetryResult instance with the results of this telemetry
425 execution.
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700426 """
Zhizhou Yang65d37042020-01-08 17:44:12 -0800427 script = os.path.join(DUT_CHROME_ROOT, TELEMETRY_RUN_GPU_TESTS_SCRIPT)
Sanika Kulkarni7ef23fe2021-02-10 15:34:07 -0800428 cmd = [
429 self._host.ssh_command(alive_interval=900,
430 connection_attempts=4), 'python2',
Zhizhou Yang65d37042020-01-08 17:44:12 -0800431 script
Sanika Kulkarni7ef23fe2021-02-10 15:34:07 -0800432 ]
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700433 cmd.extend(args)
434 cmd.append(test)
435 cmd = ' '.join(cmd)
436 stdout, stderr, exit_code = self._run_cmd(cmd)
437
Drew Davenport84395922018-09-10 10:42:37 -0600438 if exit_code:
439 raise error.TestFail('Gpu Integration Test: %s'
440 ' failed to run.' % test)
441
Zhizhou Yang65d37042020-01-08 17:44:12 -0800442 return TelemetryResult(
443 exit_code=exit_code, stdout=stdout, stderr=stderr)
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700444
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800445 def _ensure_deps(self, dut, test_name):
446 """
447 Ensure the dependencies are locally available on DUT.
448
449 @param dut: The autotest host object representing DUT.
450 @param test_name: Name of the telemetry test.
451 """
452 # Get DEPs using host's telemetry.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800453 # Example output, fetch_benchmark_deps.py --output-deps=deps octane:
454 # {'octane': ['tools/perf/page_sets/data/octane_002.wprgo']}
Zhizhou Yang65d37042020-01-08 17:44:12 -0800455 fetch_path = os.path.join(self._telemetry_path, 'tools', 'perf',
456 'fetch_benchmark_deps.py')
Chinglin Yu0adaf112020-01-20 14:48:20 +0800457 # Use a temporary file for |deps_path| to avoid race conditions. The
458 # created temporary file is assigned to |self._benchmark_deps| to make
459 # it valid until |self| is destroyed.
460 self._benchmark_deps = tempfile.NamedTemporaryFile(
Zhizhou Yang65d37042020-01-08 17:44:12 -0800461 prefix='fetch_benchmark_deps_result.', suffix='.json')
Chinglin Yu0adaf112020-01-20 14:48:20 +0800462 deps_path = self._benchmark_deps.name
Kuo-Hsin Yang66324262019-12-10 10:36:34 +0800463 format_fetch = ('python2 %s --output-deps=%s %s')
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800464 command_fetch = format_fetch % (fetch_path, deps_path, test_name)
465 command_get = 'cat %s' % deps_path
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800466
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800467 logging.info('Getting DEPs: %s', command_fetch)
468 _, _, exit_code = self._run_cmd(command_fetch)
469 if exit_code != 0:
470 raise error.TestFail('Error occurred while fetching DEPs.')
471 stdout, _, exit_code = self._run_cmd(command_get)
472 if exit_code != 0:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800473 raise error.TestFail('Error occurred while getting DEPs.')
474
475 # Download DEPs to DUT.
476 # send_file() relies on rsync over ssh. Couldn't be better.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800477 deps = json.loads(stdout)
478 for dep in deps[test_name]:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800479 src = os.path.join(self._telemetry_path, dep)
480 dst = os.path.join(DUT_CHROME_ROOT, dep)
Sanika Kulkarni7ef23fe2021-02-10 15:34:07 -0800481 if not os.path.isfile(src):
482 raise error.TestFail('Error occurred while saving DEPs.')
483 logging.info('Copying: %s -> %s', src, dst)
484 dut.send_file(src, dst)
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800485
486 @staticmethod
487 def convert_chart_json(histogram_set):
488 """
489 Convert from histogram set to chart json format.
490
491 @param histogram_set: result in histogram set format.
492
493 @returns result in chart json format.
494 """
495 value_map = {}
496
497 # Gets generic set values.
498 for obj in histogram_set:
499 if 'type' in obj and obj['type'] == 'GenericSet':
500 value_map[obj['guid']] = obj['values']
501
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800502 charts = {}
503 benchmark_name = ''
504 benchmark_desc = ''
505
506 # Checks the unit test for how this conversion works.
507 for obj in histogram_set:
508 if 'name' not in obj or 'sampleValues' not in obj:
509 continue
510 metric_name = obj['name']
511 diagnostics = obj['diagnostics']
Derek Beckett5fb683c2020-08-19 15:24:13 -0700512 if 'stories' in diagnostics:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800513 story_name = value_map[diagnostics['stories']][0]
514 else:
515 story_name = 'default'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800516 local_benchmark_name = value_map[diagnostics['benchmarks']][0]
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800517 if benchmark_name == '':
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800518 benchmark_name = local_benchmark_name
Derek Beckett5fb683c2020-08-19 15:24:13 -0700519 if 'benchmarkDescriptions' in diagnostics:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800520 benchmark_desc = value_map[
Zhizhou Yang65d37042020-01-08 17:44:12 -0800521 diagnostics['benchmarkDescriptions']][0]
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800522 if benchmark_name != local_benchmark_name:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800523 logging.warning(
524 'There are more than 1 benchmark names in the'
525 'result. old: %s, new: %s', benchmark_name,
526 local_benchmark_name)
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800527 continue
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800528
529 unit = obj['unit']
530 smaller_postfixes = ('_smallerIsBetter', '-')
531 bigger_postfixes = ('_biggerIsBetter', '+')
532 all_postfixes = smaller_postfixes + bigger_postfixes
533
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800534 improvement = 'up'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800535 for postfix in smaller_postfixes:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800536 if unit.endswith(postfix):
537 improvement = 'down'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800538 for postfix in all_postfixes:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800539 if unit.endswith(postfix):
540 unit = unit[:-len(postfix)]
541 break
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800542
543 if unit == 'unitless':
Zhizhou Yang65d37042020-01-08 17:44:12 -0800544 unit = 'score'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800545
Zhizhou Yang65d37042020-01-08 17:44:12 -0800546 values = [
547 x for x in obj['sampleValues']
548 if isinstance(x, numbers.Number)
549 ]
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800550 if metric_name not in charts:
551 charts[metric_name] = {}
552 charts[metric_name][story_name] = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800553 'improvement_direction': improvement,
554 'name': metric_name,
555 'std': numpy.std(values),
556 'type': 'list_of_scalar_values',
557 'units': unit,
558 'values': values
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800559 }
560
561 # Adds summaries.
562 for metric_name in charts:
563 values = []
564 metric_content = charts[metric_name]
565 for story_name in metric_content:
566 story_content = metric_content[story_name]
567 values += story_content['values']
568 metric_type = story_content['type']
569 units = story_content['units']
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800570 improvement = story_content['improvement_direction']
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800571 values.sort()
572 std = numpy.std(values)
573 metric_content['summary'] = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800574 'improvement_direction': improvement,
575 'name': metric_name,
576 'std': std,
577 'type': metric_type,
578 'units': units,
579 'values': values
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800580 }
581
582 benchmark_metadata = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800583 'description': benchmark_desc,
584 'name': benchmark_name,
585 'type': 'telemetry_benchmark'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800586 }
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800587 return {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800588 'benchmark_description': benchmark_desc,
589 'benchmark_metadata': benchmark_metadata,
590 'benchmark_name': benchmark_name,
591 'charts': charts,
592 'format_version': 1.0
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800593 }
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -0800594
595
596class LocalTelemetryRunner(TelemetryRunner):
597 """Specialized TelemetryRunner to handle local telemetry test runs."""
598
599 def __init__(self, *args, **kwargs):
600 """Initialize LocalTelemetryRunner.
601
602 The telemetry test will run locally. Depending on whether
603 telemetry_on_dut is True or False, there can be possible combinations
604 for the execution of this test:
605
606 telemetry_on_dut=False:
607 python2 run_benchmark --browser=cros-chrome --remote=[dut] [test]
608
609 telemetry_on_dut=True:
610 ssh [dut] python2 run_benchmark --browser=system [test]
611
612 @param args: The list of arguments to be passed. See Base class for a
613 complete list of accepted arguments.
614 @param kwargs: Any keyword arguments to be passed. See Base class for a
615 complete list of accepted keyword arguments.
616 """
617 super(LocalTelemetryRunner, self).__init__(*args, **kwargs)
618
619 def _setup_telemetry(self):
620 """Setup Telemetry to use local path to its sources.
621
622 First look for chrome source root, either externally mounted, or inside
623 the chroot. Prefer chrome-src-internal source tree to chrome-src.
624 """
625 TELEMETRY_DIR = 'src'
626 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
627 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
628
629 logging.debug('Setting up telemetry for local testing')
630
631 sources_list = ('chrome-src-internal', 'chrome-src')
632 dir_list = [CHROME_EXTERNAL_SRC]
633 dir_list.extend(
634 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
635 if 'CHROME_ROOT' in os.environ:
636 dir_list.insert(0, os.environ['CHROME_ROOT'])
637
638 telemetry_src = ''
639 for dir in dir_list:
640 if os.path.exists(dir):
641 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
642 break
643 else:
644 raise error.TestError('Telemetry source directory not found.')
645
646 self._telemetry_path = telemetry_src
647
648
649class DroneTelemetryRunner(TelemetryRunner):
650 """Handle telemetry test setup on the drone.
651
652 Users of this class are strongly advised to use this class as a context
653 manager. Since the setup for telemetry environment happens on the drone, it
654 is imperative that this setup be cleaned up once the test is done. Using
655 this class as a context manager will transfer the burden of clean up from
656 the user to Python.
657 """
658
659 def __init__(self, *args, **kwargs):
660 """Initialize DroneTelemetryRunner.
661
662 The telemetry test will run on the drone. Depending on whether
663 telemetry_on_dut is True or False, there can be possible combinations
664 for the execution of this test:
665
666 telemetry_on_dut=False:
667 python2 run_benchmark --browser=cros-chrome --remote=[dut] [test]
668
669 telemetry_on_dut=True:
670 ssh [dut] python2 run_benchmark --browser=system [test]
671
672 @param args: The list of arguments to be passed. See Base class for a
673 complete list of accepted arguments.
674 @param kwargs: Any keyword arguments to be passed. See Base class for a
675 complete list of accepted keyword arguments.
676 """
677 self._telemetry_setup = None
678 super(DroneTelemetryRunner, self).__init__(*args, **kwargs)
679
680 def __enter__(self):
681 """Called while entering context manager; does nothing."""
682 return self
683
684 def __exit__(self, exc_type, exc_value, traceback):
685 """Called while exiting context manager; cleans up temp files."""
686 logging.info('Cleaning up the telemetry environment on the drone.')
687 self._telemetry_setup.Cleanup()
688
689 def _setup_telemetry(self):
690 """Setup Telemetry on the drone."""
691 logging.debug('Setting up telemetry on the drone')
692 info = self._host.host_info_store.get()
693 if not info.build:
694 logging.error('Unable to locate build label for host: %s.',
695 self._host.host_port)
696 raise error.AutotestError('Failed to grab build for host %s.' %
697 self._host.host_port)
698
699 logging.debug('Setting up telemetry for build: %s', info.build)
700 try:
701 self._telemetry_setup = telemetry_setup.TelemetrySetup(
Sanika Kulkarni211a4ef2021-01-29 13:23:44 -0800702 hostname=self._host.hostname, build=info.build)
Sanika Kulkarni78dd7f82021-01-08 11:47:23 -0800703 self._telemetry_path = self._telemetry_setup.Setup()
704 except telemetry_setup.TelemetrySetupError as e:
705 raise error.AutotestError('Telemetry Environment could not be '
706 'setup: %s.' % e)