• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os, logging
2
3from autotest_lib.client.bin import utils
4from autotest_lib.client.common_lib import error, autotemp
5from autotest_lib.client.cros import power_status
6from autotest_lib.client.cros import storage as storage_mod
7
8
9class hardware_MultiReaderPowerConsumption(storage_mod.StorageTester):
10    version = 1
11    _files_to_delete = []
12    _ramdisk_path = None
13    _storage = None
14
15
16    def initialize(self):
17        super(hardware_MultiReaderPowerConsumption, self).initialize()
18
19        # Make sure we're not on AC power
20        self.status = power_status.get_status()
21        if self.status.on_ac():
22            raise error.TestNAError(
23                  'This test needs to be run with the AC power offline')
24
25
26    def cleanup(self):
27        # Remove intermediate files
28        for path in self._files_to_delete:
29            utils.system('rm -f %s' % path)
30
31        if self._storage and os.path.ismount(self._storage['mountpoint']):
32            self.scanner.umount_volume(storage_dict=self._storage)
33
34        if self._ramdisk_path and os.path.ismount(self._ramdisk_path.name):
35            umount_ramdisk(self._ramdisk_path.name)
36            self._ramdisk_path.clean()
37
38        super(hardware_MultiReaderPowerConsumption, self).cleanup()
39
40
41    def readwrite_test(self, path, size, delete_file=False):
42        """Heavy-duty random read/write test. Run `dd` & `tail -f` in parallel
43
44        The random write is done by writing a file from /dev/urandom into the
45        given location, while the random read is done by concurrently reading
46        that file.
47
48        @param path: The directory that will create the test file.
49        @param size: Size of the test file, in MiB.
50        @param delete_file: Flag the file to be deleted on test exit.
51               Otherwise file deletion won't be performed.
52        """
53        # Calculate the parameters for dd
54        size = 1024*1024*size
55        blocksize = 8192
56
57        # Calculate the filename and full path, flag to delete if needed
58        filename = 'tempfile.%d.delete-me' % size
59        pathfile = os.path.join(path, filename)
60        if delete_file:
61            self._files_to_delete.append(pathfile)
62
63        pid = os.fork() # We need to run two processes in parallel
64        if pid:
65            # parent
66            utils.BgJob('tail -f %s --pid=%s > /dev/null'
67                        % (pathfile, pid))
68            # Reap the dd child so that tail does not wait for the zombie
69            os.waitpid(pid, 0)
70        else:
71            # child
72            utils.system('dd if=/dev/urandom of=%s bs=%d count=%s'
73                         % (pathfile, blocksize, (size//blocksize)))
74            # A forked child is exiting here, so we really do want os._exit:
75            os._exit(0)
76
77
78    def run_once(self, ramdisk_size=513, file_size=512, drain_limit=1.05,
79                 volume_filter={'bus': 'usb'}):
80        """Test card reader CPU power consumption to be within acceptable
81        range while performing random r/w
82
83        The random r/w is performed in the readwrite_test() method, by
84        concurrently running `dd if=/dev/urandom` and `tail -f`. It is run once
85        on a ramdisk with the SD card mounted, then on the SD card with the
86        ramdisk unmounted, and then on the SD card with the ramdisk unmounted.
87        The measured values are then reported.
88
89        @param ramdisk_size: Size of ramdisk (in MiB).
90        @param file_size: Size of test file (in MiB).
91        @param volume_filter: Where to find the card reader.
92        @param drain_limit: maximum ratio between the card reader
93                            energy consumption and each of the two
94                            ramdisk read/write test energy consumption
95                            values. 1.00 means the card reader test may
96                            not consume more energy than either ramdisk
97                            test, 0.9 means it may consume no more than
98                            90% of the ramdisk value, and so forth.
99        """
100        # Switch to VT2 so the screen turns itself off automatically instead of
101        # dimming, in order to reduce the battery consuption caused by other
102        # variables.
103        utils.system('chvt 2')
104
105        logging.debug('STEP 1: ensure SD card is inserted and mounted')
106        self._storage = self.wait_for_device(volume_filter, cycles=1,
107                                             mount_volume=True)[0]
108
109        logging.debug('STEP 2: mount the ramdisk')
110        self._ramdisk_path = autotemp.tempdir(unique_id='ramdisk',
111                                              dir=self.tmpdir)
112        mount_ramdisk(self._ramdisk_path.name, ramdisk_size)
113
114        # Read current charge, as well as maximum charge.
115        self.status.refresh()
116        max_charge = self.status.battery[0].charge_full_design
117        initial_charge = self.status.battery[0].charge_now
118
119        logging.debug('STEP 3: perform heavy-duty read-write test on ramdisk')
120        self.readwrite_test(self._ramdisk_path.name, file_size)
121        # Read current charge (reading A)
122        self.status.refresh()
123        charge_A = self.status.battery[0].charge_now
124
125        logging.debug('STEP 4: unmount ramdisk')
126        umount_ramdisk(self._ramdisk_path.name)
127
128        logging.debug('STEP 5: perform identical read write test on SD card')
129        self.readwrite_test(self._storage['mountpoint'], file_size,
130                            delete_file=True)
131        # Read current charge (reading B)
132        self.status.refresh()
133        charge_B = self.status.battery[0].charge_now
134
135        logging.debug('STEP 6: unmount card')
136        self.scanner.umount_volume(storage_dict=self._storage, args='-f -l')
137
138        logging.debug('STEP 7: perform ramdisk test again')
139        mount_ramdisk(self._ramdisk_path.name, ramdisk_size)
140        self.readwrite_test(self._ramdisk_path.name, file_size)
141        # Read current charge (reading C)
142        self.status.refresh()
143        charge_C = self.status.battery[0].charge_now
144
145        # Compute the results
146        ramdisk_plus = initial_charge - charge_A
147        sd_card_solo = charge_A - charge_B
148        ramdisk_solo = charge_B - charge_C
149
150        sd_card_drain_ratio_a = (sd_card_solo / ramdisk_plus)
151        sd_card_drain_ratio_b = (sd_card_solo / ramdisk_solo)
152
153        msg = None
154        if sd_card_drain_ratio_a > drain_limit:
155            msg = ('Card reader drain exceeds mounted baseline by > %f (%f)'
156                   % (drain_limit, sd_card_drain_ratio_a))
157        elif sd_card_drain_ratio_b > drain_limit:
158            msg = ('Card reader drain exceeds unmounted baseline by > %f (%f)'
159                   % (drain_limit, sd_card_drain_ratio_b))
160
161        if msg:
162            raise error.TestError(msg)
163        else:
164            fmt = 'Card reader drain ratio Ok: mounted %f; unmounted %f'
165            logging.info(fmt % (sd_card_drain_ratio_a, sd_card_drain_ratio_b))
166
167
168def mount_ramdisk(path, size):
169    utils.system('mount -t tmpfs none %s -o size=%sm' % (path, size))
170
171
172def umount_ramdisk(path):
173    """Umount ramdisk mounted at |path|
174
175    @param path: the mountpoint for the mountd RAM disk
176    """
177    utils.system('rm -rf %s/*' % path)
178    utils.system('umount -f -l %s' % path)
179