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