179 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
| import os, logging
 | |
| 
 | |
| from autotest_lib.client.bin import utils
 | |
| from autotest_lib.client.common_lib import error, autotemp
 | |
| from autotest_lib.client.cros import storage as storage_mod
 | |
| from autotest_lib.client.cros.power import power_status
 | |
| 
 | |
| 
 | |
| class hardware_MultiReaderPowerConsumption(storage_mod.StorageTester):
 | |
|     version = 1
 | |
|     _files_to_delete = []
 | |
|     _ramdisk_path = None
 | |
|     _storage = None
 | |
| 
 | |
| 
 | |
|     def initialize(self):
 | |
|         super(hardware_MultiReaderPowerConsumption, self).initialize()
 | |
| 
 | |
|         # Make sure we're not on AC power
 | |
|         self.status = power_status.get_status()
 | |
|         if self.status.on_ac():
 | |
|             raise error.TestNAError(
 | |
|                   'This test needs to be run with the AC power offline')
 | |
| 
 | |
| 
 | |
|     def cleanup(self):
 | |
|         # Remove intermediate files
 | |
|         for path in self._files_to_delete:
 | |
|             utils.system('rm -f %s' % path)
 | |
| 
 | |
|         if self._storage and os.path.ismount(self._storage['mountpoint']):
 | |
|             self.scanner.umount_volume(storage_dict=self._storage)
 | |
| 
 | |
|         if self._ramdisk_path and os.path.ismount(self._ramdisk_path.name):
 | |
|             umount_ramdisk(self._ramdisk_path.name)
 | |
|             self._ramdisk_path.clean()
 | |
| 
 | |
|         super(hardware_MultiReaderPowerConsumption, self).cleanup()
 | |
| 
 | |
| 
 | |
|     def readwrite_test(self, path, size, delete_file=False):
 | |
|         """Heavy-duty random read/write test. Run `dd` & `tail -f` in parallel
 | |
| 
 | |
|         The random write is done by writing a file from /dev/urandom into the
 | |
|         given location, while the random read is done by concurrently reading
 | |
|         that file.
 | |
| 
 | |
|         @param path: The directory that will create the test file.
 | |
|         @param size: Size of the test file, in MiB.
 | |
|         @param delete_file: Flag the file to be deleted on test exit.
 | |
|                Otherwise file deletion won't be performed.
 | |
|         """
 | |
|         # Calculate the parameters for dd
 | |
|         size = 1024*1024*size
 | |
|         blocksize = 8192
 | |
| 
 | |
|         # Calculate the filename and full path, flag to delete if needed
 | |
|         filename = 'tempfile.%d.delete-me' % size
 | |
|         pathfile = os.path.join(path, filename)
 | |
|         if delete_file:
 | |
|             self._files_to_delete.append(pathfile)
 | |
| 
 | |
|         pid = os.fork() # We need to run two processes in parallel
 | |
|         if pid:
 | |
|             # parent
 | |
|             utils.BgJob('tail -f %s --pid=%s > /dev/null'
 | |
|                         % (pathfile, pid))
 | |
|             # Reap the dd child so that tail does not wait for the zombie
 | |
|             os.waitpid(pid, 0)
 | |
|         else:
 | |
|             # child
 | |
