1# Lint as: python2, python3 2# Copyright 2015 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Facade to access the system-related functionality.""" 7 8import six 9import os 10import threading 11import time 12 13from autotest_lib.client.bin import utils 14 15 16class SystemFacadeLocalError(Exception): 17 """Error in SystemFacadeLocal.""" 18 pass 19 20 21class SystemFacadeLocal(object): 22 """Facede to access the system-related functionality. 23 24 The methods inside this class only accept Python native types. 25 26 """ 27 SCALING_GOVERNOR_MODES = [ 28 'performance', 29 'powersave', 30 'userspace', 31 'ondemand', 32 'conservative', 33 'schedutil', 34 'interactive', # deprecated since kernel v4.14 35 'sched' # deprecated since kernel v4.14 36 ] 37 38 def __init__(self): 39 self._bg_worker = None 40 41 def set_scaling_governor_mode(self, index, mode): 42 """Set mode of CPU scaling governor on one CPU. 43 44 @param index: CPU index starting from 0. 45 46 @param mode: Mode of scaling governor, accept 'interactive' or 47 'performance'. 48 49 @returns: The original mode. 50 51 """ 52 if mode not in self.SCALING_GOVERNOR_MODES: 53 raise SystemFacadeLocalError('mode %s is invalid' % mode) 54 55 governor_path = os.path.join( 56 '/sys/devices/system/cpu/cpu%d' % index, 57 'cpufreq/scaling_governor') 58 if not os.path.exists(governor_path): 59 raise SystemFacadeLocalError( 60 'scaling governor of CPU %d is not available' % index) 61 62 original_mode = utils.read_one_line(governor_path) 63 utils.open_write_close(governor_path, mode) 64 65 return original_mode 66 67 68 def get_cpu_usage(self): 69 """Returns machine's CPU usage. 70 71 Returns: 72 A dictionary with 'user', 'nice', 'system' and 'idle' values. 73 Sample dictionary: 74 { 75 'user': 254544, 76 'nice': 9, 77 'system': 254768, 78 'idle': 2859878, 79 } 80 """ 81 return utils.get_cpu_usage() 82 83 84 def compute_active_cpu_time(self, cpu_usage_start, cpu_usage_end): 85 """Computes the fraction of CPU time spent non-idling. 86 87 This function should be invoked using before/after values from calls to 88 get_cpu_usage(). 89 """ 90 return utils.compute_active_cpu_time(cpu_usage_start, 91 cpu_usage_end) 92 93 94 def get_mem_total(self): 95 """Returns the total memory available in the system in MBytes.""" 96 return utils.get_mem_total() 97 98 99 def get_mem_free(self): 100 """Returns the currently free memory in the system in MBytes.""" 101 return utils.get_mem_free() 102 103 def get_mem_free_plus_buffers_and_cached(self): 104 """ 105 Returns the free memory in MBytes, counting buffers and cached as free. 106 107 This is most often the most interesting number since buffers and cached 108 memory can be reclaimed on demand. Note however, that there are cases 109 where this as misleading as well, for example used tmpfs space 110 count as Cached but can not be reclaimed on demand. 111 See https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt. 112 """ 113 return utils.get_mem_free_plus_buffers_and_cached() 114 115 def get_ec_temperatures(self): 116 """Uses ectool to return a list of all sensor temperatures in Celsius. 117 """ 118 return utils.get_ec_temperatures() 119 120 def get_current_temperature_max(self): 121 """ 122 Returns the highest reported board temperature (all sensors) in Celsius. 123 """ 124 return utils.get_current_temperature_max() 125 126 def get_current_board(self): 127 """Returns the current device board name.""" 128 return utils.get_current_board() 129 130 131 def get_chromeos_release_version(self): 132 """Returns chromeos version in device under test as string. None on 133 fail. 134 """ 135 return utils.get_chromeos_release_version() 136 137 def get_num_allocated_file_handles(self): 138 """ 139 Returns the number of currently allocated file handles. 140 """ 141 return utils.get_num_allocated_file_handles() 142 143 def get_storage_statistics(self, device=None): 144 """ 145 Fetches statistics for a storage device. 146 """ 147 return utils.get_storage_statistics(device) 148 149 def get_energy_usage(self): 150 """ 151 Gets the energy counter value as a string. 152 """ 153 return utils.get_energy_usage() 154 155 def start_bg_worker(self, command): 156 """ 157 Start executing the command in a background worker. 158 """ 159 self._bg_worker = BackgroundWorker(command, do_process_output=True) 160 self._bg_worker.start() 161 162 def get_and_discard_bg_worker_output(self): 163 """ 164 Returns the output collected so far since the last call to this method. 165 """ 166 if self._bg_worker is None: 167 SystemFacadeLocalError('Background worker has not been started.') 168 169 return self._bg_worker.get_and_discard_output() 170 171 def stop_bg_worker(self): 172 """ 173 Stop the worker. 174 """ 175 if self._bg_worker is None: 176 SystemFacadeLocalError('Background worker has not been started.') 177 178 self._bg_worker.stop() 179 self._bg_worker = None 180 181 182class BackgroundWorker(object): 183 """ 184 Worker intended for executing a command in the background and collecting its 185 output. 186 """ 187 188 def __init__(self, command, do_process_output=False): 189 self._bg_job = None 190 self._command = command 191 self._do_process_output = do_process_output 192 self._output_lock = threading.Lock() 193 self._process_output_thread = None 194 self._stdout = six.StringIO() 195 196 def start(self): 197 """ 198 Start executing the command. 199 """ 200 self._bg_job = utils.BgJob(self._command, stdout_tee=self._stdout) 201 self._bg_job.sp.poll() 202 if self._bg_job.sp.returncode is not None: 203 self._exit_bg_job() 204 205 if self._do_process_output: 206 self._process_output_thread = threading.Thread( 207 target=self._process_output) 208 self._process_output_thread.start() 209 210 def _process_output(self, sleep_interval=0.01): 211 while self._do_process_output: 212 with self._output_lock: 213 self._bg_job.process_output() 214 time.sleep(sleep_interval) 215 216 def get_and_discard_output(self): 217 """ 218 Returns the output collected so far and then clears the output buffer. 219 In other words, subsequent calls to this method will not include output 220 that has already been returned before. 221 """ 222 output = "" 223 with self._output_lock: 224 self._stdout.flush() 225 output = self._stdout.getvalue() 226 self._stdout.truncate(0) 227 self._stdout.seek(0) 228 return output 229 230 def stop(self): 231 """ 232 Stop executing the command. 233 """ 234 if self._do_process_output: 235 self._do_process_output = False 236 self._process_output_thread.join(1) 237 self._exit_bg_job() 238 239 def _exit_bg_job(self): 240 utils.nuke_subprocess(self._bg_job.sp) 241 utils.join_bg_jobs([self._bg_job]) 242 if self._bg_job.result.exit_status > 0: 243 raise SystemFacadeLocalError('Background job failed: %s' % 244 self._bg_job.result.command) 245