• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 SystemFacadeNativeError(Exception):
17    """Error in SystemFacadeNative."""
18    pass
19
20
21class SystemFacadeNative(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 SystemFacadeNativeError('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 SystemFacadeNativeError(
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 start_bg_worker(self, command):
150        """
151        Start executing the command in a background worker.
152        """
153        self._bg_worker = BackgroundWorker(command, do_process_output=True)
154        self._bg_worker.start()
155
156    def get_and_discard_bg_worker_output(self):
157        """
158        Returns the output collected so far since the last call to this method.
159        """
160        if self._bg_worker is None:
161            SystemFacadeNativeError('Background worker has not been started.')
162
163        return self._bg_worker.get_and_discard_output()
164
165    def stop_bg_worker(self):
166        """
167        Stop the worker.
168        """
169        if self._bg_worker is None:
170            SystemFacadeNativeError('Background worker has not been started.')
171
172        self._bg_worker.stop()
173        self._bg_worker = None
174
175
176class BackgroundWorker(object):
177    """
178    Worker intended for executing a command in the background and collecting its
179    output.
180    """
181
182    def __init__(self, command, do_process_output=False):
183        self._bg_job = None
184        self._command = command
185        self._do_process_output = do_process_output
186        self._output_lock = threading.Lock()
187        self._process_output_thread = None
188        self._stdout = six.StringIO()
189
190    def start(self):
191        """
192        Start executing the command.
193        """
194        self._bg_job = utils.BgJob(self._command, stdout_tee=self._stdout)
195        self._bg_job.sp.poll()
196        if self._bg_job.sp.returncode is not None:
197            self._exit_bg_job()
198
199        if self._do_process_output:
200            self._process_output_thread = threading.Thread(
201                    target=self._process_output)
202            self._process_output_thread.start()
203
204    def _process_output(self, sleep_interval=0.01):
205        while self._do_process_output:
206            with self._output_lock:
207                self._bg_job.process_output()
208            time.sleep(sleep_interval)
209
210    def get_and_discard_output(self):
211        """
212        Returns the output collected so far and then clears the output buffer.
213        In other words, subsequent calls to this method will not include output
214        that has already been returned before.
215        """
216        output = ""
217        with self._output_lock:
218            self._stdout.flush()
219            output = self._stdout.getvalue()
220            self._stdout.truncate(0)
221            self._stdout.seek(0)
222        return output
223
224    def stop(self):
225        """
226        Stop executing the command.
227        """
228        if self._do_process_output:
229            self._do_process_output = False
230            self._process_output_thread.join(1)
231        self._exit_bg_job()
232
233    def _exit_bg_job(self):
234        utils.nuke_subprocess(self._bg_job.sp)
235        utils.join_bg_jobs([self._bg_job])
236        if self._bg_job.result.exit_status > 0:
237            raise SystemFacadeNativeError('Background job failed: %s' %
238                                          self._bg_job.result.command)
239