blob: 4b0dd06de47eeb7b1703d1c1f8d7266ad9d56b0b [file] [log] [blame]
# Copyright 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.
"""A factory test to ensure the functionality of CPU fan.
It provides two types of test:
A. target_rpm mode
B. spin_max_then_half mode.
For mode B, it first spins fan up to a max_rpm to get an empirical maximum
fan rpm; then it runs mode A with half of the empirical max rpm as target_rpm.
In mode A, the steps are:
1. Sets the fan speed to a target RPM.
2. Monitors the fan speed for a given period (duration_secs) with sampling
interval (probe_interval_secs). Then it takes average of the latest
#num_samples_to_use samples as the stabilized fan speed reading.
3. Checks that the averaged reading is within range
[target_rpm - error_margin, target_rpm + error_margin].
"""
import logging
import time
import factory_common # pylint: disable=unused-import
from cros.factory.device import device_utils
from cros.factory.test.i18n import _
from cros.factory.test import test_case
from cros.factory.utils.arg_utils import Arg
def _Average(numbers):
# Use 0.0 as the first term to sum to make sum a floating point.
return sum(numbers, 0.0) / len(numbers)
class FanSpeedTest(test_case.TestCase):
"""A factory test for testing system fan."""
ARGS = [
Arg('target_rpm', (int, list),
'A list of target RPM to set during test.'
'Unused if spin_max_then_half is set.',
default=0),
Arg('error_margin', int,
'Fail the test if actual fan speed is off the target by the margin.',
default=200),
Arg('duration_secs', (int, float),
'Duration of monitoring fan speed in seconds.', default=10),
Arg('spin_max_then_half', bool,
'If True, spin the fan to max_rpm, measure the actual reading, and '
'set fan speed to half of actual max speed. Note that if True, '
'target_rpm is invalid.', default=False),
Arg('max_rpm', int,
'A relatively high RPM for probing maximum fan speed. It is used '
'when spin_max_then_half=True.', default=10000),
Arg('probe_interval_secs', float,
'Interval of probing fan speed in seconds.', default=0.2),
Arg('num_samples_to_use', int,
'Number of lastest samples to count average as stablized speed.',
default=5),
Arg('use_percentage', bool, 'Use percentage to set fan speed',
default=False),
Arg('fan_id', int, 'The ID of fan to test, use None to test all fans.',
default=None)
]
def setUp(self):
self.assertTrue(
self.args.target_rpm > 0 or self.args.spin_max_then_half,
'Either set a valid target_rpm or spin_max_then_half=True.')
self._fan = device_utils.CreateDUTInterface().fan
if isinstance(self.args.target_rpm, int):
self.args.target_rpm = [self.args.target_rpm]
def tearDown(self):
logging.info('Set auto fan speed control.')
self._fan.SetFanRPM(self._fan.AUTO, self.args.fan_id)
def SetAndGetFanSpeed(self, target_rpm):
"""Sets fan speed and observes readings for a while (blocking call).
Args:
target_rpm: target fan speed.
Returns:
List of fan speed, each fan speed if the average of the latest
#num_samples_to_use samples as stabilized fan speed reading.
"""
observed_rpm = self._fan.GetFanRPM(self.args.fan_id)
fan_count = len(observed_rpm)
spin_up = target_rpm > _Average(observed_rpm)
status = _(
'{fan_spin_direction}: {observed_rpm} -> {target_rpm} RPM.',
spin_direction=_('Spin up fan') if spin_up else _('Spin down fan'),
observed_rpm=observed_rpm,
target_rpm=target_rpm)
self.ui.SetHTML(status, id='fs-status')
self.ui.SetHTML(str(observed_rpm), id='fs-rpm')
if self.args.use_percentage:
self._fan.SetFanRPM(int(target_rpm * 100 / self.args.max_rpm),
self.args.fan_id)
else:
self._fan.SetFanRPM(int(target_rpm), self.args.fan_id)
# Probe fan speed for duration_secs seconds with sampling interval
# probe_interval_secs.
end_time = time.time() + self.args.duration_secs
# Samples of all fan speed with sample period: probe_interval_secs.
ith_fan_samples = [[] for unused_i in xrange(fan_count)]
while time.time() < end_time:
observed_rpm = self._fan.GetFanRPM(self.args.fan_id)
for i, ith_fan_rpm in enumerate(observed_rpm):
ith_fan_samples[i].append(ith_fan_rpm)
self.ui.SetHTML(str(observed_rpm), id='fs-rpm')
logging.info('Observed fan RPM: %s', observed_rpm)
self.Sleep(self.args.probe_interval_secs)
num_samples = self.args.num_samples_to_use
total_samples = len(ith_fan_samples[0])
if num_samples > total_samples / 2:
logging.error('Insufficient #samples to get average fan speed. '
'Use latest one instead.')
num_samples = 1
# Average the latest #num_samples readings as stabilized fan speed.
return [_Average(samples[-num_samples:]) for samples in ith_fan_samples]
def VerifyResult(self, observed_rpm, target_rpm):
"""Verify observed rpms are in the range
(target_rpm - error_margin, target_rpm + error_margin)
Args:
observed_rpm: a list of fan rpm readings.
target_rpm: target fan speed.
"""
lower_bound = target_rpm - self.args.error_margin
upper_bound = target_rpm + self.args.error_margin
error_messages = []
for i, rpm in enumerate(observed_rpm):
if lower_bound <= rpm <= upper_bound:
logging.info('Observed fan %d RPM: %d within target range: [%d, %d].',
i, rpm, lower_bound, upper_bound)
else:
error_messages.append(
'Observed fan %d RPM: %d out of target range: [%d, %d].' %
(i, rpm, lower_bound, upper_bound))
if error_messages:
self.FailTask('\n'.join(error_messages))
def runTest(self):
"""Main test function."""
if self.args.spin_max_then_half:
logging.info('Spinning fan up to get max fan speed...')
max_rpm = self.SetAndGetFanSpeed(self.args.max_rpm)
for i in xrange(len(max_rpm)):
if max_rpm[i] == 0:
self.FailTask('Fan %d is not reporting any RPM' % i)
target_rpm = _Average(max_rpm) / 2
observed_rpm = self.SetAndGetFanSpeed(target_rpm)
self.VerifyResult(observed_rpm, target_rpm)
else:
for target_rpm in self.args.target_rpm:
observed_rpm = self.SetAndGetFanSpeed(target_rpm)
self.VerifyResult(observed_rpm, target_rpm)