• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2020 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Provides a management class for using graphics_Power in server tests."""
5
6import logging
7import os
8import tempfile
9import threading
10import time
11
12from autotest_lib.client.common_lib import error
13from autotest_lib.server import autotest
14
15ROOT_DIR = '/tmp/graphics_Power/'
16DEFAULT_SIGNAL_RUNNING_FILE = os.path.join(ROOT_DIR, 'signal_running')
17DEFAULT_SIGNAL_CHECKPOINT_FILE = os.path.join(ROOT_DIR, 'signal_checkpoint')
18
19
20class GraphicsPowerThread(threading.Thread):
21    """Thread for running the graphics_Power client test.
22
23    Provides a threaded management interface for the graphics_Power subtest.
24    This class can be used from an autotest server test to log system
25    performance metrics in the background on the test host.
26    """
27
28    class Error(Exception):
29        """Base error that can be inherited to define more specific errors."""
30        pass
31
32    class ThreadNotInitializedError(Error):
33        """An error indicating that the thread was not properly initialized."""
34        pass
35
36    class InitTimeoutError(Error):
37        """An error indicating that a timeout occurred during a blocking call."""
38        pass
39
40    def __init__(self,
41                 host,
42                 max_duration_minutes,
43                 sample_rate_seconds=1,
44                 test_tag=None,
45                 pdash_note=None,
46                 result_dir=None,
47                 signal_running_file=DEFAULT_SIGNAL_RUNNING_FILE,
48                 signal_checkpoint_file=DEFAULT_SIGNAL_CHECKPOINT_FILE):
49        """Initializes the thread.
50
51        Args:
52            host: An autotest host instance.
53            max_duration_minutes: Float defining the maximum running time of the
54                managed sub-test.
55            sample_rate_seconds: Optional; Number defining seconds between data
56                point acquisition.
57            test_tag: Optional; String describing the test that initiated this
58                monitoring process; appended to the true test name.
59            pdash_note: Optional; A tag that is included as a filter field on
60                the ChromeOS power-dashboard.
61            result_dir: Optional; String defining the location on the test
62                target where post-processed results from this sub-test should be
63                saved for retrieval by the managing test process. Set to None if
64                results output is not be created.
65            signal_running_file: Optional; String defining the location of the
66                'running' RPC flag file on the test target. Removal of this file
67                triggers the subtest to finish logging and stop gracefully.
68            signal_checkpoint_file: Optional; String defining the location of
69                the 'checkpoint' RPC flag file on the test target. Modifying
70                this file triggers the subtest to create a checkpoint with name
71                equal to the utf-8-encoded contents of the first-line and
72                optional alternative start time (in seconds since the epoch)
73                equal to the second line of the file.
74        """
75        super(GraphicsPowerThread, self).__init__(name=__name__)
76        self._running = False
77        self._autotest_client = autotest.Autotest(host)
78        self._host = host
79        self._test_thread = None
80
81        self.max_duration_minutes = max_duration_minutes
82        self.sample_rate_seconds = sample_rate_seconds
83        self.test_tag = test_tag
84        self.pdash_note = pdash_note
85        self.result_dir = result_dir
86        self.signal_running_file = signal_running_file
87        self.signal_checkpoint_file = signal_checkpoint_file
88
89    def is_running(self):
90        """Return a bool indicating the 'running' state of the subtest.
91
92        This check can be used to ensure system logging is initialized and
93        running before beginning other subtests.
94        """
95        try:
96            self._host.run('test -f %s' % self.signal_running_file)
97            return True
98        except (error.AutotestHostRunCmdError, error.AutoservRunError):
99            return False
100
101    def wait_until_running(self, timeout=120):
102        """Block execution until the subtest reports it is logging properly.
103
104        Args:
105            timeout: Optional; Float that defines how long to block before
106                timeout occurs. If timeout=None, then block forever
107
108        Raises:
109            RuntimeError: The subtest ended unexpectedly before initialization
110                finished.
111            GraphicsPowerThread.ThreadNotInitializedError: The thread hasn't
112                been started by the managing server test yet.
113            GraphicsPowerThread.InitTimeoutError: A timeout occurred while
114                waiting for subtest to report itself as running.
115        """
116        if timeout:
117            time_start = time.time()
118            time_end = time_start + timeout
119        while True:
120            if timeout and time.time() >= time_end:
121                self.stop()
122                raise self.InitTimeoutError(
123                    'The graphics_Power subtest initialization timed out')
124            if not self.is_alive():
125                raise RuntimeError(
126                    'The graphics_Power subtest failed to initialize')
127            if self.is_running():
128                break
129            time.sleep(1)
130
131        if not self._test_thread:
132            raise self.ThreadNotInitializedError
133
134    def stop(self, timeout=None):
135        """Gracefully stop the subtest on the test host.
136
137        If timeout is None, then this is a blocking call that waits forever.
138        If timeout is a positive number, then it waits for 'timeout' seconds.
139        If timeout is 0, then it returns immediately.
140
141        Args:
142            timeout: Time (seconds) before giving up on joining the thread.
143
144        Returns:
145            A bool indicating if thread was stopped.
146        """
147        self._running = False
148        self.join(timeout)
149        return not self.is_alive()
150
151    def checkpoint_measurements(self, name, start_time=None):
152        """Save the current log buffers with an associated name.
153
154        The power-dashboard displays time series data in one or more
155        checkpoints that can be used to annotate different phases of a test.
156
157        By saving a checkpoint, the time series data collected since the end of
158        the most recently committed checkpoint (or the test start if no
159        checkpoints are saved yet) is annotated on the power-dashboard with the
160        specified name. The checkpoint start time can be adjusted with the
161        optional 'start_time' argument.
162
163        Args:
164            name: String defining the saved checkpoint's name.
165            start_time: Optional; Float indicating the time (in seconds since
166                the epoch) at which this checkpoint should actually start. This
167                functionally discards data from the beginning of the logged
168                duration until start_time.
169        """
170        with tempfile.NamedTemporaryFile('w') as tf:
171            tf.write(str(name) + '\n')
172            if start_time:
173                tf.write(str(start_time))
174            tf.flush()
175            self._host.send_file(tf.name, self.signal_checkpoint_file)
176
177    def _run_test_async(self):
178        self._autotest_client.run_test(
179            'graphics_Power',
180            tag=self.test_tag,
181            max_duration_minutes=self.max_duration_minutes,
182            sample_rate_seconds=self.sample_rate_seconds,
183            pdash_note=self.pdash_note,
184            result_dir=self.result_dir,
185            signal_running_file=self.signal_running_file,
186            signal_checkpoint_file=self.signal_checkpoint_file)
187
188    def run(self):
189        self._running = True
190        self._test_thread = threading.Thread(target=self._run_test_async)
191        self._test_thread.start()
192        logging.info('Started thread: %s', self.__class__.__name__)
193
194        def send_stop_signal_and_join():
195            """Emits a stop signal to the test host and joins the thread.
196
197            Deletes a monitored file on the test host over ssh and waits for
198            the graphics_Power sub-test to end gracefully as a consequence.
199            """
200            while True:
201                self._host.run('rm %s 2>/dev/null || true' %
202                               self.signal_running_file)
203                self._test_thread.join(5)
204                if not self._test_thread.is_alive():
205                    break
206
207        while True:
208            time.sleep(1)
209            if not self._test_thread.is_alive():
210                logging.debug('The graphics_Power subtest ended')
211                break
212            elif not self._running:
213                logging.debug(
214                    'Sending stop signal to the graphics_Power subtest')
215                send_stop_signal_and_join()
216                break
217