1 import os, logging 2 3 from autotest_lib.client.bin import utils 4 from autotest_lib.client.common_lib import error, autotemp 5 from autotest_lib.client.cros import power_status 6 from autotest_lib.client.cros import storage as storage_mod 7 8 9 class 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 168 def mount_ramdisk(path, size): 169 utils.system('mount -t tmpfs none %s -o size=%sm' % (path, size)) 170 171 172 def 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