| # Copyright 2019 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Provides device interactions for CPU temperature monitoring.""" |
| # pylint: disable=unused-argument |
| |
| import logging |
| |
| from devil.android import device_utils |
| from devil.android.perf import perf_control |
| from devil.utils import timeout_retry |
| |
| logger = logging.getLogger(__name__) |
| |
| # NB: when adding devices to this structure, be aware of the impact it may |
| # have on the chromium.perf waterfall, as it may increase testing time. |
| # Please contact a person responsible for the waterfall to see if the |
| # device you're adding is currently being tested. |
| _DEVICE_THERMAL_INFORMATION = { |
| # Pixel 3 |
| 'blueline': { |
| 'cpu_temps': { |
| # See /sys/class/thermal/thermal_zone<number>/type for description |
| # Types: |
| # cpu0: cpu0-silver-step |
| # cpu1: cpu1-silver-step |
| # cpu2: cpu2-silver-step |
| # cpu3: cpu3-silver-step |
| # cpu4: cpu0-gold-step |
| # cpu5: cpu1-gold-step |
| # cpu6: cpu2-gold-step |
| # cpu7: cpu3-gold-step |
| 'cpu0': '/sys/class/thermal/thermal_zone11/temp', |
| 'cpu1': '/sys/class/thermal/thermal_zone12/temp', |
| 'cpu2': '/sys/class/thermal/thermal_zone13/temp', |
| 'cpu3': '/sys/class/thermal/thermal_zone14/temp', |
| 'cpu4': '/sys/class/thermal/thermal_zone15/temp', |
| 'cpu5': '/sys/class/thermal/thermal_zone16/temp', |
| 'cpu6': '/sys/class/thermal/thermal_zone17/temp', |
| 'cpu7': '/sys/class/thermal/thermal_zone18/temp' |
| }, |
| # Different device sensors use different multipliers |
| # e.g. Pixel 3 35 degrees c is 35000 |
| 'temp_multiplier': 1000 |
| }, |
| # Pixel |
| 'sailfish': { |
| 'cpu_temps': { |
| # The following thermal zones tend to produce the most accurate |
| # readings |
| # Types: |
| # cpu0: tsens_tz_sensor0 |
| # cpu1: tsens_tz_sensor1 |
| # cpu2: tsens_tz_sensor2 |
| # cpu3: tsens_tz_sensor3 |
| 'cpu0': '/sys/class/thermal/thermal_zone1/temp', |
| 'cpu1': '/sys/class/thermal/thermal_zone2/temp', |
| 'cpu2': '/sys/class/thermal/thermal_zone3/temp', |
| 'cpu3': '/sys/class/thermal/thermal_zone4/temp' |
| }, |
| 'temp_multiplier': 10 |
| } |
| } |
| |
| |
| class CpuTemperature(object): |
| |
| def __init__(self, device): |
| """CpuTemperature constructor. |
| |
| Args: |
| device: A DeviceUtils instance. |
| Raises: |
| TypeError: If it is not passed a DeviceUtils instance. |
| """ |
| if not isinstance(device, device_utils.DeviceUtils): |
| raise TypeError('Must be initialized with DeviceUtils object.') |
| self._device = device |
| self._perf_control = perf_control.PerfControl(self._device) |
| self._device_info = None |
| |
| def InitThermalDeviceInformation(self): |
| """Init the current devices thermal information. |
| """ |
| self._device_info = _DEVICE_THERMAL_INFORMATION.get( |
| self._device.build_product) |
| |
| def IsSupported(self): |
| """Check if the current device is supported. |
| |
| Returns: |
| True if the device is in _DEVICE_THERMAL_INFORMATION and the temp |
| files exist. False otherwise. |
| """ |
| # Init device info if it hasnt been manually initialised already |
| if self._device_info is None: |
| self.InitThermalDeviceInformation() |
| |
| if self._device_info is not None: |
| return all( |
| self._device.FileExists(f) |
| for f in self._device_info['cpu_temps'].values()) |
| return False |
| |
| def LetCpuCoolToTemperature(self, target_temp, wait_period=30): |
| """Lets device sit to give CPU time to cool down. |
| |
| Implements a similar mechanism to |
| battery_utils.LetBatteryCoolToTemperature |
| |
| Args: |
| temp: A float containing the maximum temperature to allow |
| in degrees c. |
| wait_period: An integer indicating time in seconds to wait |
| between checking. |
| """ |
| target_temp = int(target_temp * self._device_info['temp_multiplier']) |
| |
| def cool_cpu(): |
| # Get the temperatures |
| cpu_temp_paths = self._device_info['cpu_temps'] |
| temps = [] |
| for temp_path in cpu_temp_paths.values(): |
| temp_return = self._device.ReadFile(temp_path) |
| # Output is an array of strings, only need the first line. |
| temps.append(int(temp_return)) |
| |
| if not temps: |
| logger.warning('Unable to read temperature files provided.') |
| return True |
| |
| logger.info('Current CPU temperatures: %s', str(temps)[1:-1]) |
| |
| return all(t <= target_temp for t in temps) |
| |
| logger.info('Waiting for the CPU to cool down to %s', |
| target_temp / self._device_info['temp_multiplier']) |
| |
| # Set the governor to powersave to aid the cooling down of the CPU |
| self._perf_control.SetScalingGovernor('powersave') |
| |
| # Retry 3 times, each time waiting 30 seconds. |
| # This negates most (if not all) of the noise in recorded results without |
| # taking too long |
| timeout_retry.WaitFor(cool_cpu, wait_period=wait_period, max_tries=3) |
| |
| # Set the performance mode |
| self._perf_control.SetHighPerfMode() |
| |
| def GetDeviceForTesting(self): |
| return self._device |
| |
| def GetDeviceInfoForTesting(self): |
| return self._device_info |