blob: c78bb94937217fc837e97c7ce28ddaf3fdc0b68d [file] [log] [blame]
Simran Basi833814b2013-01-29 13:13:43 -08001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import os
Simran Basi833814b2013-01-29 13:13:43 -08007import StringIO
8
Simran Basi833814b2013-01-29 13:13:43 -08009from autotest_lib.client.common_lib import error, utils
10from autotest_lib.client.common_lib.cros import dev_server
11
12
Dave Tu6a404e62013-11-05 15:54:48 -080013TELEMETRY_RUN_BENCHMARKS_SCRIPT = 'tools/perf/run_benchmark'
Ilja H. Friedel086bc3f2014-02-27 22:17:55 -080014TELEMETRY_RUN_TESTS_SCRIPT = 'tools/telemetry/run_tests'
Gurchetan Singhfaf75e92017-04-17 18:09:44 -070015TELEMETRY_RUN_GPU_TESTS_SCRIPT = 'content/test/gpu/run_gpu_integration_test.py'
Achuith Bhandarkar124e4732014-01-21 15:27:54 -080016TELEMETRY_TIMEOUT_MINS = 120
Simran Basi833814b2013-01-29 13:13:43 -080017
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080018DUT_CHROME_ROOT = '/usr/local/telemetry/src'
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +080019DUT_COMMON_SSH_OPTIONS = ['-o StrictHostKeyChecking=no',
20 '-o UserKnownHostsFile=/dev/null',
21 '-o BatchMode=yes',
22 '-o ConnectTimeout=30',
23 '-o ServerAliveInterval=900',
24 '-o ServerAliveCountMax=3',
25 '-o ConnectionAttempts=4',
26 '-o Protocol=2']
27DUT_SSH_OPTIONS = ' '.join(DUT_COMMON_SSH_OPTIONS + ['-x', '-a', '-l root'])
28DUT_SCP_OPTIONS = ' '.join(DUT_COMMON_SSH_OPTIONS)
29DUT_RSYNC_OPTIONS = ' '.join(['--rsh="/usr/bin/ssh %s"' % DUT_SSH_OPTIONS,
30 '-L', '--timeout=1800', '-az',
31 '--no-o', '--no-g'])
Ting-Yuan Huang74036ae2016-04-08 17:03:34 +080032# Prevent double quotes from being unfolded.
33DUT_RSYNC_OPTIONS = utils.sh_escape(DUT_RSYNC_OPTIONS)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080034
Simran Basi833814b2013-01-29 13:13:43 -080035# Result Statuses
36SUCCESS_STATUS = 'SUCCESS'
37WARNING_STATUS = 'WARNING'
38FAILED_STATUS = 'FAILED'
39
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080040# A list of benchmarks with that the telemetry test harness can run on dut.
41ON_DUT_WHITE_LIST = ['dromaeo.domcoreattr',
42 'dromaeo.domcoremodify',
43 'dromaeo.domcorequery',
44 'dromaeo.domcoretraverse',
45 'image_decoding.image_decoding_measurement',
46 'jetstream',
47 'kraken',
48 'memory.top_7_stress',
49 'octane',
50 'page_cycler.typical_25',
Chung-yih Wang80ba5812017-01-20 17:22:11 +080051 'page_cycler_v2.typical_25',
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080052 'robohornet_pro',
53 'smoothness.top_25_smooth',
54 'smoothness.tough_animation_cases',
55 'smoothness.tough_canvas_cases',
56 'smoothness.tough_filters_cases',
57 'smoothness.tough_pinch_zoom_cases',
58 'smoothness.tough_scrolling_cases',
59 'smoothness.tough_webgl_cases',
60 'speedometer',
61 'startup.cold.blank_page',
62 'sunspider',
63 'tab_switching.top_10',
Chung-yih Wang80ba5812017-01-20 17:22:11 +080064 'tab_switching.typical_25',
Patrik Höglund2ef2e172016-11-16 10:07:29 +010065 'webrtc.peerconnection',
66 'webrtc.stress']
Ting-Yuan Huange5b19132016-03-22 13:02:41 +080067
68# BLACK LIST
69# 'session_restore.cold.typical_25', # profile generator not implemented on
70 # CrOS.
Simran Basi833814b2013-01-29 13:13:43 -080071
72class TelemetryResult(object):
73 """Class to represent the results of a telemetry run.
74
75 This class represents the results of a telemetry run, whether it ran
76 successful, failed or had warnings.
77 """
78
79
80 def __init__(self, exit_code=0, stdout='', stderr=''):
81 """Initializes this TelemetryResultObject instance.
82
83 @param status: Status of the telemtry run.
84 @param stdout: Stdout of the telemetry run.
85 @param stderr: Stderr of the telemetry run.
86 """
87 if exit_code == 0:
88 self.status = SUCCESS_STATUS
89 else:
90 self.status = FAILED_STATUS
91
Simran Basi833814b2013-01-29 13:13:43 -080092 self._stdout = stdout
93 self._stderr = stderr
94 self.output = '\n'.join([stdout, stderr])
95
96
Simran Basi833814b2013-01-29 13:13:43 -080097class TelemetryRunner(object):
98 """Class responsible for telemetry for a given build.
99
100 This class will extract and install telemetry on the devserver and is
101 responsible for executing the telemetry benchmarks and returning their
102 output to the caller.
103 """
104
Ting-Yuan Huang85dcde82016-04-08 17:41:32 +0800105 def __init__(self, host, local=False, telemetry_on_dut=True):
Simran Basi833814b2013-01-29 13:13:43 -0800106 """Initializes this telemetry runner instance.
107
108 If telemetry is not installed for this build, it will be.
Luis Lozano23ae3192013-11-08 16:22:46 -0800109
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800110 Basically, the following commands on the local pc on which test_that
111 will be executed, depending on the 4 possible combinations of
112 local x telemetry_on_dut:
113
114 local=True, telemetry_on_dut=False:
115 run_benchmark --browser=cros-chrome --remote=[dut] [test]
116
117 local=True, telemetry_on_dut=True:
118 ssh [dut] run_benchmark --browser=system [test]
119
120 local=False, telemetry_on_dut=False:
121 ssh [devserver] run_benchmark --browser=cros-chrome --remote=[dut] [test]
122
123 local=False, telemetry_on_dut=True:
124 ssh [devserver] ssh [dut] run_benchmark --browser=system [test]
125
Luis Lozano23ae3192013-11-08 16:22:46 -0800126 @param host: Host where the test will be run.
127 @param local: If set, no devserver will be used, test will be run
128 locally.
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800129 If not set, "ssh [devserver] " will be appended to test
130 commands.
131 @param telemetry_on_dut: If set, telemetry itself (the test harness)
132 will run on dut.
133 It decides browser=[system|cros-chrome]
Simran Basi833814b2013-01-29 13:13:43 -0800134 """
135 self._host = host
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700136 self._devserver = None
137 self._telemetry_path = None
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800138 self._telemetry_on_dut = telemetry_on_dut
Luis Lozano23ae3192013-11-08 16:22:46 -0800139 # TODO (llozano crbug.com/324964). Remove conditional code.
140 # Use a class hierarchy instead.
141 if local:
142 self._setup_local_telemetry()
143 else:
144 self._setup_devserver_telemetry()
145
146 logging.debug('Telemetry Path: %s', self._telemetry_path)
147
148
149 def _setup_devserver_telemetry(self):
150 """Setup Telemetry to use the devserver."""
151 logging.debug('Setting up telemetry for devserver testing')
Simran Basi833814b2013-01-29 13:13:43 -0800152 logging.debug('Grabbing build from AFE.')
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800153 info = self._host.host_info_store.get()
154 if not info.build:
Simran Basi833814b2013-01-29 13:13:43 -0800155 logging.error('Unable to locate build label for host: %s.',
156 self._host.hostname)
157 raise error.AutotestError('Failed to grab build for host %s.' %
158 self._host.hostname)
159
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800160 logging.debug('Setting up telemetry for build: %s', info.build)
Simran Basi833814b2013-01-29 13:13:43 -0800161
Prathmesh Prabhucfff58a2017-02-06 10:07:43 -0800162 self._devserver = dev_server.ImageServer.resolve(
163 info.build, hostname=self._host.hostname)
164 self._devserver.stage_artifacts(info.build, ['autotest_packages'])
165 self._telemetry_path = self._devserver.setup_telemetry(build=info.build)
Luis Lozano23ae3192013-11-08 16:22:46 -0800166
167
168 def _setup_local_telemetry(self):
169 """Setup Telemetry to use local path to its sources.
170
171 First look for chrome source root, either externally mounted, or inside
172 the chroot. Prefer chrome-src-internal source tree to chrome-src.
173 """
174 TELEMETRY_DIR = 'src'
175 CHROME_LOCAL_SRC = '/var/cache/chromeos-cache/distfiles/target/'
Josh Triplett05208c92014-07-17 13:21:29 -0700176 CHROME_EXTERNAL_SRC = os.path.expanduser('~/chrome_root/')
Luis Lozano23ae3192013-11-08 16:22:46 -0800177
178 logging.debug('Setting up telemetry for local testing')
179
180 sources_list = ('chrome-src-internal', 'chrome-src')
Josh Triplett05208c92014-07-17 13:21:29 -0700181 dir_list = [CHROME_EXTERNAL_SRC]
Luis Lozano23ae3192013-11-08 16:22:46 -0800182 dir_list.extend(
183 [os.path.join(CHROME_LOCAL_SRC, x) for x in sources_list])
184 if 'CHROME_ROOT' in os.environ:
185 dir_list.insert(0, os.environ['CHROME_ROOT'])
186
187 telemetry_src = ''
188 for dir in dir_list:
189 if os.path.exists(dir):
190 telemetry_src = os.path.join(dir, TELEMETRY_DIR)
191 break
192 else:
193 raise error.TestError('Telemetry source directory not found.')
194
195 self._devserver = None
196 self._telemetry_path = telemetry_src
197
198
Luis Lozano814c7182015-09-08 11:20:47 -0700199 def _get_telemetry_cmd(self, script, test_or_benchmark, *args):
Luis Lozano23ae3192013-11-08 16:22:46 -0800200 """Build command to execute telemetry based on script and benchmark.
201
202 @param script: Telemetry script we want to run. For example:
203 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
204 @param test_or_benchmark: Name of the test or benchmark we want to run,
205 with the page_set (if required) as part of
206 the string.
Luis Lozano814c7182015-09-08 11:20:47 -0700207 @param args: additional list of arguments to pass to the script.
208
Luis Lozano23ae3192013-11-08 16:22:46 -0800209 @returns Full telemetry command to execute the script.
210 """
211 telemetry_cmd = []
212 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800213 devserver_hostname = self._devserver.hostname
Luis Lozano23ae3192013-11-08 16:22:46 -0800214 telemetry_cmd.extend(['ssh', devserver_hostname])
215
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800216 if self._telemetry_on_dut:
217 telemetry_cmd.extend(
218 ['ssh',
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800219 DUT_SSH_OPTIONS,
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800220 self._host.hostname,
221 'python',
222 script,
223 '--verbose',
224 '--output-format=chartjson',
225 '--output-dir=%s' % DUT_CHROME_ROOT,
226 '--browser=system'])
227 else:
228 telemetry_cmd.extend(
229 ['python',
230 script,
231 '--verbose',
232 '--browser=cros-chrome',
233 '--output-format=chartjson',
234 '--output-dir=%s' % self._telemetry_path,
235 '--remote=%s' % self._host.hostname])
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
241
242 def _scp_telemetry_results_cmd(self, perf_results_dir):
243 """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.
247 @returns SCP command to copy the results json to the specified directory.
248 """
249 scp_cmd = []
Ricky Liangd186f3e2016-03-15 16:50:55 +0800250 devserver_hostname = ''
251 if perf_results_dir:
252 if self._devserver:
Allen Lia5cfb972016-12-27 17:17:22 -0800253 devserver_hostname = self._devserver.hostname + ':'
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800254 if self._telemetry_on_dut:
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800255 src = ('root@%s:%s/results-chart.json' %
256 (self._host.hostname, DUT_CHROME_ROOT))
257 scp_cmd.extend(['scp', DUT_SCP_OPTIONS, src, perf_results_dir])
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800258 else:
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800259 src = ('%s%s/results-chart.json' %
260 (devserver_hostname, self._telemetry_path))
261 scp_cmd.extend(['scp', src, perf_results_dir])
Keith Haddow1e5c7012016-03-09 16:05:37 -0800262
263 return ' '.join(scp_cmd)
264
265
266 def _run_cmd(self, cmd):
267 """Execute an command in a external shell and capture the output.
268
269 @param cmd: String of is a valid shell command.
270
271 @returns The standard out, standard error and the integer exit code of
272 the executed command.
273 """
274 logging.debug('Running: %s', cmd)
275
276 output = StringIO.StringIO()
277 error_output = StringIO.StringIO()
278 exit_code = 0
279 try:
280 result = utils.run(cmd, stdout_tee=output,
281 stderr_tee=error_output,
282 timeout=TELEMETRY_TIMEOUT_MINS*60)
283 exit_code = result.exit_status
284 except error.CmdError as e:
285 logging.debug('Error occurred executing.')
286 exit_code = e.result_obj.exit_status
287
288 stdout = output.getvalue()
289 stderr = error_output.getvalue()
290 logging.debug('Completed with exit code: %d.\nstdout:%s\n'
291 'stderr:%s', exit_code, stdout, stderr)
292 return stdout, stderr, exit_code
Simran Basi833814b2013-01-29 13:13:43 -0800293
294
Luis Lozano814c7182015-09-08 11:20:47 -0700295 def _run_telemetry(self, script, test_or_benchmark, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800296 """Runs telemetry on a dut.
297
298 @param script: Telemetry script we want to run. For example:
Luis Lozano23ae3192013-11-08 16:22:46 -0800299 [path_to_telemetry_src]/src/tools/telemetry/run_tests.
Simran Basi833814b2013-01-29 13:13:43 -0800300 @param test_or_benchmark: Name of the test or benchmark we want to run,
301 with the page_set (if required) as part of the
302 string.
Luis Lozano814c7182015-09-08 11:20:47 -0700303 @param args: additional list of arguments to pass to the script.
Simran Basi833814b2013-01-29 13:13:43 -0800304
305 @returns A TelemetryResult Instance with the results of this telemetry
306 execution.
307 """
Simran Basi1dbfc132013-05-02 10:11:02 -0700308 # TODO (sbasi crbug.com/239933) add support for incognito mode.
Simran Basi833814b2013-01-29 13:13:43 -0800309
Luis Lozano814c7182015-09-08 11:20:47 -0700310 telemetry_cmd = self._get_telemetry_cmd(script,
311 test_or_benchmark,
312 *args)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800313 logging.debug('Running Telemetry: %s', telemetry_cmd)
Luis Lozano23ae3192013-11-08 16:22:46 -0800314
Keith Haddow1e5c7012016-03-09 16:05:37 -0800315 stdout, stderr, exit_code = self._run_cmd(telemetry_cmd)
Simran Basi833814b2013-01-29 13:13:43 -0800316
317 return TelemetryResult(exit_code=exit_code, stdout=stdout,
318 stderr=stderr)
319
320
Keith Haddow1e5c7012016-03-09 16:05:37 -0800321 def _run_scp(self, perf_results_dir):
322 """Runs telemetry on a dut.
323
324 @param perf_results_dir: The local directory that results are being
325 collected.
326 """
327 scp_cmd = self._scp_telemetry_results_cmd(perf_results_dir)
328 logging.debug('Retrieving Results: %s', scp_cmd)
329
330 self._run_cmd(scp_cmd)
331
332
Luis Lozano814c7182015-09-08 11:20:47 -0700333 def _run_test(self, script, test, *args):
Simran Basi1dbfc132013-05-02 10:11:02 -0700334 """Runs a telemetry test on a dut.
335
336 @param script: Which telemetry test script we want to run. Can be
337 telemetry's base test script or the Chrome OS specific
338 test script.
339 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700340 @param args: additional list of arguments to pass to the script.
Simran Basi1dbfc132013-05-02 10:11:02 -0700341
342 @returns A TelemetryResult Instance with the results of this telemetry
343 execution.
344 """
345 logging.debug('Running telemetry test: %s', test)
346 telemetry_script = os.path.join(self._telemetry_path, script)
Luis Lozano814c7182015-09-08 11:20:47 -0700347 result = self._run_telemetry(telemetry_script, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700348 if result.status is FAILED_STATUS:
Ilja H. Friedelc7bf3102014-05-13 17:31:25 -0700349 raise error.TestFail('Telemetry test %s failed.' % test)
Simran Basi1dbfc132013-05-02 10:11:02 -0700350 return result
351
352
Luis Lozano814c7182015-09-08 11:20:47 -0700353 def run_telemetry_test(self, test, *args):
Simran Basi833814b2013-01-29 13:13:43 -0800354 """Runs a telemetry test on a dut.
355
356 @param test: Telemetry test we want to run.
Luis Lozano814c7182015-09-08 11:20:47 -0700357 @param args: additional list of arguments to pass to the telemetry
358 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800359
360 @returns A TelemetryResult Instance with the results of this telemetry
361 execution.
362 """
Luis Lozano814c7182015-09-08 11:20:47 -0700363 return self._run_test(TELEMETRY_RUN_TESTS_SCRIPT, test, *args)
Simran Basi1dbfc132013-05-02 10:11:02 -0700364
365
Luis Lozano814c7182015-09-08 11:20:47 -0700366 def run_telemetry_benchmark(self, benchmark, perf_value_writer=None,
367 *args):
Simran Basi833814b2013-01-29 13:13:43 -0800368 """Runs a telemetry benchmark on a dut.
369
370 @param benchmark: Benchmark we want to run.
Fang Denge689e712013-11-13 18:27:06 -0800371 @param perf_value_writer: Should be an instance with the function
372 output_perf_value(), if None, no perf value
373 will be written. Typically this will be the
374 job object from an autotest test.
Luis Lozano814c7182015-09-08 11:20:47 -0700375 @param args: additional list of arguments to pass to the telemetry
376 execution script.
Simran Basi833814b2013-01-29 13:13:43 -0800377
378 @returns A TelemetryResult Instance with the results of this telemetry
379 execution.
380 """
Dave Tu6a404e62013-11-05 15:54:48 -0800381 logging.debug('Running telemetry benchmark: %s', benchmark)
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800382
383 if benchmark not in ON_DUT_WHITE_LIST:
384 self._telemetry_on_dut = False
385
386 if self._telemetry_on_dut:
387 telemetry_script = os.path.join(DUT_CHROME_ROOT,
388 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
389 self._ensure_deps(self._host, benchmark)
390 else:
391 telemetry_script = os.path.join(self._telemetry_path,
392 TELEMETRY_RUN_BENCHMARKS_SCRIPT)
393
Luis Lozano814c7182015-09-08 11:20:47 -0700394 result = self._run_telemetry(telemetry_script, benchmark, *args)
Simran Basi833814b2013-01-29 13:13:43 -0800395
396 if result.status is WARNING_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800397 raise error.TestWarn('Telemetry Benchmark: %s'
398 ' exited with Warnings.' % benchmark)
Simran Basi833814b2013-01-29 13:13:43 -0800399 if result.status is FAILED_STATUS:
Dave Tu6a404e62013-11-05 15:54:48 -0800400 raise error.TestFail('Telemetry Benchmark: %s'
401 ' failed to run.' % benchmark)
Keith Haddow1e5c7012016-03-09 16:05:37 -0800402 if perf_value_writer:
403 self._run_scp(perf_value_writer.resultsdir)
Simran Basi833814b2013-01-29 13:13:43 -0800404 return result
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800405
Gurchetan Singhfaf75e92017-04-17 18:09:44 -0700406
407 def run_gpu_integration_test(self, test, *args):
408 """Runs a gpu test on a dut.
409
410 @param test: Gpu test we want to run.
411 @param args: additional list of arguments to pass to the telemetry
412 execution script.
413
414 @returns A TelemetryResult instance with the results of this telemetry
415 execution.
416 """
417 script = os.path.join(DUT_CHROME_ROOT,
418 TELEMETRY_RUN_GPU_TESTS_SCRIPT)
419 cmd = []
420 if self._devserver:
421 devserver_hostname = self._devserver.hostname
422 cmd.extend(['ssh', devserver_hostname])
423
424 cmd.extend(
425 ['ssh',
426 DUT_SSH_OPTIONS,
427 self._host.hostname,
428 'python',
429 script])
430
431 cmd.extend(args)
432 cmd.append(test)
433 cmd = ' '.join(cmd)
434 stdout, stderr, exit_code = self._run_cmd(cmd)
435
436 return TelemetryResult(exit_code=exit_code, stdout=stdout,
437 stderr=stderr)
438
439
Ting-Yuan Huange5b19132016-03-22 13:02:41 +0800440 def _ensure_deps(self, dut, test_name):
441 """
442 Ensure the dependencies are locally available on DUT.
443
444 @param dut: The autotest host object representing DUT.
445 @param test_name: Name of the telemetry test.
446 """
447 # Get DEPs using host's telemetry.
448 format_string = ('python %s/tools/perf/fetch_benchmark_deps.py %s')
449 command = format_string % (self._telemetry_path, test_name)
450 stdout = StringIO.StringIO()
451 stderr = StringIO.StringIO()
452
453 if self._devserver:
454 devserver_hostname = self._devserver.url().split(
455 'http://')[1].split(':')[0]
456 command = 'ssh %s %s' % (devserver_hostname, command)
457
458 logging.info('Getting DEPs: %s', command)
459 try:
460 result = utils.run(command, stdout_tee=stdout,
461 stderr_tee=stderr)
462 except error.CmdError as e:
463 logging.debug('Error occurred getting DEPs: %s\n %s\n',
464 stdout.getvalue(), stderr.getvalue())
465 raise error.TestFail('Error occurred while getting DEPs.')
466
467 # Download DEPs to DUT.
468 # send_file() relies on rsync over ssh. Couldn't be better.
469 stdout_str = stdout.getvalue()
470 stdout.close()
471 stderr.close()
472 for dep in stdout_str.split():
473 src = os.path.join(self._telemetry_path, dep)
474 dst = os.path.join(DUT_CHROME_ROOT, dep)
Ting-Yuan Huang8a2c7f72016-03-28 22:01:07 +0800475 if self._devserver:
476 logging.info('Copying: %s -> %s', src, dst)
477 utils.run('ssh %s rsync %s %s %s:%s' %
478 (devserver_hostname, DUT_RSYNC_OPTIONS, src,
479 self._host.hostname, dst))
480 else:
481 if not os.path.isfile(src):
482 raise error.TestFail('Error occurred while saving DEPs.')
483 logging.info('Copying: %s -> %s', src, dst)
484 dut.send_file(src, dst)