|             utils.system('dd if=/dev/urandom of=%s bs=%d count=%s'
 | |
|                          % (pathfile, blocksize, (size//blocksize)))
 | |
|             # A forked child is exiting here, so we really do want os._exit:
 | |
|             os._exit(0)
 | |
| 
 | |
| 
 | |
|     def run_once(self, ramdisk_size=513, file_size=512, drain_limit=1.05,
 | |
|                  volume_filter={'bus': 'usb'}):
 | |
|         """Test card reader CPU power consumption to be within acceptable
 | |
|         range while performing random r/w
 | |
| 
 | |
|         The random r/w is performed in the readwrite_test() method, by
 | |
|         concurrently running `dd if=/dev/urandom` and `tail -f`. It is run once
 | |
|         on a ramdisk with the SD card mounted, then on the SD card with the
 | |
|         ramdisk unmounted, and then on the SD card with the ramdisk unmounted.
 | |
|         The measured values are then reported.
 | |
| 
 | |
|         @param ramdisk_size: Size of ramdisk (in MiB).
 | |
|         @param file_size: Size of test file (in MiB).
 | |
|         @param volume_filter: Where to find the card reader.
 | |
|         @param drain_limit: maximum ratio between the card reader
 | |
|                             energy consumption and each of the two
 | |
|                             ramdisk read/write test energy consumption
 | |
|                             values. 1.00 means the card reader test may
 | |
|                             not consume more energy than either ramdisk
 | |
|                             test, 0.9 means it may consume no more than
 | |
|                             90% of the ramdisk value, and so forth.
 | |
|         """
 | |
|         # Switch to VT2 so the screen turns itself off automatically instead of
 | |
|         # dimming, in order to reduce the battery consuption caused by other
 | |
|         # variables.
 | |
|         utils.system('chvt 2')
 | |
| 
 | |
|         logging.debug('STEP 1: ensure SD card is inserted and mounted')
 | |
|         self._storage = self.wait_for_device(volume_filter, cycles=1,
 | |
|                                              mount_volume=True)[0]
 | |
| 
 | |
|         logging.debug('STEP 2: mount the ramdisk')
 | |
|         self._ramdisk_path = autotemp.tempdir(unique_id='ramdisk',
 | |
|                                               dir=self.tmpdir)
 | |
|         mount_ramdisk(self._ramdisk_path.name, ramdisk_size)
 | |
| 
 | |
|         # Read current charge, as well as maximum charge.
 | |
|         self.status.refresh()
 | |
|         max_charge = self.status.battery.charge_full_design
 | |
|         initial_charge = self.status.battery.charge_now
 | |
| 
 | |
|         logging.debug('STEP 3: perform heavy-duty read-write test on ramdisk')
 | |
|         self.readwrite_test(self._ramdisk_path.name, file_size)
 | |
|         # Read current charge (reading A)
 | |
|         self.status.refresh()
 | |
|         charge_A = self.status.battery.charge_now
 | |
| 
 | |
|         logging.debug('STEP 4: unmount ramdisk')
 | |
|         umount_ramdisk(self._ramdisk_path.name)
 | |
| 
 | |
|         logging.debug('STEP 5: perform identical read write test on SD card')
 | |
|         self.readwrite_test(self._storage['mountpoint'], file_size,
 | |
|                             delete_file=True)
 | |
|         # Read current charge (reading B)
 | |
|         self.status.refresh()
 | |
|         charge_B = self.status.battery.charge_now
 | |
| 
 | |
|         logging.debug('STEP 6: unmount card')
 | |
|         self.scanner.umount_volume(storage_dict=self._storage, args='-f -l')
 | |
| 
 | |
|         logging.debug('STEP 7: perform ramdisk test again')
 | |
|         mount_ramdisk(self._ramdisk_path.name, ramdisk_size)
 | |
|         self.readwrite_test(self._ramdisk_path.name, file_size)
 | |
|         # Read current charge (reading C)
 | |
|         self.status.refresh()
 | |
|         charge_C = self.status.battery.charge_now
 | |
| 
 | |
|         # Compute the results
 | |
|         ramdisk_plus = initial_charge - charge_A
 | |
|         sd_card_solo = charge_A - charge_B
 | |
|         ramdisk_solo = charge_B - charge_C
 | |
| 
 | |
|         sd_card_drain_ratio_a = (sd_card_solo / ramdisk_plus)
 | |
|         sd_card_drain_ratio_b = (sd_card_solo / ramdisk_solo)
 | |
| 
 | |
|         msg = None
 | |
|         if sd_card_drain_ratio_a > drain_limit:
 | |
|             msg = ('Card reader drain exceeds mounted baseline by > %f (%f)'
 | |
|                    % (drain_limit, sd_card_drain_ratio_a))
 | |
|         elif sd_card_drain_ratio_b > drain_limit:
 | |
|             msg = ('Card reader drain exceeds unmounted baseline by > %f (%f)'
 | |
|                    % (drain_limit, sd_card_drain_ratio_b))
 | |
| 
 | |
|         if msg:
 | |
|             raise error.TestError(msg)
 | |
|         else:
 | |
|             fmt = 'Card reader drain ratio Ok: mounted %f; unmounted %f'
 | |
|             logging.info(fmt % (sd_card_drain_ratio_a, sd_card_drain_ratio_b))
 | |
| 
 | |
| 
 | |
| def mount_ramdisk(path, size):
 | |
|     utils.system('mount -t tmpfs none %s -o size=%sm' % (path, size))
 | |
| 
 | |
| 
 | |
| def umount_ramdisk(path):
 | |
|     """Umount ramdisk mounted at |path|
 | |
| 
 | |
|     @param path: the mountpoint for the mountd RAM disk
 | |
|     """
 | |
|     utils.system('rm -rf %s/*' % path)
 | |
|     utils.system('umount -f -l %s' % path)
 |