blob: bd170f14bf8593191ef113fd3b339311a87348ad [file] [log] [blame]
Simran Basi833814b2013-01-29 13:13:43 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +08005import json
Simran Basi833814b2013-01-29 13:13:43 -08006import logging
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +08007import numbers
Simran Basi833814b2013-01-29 13:13:43 -08008import os
Chinglin Yu0adaf112020-01-20 14:48:20 +08009import tempfile
Simran Basi833814b2013-01-29 13:13:43 -080010import StringIO
11
Zhizhou Yang65d37042020-01-08 17:44:12 -080012import numpy
13
Simran Basi833814b2013-01-29 13:13:43 -080014from autotest_lib.client.common_lib import error, utils
15from autotest_lib.client.common_lib.cros import dev_server
16
Dave Tu6a404e62013-11-05 15:54:48 -080017TELEMETRY_RUN_BENCHMARKS_SCRIPT = 'tools/perf/run_benchmark'
Ilja H. Friedel086bc3f2014-02-27 22:17:55 -080018TELEMETRY_RUN_TESTS_SCRIPT = 'tools/telemetry/run_tests'
Gurchetan Singhfaf75e92017-04-17 18:09:44 -070019TELEMETRY_RUN_GPU_TESTS_SCRIPT = 'content/test/gpu/run_gpu_integration_test.py'
Mao Huangc9642d72017-09-28 16:50:02 +080020TELEMETRY_TIMEOUT_MINS = 150
Simran Basi833814b2013-01-29 13:13:43 -080021
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080022DUT_CHROME_ROOT = '/usr/local/telemetry/src'
23
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +080024CHART_JSON_RESULT = 'results-chart.json'
25HISTOGRAM_SET_RESULT = 'histograms.json'
Zhizhou Yang65d37042020-01-08 17:44:12 -080026PROFILE_ARTIFACTS = 'artifacts'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +080027
Simran Basi833814b2013-01-29 13:13:43 -080028# Result Statuses
29SUCCESS_STATUS = 'SUCCESS'
30WARNING_STATUS = 'WARNING'
31FAILED_STATUS = 'FAILED'
32
Kuo-Hsin Yang07da7b62018-08-08 16:56:06 +080033# A list of telemetry tests that cannot run on dut.
Kuo-Hsin Yange0915472018-09-10 10:36:16 +080034ON_DUT_BLACKLIST = [
Zhizhou Yang65d37042020-01-08 17:44:12 -080035 'cros_ui_smoothness', # crbug/976839
36 'loading.desktop', # crbug/882299
37 'rendering.desktop', # crbug/882291
38 'system_health.memory_desktop', # crbug/874386
Kuo-Hsin Yange0915472018-09-10 10:36:16 +080039]
Simran Basi833814b2013-01-29 13:13:43 -080040
Zhizhou Yang65d37042020-01-08 17:44:12 -080041
Simran Basi833814b2013-01-29 13:13:43 -080042class TelemetryResult(object):
43 """Class to represent the results of a telemetry run.
44
45 This class represents the results of a telemetry run, whether it ran
46 successful, failed or had warnings.
47 """
48
Simran Basi833814b2013-01-29 13:13:43 -080049 def __init__(self, exit_code=0, stdout='', stderr=''):
50 """Initializes this TelemetryResultObject instance.
51
52 @param status: Status of the telemtry run.
53 @param stdout: Stdout of the telemetry run.
54 @param stderr: Stderr of the telemetry run.
55 """
56 if exit_code == 0:
57 self.status = SUCCESS_STATUS
58 else:
59 self.status = FAILED_STATUS
60
Simran Basi833814b2013-01-29 13:13:43 -080061 self._stdout = stdout
62 self._stderr = stderr
63 self.output = '\n'.join([stdout, stderr])
64
65
Simran Basi833814b2013-01-29 13:13:43 -080066class TelemetryRunner(object):
67 """Class responsible for telemetry for a given build.
68
69 This class will extract and install telemetry on the devserver and is
70 responsible for executing the telemetry benchmarks and returning their
71 output to the caller.
72 """
73
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080074 def __init__(self, host, local=False, telemetry_on_dut=True):
Simran Basi833814b2013-01-29 13:13:43 -080075 """Initializes this telemetry runner instance.
76
77 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -080078
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080079 Basically, the following commands on the local pc on which test_that
80 will be executed, depending on the 4 possible combinations of
81 local x telemetry_on_dut:
82
83 local=True, telemetry_on_dut=False:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080084 python2 run_benchmark --browser=cros-chrome --remote=[dut] [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080085
86 local=True, telemetry_on_dut=True:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080087 ssh [dut] python2 run_benchmark --browser=system [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080088
89 local=False, telemetry_on_dut=False:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080090 ssh [devserver] python2 run_benchmark --browser=cros-chrome
91 --remote=[dut] [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080092
93 local=False, telemetry_on_dut=True:
Kuo-Hsin Yang66324262019-12-10 10:36:34 +080094 ssh [devserver] ssh [dut] python2 run_benchmark --browser=system [test]
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080095
Luis Lozano23ae3192013-11-08 16:22:46 -080096 @param host: Host where the test will be run.
97 @param local: If set, no devserver will be used, test will be run
98 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080099 If not set, "ssh [devserver] " will be appended to test
100 commands.
101 @param telemetry_on_dut: If set, telemetry itself (the test harness)
102 will run on dut.
103 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -0800104 """
105 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700106 self._devserver = None
107 self._telemetry_path = None
Zhizhou Yang65d37042020-01-08 17:44:12 -0800108 self._perf_value_writer = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800109 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -0800110 # TODO (llozano crbug.com/324964). Remove conditional code.
111 # Use a class hierarchy instead.
112 if local:
113 self._setup_local_telemetry()
114 else:
115 self._setup_devserver_telemetry()
Chinglin Yu0adaf112020-01-20 14:48:20 +0800116 self._benchmark_deps = None
Luis Lozano23ae3192013-11-08 16:22:46 -0800117
118 logging.debug('Telemetry Path: %s', self._telemetry_path)
119
Luis Lozano23ae3192013-11-08 16:22:46 -0800120 def _setup_devserver_telemetry(self):
121 """Setup Telemetry to use the devserver."""
122 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800123 logging.debug('Grabbing build from AFE.')
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800124 info = self._host.host_info_store.get()
125 if not info.build:
Simran Basi833814b2013-01-29 13:13:43 -0800126 logging.error('Unable to locate build label for host: %s.',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800127 self._host.host_port)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800128 raise error.AutotestError(
129 'Failed to grab build for host %s.' % self._host.host_port)
Simran Basi833814b2013-01-29 13:13:43 -0800130
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800131 logging.debug('Setting up telemetry for build: %s', info.build)
Simran Basi833814b2013-01-29 13:13:43 -0800132
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800133 self._devserver = dev_server.ImageServer.resolve(
134 info.build, hostname=self._host.hostname)
135 self._devserver.stage_artifacts(info.build, ['autotest_packages'])
Zhizhou Yang65d37042020-01-08 17:44:12 -0800136 self._telemetry_path = self._devserver.setup_telemetry(
137 build=info.build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800138
139 def _setup_local_telemetry(self):
140 """Setup Telemetry to use local path to its sources.
141
142 First look for chrome source root, either externally mounted, or inside
143 the chroot. Prefer chrome-src-internal source tree to chrome-src.
144 """
145 TELEMETRY_DIR = 'src'
146 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700147 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800148
149 logging.debug('Setting up telemetry for local testing')
150
151 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700152 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800153 dir_list.extend(
154 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
155 if 'CHROME_ROOT' in os.environ:
156 dir_list.insert(0, os.environ['CHROME_ROOT'])
157
158 telemetry_src = ''
159 for dir in dir_list:
160 if os.path.exists(dir):
161 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
162 break
163 else:
164 raise error.TestError('Telemetry source directory not found.')
165
166 self._devserver = None
167 self._telemetry_path = telemetry_src
168
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800169 def _get_telemetry_cmd(self, script, test_or_benchmark, output_format,
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700170 *args, **kwargs):
Luis Lozano23ae3192013-11-08 16:22:46 -0800171 """Build command to execute telemetry based on script and benchmark.
172
173 @param script: Telemetry script we want to run. For example:
174 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
175 @param test_or_benchmark: Name of the test or benchmark we want to run,
176 with the page_set (if required) as part of
177 the string.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800178 @param output_format: Format of the json result file: histogram or
179 chart-json.
Luis Lozano814c7182015-09-08 11:20:47 -0700180 @param args: additional list of arguments to pass to the script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700181 @param kwargs: additional list of keyword arguments to pass to the
182 script.
Luis Lozano814c7182015-09-08 11:20:47 -0700183
Luis Lozano23ae3192013-11-08 16:22:46 -0800184 @returns Full telemetry command to execute the script.
185 """
186 telemetry_cmd = []
187 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800188 devserver_hostname = self._devserver.hostname
Luis Lozano23ae3192013-11-08 16:22:46 -0800189 telemetry_cmd.extend(['ssh', devserver_hostname])
190
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700191 no_verbose = kwargs.get('no_verbose', False)
192
Zhizhou Yang65d37042020-01-08 17:44:12 -0800193 output_dir = (DUT_CHROME_ROOT
194 if self._telemetry_on_dut else self._telemetry_path)
195 # Create a temp directory to hold single test run.
196 if self._perf_value_writer:
197 output_dir = os.path.join(
198 output_dir, self._perf_value_writer.tmpdir.strip('/'))
199
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800200 if self._telemetry_on_dut:
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800201 telemetry_cmd.extend([
Zhizhou Yang65d37042020-01-08 17:44:12 -0800202 self._host.ssh_command(
203 alive_interval=900, connection_attempts=4),
204 'python2',
205 script,
206 '--output-format=%s' % output_format,
207 '--output-dir=%s' % output_dir,
208 '--browser=system',
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800209 ])
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800210 else:
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800211 telemetry_cmd.extend([
Zhizhou Yang65d37042020-01-08 17:44:12 -0800212 'python2',
213 script,
214 '--browser=cros-chrome',
215 '--output-format=%s' % output_format,
216 '--output-dir=%s' % output_dir,
Tiancong Wangbb13ed32020-03-25 16:50:28 -0700217 '--remote=%s' % self._host.hostname,
Denis Nikitinfdc2c712019-11-15 15:33:52 -0800218 ])
Tiancong Wangbb13ed32020-03-25 16:50:28 -0700219 if self._host.host_port != self._host.hostname:
220 # If the user specify a different port for the DUT, we should
221 # use different telemetry argument to set it up.
222 #
223 # e.g. When user is running experiments with ssh port
224 # forwarding, they specify remote as 127.0.0.1:2222. Now
225 # host_port is 127.0.0.1:2222 and hostname is 127.0.0.1
226 # port is 2222
227 telemetry_cmd.append('--remote-ssh-port=%s' % self._host.port)
228
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700229 if not no_verbose:
230 telemetry_cmd.append('--verbose')
Luis Lozano814c7182015-09-08 11:20:47 -0700231 telemetry_cmd.extend(args)
232 telemetry_cmd.append(test_or_benchmark)
233
Keith Haddow1e5c7012016-03-09 16:05:37 -0800234 return ' '.join(telemetry_cmd)
235
Zhizhou Yang65d37042020-01-08 17:44:12 -0800236 def _scp_telemetry_results_cmd(self, perf_results_dir, output_format,
237 artifacts):
Keith Haddow1e5c7012016-03-09 16:05:37 -0800238 """Build command to copy the telemetry results from the devserver.
239
240 @param perf_results_dir: directory path where test output is to be
241 collected.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800242 @param output_format: Format of the json result file: histogram or
243 chart-json.
244 @param artifacts: Whether we want to copy artifacts directory.
245
246 @returns SCP command to copy the results json to the specified
247 directory.
Keith Haddow1e5c7012016-03-09 16:05:37 -0800248 """
Dean Liaoe3e75f62017-11-14 10:36:43 +0800249 if not perf_results_dir:
250 return ''
251
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800252 output_filename = CHART_JSON_RESULT
253 if output_format == 'histograms':
254 output_filename = HISTOGRAM_SET_RESULT
Zhizhou Yang65d37042020-01-08 17:44:12 -0800255 scp_cmd = []
Dean Liaoe3e75f62017-11-14 10:36:43 +0800256 if self._telemetry_on_dut:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800257 scp_cmd.extend(['scp', '-r'])
258 scp_cmd.append(
259 self._host.make_ssh_options(
260 alive_interval=900, connection_attempts=4))
Dean Liaoe3e75f62017-11-14 10:36:43 +0800261 if not self._host.is_default_port:
262 scp_cmd.append('-P %d' % self._host.port)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800263 src = 'root@%s:%s' % (self._host.hostname, DUT_CHROME_ROOT)
Dean Liaoe3e75f62017-11-14 10:36:43 +0800264 else:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800265 # Use rsync --remove-source-file to move rather than copy from
266 # server. This is because each run will generate certain artifacts
267 # and will not be removed after, making result size getting larger.
268 # We don't do this for results on DUT because 1) rsync doesn't work
269 # 2) DUT will be reflashed frequently and no need to worry about
270 # result size.
271 scp_cmd.extend(['rsync', '-avz', '--remove-source-files'])
Dean Liaoe3e75f62017-11-14 10:36:43 +0800272 devserver_hostname = ''
Ricky Liangd186f3e2016-03-15 16:50:55 +0800273 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800274 devserver_hostname = self._devserver.hostname + ':'
Zhizhou Yang65d37042020-01-08 17:44:12 -0800275 src = '%s%s' % (devserver_hostname, self._telemetry_path)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800276
Zhizhou Yang65d37042020-01-08 17:44:12 -0800277 if self._perf_value_writer:
278 src = os.path.join(src, self._perf_value_writer.tmpdir.strip('/'))
279
280 scp_cmd.append(os.path.join(src, output_filename))
281
282 # Copy artifacts back to result directory if needed.
283 if artifacts:
284 scp_cmd.append(os.path.join(src, PROFILE_ARTIFACTS))
285
286 scp_cmd.append(perf_results_dir)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800287 return ' '.join(scp_cmd)
288
Keith Haddow1e5c7012016-03-09 16:05:37 -0800289 def _run_cmd(self, cmd):
290 """Execute an command in a external shell and capture the output.
291
292 @param cmd: String of is a valid shell command.
293
294 @returns The standard out, standard error and the integer exit code of
295 the executed command.
296 """
297 logging.debug('Running: %s', cmd)
298
299 output = StringIO.StringIO()
300 error_output = StringIO.StringIO()
301 exit_code = 0
302 try:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800303 result = utils.run(
304 cmd,
305 stdout_tee=output,
306 stderr_tee=error_output,
307 timeout=TELEMETRY_TIMEOUT_MINS * 60)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800308 exit_code = result.exit_status
309 except error.CmdError as e:
310 logging.debug('Error occurred executing.')
311 exit_code = e.result_obj.exit_status
312
313 stdout = output.getvalue()
314 stderr = error_output.getvalue()
315 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
316 'stderr:%s', exit_code, stdout, stderr)
317 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800318
Zhizhou Yang65d37042020-01-08 17:44:12 -0800319 def _run_telemetry(self, script, test_or_benchmark, output_format, *args,
320 **kwargs):
Simran Basi833814b2013-01-29 13:13:43 -0800321 """Runs telemetry on a dut.
322
323 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800324 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800325 @param test_or_benchmark: Name of the test or benchmark we want to run,
326 with the page_set (if required) as part of the
327 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700328 @param args: additional list of arguments to pass to the script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700329 @param kwargs: additional list of keyword arguments to pass to the
330 script.
Simran Basi833814b2013-01-29 13:13:43 -0800331
332 @returns A TelemetryResult Instance with the results of this telemetry
333 execution.
334 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700335 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800336
Zhizhou Yang65d37042020-01-08 17:44:12 -0800337 telemetry_cmd = self._get_telemetry_cmd(script, test_or_benchmark,
338 output_format, *args, **kwargs)
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700339 logging.info('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800340
Keith Haddow1e5c7012016-03-09 16:05:37 -0800341 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800342
Zhizhou Yang65d37042020-01-08 17:44:12 -0800343 return TelemetryResult(
344 exit_code=exit_code, stdout=stdout, stderr=stderr)
Simran Basi833814b2013-01-29 13:13:43 -0800345
Zhizhou Yang65d37042020-01-08 17:44:12 -0800346 def _run_scp(self, perf_results_dir, output_format, artifacts=False):
Keith Haddow1e5c7012016-03-09 16:05:37 -0800347 """Runs telemetry on a dut.
348
349 @param perf_results_dir: The local directory that results are being
350 collected.
Zhizhou Yang65d37042020-01-08 17:44:12 -0800351 @param output_format: Format of the json result file.
352 @param artifacts: Whether we want to copy artifacts directory.
Keith Haddow1e5c7012016-03-09 16:05:37 -0800353 """
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800354 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir,
Zhizhou Yang65d37042020-01-08 17:44:12 -0800355 output_format, artifacts)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800356 logging.debug('Retrieving Results: %s', scp_cmd)
Dean Liaoe4773c72017-11-09 16:15:38 +0800357 _, _, exit_code = self._run_cmd(scp_cmd)
358 if exit_code != 0:
359 raise error.TestFail('Unable to retrieve results.')
Keith Haddow1e5c7012016-03-09 16:05:37 -0800360
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800361 if output_format == 'histograms':
362 # Converts to chart json format.
363 input_filename = os.path.join(perf_results_dir,
364 HISTOGRAM_SET_RESULT)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800365 output_filename = os.path.join(perf_results_dir, CHART_JSON_RESULT)
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800366 histograms = json.loads(open(input_filename).read())
367 chartjson = TelemetryRunner.convert_chart_json(histograms)
368 with open(output_filename, 'w') as fout:
369 fout.write(json.dumps(chartjson, indent=2))
Keith Haddow1e5c7012016-03-09 16:05:37 -0800370
Luis Lozano814c7182015-09-08 11:20:47 -0700371 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700372 """Runs a telemetry test on a dut.
373
374 @param script: Which telemetry test script we want to run. Can be
375 telemetry's base test script or the Chrome OS specific
376 test script.
377 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700378 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700379
380 @returns A TelemetryResult Instance with the results of this telemetry
381 execution.
382 """
383 logging.debug('Running telemetry test: %s', test)
384 telemetry_script = os.path.join(self._telemetry_path, script)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800385 result = self._run_telemetry(telemetry_script, test, 'chartjson',
386 *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700387 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700388 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700389 return result
390
Luis Lozano814c7182015-09-08 11:20:47 -0700391 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800392 """Runs a telemetry test on a dut.
393
394 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700395 @param args: additional list of arguments to pass to the telemetry
396 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800397
398 @returns A TelemetryResult Instance with the results of this telemetry
399 execution.
400 """
Luis Lozano814c7182015-09-08 11:20:47 -0700401 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700402
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700403 def run_telemetry_benchmark(self,
404 benchmark,
405 perf_value_writer=None,
406 *args,
407 **kwargs):
Simran Basi833814b2013-01-29 13:13:43 -0800408 """Runs a telemetry benchmark on a dut.
409
410 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800411 @param perf_value_writer: Should be an instance with the function
412 output_perf_value(), if None, no perf value
413 will be written. Typically this will be the
414 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700415 @param args: additional list of arguments to pass to the telemetry
416 execution script.
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700417 @param kwargs: additional list of keyword arguments to pass to the
418 telemetry execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800419
420 @returns A TelemetryResult Instance with the results of this telemetry
421 execution.
422 """
Dave Tu6a404e62013-11-05 15:54:48 -0800423 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800424
Zhizhou Yang65d37042020-01-08 17:44:12 -0800425 self._perf_value_writer = perf_value_writer
426
Kuo-Hsin Yang07da7b62018-08-08 16:56:06 +0800427 if benchmark in ON_DUT_BLACKLIST:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800428 self._telemetry_on_dut = False
429
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700430 output_format = kwargs.get('ex_output_format', '')
431
432 if not output_format:
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800433 output_format = 'histograms'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800434
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800435 if self._telemetry_on_dut:
436 telemetry_script = os.path.join(DUT_CHROME_ROOT,
437 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
438 self._ensure_deps(self._host, benchmark)
439 else:
440 telemetry_script = os.path.join(self._telemetry_path,
441 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
442
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800443 result = self._run_telemetry(telemetry_script, benchmark,
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700444 output_format, *args, **kwargs)
Simran Basi833814b2013-01-29 13:13:43 -0800445
446 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800447 raise error.TestWarn('Telemetry Benchmark: %s'
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700448 ' exited with Warnings.\nOutput:\n%s\n' %
449 (benchmark, result.output))
450 elif result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800451 raise error.TestFail('Telemetry Benchmark: %s'
Zhizhou Yangf32fda02019-09-25 14:01:45 -0700452 ' failed to run.\nOutput:\n%s\n' %
453 (benchmark, result.output))
454 elif '[ PASSED ] 0 tests.' in result.output:
455 raise error.TestWarn('Telemetry Benchmark: %s exited successfully,'
456 ' but no test actually passed.\nOutput\n%s\n'
457 % (benchmark, result.output))
Keith Haddow1e5c7012016-03-09 16:05:37 -0800458 if perf_value_writer:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800459 artifacts = kwargs.get('artifacts', False)
460 self._run_scp(perf_value_writer.resultsdir, output_format,
461 artifacts)
Simran Basi833814b2013-01-29 13:13:43 -0800462 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800463
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700464 def run_gpu_integration_test(self, test, *args):
465 """Runs a gpu test on a dut.
466
467 @param test: Gpu test we want to run.
468 @param args: additional list of arguments to pass to the telemetry
469 execution script.
470
Drew Davenport84395922018-09-10 10:42:37 -0600471 @returns A TelemetryResult instance with the results of this telemetry
472 execution.
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700473 """
Zhizhou Yang65d37042020-01-08 17:44:12 -0800474 script = os.path.join(DUT_CHROME_ROOT, TELEMETRY_RUN_GPU_TESTS_SCRIPT)
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700475 cmd = []
476 if self._devserver:
477 devserver_hostname = self._devserver.hostname
478 cmd.extend(['ssh', devserver_hostname])
479
Zhizhou Yang65d37042020-01-08 17:44:12 -0800480 cmd.extend([
481 self._host.ssh_command(
482 alive_interval=900, connection_attempts=4), 'python2',
483 script
484 ])
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700485 cmd.extend(args)
486 cmd.append(test)
487 cmd = ' '.join(cmd)
488 stdout, stderr, exit_code = self._run_cmd(cmd)
489
Drew Davenport84395922018-09-10 10:42:37 -0600490 if exit_code:
491 raise error.TestFail('Gpu Integration Test: %s'
492 ' failed to run.' % test)
493
Zhizhou Yang65d37042020-01-08 17:44:12 -0800494 return TelemetryResult(
495 exit_code=exit_code, stdout=stdout, stderr=stderr)
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700496
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800497 def _ensure_deps(self, dut, test_name):
498 """
499 Ensure the dependencies are locally available on DUT.
500
501 @param dut: The autotest host object representing DUT.
502 @param test_name: Name of the telemetry test.
503 """
504 # Get DEPs using host's telemetry.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800505 # Example output, fetch_benchmark_deps.py --output-deps=deps octane:
506 # {'octane': ['tools/perf/page_sets/data/octane_002.wprgo']}
Zhizhou Yang65d37042020-01-08 17:44:12 -0800507 fetch_path = os.path.join(self._telemetry_path, 'tools', 'perf',
508 'fetch_benchmark_deps.py')
Chinglin Yu0adaf112020-01-20 14:48:20 +0800509 # Use a temporary file for |deps_path| to avoid race conditions. The
510 # created temporary file is assigned to |self._benchmark_deps| to make
511 # it valid until |self| is destroyed.
512 self._benchmark_deps = tempfile.NamedTemporaryFile(
Zhizhou Yang65d37042020-01-08 17:44:12 -0800513 prefix='fetch_benchmark_deps_result.', suffix='.json')
Chinglin Yu0adaf112020-01-20 14:48:20 +0800514 deps_path = self._benchmark_deps.name
Kuo-Hsin Yang66324262019-12-10 10:36:34 +0800515 format_fetch = ('python2 %s --output-deps=%s %s')
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800516 command_fetch = format_fetch % (fetch_path, deps_path, test_name)
517 command_get = 'cat %s' % deps_path
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800518
519 if self._devserver:
520 devserver_hostname = self._devserver.url().split(
521 'http://')[1].split(':')[0]
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800522 command_fetch = 'ssh %s %s' % (devserver_hostname, command_fetch)
523 command_get = 'ssh %s %s' % (devserver_hostname, command_get)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800524
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800525 logging.info('Getting DEPs: %s', command_fetch)
526 _, _, exit_code = self._run_cmd(command_fetch)
527 if exit_code != 0:
528 raise error.TestFail('Error occurred while fetching DEPs.')
529 stdout, _, exit_code = self._run_cmd(command_get)
530 if exit_code != 0:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800531 raise error.TestFail('Error occurred while getting DEPs.')
532
533 # Download DEPs to DUT.
534 # send_file() relies on rsync over ssh. Couldn't be better.
Kuo-Hsin Yang4a006172018-04-25 14:44:55 +0800535 deps = json.loads(stdout)
536 for dep in deps[test_name]:
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800537 src = os.path.join(self._telemetry_path, dep)
538 dst = os.path.join(DUT_CHROME_ROOT, dep)
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800539 if self._devserver:
540 logging.info('Copying: %s -> %s', src, dst)
Zhizhou Yang65d37042020-01-08 17:44:12 -0800541 rsync_cmd = utils.sh_escape(
542 'rsync %s %s %s:%s' % (self._host.rsync_options(), src,
543 self._host.hostname, dst))
Chung-yih Wangfd8eb242017-12-09 19:23:04 +0800544 utils.run('ssh %s "%s"' % (devserver_hostname, rsync_cmd))
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800545 else:
546 if not os.path.isfile(src):
547 raise error.TestFail('Error occurred while saving DEPs.')
548 logging.info('Copying: %s -> %s', src, dst)
549 dut.send_file(src, dst)
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800550
551 @staticmethod
552 def convert_chart_json(histogram_set):
553 """
554 Convert from histogram set to chart json format.
555
556 @param histogram_set: result in histogram set format.
557
558 @returns result in chart json format.
559 """
560 value_map = {}
561
562 # Gets generic set values.
563 for obj in histogram_set:
564 if 'type' in obj and obj['type'] == 'GenericSet':
565 value_map[obj['guid']] = obj['values']
566
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800567 charts = {}
568 benchmark_name = ''
569 benchmark_desc = ''
570
571 # Checks the unit test for how this conversion works.
572 for obj in histogram_set:
573 if 'name' not in obj or 'sampleValues' not in obj:
574 continue
575 metric_name = obj['name']
576 diagnostics = obj['diagnostics']
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800577 if diagnostics.has_key('stories'):
578 story_name = value_map[diagnostics['stories']][0]
579 else:
580 story_name = 'default'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800581 local_benchmark_name = value_map[diagnostics['benchmarks']][0]
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800582 if benchmark_name == '':
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800583 benchmark_name = local_benchmark_name
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800584 if diagnostics.has_key('benchmarkDescriptions'):
585 benchmark_desc = value_map[
Zhizhou Yang65d37042020-01-08 17:44:12 -0800586 diagnostics['benchmarkDescriptions']][0]
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800587 if benchmark_name != local_benchmark_name:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800588 logging.warning(
589 'There are more than 1 benchmark names in the'
590 'result. old: %s, new: %s', benchmark_name,
591 local_benchmark_name)
Kuo-Hsin Yang72901f32019-10-28 14:24:58 +0800592 continue
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800593
594 unit = obj['unit']
595 smaller_postfixes = ('_smallerIsBetter', '-')
596 bigger_postfixes = ('_biggerIsBetter', '+')
597 all_postfixes = smaller_postfixes + bigger_postfixes
598
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800599 improvement = 'up'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800600 for postfix in smaller_postfixes:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800601 if unit.endswith(postfix):
602 improvement = 'down'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800603 for postfix in all_postfixes:
Zhizhou Yang65d37042020-01-08 17:44:12 -0800604 if unit.endswith(postfix):
605 unit = unit[:-len(postfix)]
606 break
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800607
608 if unit == 'unitless':
Zhizhou Yang65d37042020-01-08 17:44:12 -0800609 unit = 'score'
Kuo-Hsin Yang99622172019-09-16 20:02:53 +0800610
Zhizhou Yang65d37042020-01-08 17:44:12 -0800611 values = [
612 x for x in obj['sampleValues']
613 if isinstance(x, numbers.Number)
614 ]
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800615 if metric_name not in charts:
616 charts[metric_name] = {}
617 charts[metric_name][story_name] = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800618 'improvement_direction': improvement,
619 'name': metric_name,
620 'std': numpy.std(values),
621 'type': 'list_of_scalar_values',
622 'units': unit,
623 'values': values
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800624 }
625
626 # Adds summaries.
627 for metric_name in charts:
628 values = []
629 metric_content = charts[metric_name]
630 for story_name in metric_content:
631 story_content = metric_content[story_name]
632 values += story_content['values']
633 metric_type = story_content['type']
634 units = story_content['units']
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800635 improvement = story_content['improvement_direction']
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800636 values.sort()
637 std = numpy.std(values)
638 metric_content['summary'] = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800639 'improvement_direction': improvement,
640 'name': metric_name,
641 'std': std,
642 'type': metric_type,
643 'units': units,
644 'values': values
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800645 }
646
647 benchmark_metadata = {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800648 'description': benchmark_desc,
649 'name': benchmark_name,
650 'type': 'telemetry_benchmark'
Kuo-Hsin Yang14d002b2019-06-06 13:18:04 +0800651 }
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800652 return {
Zhizhou Yang65d37042020-01-08 17:44:12 -0800653 'benchmark_description': benchmark_desc,
654 'benchmark_metadata': benchmark_metadata,
655 'benchmark_name': benchmark_name,
656 'charts': charts,
657 'format_version': 1.0
Kuo-Hsin Yange01ff602019-06-14 11:33:31 +0800658 }