Add USB 3.0 throughput test

BUG=chromium-os:32344
TEST=insert USB 3.0 drive and run test

Change-Id: If5fdc572f7e12f8ebba3701ae4d1ac82d0343f42
Reviewed-on: https://gerrit.chromium.org/gerrit/26679
Reviewed-by: Chris Sosa <sosa@chromium.org>
Reviewed-by: Ben Chan <benchan@chromium.org>
Commit-Ready: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
Tested-by: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
diff --git a/client/site_tests/hardware_Usb30Throughput/control b/client/site_tests/hardware_Usb30Throughput/control
new file mode 100644
index 0000000..c7f85b9
--- /dev/null
+++ b/client/site_tests/hardware_Usb30Throughput/control
@@ -0,0 +1,40 @@
+# Copyright (c) 2012 Collabora Ltd. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from autotest_lib.client.cros import storage as storage_mod
+
+NAME = "hardware_Usb30Throughput"
+AUTHOR = "Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>"
+PURPOSE = "Check that there are no transfer speed downgrade after suspend."
+CRITERIA = "Fails if transfer rate is below expectations"
+TIME="SHORT"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "hardware"
+TEST_TYPE = "client"
+
+DOC = """Measure transfer rates for a number of times.
+If any of the transfer rate is below expectation the test fails.
+
+This test need a high-speed USB 3.0 device connected, with a mountable file
+system on a single partition.
+No user should be logged in via GUI to avoid automounter interfere with the
+test.
+
+@param measurements: (10) number of times to repeat xfer rate measureaments
+@param size: (10Mb) the size of the file to transfer for each |measureaments|
+@param fs_uuid: UUID for USB storage define volume, if auto detection does not
+       work.
+@param min_speed: (300Mb/sec) a float number for the minimum speed accepted.
+       Any |measureaments| performing below it will make the test fail
+"""
+
+storage_filter, args_dict = storage_mod.args_to_storage_dict(args)
+if not storage_filter:
+    storage_filter = {'bus': 'usb'}
+measurements = int(args_dict.get('measurements', 10))
+size = int(args_dict.get('size', 10))
+min_speed = float(args_dict.get('min_speed', 300))
+
+job.run_test('hardware_Usb30Throughput', storage_filter=storage_filter,
+             measurements=measurements, size=size, min_speed=min_speed)
diff --git a/client/site_tests/hardware_Usb30Throughput/hardware_Usb30Throughput.py b/client/site_tests/hardware_Usb30Throughput/hardware_Usb30Throughput.py
new file mode 100644
index 0000000..72a1332
--- /dev/null
+++ b/client/site_tests/hardware_Usb30Throughput/hardware_Usb30Throughput.py
@@ -0,0 +1,123 @@
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+# Author: Cosimo Alfarano <cosimo.alfarano@collabora.co.uk>
+
+import datetime
+import logging
+import os
+
+from autotest_lib.client.cros import storage as storage_mod
+from autotest_lib.client.common_lib import autotemp, error
+from autotest_lib.client.bin  import base_utils
+
+USECS_IN_SEC = 1000000.0
+
+class hardware_Usb30Throughput(storage_mod.StorageTester):
+    version = 1
+    preserve_srcdir = True
+    _autosrc = None
+    _autodst = None
+    results = {}
+
+
+    def cleanup(self):
+        if self._autosrc:
+            self._autosrc.clean()
+        if self._autodst:
+            self._autodst.clean()
+
+        self.scanner.unmount_all()
+
+        super(hardware_Usb30Throughput, self).cleanup()
+
+
+    def run_once(self, measurements=5, size=1, min_speed=300.0):
+        """
+        @param measurements: (int) the number of measurements to do.
+                For the test to fail at least one measurement needs to be
+                below |min_speed|
+        @param size: (int) size of the file to be copied for testing the
+                transfer rate, it represent the size in megabytes.
+                Generally speaking, the bigger is the file used for
+                |measurements| the slower the test will run and the more
+                accurate it will be.
+                e.g.: 10 is 10MB, 101 is 101MB
+        @param min_speed: (float) in Mbit/sec. It's the min throughput a USB 3.0
+                device should perform to be accepted. Conceptually it's the max
+                USB 3.0 throughput minus a tollerance.
+                Defaults to 300Mbit/sec (ie 350Mbits/sec minus ~15% tollerance)
+        """
+        volume_filter = {'bus': 'usb'}
+        storage = self.wait_for_device(volume_filter, cycles=1,
+                                       mount_volume=True)[0]
+
+        # in Megabytes (power of 10, to be consistent with the throughput unit)
+        size *= 1000*1000
+
+        self._autosrc = autotemp.tempfile(unique_id='autotest.src',
+                                          dir=storage['mountpoint'])
+        self._autodst = autotemp.tempfile(unique_id='autotest.dst',
+                                          dir=self.tmpdir)
+
+        # Create random file
+        storage_mod.create_file(self._autosrc.name, size)
+
+        num_failures = 0
+        for measurement in range(measurements):
+            xfer_rate = get_xfer_rate(self._autosrc.name, self._autodst.name)
+            key = 'Mbit_per_sec_measurement_%d' % measurement
+            self.results[key] = xfer_rate
+            logging.debug('xfer rate (measurement %d) %.2f (min=%.2f)',
+                          measurement, xfer_rate, min_speed)
+
+            if xfer_rate < min_speed:
+                num_failures += 1
+
+        # Apparently self.postprocess_iteration is not called on TestFail
+        # so we need to process data here in order to have some performance log
+        # even on TestFail
+        self.results['Mbit_per_sec_average'] = (sum(self.results.values()) /
+            len(self.results))
+        self.write_perf_keyval(self.results)
+
+        if num_failures > 0:
+            msg = ('%d/%d measured transfer rates under performed '
+                   '(min_speed=%.2fMbit/sec)' % (num_failures, measurements,
+                   min_speed))
+            raise error.TestFail(msg)
+
+
+def get_xfer_rate(src, dst):
+    """Compute transfer rate from src to dst as Mbit/sec
+
+    Execute a copy from |src| to |dst| and returns the file copy transfer rate
+    in Mbit/sec
+
+    @param src, dst: paths for source and destination
+
+    @return trasfer rate (float) in Mbit/sec
+    """
+    assert os.path.isfile(src)
+    assert os.path.isfile(dst)
+
+    base_utils.drop_caches()
+    start = datetime.datetime.now()
+    base_utils.force_copy(src, dst)
+    end = datetime.datetime.now()
+    delta = end - start
+
+    # compute seconds (as float) from microsecs
+    delta_secs = delta.seconds + (delta.microseconds/USECS_IN_SEC)
+    # compute Mbit from bytes
+    size_Mbit = (os.path.getsize(src)*8.0)/(1000*1000)
+
+    logging.info('file trasferred: size (Mbits): %f, start: %f, end: %d,'
+                 ' delta (secs): %f',
+                 size_Mbit,
+                 start.second+start.microsecond/USECS_IN_SEC,
+                 end.second+end.microsecond/USECS_IN_SEC,
+                 delta_secs)
+
+    # return the xfer rate in Mbits/secs having bytes/microsec
+    return size_Mbit / delta_secs