blob: dc8987c6484888a1b9434230198f8b220a53fee4 [file] [log] [blame]
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 power_status
from autotest_lib.client.cros import storage as storage_mod
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[0].charge_full_design
initial_charge = self.status.battery[0].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[0].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[0].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[0].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)