xref: /aosp_15_r20/external/autotest/client/cros/multimedia/system_facade.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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