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