blob: c795ba86d54e23919a8bf1edca701ce60476a22f [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.
Kuo-Hsin Yange0915472018-09-10 10:36:16 +080039ON_DUT_BLACKLIST = [
Zhizhou Yang65d37042020-01-08 17:44:12 -080040 'cros_ui_smoothness', # crbug/976839
41 'loading.desktop', # crbug/882299
42 'rendering.desktop', # crbug/882291
43 'system_health.memory_desktop', # crbug/874386
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
Simran Basi833814b2013-01-29 13:13:43 -080071class TelemetryRunner(object):
72 """Class responsible for telemetry for a given build.
73
74 This class will extract and install telemetry on the devserver and is
75 responsible for executing the telemetry benchmarks and returning their
76 output to the caller.
77 """
78
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080079 def __init__(self, host, local=False, telemetry_on_dut=True):
Simran Basi833814b2013-01-29 13:13:43 -080080 """Initializes this telemetry runner instance.
81
82 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -080083
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080084 Basically, the following commands on the local pc on which test_that
85 will be executed, depending on the 4 possible combinations of
86 local x telemetry_on_dut:
87
88 local=True, telemetry_on_dut=False:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080089 python2 run_benchmark --browser=cros-chrome --remote=[dut] [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080090
91 local=True, telemetry_on_dut=True:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080092 ssh [dut] python2 run_benchmark --browser=system [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080093
94 local=False, telemetry_on_dut=False:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080095 ssh [devserver] python2 run_benchmark --browser=cros-chrome
96 --remote=[dut] [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080097
98 local=False, telemetry_on_dut=True:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080099 ssh [devserver] ssh [dut] python2 run_benchmark --browser=system [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800100
Luis Lozano23ae3192013-11-08 16:22:46 -0800101 @param host: Host where the test will be run.
102 @param local: If set, no devserver will be used, test will be run
103 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800104 If not set, "ssh [devserver] " will be appended to test
105 commands.
106 @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._devserver = None
112 self._telemetry_path = None
Zhizhou Yang65d37042020-01-08 17:44:12 -0800113 self._perf_value_writer = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800114 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -0800115 # TODO (llozano crbug.com/324964). Remove conditional code.
116 # Use a class hierarchy instead.
117 if local:
118 self._setup_local_telemetry()
119 else:
120 self._setup_devserver_telemetry()
Chinglin Yu0adaf112020-01-20 14:48:20 +0800121 self._benchmark_deps = None
Luis Lozano23ae3192013-11-08 16:22:46 -0800122
123 logging.debug('Telemetry Path: %s', self._telemetry_path)
124
Luis Lozano23ae3192013-11-08 16:22:46 -0800125 def _setup_devserver_telemetry(self):
126 """Setup Telemetry to use the devserver."""
127 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800128 logging.debug('Grabbing build from AFE.')
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800129 info = self._host.host_info_store.get()
130 if not info.build:
Simran Basi833814b2013-01-29 13:13:43 -0800131 logging.error('Unable to locate build label for host: %s.',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800132 self._host.host_port)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800133 raise error.AutotestError(
134 'Failed to grab build for host %s.' % self._host.host_port)
Simran Basi833814b2013-01-29 13:13:43 -0800135
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800136 logging.debug('Setting up telemetry for build: %s', info.build)
Simran Basi833814b2013-01-29 13:13:43 -0800137
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800138 self._devserver = dev_server.ImageServer.resolve(
139 info.build, hostname=self._host.hostname)
140 self._devserver.stage_artifacts(info.build, ['autotest_packages'])
Zhizhou Yang65d37042020-01-08 17:44:12 -0800141 self._telemetry_path = self._devserver.setup_telemetry(
142 build=info.build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800143
144 def _setup_local_telemetry(self):
145 """Setup Telemetry to use local path to its sources.
146
147 First look for chrome source root, either externally mounted, or inside
148 the chroot. Prefer chrome-src-internal source tree to chrome-src.
149 """
150 TELEMETRY_DIR = 'src'
151 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700152 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800153
154 logging.debug('Setting up telemetry for local testing')
155
156 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700157 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800158 dir_list.extend(
159 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
160 if 'CHROME_ROOT' in os.environ:
161 dir_list.insert(0, os.environ['CHROME_ROOT'])
162
163 telemetry_src = ''
164 for dir in dir_list:
165 if os.path.exists(dir):
166 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
167 break
168 else:
169 raise error.TestError('Telemetry source directory not found.')
170
171 self._devserver = None
172 self._telemetry_path = telemetry_src
173
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800174 def _get_telemetry_cmd(self, script, test_or_benchmark, output_format,
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700175 *args, **kwargs):
Luis Lozano23ae3192013-11-08 16:22:46 -0800176 """Build command to execute telemetry based on script and benchmark.
177
178 @param script: Telemetry script we want to run. For example:
179 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
180 @param test_or_benchmark: Name of the test or benchmark we want to run,
181 with the page_set (if required) as part of
182 the string.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800183 @param output_format: Format of the json result file: histogram or
184 chart-json.
Luis Lozano814c7182015-09-08 11:20:47 -0700185 @param args: additional list of arguments to pass to the script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700186 @param kwargs: additional list of keyword arguments to pass to the
187 script.
Luis Lozano814c7182015-09-08 11:20:47 -0700188
Luis Lozano23ae3192013-11-08 16:22:46 -0800189 @returns Full telemetry command to execute the script.
190 """
191 telemetry_cmd = []
192 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800193 devserver_hostname = self._devserver.hostname
Luis Lozano23ae3192013-11-08 16:22:46 -0800194 telemetry_cmd.extend(['ssh', devserver_hostname])
195
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700196 no_verbose = kwargs.get('no_verbose', False)
197
Zhizhou Yang65d37042020-01-08 17:44:12 -0800198 output_dir = (DUT_CHROME_ROOT
199 if self._telemetry_on_dut else self._telemetry_path)
200 # Create a temp directory to hold single test run.
201 if self._perf_value_writer:
202 output_dir = os.path.join(
203 output_dir, self._perf_value_writer.tmpdir.strip('/'))
204
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800205 if self._telemetry_on_dut:
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800206 telemetry_cmd.extend([
Zhizhou Yang65d37042020-01-08 17:44:12 -0800207 self._host.ssh_command(
208 alive_interval=900, connection_attempts=4),
209 'python2',
210 script,
211 '--output-format=%s' % output_format,
212 '--output-dir=%s' % output_dir,
213 '--browser=system',
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800214 ])
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800215 else:
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800216 telemetry_cmd.extend([
Zhizhou Yang65d37042020-01-08 17:44:12 -0800217 'python2',
218 script,
219 '--browser=cros-chrome',
220 '--output-format=%s' % output_format,
221 '--output-dir=%s' % output_dir,
Tiancong Wangbb13ed32020-03-25 16:50:28 -0700222 '--remote=%s' % self._host.hostname,
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800223 ])
Tiancong Wangbb13ed32020-03-25 16:50:28 -0700224 if self._host.host_port != self._host.hostname:
225 # If the user specify a different port for the DUT, we should
226 # use different telemetry argument to set it up.
227 #
228 # e.g. When user is running experiments with ssh port
229 # forwarding, they specify remote as 127.0.0.1:2222. Now
230 # host_port is 127.0.0.1:2222 and hostname is 127.0.0.1
231 # port is 2222
232 telemetry_cmd.append('--remote-ssh-port=%s' % self._host.port)
233
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700234 if not no_verbose:
235 telemetry_cmd.append('--verbose')
Luis Lozano814c7182015-09-08 11:20:47 -0700236 telemetry_cmd.extend(args)
237 telemetry_cmd.append(test_or_benchmark)
238
Keith Haddow1e5c7012016-03-09 16:05:37 -0800239 return ' '.join(telemetry_cmd)
240
Zhizhou Yang65d37042020-01-08 17:44:12 -0800241 def _scp_telemetry_results_cmd(self, perf_results_dir, output_format,
242 artifacts):
Keith Haddow1e5c7012016-03-09 16:05:37 -0800243 """Build command to copy the telemetry results from the devserver.
244
245 @param perf_results_dir: directory path where test output is to be
246 collected.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800247 @param output_format: Format of the json result file: histogram or
248 chart-json.
249 @param artifacts: Whether we want to copy artifacts directory.
250
251 @returns SCP command to copy the results json to the specified
252 directory.
Keith Haddow1e5c7012016-03-09 16:05:37 -0800253 """
Dean Liaoe3e75f62017-11-14 10:36:43 +0800254 if not perf_results_dir:
255 return ''
256
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800257 output_filename = CHART_JSON_RESULT
258 if output_format == 'histograms':
259 output_filename = HISTOGRAM_SET_RESULT
Zhizhou Yang65d37042020-01-08 17:44:12 -0800260 scp_cmd = []
Dean Liaoe3e75f62017-11-14 10:36:43 +0800261 if self._telemetry_on_dut:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800262 scp_cmd.extend(['scp', '-r'])
263 scp_cmd.append(
264 self._host.make_ssh_options(
265 alive_interval=900, connection_attempts=4))
Dean Liaoe3e75f62017-11-14 10:36:43 +0800266 if not self._host.is_default_port:
267 scp_cmd.append('-P %d' % self._host.port)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800268 src = 'root@%s:%s' % (self._host.hostname, DUT_CHROME_ROOT)
Dean Liaoe3e75f62017-11-14 10:36:43 +0800269 else:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800270 # Use rsync --remove-source-file to move rather than copy from
271 # server. This is because each run will generate certain artifacts
272 # and will not be removed after, making result size getting larger.
273 # We don't do this for results on DUT because 1) rsync doesn't work
274 # 2) DUT will be reflashed frequently and no need to worry about
275 # result size.
276 scp_cmd.extend(['rsync', '-avz', '--remove-source-files'])
Dean Liaoe3e75f62017-11-14 10:36:43 +0800277 devserver_hostname = ''
Ricky Liangd186f3e2016-03-15 16:50:55 +0800278 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800279 devserver_hostname = self._devserver.hostname + ':'
Zhizhou Yang65d37042020-01-08 17:44:12 -0800280 src = '%s%s' % (devserver_hostname, self._telemetry_path)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800281
Zhizhou Yang65d37042020-01-08 17:44:12 -0800282 if self._perf_value_writer:
283 src = os.path.join(src, self._perf_value_writer.tmpdir.strip('/'))
284
285 scp_cmd.append(os.path.join(src, output_filename))
286
287 # Copy artifacts back to result directory if needed.
288 if artifacts:
289 scp_cmd.append(os.path.join(src, PROFILE_ARTIFACTS))
290
291 scp_cmd.append(perf_results_dir)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800292 return ' '.join(scp_cmd)
293
Keith Haddow1e5c7012016-03-09 16:05:37 -0800294 def _run_cmd(self, cmd):
295 """Execute an command in a external shell and capture the output.
296
297 @param cmd: String of is a valid shell command.
298
299 @returns The standard out, standard error and the integer exit code of
300 the executed command.
301 """
302 logging.debug('Running: %s', cmd)
303
Derek Beckett5fb683c2020-08-19 15:24:13 -0700304 output = six.StringIO()
305 error_output = six.StringIO()
Keith Haddow1e5c7012016-03-09 16:05:37 -0800306 exit_code = 0
307 try:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800308 result = utils.run(
309 cmd,
310 stdout_tee=output,
311 stderr_tee=error_output,
312 timeout=TELEMETRY_TIMEOUT_MINS * 60)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800313 exit_code = result.exit_status
314 except error.CmdError as e:
315 logging.debug('Error occurred executing.')
316 exit_code = e.result_obj.exit_status
317
318 stdout = output.getvalue()
319 stderr = error_output.getvalue()
320 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
321 'stderr:%s', exit_code, stdout, stderr)
322 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800323
Zhizhou Yang65d37042020-01-08 17:44:12 -0800324 def _run_telemetry(self, script, test_or_benchmark, output_format, *args,
325 **kwargs):
Simran Basi833814b2013-01-29 13:13:43 -0800326 """Runs telemetry on a dut.
327
328 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800329 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800330 @param test_or_benchmark: Name of the test or benchmark we want to run,
331 with the page_set (if required) as part of the
332 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700333 @param args: additional list of arguments to pass to the script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700334 @param kwargs: additional list of keyword arguments to pass to the
335 script.
Simran Basi833814b2013-01-29 13:13:43 -0800336
337 @returns A TelemetryResult Instance with the results of this telemetry
338 execution.
339 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700340 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800341
Zhizhou Yang65d37042020-01-08 17:44:12 -0800342 telemetry_cmd = self._get_telemetry_cmd(script, test_or_benchmark,
343 output_format, *args, **kwargs)
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700344 logging.info('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800345
Keith Haddow1e5c7012016-03-09 16:05:37 -0800346 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800347
Zhizhou Yang65d37042020-01-08 17:44:12 -0800348 return TelemetryResult(
349 exit_code=exit_code, stdout=stdout, stderr=stderr)
Simran Basi833814b2013-01-29 13:13:43 -0800350
Zhizhou Yang65d37042020-01-08 17:44:12 -0800351 def _run_scp(self, perf_results_dir, output_format, artifacts=False):
Keith Haddow1e5c7012016-03-09 16:05:37 -0800352 """Runs telemetry on a dut.
353
354 @param perf_results_dir: The local directory that results are being
355 collected.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800356 @param output_format: Format of the json result file.
357 @param artifacts: Whether we want to copy artifacts directory.
Keith Haddow1e5c7012016-03-09 16:05:37 -0800358 """
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800359 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir,
Zhizhou Yang65d37042020-01-08 17:44:12 -0800360 output_format, artifacts)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800361 logging.debug('Retrieving Results: %s', scp_cmd)
Dean Liaoe4773c72017-11-09 16:15:38 +0800362 _, _, exit_code = self._run_cmd(scp_cmd)
363 if exit_code != 0:
364 raise error.TestFail('Unable to retrieve results.')
Keith Haddow1e5c7012016-03-09 16:05:37 -0800365
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800366 if output_format == 'histograms':
367 # Converts to chart json format.
368 input_filename = os.path.join(perf_results_dir,
369 HISTOGRAM_SET_RESULT)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800370 output_filename = os.path.join(perf_results_dir, CHART_JSON_RESULT)
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800371 histograms = json.loads(open(input_filename).read())
372 chartjson = TelemetryRunner.convert_chart_json(histograms)
373 with open(output_filename, 'w') as fout:
374 fout.write(json.dumps(chartjson, indent=2))
Keith Haddow1e5c7012016-03-09 16:05:37 -0800375
Luis Lozano814c7182015-09-08 11:20:47 -0700376 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700377 """Runs a telemetry test on a dut.
378
379 @param script: Which telemetry test script we want to run. Can be
380 telemetry's base test script or the Chrome OS specific
381 test script.
382 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700383 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700384
385 @returns A TelemetryResult Instance with the results of this telemetry
386 execution.
387 """
388 logging.debug('Running telemetry test: %s', test)
389 telemetry_script = os.path.join(self._telemetry_path, script)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800390 result = self._run_telemetry(telemetry_script, test, 'chartjson',
391 *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700392 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700393 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700394 return result
395
Luis Lozano814c7182015-09-08 11:20:47 -0700396 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800397 """Runs a telemetry test on a dut.
398
399 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700400 @param args: additional list of arguments to pass to the telemetry
401 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800402
403 @returns A TelemetryResult Instance with the results of this telemetry
404 execution.
405 """
Luis Lozano814c7182015-09-08 11:20:47 -0700406 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700407
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700408 def run_telemetry_benchmark(self,
409 benchmark,
410 perf_value_writer=None,
411 *args,
412 **kwargs):
Simran Basi833814b2013-01-29 13:13:43 -0800413 """Runs a telemetry benchmark on a dut.
414
415 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800416 @param perf_value_writer: Should be an instance with the function
417 output_perf_value(), if None, no perf value
418 will be written. Typically this will be the
419 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700420 @param args: additional list of arguments to pass to the telemetry
421 execution script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700422 @param kwargs: additional list of keyword arguments to pass to the
423 telemetry execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800424
425 @returns A TelemetryResult Instance with the results of this telemetry
426 execution.
427 """
Dave Tu6a404e62013-11-05 15:54:48 -0800428 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800429
Zhizhou Yang65d37042020-01-08 17:44:12 -0800430 self._perf_value_writer = perf_value_writer
431
Kuo-Hsin Yang07da7b62018-08-08 16:56:06 +0800432 if benchmark in ON_DUT_BLACKLIST:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800433 self._telemetry_on_dut = False
434
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700435 output_format = kwargs.get('ex_output_format', '')
436
437 if not output_format:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800438 output_format = 'histograms'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800439
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800440 if self._telemetry_on_dut:
441 telemetry_script = os.path.join(DUT_CHROME_ROOT,
442 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
443 self._ensure_deps(self._host, benchmark)
444 else:
445 telemetry_script = os.path.join(self._telemetry_path,
446 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
447
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800448 result = self._run_telemetry(telemetry_script, benchmark,
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700449 output_format, *args, **kwargs)
Simran Basi833814b2013-01-29 13:13:43 -0800450
451 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800452 raise error.TestWarn('Telemetry Benchmark: %s'
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700453 ' exited with Warnings.\nOutput:\n%s\n' %
454 (benchmark, result.output))
455 elif result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800456 raise error.TestFail('Telemetry Benchmark: %s'
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700457 ' failed to run.\nOutput:\n%s\n' %
458 (benchmark, result.output))
459 elif '[ PASSED ] 0 tests.' in result.output:
460 raise error.TestWarn('Telemetry Benchmark: %s exited successfully,'
461 ' but no test actually passed.\nOutput\n%s\n'
462 % (benchmark, result.output))
Keith Haddow1e5c7012016-03-09 16:05:37 -0800463 if perf_value_writer:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800464 artifacts = kwargs.get('artifacts', False)
465 self._run_scp(perf_value_writer.resultsdir, output_format,
466 artifacts)
Simran Basi833814b2013-01-29 13:13:43 -0800467 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800468
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700469 def run_gpu_integration_test(self, test, *args):
470 """Runs a gpu test on a dut.
471
472 @param test: Gpu test we want to run.
473 @param args: additional list of arguments to pass to the telemetry
474 execution script.
475
Drew Davenport84395922018-09-10 10:42:37 -0600476 @returns A TelemetryResult instance with the results of this telemetry
477 execution.
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700478 """
Zhizhou Yang65d37042020-01-08 17:44:12 -0800479 script = os.path.join(DUT_CHROME_ROOT, TELEMETRY_RUN_GPU_TESTS_SCRIPT)
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700480 cmd = []
481 if self._devserver:
482 devserver_hostname = self._devserver.hostname
483 cmd.extend(['ssh', devserver_hostname])
484
Zhizhou Yang65d37042020-01-08 17:44:12 -0800485 cmd.extend([
486 self._host.ssh_command(
487 alive_interval=900, connection_attempts=4), 'python2',
488 script
489 ])
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700490 cmd.extend(args)
491 cmd.append(test)
492 cmd = ' '.join(cmd)
493 stdout, stderr, exit_code = self._run_cmd(cmd)
494
Drew Davenport84395922018-09-10 10:42:37 -0600495 if exit_code:
496 raise error.TestFail('Gpu Integration Test: %s'
497 ' failed to run.' % test)
498
Zhizhou Yang65d37042020-01-08 17:44:12 -0800499 return TelemetryResult(
500 exit_code=exit_code, stdout=stdout, stderr=stderr)
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700501
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800502 def _ensure_deps(self, dut, test_name):
503 """
504 Ensure the dependencies are locally available on DUT.
505
506 @param dut: The autotest host object representing DUT.
507 @param test_name: Name of the telemetry test.
508 """
509 # Get DEPs using host's telemetry.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800510 # Example output, fetch_benchmark_deps.py --output-deps=deps octane:
511 # {'octane': ['tools/perf/page_sets/data/octane_002.wprgo']}
Zhizhou Yang65d37042020-01-08 17:44:12 -0800512 fetch_path = os.path.join(self._telemetry_path, 'tools', 'perf',
513 'fetch_benchmark_deps.py')
Chinglin Yu0adaf112020-01-20 14:48:20 +0800514 # Use a temporary file for |deps_path| to avoid race conditions. The
515 # created temporary file is assigned to |self._benchmark_deps| to make
516 # it valid until |self| is destroyed.
517 self._benchmark_deps = tempfile.NamedTemporaryFile(
Zhizhou Yang65d37042020-01-08 17:44:12 -0800518 prefix='fetch_benchmark_deps_result.', suffix='.json')
Chinglin Yu0adaf112020-01-20 14:48:20 +0800519 deps_path = self._benchmark_deps.name
Kuo-Hsin Yang66324262019-12-10 10:36:34 +0800520 format_fetch = ('python2 %s --output-deps=%s %s')
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800521 command_fetch = format_fetch % (fetch_path, deps_path, test_name)
522 command_get = 'cat %s' % deps_path
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800523
524 if self._devserver:
525 devserver_hostname = self._devserver.url().split(
526 'http://')[1].split(':')[0]
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800527 command_fetch = 'ssh %s %s' % (devserver_hostname, command_fetch)
528 command_get = 'ssh %s %s' % (devserver_hostname, command_get)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800529
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800530 logging.info('Getting DEPs: %s', command_fetch)
531 _, _, exit_code = self._run_cmd(command_fetch)
532 if exit_code != 0:
533 raise error.TestFail('Error occurred while fetching DEPs.')
534 stdout, _, exit_code = self._run_cmd(command_get)
535 if exit_code != 0:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800536 raise error.TestFail('Error occurred while getting DEPs.')
537
538 # Download DEPs to DUT.
539 # send_file() relies on rsync over ssh. Couldn't be better.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800540 deps = json.loads(stdout)
541 for dep in deps[test_name]:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800542 src = os.path.join(self._telemetry_path, dep)
543 dst = os.path.join(DUT_CHROME_ROOT, dep)
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800544 if self._devserver:
545 logging.info('Copying: %s -> %s', src, dst)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800546 rsync_cmd = utils.sh_escape(
547 'rsync %s %s %s:%s' % (self._host.rsync_options(), src,
548 self._host.hostname, dst))
Chung-yih Wangfd8eb242017-12-09 19:23:04 +0800549 utils.run('ssh %s "%s"' % (devserver_hostname, rsync_cmd))
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800550 else:
551 if not os.path.isfile(src):
552 raise error.TestFail('Error occurred while saving DEPs.')
553 logging.info('Copying: %s -> %s', src, dst)
554 dut.send_file(src, dst)
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800555
556 @staticmethod
557 def convert_chart_json(histogram_set):
558 """
559 Convert from histogram set to chart json format.
560
561 @param histogram_set: result in histogram set format.
562
563 @returns result in chart json format.
564 """
565 value_map = {}
566
567 # Gets generic set values.
568 for obj in histogram_set:
569 if 'type' in obj and obj['type'] == 'GenericSet':
570 value_map[obj['guid']] = obj['values']
571
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800572 charts = {}
573 benchmark_name = ''
574 benchmark_desc = ''
575
576 # Checks the unit test for how this conversion works.
577 for obj in histogram_set:
578 if 'name' not in obj or 'sampleValues' not in obj:
579 continue
580 metric_name = obj['name']
581 diagnostics = obj['diagnostics']
Derek Beckett5fb683c2020-08-19 15:24:13 -0700582 if 'stories' in diagnostics:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800583 story_name = value_map[diagnostics['stories']][0]
584 else:
585 story_name = 'default'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800586 local_benchmark_name = value_map[diagnostics['benchmarks']][0]
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800587 if benchmark_name == '':
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800588 benchmark_name = local_benchmark_name
Derek Beckett5fb683c2020-08-19 15:24:13 -0700589 if 'benchmarkDescriptions' in diagnostics:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800590 benchmark_desc = value_map[
Zhizhou Yang65d37042020-01-08 17:44:12 -0800591 diagnostics['benchmarkDescriptions']][0]
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800592 if benchmark_name != local_benchmark_name:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800593 logging.warning(
594 'There are more than 1 benchmark names in the'
595 'result. old: %s, new: %s', benchmark_name,
596 local_benchmark_name)
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800597 continue
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800598
599 unit = obj['unit']
600 smaller_postfixes = ('_smallerIsBetter', '-')
601 bigger_postfixes = ('_biggerIsBetter', '+')
602 all_postfixes = smaller_postfixes + bigger_postfixes
603
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800604 improvement = 'up'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800605 for postfix in smaller_postfixes:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800606 if unit.endswith(postfix):
607 improvement = 'down'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800608 for postfix in all_postfixes:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800609 if unit.endswith(postfix):
610 unit = unit[:-len(postfix)]
611 break
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800612
613 if unit == 'unitless':
Zhizhou Yang65d37042020-01-08 17:44:12 -0800614 unit = 'score'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800615
Zhizhou Yang65d37042020-01-08 17:44:12 -0800616 values = [
617 x for x in obj['sampleValues']
618 if isinstance(x, numbers.Number)
619 ]
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800620 if metric_name not in charts:
621 charts[metric_name] = {}
622 charts[metric_name][story_name] = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800623 'improvement_direction': improvement,
624 'name': metric_name,
625 'std': numpy.std(values),
626 'type': 'list_of_scalar_values',
627 'units': unit,
628 'values': values
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800629 }
630
631 # Adds summaries.
632 for metric_name in charts:
633 values = []
634 metric_content = charts[metric_name]
635 for story_name in metric_content:
636 story_content = metric_content[story_name]
637 values += story_content['values']
638 metric_type = story_content['type']
639 units = story_content['units']
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800640 improvement = story_content['improvement_direction']
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800641 values.sort()
642 std = numpy.std(values)
643 metric_content['summary'] = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800644 'improvement_direction': improvement,
645 'name': metric_name,
646 'std': std,
647 'type': metric_type,
648 'units': units,
649 'values': values
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800650 }
651
652 benchmark_metadata = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800653 'description': benchmark_desc,
654 'name': benchmark_name,
655 'type': 'telemetry_benchmark'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800656 }
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800657 return {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800658 'benchmark_description': benchmark_desc,
659 'benchmark_metadata': benchmark_metadata,
660 'benchmark_name': benchmark_name,
661 'charts': charts,
662 'format_version': 1.0
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800663 }