1# Copyright (c) 2012 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# Author: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
5
6import datetime
7import logging
8import os
9
10from autotest_lib.client.cros import storage as storage_mod
11from autotest_lib.client.common_lib import autotemp, error
12from autotest_lib.client.bin  import base_utils
13
14USECS_IN_SEC = 1000000.0
15
16class hardware_Usb30Throughput(storage_mod.StorageTester):
17    version = 1
18    preserve_srcdir = True
19    _autosrc = None
20    _autodst = None
21    results = {}
22
23
24    def cleanup(self):
25        if self._autosrc:
26            self._autosrc.clean()
27        if self._autodst:
28            self._autodst.clean()
29
30        self.scanner.unmount_all()
31
32        super(hardware_Usb30Throughput, self).cleanup()
33
34
35    def run_once(self, measurements=5, size=1, min_speed=300.0):
36        """
37        @param measurements: (int) the number of measurements to do.
38                For the test to fail at least one measurement needs to be
39                below |min_speed|
40        @param size: (int) size of the file to be copied for testing the
41                transfer rate, it represent the size in megabytes.
42                Generally speaking, the bigger is the file used for
43                |measurements| the slower the test will run and the more
44                accurate it will be.
45                e.g.: 10 is 10MB, 101 is 101MB
46        @param min_speed: (float) in Mbit/sec. It's the min throughput a USB 3.0
47                device should perform to be accepted. Conceptually it's the max
48                USB 3.0 throughput minus a tollerance.
49                Defaults to 300Mbit/sec (ie 350Mbits/sec minus ~15% tollerance)
50        """
51        volume_filter = {'bus': 'usb'}
52        storage = self.wait_for_device(volume_filter, cycles=1,
53                                       mount_volume=True)[0]
54
55        # in Megabytes (power of 10, to be consistent with the throughput unit)
56        size *= 1000*1000
57
58        self._autosrc = autotemp.tempfile(unique_id='autotest.src',
59                                          dir=storage['mountpoint'])
60        self._autodst = autotemp.tempfile(unique_id='autotest.dst',
61                                          dir=self.tmpdir)
62
63        # Create random file
64        storage_mod.create_file(self._autosrc.name, size)
65
66        num_failures = 0
67        for measurement in range(measurements):
68            xfer_rate = get_xfer_rate(self._autosrc.name, self._autodst.name)
69            key = 'Mbit_per_sec_measurement_%d' % measurement
70            self.results[key] = xfer_rate
71            logging.debug('xfer rate (measurement %d) %.2f (min=%.2f)',
72                          measurement, xfer_rate, min_speed)
73
74            if xfer_rate < min_speed:
75                num_failures += 1
76
77        # Apparently self.postprocess_iteration is not called on TestFail
78        # so we need to process data here in order to have some performance log
79        # even on TestFail
80        self.results['Mbit_per_sec_average'] = (sum(self.results.values()) /
81            len(self.results))
82        self.write_perf_keyval(self.results)
83
84        if num_failures > 0:
85            msg = ('%d/%d measured transfer rates under performed '
86                   '(min_speed=%.2fMbit/sec)' % (num_failures, measurements,
87                   min_speed))
88            raise error.TestFail(msg)
89
90
91def get_xfer_rate(src, dst):
92    """Compute transfer rate from src to dst as Mbit/sec
93
94    Execute a copy from |src| to |dst| and returns the file copy transfer rate
95    in Mbit/sec
96
97    @param src, dst: paths for source and destination
98
99    @return trasfer rate (float) in Mbit/sec
100    """
101    assert os.path.isfile(src)
102    assert os.path.isfile(dst)
103
104    base_utils.drop_caches()
105    start = datetime.datetime.now()
106    base_utils.force_copy(src, dst)
107    end = datetime.datetime.now()
108    delta = end - start
109
110    # compute seconds (as float) from microsecs
111    delta_secs = delta.seconds + (delta.microseconds/USECS_IN_SEC)
112    # compute Mbit from bytes
113    size_Mbit = (os.path.getsize(src)*8.0)/(1000*1000)
114
115    logging.info('file trasferred: size (Mbits): %f, start: %f, end: %d,'
116                 ' delta (secs): %f',
117                 size_Mbit,
118                 start.second+start.microsecond/USECS_IN_SEC,
119                 end.second+end.microsecond/USECS_IN_SEC,
120                 delta_secs)
121
122    # return the xfer rate in Mbits/secs having bytes/microsec
123    return size_Mbit / delta_secs
124