blob: 1010f101c46f1bc1caf9dbc91723c39720d66c91 [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
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +080010import json
Simran Basi833814b2013-01-29 13:13:43 -080011import logging
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +080012import numbers
Simran Basi833814b2013-01-29 13:13:43 -080013import os
Chinglin Yu0adaf112020-01-20 14:48:20 +080014import tempfile
Derek Beckett5fb683c2020-08-19 15:24:13 -070015import six
Simran Basi833814b2013-01-29 13:13:43 -080016
Zhizhou Yang65d37042020-01-08 17:44:12 -080017import numpy
18
Simran Basi833814b2013-01-29 13:13:43 -080019from autotest_lib.client.common_lib import error, utils
20from autotest_lib.client.common_lib.cros import dev_server
21
Dave Tu6a404e62013-11-05 15:54:48 -080022TELEMETRY_RUN_BENCHMARKS_SCRIPT = 'tools/perf/run_benchmark'
Ilja H. Friedel086bc3f2014-02-27 22:17:55 -080023TELEMETRY_RUN_TESTS_SCRIPT = 'tools/telemetry/run_tests'
Gurchetan Singhfaf75e92017-04-17 18:09:44 -070024TELEMETRY_RUN_GPU_TESTS_SCRIPT = 'content/test/gpu/run_gpu_integration_test.py'
Mao Huangc9642d72017-09-28 16:50:02 +080025TELEMETRY_TIMEOUT_MINS = 150
Simran Basi833814b2013-01-29 13:13:43 -080026
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080027DUT_CHROME_ROOT = '/usr/local/telemetry/src'
28
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +080029CHART_JSON_RESULT = 'results-chart.json'
30HISTOGRAM_SET_RESULT = 'histograms.json'
Zhizhou Yang65d37042020-01-08 17:44:12 -080031PROFILE_ARTIFACTS = 'artifacts'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +080032
Simran Basi833814b2013-01-29 13:13:43 -080033# Result Statuses
34SUCCESS_STATUS = 'SUCCESS'
35WARNING_STATUS = 'WARNING'
36FAILED_STATUS = 'FAILED'
37
Kuo-Hsin Yang07da7b62018-08-08 16:56:06 +080038# A list of telemetry tests that cannot run on dut.
Derek Beckett6b7cc0f2020-11-30 13:15:26 -080039ON_DUT_BLOCKLIST = [
Zhizhou Yang65d37042020-01-08 17:44:12 -080040 'loading.desktop', # crbug/882299
41 'rendering.desktop', # crbug/882291
Kuo-Hsin Yange0915472018-09-10 10:36:16 +080042]
Simran Basi833814b2013-01-29 13:13:43 -080043
Zhizhou Yang65d37042020-01-08 17:44:12 -080044
Simran Basi833814b2013-01-29 13:13:43 -080045class TelemetryResult(object):
46 """Class to represent the results of a telemetry run.
47
48 This class represents the results of a telemetry run, whether it ran
49 successful, failed or had warnings.
50 """
51
Simran Basi833814b2013-01-29 13:13:43 -080052 def __init__(self, exit_code=0, stdout='', stderr=''):
53 """Initializes this TelemetryResultObject instance.
54
55 @param status: Status of the telemtry run.
56 @param stdout: Stdout of the telemetry run.
57 @param stderr: Stderr of the telemetry run.
58 """
59 if exit_code == 0:
60 self.status = SUCCESS_STATUS
61 else:
62 self.status = FAILED_STATUS
63
Simran Basi833814b2013-01-29 13:13:43 -080064 self._stdout = stdout
65 self._stderr = stderr
66 self.output = '\n'.join([stdout, stderr])
67
68
Simran Basi833814b2013-01-29 13:13:43 -080069class TelemetryRunner(object):
70 """Class responsible for telemetry for a given build.
71
72 This class will extract and install telemetry on the devserver and is
73 responsible for executing the telemetry benchmarks and returning their
74 output to the caller.
75 """
76
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080077 def __init__(self, host, local=False, telemetry_on_dut=True):
Simran Basi833814b2013-01-29 13:13:43 -080078 """Initializes this telemetry runner instance.
79
80 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -080081
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080082 Basically, the following commands on the local pc on which test_that
83 will be executed, depending on the 4 possible combinations of
84 local x telemetry_on_dut:
85
86 local=True, telemetry_on_dut=False:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080087 python2 run_benchmark --browser=cros-chrome --remote=[dut] [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080088
89 local=True, telemetry_on_dut=True:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080090 ssh [dut] python2 run_benchmark --browser=system [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080091
92 local=False, telemetry_on_dut=False:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080093 ssh [devserver] python2 run_benchmark --browser=cros-chrome
94 --remote=[dut] [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080095
96 local=False, telemetry_on_dut=True:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080097 ssh [devserver] ssh [dut] python2 run_benchmark --browser=system [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080098
Luis Lozano23ae3192013-11-08 16:22:46 -080099 @param host: Host where the test will be run.
100 @param local: If set, no devserver will be used, test will be run
101 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800102 If not set, "ssh [devserver] " will be appended to test
103 commands.
104 @param telemetry_on_dut: If set, telemetry itself (the test harness)
105 will run on dut.
106 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -0800107 """
108 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700109 self._devserver = None
110 self._telemetry_path = None
Zhizhou Yang65d37042020-01-08 17:44:12 -0800111 self._perf_value_writer = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800112 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -0800113 # TODO (llozano crbug.com/324964). Remove conditional code.
114 # Use a class hierarchy instead.
115 if local:
116 self._setup_local_telemetry()
117 else:
118 self._setup_devserver_telemetry()
Chinglin Yu0adaf112020-01-20 14:48:20 +0800119 self._benchmark_deps = None
Luis Lozano23ae3192013-11-08 16:22:46 -0800120
121 logging.debug('Telemetry Path: %s', self._telemetry_path)
122
Luis Lozano23ae3192013-11-08 16:22:46 -0800123 def _setup_devserver_telemetry(self):
124 """Setup Telemetry to use the devserver."""
125 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800126 logging.debug('Grabbing build from AFE.')
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800127 info = self._host.host_info_store.get()
128 if not info.build:
Simran Basi833814b2013-01-29 13:13:43 -0800129 logging.error('Unable to locate build label for host: %s.',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800130 self._host.host_port)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800131 raise error.AutotestError(
132 'Failed to grab build for host %s.' % self._host.host_port)
Simran Basi833814b2013-01-29 13:13:43 -0800133
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800134 logging.debug('Setting up telemetry for build: %s', info.build)
Simran Basi833814b2013-01-29 13:13:43 -0800135
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800136 self._devserver = dev_server.ImageServer.resolve(
137 info.build, hostname=self._host.hostname)
138 self._devserver.stage_artifacts(info.build, ['autotest_packages'])
Zhizhou Yang65d37042020-01-08 17:44:12 -0800139 self._telemetry_path = self._devserver.setup_telemetry(
140 build=info.build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800141
142 def _setup_local_telemetry(self):
143 """Setup Telemetry to use local path to its sources.
144
145 First look for chrome source root, either externally mounted, or inside
146 the chroot. Prefer chrome-src-internal source tree to chrome-src.
147 """
148 TELEMETRY_DIR = 'src'
149 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700150 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800151
152 logging.debug('Setting up telemetry for local testing')
153
154 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700155 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800156 dir_list.extend(
157 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
158 if 'CHROME_ROOT' in os.environ:
159 dir_list.insert(0, os.environ['CHROME_ROOT'])
160
161 telemetry_src = ''
162 for dir in dir_list:
163 if os.path.exists(dir):
164 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
165 break
166 else:
167 raise error.TestError('Telemetry source directory not found.')
168
169 self._devserver = None
170 self._telemetry_path = telemetry_src
171
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800172 def _get_telemetry_cmd(self, script, test_or_benchmark, output_format,
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700173 *args, **kwargs):
Luis Lozano23ae3192013-11-08 16:22:46 -0800174 """Build command to execute telemetry based on script and benchmark.
175
176 @param script: Telemetry script we want to run. For example:
177 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
178 @param test_or_benchmark: Name of the test or benchmark we want to run,
179 with the page_set (if required) as part of
180 the string.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800181 @param output_format: Format of the json result file: histogram or
182 chart-json.
Luis Lozano814c7182015-09-08 11:20:47 -0700183 @param args: additional list of arguments to pass to the script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700184 @param kwargs: additional list of keyword arguments to pass to the
185 script.
Luis Lozano814c7182015-09-08 11:20:47 -0700186
Luis Lozano23ae3192013-11-08 16:22:46 -0800187 @returns Full telemetry command to execute the script.
188 """
189 telemetry_cmd = []
190 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800191 devserver_hostname = self._devserver.hostname
Luis Lozano23ae3192013-11-08 16:22:46 -0800192 telemetry_cmd.extend(['ssh', devserver_hostname])
193
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700194 no_verbose = kwargs.get('no_verbose', False)
195
Zhizhou Yang65d37042020-01-08 17:44:12 -0800196 output_dir = (DUT_CHROME_ROOT
197 if self._telemetry_on_dut else self._telemetry_path)
198 # Create a temp directory to hold single test run.
199 if self._perf_value_writer:
200 output_dir = os.path.join(
201 output_dir, self._perf_value_writer.tmpdir.strip('/'))
202
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800203 if self._telemetry_on_dut:
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800204 telemetry_cmd.extend([
Zhizhou Yang65d37042020-01-08 17:44:12 -0800205 self._host.ssh_command(
206 alive_interval=900, connection_attempts=4),
207 'python2',
208 script,
209 '--output-format=%s' % output_format,
210 '--output-dir=%s' % output_dir,
211 '--browser=system',
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800212 ])
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800213 else:
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800214 telemetry_cmd.extend([
Zhizhou Yang65d37042020-01-08 17:44:12 -0800215 'python2',
216 script,
217 '--browser=cros-chrome',
218 '--output-format=%s' % output_format,
219 '--output-dir=%s' % output_dir,
Tiancong Wangbb13ed32020-03-25 16:50:28 -0700220 '--remote=%s' % self._host.hostname,
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800221 ])
Tiancong Wangbb13ed32020-03-25 16:50:28 -0700222 if self._host.host_port != self._host.hostname:
223 # If the user specify a different port for the DUT, we should
224 # use different telemetry argument to set it up.
225 #
226 # e.g. When user is running experiments with ssh port
227 # forwarding, they specify remote as 127.0.0.1:2222. Now
228 # host_port is 127.0.0.1:2222 and hostname is 127.0.0.1
229 # port is 2222
230 telemetry_cmd.append('--remote-ssh-port=%s' % self._host.port)
231
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700232 if not no_verbose:
233 telemetry_cmd.append('--verbose')
Luis Lozano814c7182015-09-08 11:20:47 -0700234 telemetry_cmd.extend(args)
235 telemetry_cmd.append(test_or_benchmark)
236
Keith Haddow1e5c7012016-03-09 16:05:37 -0800237 return ' '.join(telemetry_cmd)
238
Zhizhou Yang65d37042020-01-08 17:44:12 -0800239 def _scp_telemetry_results_cmd(self, perf_results_dir, output_format,
240 artifacts):
Keith Haddow1e5c7012016-03-09 16:05:37 -0800241 """Build command to copy the telemetry results from the devserver.
242
243 @param perf_results_dir: directory path where test output is to be
244 collected.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800245 @param output_format: Format of the json result file: histogram or
246 chart-json.
247 @param artifacts: Whether we want to copy artifacts directory.
248
249 @returns SCP command to copy the results json to the specified
250 directory.
Keith Haddow1e5c7012016-03-09 16:05:37 -0800251 """
Dean Liaoe3e75f62017-11-14 10:36:43 +0800252 if not perf_results_dir:
253 return ''
254
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800255 output_filename = CHART_JSON_RESULT
256 if output_format == 'histograms':
257 output_filename = HISTOGRAM_SET_RESULT
Zhizhou Yang65d37042020-01-08 17:44:12 -0800258 scp_cmd = []
Dean Liaoe3e75f62017-11-14 10:36:43 +0800259 if self._telemetry_on_dut:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800260 scp_cmd.extend(['scp', '-r'])
261 scp_cmd.append(
262 self._host.make_ssh_options(
263 alive_interval=900, connection_attempts=4))
Dean Liaoe3e75f62017-11-14 10:36:43 +0800264 if not self._host.is_default_port:
265 scp_cmd.append('-P %d' % self._host.port)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800266 src = 'root@%s:%s' % (self._host.hostname, DUT_CHROME_ROOT)
Dean Liaoe3e75f62017-11-14 10:36:43 +0800267 else:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800268 # Use rsync --remove-source-file to move rather than copy from
269 # server. This is because each run will generate certain artifacts
270 # and will not be removed after, making result size getting larger.
271 # We don't do this for results on DUT because 1) rsync doesn't work
272 # 2) DUT will be reflashed frequently and no need to worry about
273 # result size.
274 scp_cmd.extend(['rsync', '-avz', '--remove-source-files'])
Dean Liaoe3e75f62017-11-14 10:36:43 +0800275 devserver_hostname = ''
Ricky Liangd186f3e2016-03-15 16:50:55 +0800276 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800277 devserver_hostname = self._devserver.hostname + ':'
Zhizhou Yang65d37042020-01-08 17:44:12 -0800278 src = '%s%s' % (devserver_hostname, self._telemetry_path)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800279
Zhizhou Yang65d37042020-01-08 17:44:12 -0800280 if self._perf_value_writer:
281 src = os.path.join(src, self._perf_value_writer.tmpdir.strip('/'))
282
283 scp_cmd.append(os.path.join(src, output_filename))
284
285 # Copy artifacts back to result directory if needed.
286 if artifacts:
287 scp_cmd.append(os.path.join(src, PROFILE_ARTIFACTS))
288
289 scp_cmd.append(perf_results_dir)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800290 return ' '.join(scp_cmd)
291
Keith Haddow1e5c7012016-03-09 16:05:37 -0800292 def _run_cmd(self, cmd):
293 """Execute an command in a external shell and capture the output.
294
295 @param cmd: String of is a valid shell command.
296
297 @returns The standard out, standard error and the integer exit code of
298 the executed command.
299 """
300 logging.debug('Running: %s', cmd)
301
Derek Beckett5fb683c2020-08-19 15:24:13 -0700302 output = six.StringIO()
303 error_output = six.StringIO()
Keith Haddow1e5c7012016-03-09 16:05:37 -0800304 exit_code = 0
305 try:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800306 result = utils.run(
307 cmd,
308 stdout_tee=output,
309 stderr_tee=error_output,
310 timeout=TELEMETRY_TIMEOUT_MINS * 60)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800311 exit_code = result.exit_status
312 except error.CmdError as e:
313 logging.debug('Error occurred executing.')
314 exit_code = e.result_obj.exit_status
315
316 stdout = output.getvalue()
317 stderr = error_output.getvalue()
318 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
319 'stderr:%s', exit_code, stdout, stderr)
320 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800321
Zhizhou Yang65d37042020-01-08 17:44:12 -0800322 def _run_telemetry(self, script, test_or_benchmark, output_format, *args,
323 **kwargs):
Simran Basi833814b2013-01-29 13:13:43 -0800324 """Runs telemetry on a dut.
325
326 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800327 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800328 @param test_or_benchmark: Name of the test or benchmark we want to run,
329 with the page_set (if required) as part of the
330 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700331 @param args: additional list of arguments to pass to the script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700332 @param kwargs: additional list of keyword arguments to pass to the
333 script.
Simran Basi833814b2013-01-29 13:13:43 -0800334
335 @returns A TelemetryResult Instance with the results of this telemetry
336 execution.
337 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700338 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800339
Zhizhou Yang65d37042020-01-08 17:44:12 -0800340 telemetry_cmd = self._get_telemetry_cmd(script, test_or_benchmark,
341 output_format, *args, **kwargs)
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700342 logging.info('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800343
Keith Haddow1e5c7012016-03-09 16:05:37 -0800344 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800345
Zhizhou Yang65d37042020-01-08 17:44:12 -0800346 return TelemetryResult(
347 exit_code=exit_code, stdout=stdout, stderr=stderr)
Simran Basi833814b2013-01-29 13:13:43 -0800348
Zhizhou Yang65d37042020-01-08 17:44:12 -0800349 def _run_scp(self, perf_results_dir, output_format, artifacts=False):
Keith Haddow1e5c7012016-03-09 16:05:37 -0800350 """Runs telemetry on a dut.
351
352 @param perf_results_dir: The local directory that results are being
353 collected.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800354 @param output_format: Format of the json result file.
355 @param artifacts: Whether we want to copy artifacts directory.
Keith Haddow1e5c7012016-03-09 16:05:37 -0800356 """
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800357 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir,
Zhizhou Yang65d37042020-01-08 17:44:12 -0800358 output_format, artifacts)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800359 logging.debug('Retrieving Results: %s', scp_cmd)
Dean Liaoe4773c72017-11-09 16:15:38 +0800360 _, _, exit_code = self._run_cmd(scp_cmd)
361 if exit_code != 0:
362 raise error.TestFail('Unable to retrieve results.')
Keith Haddow1e5c7012016-03-09 16:05:37 -0800363
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800364 if output_format == 'histograms':
365 # Converts to chart json format.
366 input_filename = os.path.join(perf_results_dir,
367 HISTOGRAM_SET_RESULT)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800368 output_filename = os.path.join(perf_results_dir, CHART_JSON_RESULT)
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800369 histograms = json.loads(open(input_filename).read())
370 chartjson = TelemetryRunner.convert_chart_json(histograms)
371 with open(output_filename, 'w') as fout:
372 fout.write(json.dumps(chartjson, indent=2))
Keith Haddow1e5c7012016-03-09 16:05:37 -0800373
Luis Lozano814c7182015-09-08 11:20:47 -0700374 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700375 """Runs a telemetry test on a dut.
376
377 @param script: Which telemetry test script we want to run. Can be
378 telemetry's base test script or the Chrome OS specific
379 test script.
380 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700381 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700382
383 @returns A TelemetryResult Instance with the results of this telemetry
384 execution.
385 """
386 logging.debug('Running telemetry test: %s', test)
387 telemetry_script = os.path.join(self._telemetry_path, script)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800388 result = self._run_telemetry(telemetry_script, test, 'chartjson',
389 *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700390 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700391 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700392 return result
393
Luis Lozano814c7182015-09-08 11:20:47 -0700394 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800395 """Runs a telemetry test on a dut.
396
397 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700398 @param args: additional list of arguments to pass to the telemetry
399 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800400
401 @returns A TelemetryResult Instance with the results of this telemetry
402 execution.
403 """
Luis Lozano814c7182015-09-08 11:20:47 -0700404 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700405
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700406 def run_telemetry_benchmark(self,
407 benchmark,
408 perf_value_writer=None,
409 *args,
410 **kwargs):
Simran Basi833814b2013-01-29 13:13:43 -0800411 """Runs a telemetry benchmark on a dut.
412
413 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800414 @param perf_value_writer: Should be an instance with the function
415 output_perf_value(), if None, no perf value
416 will be written. Typically this will be the
417 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700418 @param args: additional list of arguments to pass to the telemetry
419 execution script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700420 @param kwargs: additional list of keyword arguments to pass to the
421 telemetry execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800422
423 @returns A TelemetryResult Instance with the results of this telemetry
424 execution.
425 """
Dave Tu6a404e62013-11-05 15:54:48 -0800426 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800427
Zhizhou Yang65d37042020-01-08 17:44:12 -0800428 self._perf_value_writer = perf_value_writer
429
Derek Beckett6b7cc0f2020-11-30 13:15:26 -0800430 if benchmark in ON_DUT_BLOCKLIST:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800431 self._telemetry_on_dut = False
432
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700433 output_format = kwargs.get('ex_output_format', '')
434
435 if not output_format:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800436 output_format = 'histograms'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800437
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800438 if self._telemetry_on_dut:
439 telemetry_script = os.path.join(DUT_CHROME_ROOT,
440 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
441 self._ensure_deps(self._host, benchmark)
442 else:
443 telemetry_script = os.path.join(self._telemetry_path,
444 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
445
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800446 result = self._run_telemetry(telemetry_script, benchmark,
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700447 output_format, *args, **kwargs)
Simran Basi833814b2013-01-29 13:13:43 -0800448
449 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800450 raise error.TestWarn('Telemetry Benchmark: %s'
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700451 ' exited with Warnings.\nOutput:\n%s\n' %
452 (benchmark, result.output))
453 elif result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800454 raise error.TestFail('Telemetry Benchmark: %s'
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700455 ' failed to run.\nOutput:\n%s\n' %
456 (benchmark, result.output))
457 elif '[ PASSED ] 0 tests.' in result.output:
458 raise error.TestWarn('Telemetry Benchmark: %s exited successfully,'
459 ' but no test actually passed.\nOutput\n%s\n'
460 % (benchmark, result.output))
Keith Haddow1e5c7012016-03-09 16:05:37 -0800461 if perf_value_writer:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800462 artifacts = kwargs.get('artifacts', False)
463 self._run_scp(perf_value_writer.resultsdir, output_format,
464 artifacts)
Simran Basi833814b2013-01-29 13:13:43 -0800465 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800466
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700467 def run_gpu_integration_test(self, test, *args):
468 """Runs a gpu test on a dut.
469
470 @param test: Gpu test we want to run.
471 @param args: additional list of arguments to pass to the telemetry
472 execution script.
473
Drew Davenport84395922018-09-10 10:42:37 -0600474 @returns A TelemetryResult instance with the results of this telemetry
475 execution.
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700476 """
Zhizhou Yang65d37042020-01-08 17:44:12 -0800477 script = os.path.join(DUT_CHROME_ROOT, TELEMETRY_RUN_GPU_TESTS_SCRIPT)
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700478 cmd = []
479 if self._devserver:
480 devserver_hostname = self._devserver.hostname
481 cmd.extend(['ssh', devserver_hostname])
482
Zhizhou Yang65d37042020-01-08 17:44:12 -0800483 cmd.extend([
484 self._host.ssh_command(
485 alive_interval=900, connection_attempts=4), 'python2',
486 script
487 ])
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700488 cmd.extend(args)
489 cmd.append(test)
490 cmd = ' '.join(cmd)
491 stdout, stderr, exit_code = self._run_cmd(cmd)
492
Drew Davenport84395922018-09-10 10:42:37 -0600493 if exit_code:
494 raise error.TestFail('Gpu Integration Test: %s'
495 ' failed to run.' % test)
496
Zhizhou Yang65d37042020-01-08 17:44:12 -0800497 return TelemetryResult(
498 exit_code=exit_code, stdout=stdout, stderr=stderr)
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700499
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800500 def _ensure_deps(self, dut, test_name):
501 """
502 Ensure the dependencies are locally available on DUT.
503
504 @param dut: The autotest host object representing DUT.
505 @param test_name: Name of the telemetry test.
506 """
507 # Get DEPs using host's telemetry.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800508 # Example output, fetch_benchmark_deps.py --output-deps=deps octane:
509 # {'octane': ['tools/perf/page_sets/data/octane_002.wprgo']}
Zhizhou Yang65d37042020-01-08 17:44:12 -0800510 fetch_path = os.path.join(self._telemetry_path, 'tools', 'perf',
511 'fetch_benchmark_deps.py')
Chinglin Yu0adaf112020-01-20 14:48:20 +0800512 # Use a temporary file for |deps_path| to avoid race conditions. The
513 # created temporary file is assigned to |self._benchmark_deps| to make
514 # it valid until |self| is destroyed.
515 self._benchmark_deps = tempfile.NamedTemporaryFile(
Zhizhou Yang65d37042020-01-08 17:44:12 -0800516 prefix='fetch_benchmark_deps_result.', suffix='.json')
Chinglin Yu0adaf112020-01-20 14:48:20 +0800517 deps_path = self._benchmark_deps.name
Kuo-Hsin Yang66324262019-12-10 10:36:34 +0800518 format_fetch = ('python2 %s --output-deps=%s %s')
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800519 command_fetch = format_fetch % (fetch_path, deps_path, test_name)
520 command_get = 'cat %s' % deps_path
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800521
522 if self._devserver:
523 devserver_hostname = self._devserver.url().split(
524 'http://')[1].split(':')[0]
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800525 command_fetch = 'ssh %s %s' % (devserver_hostname, command_fetch)
526 command_get = 'ssh %s %s' % (devserver_hostname, command_get)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800527
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800528 logging.info('Getting DEPs: %s', command_fetch)
529 _, _, exit_code = self._run_cmd(command_fetch)
530 if exit_code != 0:
531 raise error.TestFail('Error occurred while fetching DEPs.')
532 stdout, _, exit_code = self._run_cmd(command_get)
533 if exit_code != 0:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800534 raise error.TestFail('Error occurred while getting DEPs.')
535
536 # Download DEPs to DUT.
537 # send_file() relies on rsync over ssh. Couldn't be better.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800538 deps = json.loads(stdout)
539 for dep in deps[test_name]:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800540 src = os.path.join(self._telemetry_path, dep)
541 dst = os.path.join(DUT_CHROME_ROOT, dep)
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800542 if self._devserver:
543 logging.info('Copying: %s -> %s', src, dst)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800544 rsync_cmd = utils.sh_escape(
545 'rsync %s %s %s:%s' % (self._host.rsync_options(), src,
546 self._host.hostname, dst))
Chung-yih Wangfd8eb242017-12-09 19:23:04 +0800547 utils.run('ssh %s "%s"' % (devserver_hostname, rsync_cmd))
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800548 else:
549 if not os.path.isfile(src):
550 raise error.TestFail('Error occurred while saving DEPs.')
551 logging.info('Copying: %s -> %s', src, dst)
552 dut.send_file(src, dst)
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800553
554 @staticmethod
555 def convert_chart_json(histogram_set):
556 """
557 Convert from histogram set to chart json format.
558
559 @param histogram_set: result in histogram set format.
560
561 @returns result in chart json format.
562 """
563 value_map = {}
564
565 # Gets generic set values.
566 for obj in histogram_set:
567 if 'type' in obj and obj['type'] == 'GenericSet':
568 value_map[obj['guid']] = obj['values']
569
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800570 charts = {}
571 benchmark_name = ''
572 benchmark_desc = ''
573
574 # Checks the unit test for how this conversion works.
575 for obj in histogram_set:
576 if 'name' not in obj or 'sampleValues' not in obj:
577 continue
578 metric_name = obj['name']
579 diagnostics = obj['diagnostics']
Derek Beckett5fb683c2020-08-19 15:24:13 -0700580 if 'stories' in diagnostics:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800581 story_name = value_map[diagnostics['stories']][0]
582 else:
583 story_name = 'default'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800584 local_benchmark_name = value_map[diagnostics['benchmarks']][0]
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800585 if benchmark_name == '':
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800586 benchmark_name = local_benchmark_name
Derek Beckett5fb683c2020-08-19 15:24:13 -0700587 if 'benchmarkDescriptions' in diagnostics:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800588 benchmark_desc = value_map[
Zhizhou Yang65d37042020-01-08 17:44:12 -0800589 diagnostics['benchmarkDescriptions']][0]
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800590 if benchmark_name != local_benchmark_name:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800591 logging.warning(
592 'There are more than 1 benchmark names in the'
593 'result. old: %s, new: %s', benchmark_name,
594 local_benchmark_name)
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800595 continue
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800596
597 unit = obj['unit']
598 smaller_postfixes = ('_smallerIsBetter', '-')
599 bigger_postfixes = ('_biggerIsBetter', '+')
600 all_postfixes = smaller_postfixes + bigger_postfixes
601
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800602 improvement = 'up'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800603 for postfix in smaller_postfixes:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800604 if unit.endswith(postfix):
605 improvement = 'down'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800606 for postfix in all_postfixes:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800607 if unit.endswith(postfix):
608 unit = unit[:-len(postfix)]
609 break
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800610
611 if unit == 'unitless':
Zhizhou Yang65d37042020-01-08 17:44:12 -0800612 unit = 'score'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800613
Zhizhou Yang65d37042020-01-08 17:44:12 -0800614 values = [
615 x for x in obj['sampleValues']
616 if isinstance(x, numbers.Number)
617 ]
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800618 if metric_name not in charts:
619 charts[metric_name] = {}
620 charts[metric_name][story_name] = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800621 'improvement_direction': improvement,
622 'name': metric_name,
623 'std': numpy.std(values),
624 'type': 'list_of_scalar_values',
625 'units': unit,
626 'values': values
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800627 }
628
629 # Adds summaries.
630 for metric_name in charts:
631 values = []
632 metric_content = charts[metric_name]
633 for story_name in metric_content:
634 story_content = metric_content[story_name]
635 values += story_content['values']
636 metric_type = story_content['type']
637 units = story_content['units']
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800638 improvement = story_content['improvement_direction']
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800639 values.sort()
640 std = numpy.std(values)
641 metric_content['summary'] = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800642 'improvement_direction': improvement,
643 'name': metric_name,
644 'std': std,
645 'type': metric_type,
646 'units': units,
647 'values': values
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800648 }
649
650 benchmark_metadata = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800651 'description': benchmark_desc,
652 'name': benchmark_name,
653 'type': 'telemetry_benchmark'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800654 }
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800655 return {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800656 'benchmark_description': benchmark_desc,
657 'benchmark_metadata': benchmark_metadata,
658 'benchmark_name': benchmark_name,
659 'charts': charts,
660 'format_version': 1.0
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800661 }