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 after' 124 ' %d second(s).' % timeout) 125 if not self.is_alive(): 126 raise RuntimeError( 127 'The graphics_Power subtest failed to initialize') 128 if self.is_running(): 129 break 130 time.sleep(1) 131 132 if not self._test_thread: 133 raise self.ThreadNotInitializedError 134 135 def stop(self, timeout=None): 136 """Gracefully stop the subtest on the test host. 137 138 If timeout is None, then this is a blocking call that waits forever. 139 If timeout is a positive number, then it waits for 'timeout' seconds. 140 If timeout is 0, then it returns immediately. 141 142 Args: 143 timeout: Time (seconds) before giving up on joining the thread. 144 145 Returns: 146 A bool indicating if thread was stopped. 147 """ 148 self._running = False 149 self.join(timeout) 150 return not self.is_alive() 151 152 def checkpoint_measurements(self, name, start_time=None): 153 """Save the current log buffers with an associated name. 154 155 The power-dashboard displays time series data in one or more 156 checkpoints that can be used to annotate different phases of a test. 157 158 By saving a checkpoint, the time series data collected since the end of 159 the most recently committed checkpoint (or the test start if no 160 checkpoints are saved yet) is annotated on the power-dashboard with the 161 specified name. The checkpoint start time can be adjusted with the 162 optional 'start_time' argument. 163 164 Args: 165 name: String defining the saved checkpoint's name. 166 start_time: Optional; Float indicating the time (in seconds since 167 the epoch) at which this checkpoint should actually start. This 168 functionally discards data from the beginning of the logged 169 duration until start_time. 170 """ 171 with tempfile.NamedTemporaryFile('w') as tf: 172 tf.write(str(name) + '\n') 173 if start_time: 174 tf.write(str(start_time)) 175 tf.flush() 176 self._host.send_file(tf.name, self.signal_checkpoint_file) 177 178 def _run_test_async(self): 179 self._autotest_client.run_test( 180 'graphics_Power', 181 tag=self.test_tag, 182 max_duration_minutes=self.max_duration_minutes, 183 sample_rate_seconds=self.sample_rate_seconds, 184 pdash_note=self.pdash_note, 185 result_dir=self.result_dir, 186 signal_running_file=self.signal_running_file, 187 signal_checkpoint_file=self.signal_checkpoint_file) 188 189 def run(self): 190 self._running = True 191 self._test_thread = threading.Thread(target=self._run_test_async) 192 self._test_thread.start() 193 logging.info('Started thread: %s', self.__class__.__name__) 194 195 def send_stop_signal_and_join(): 196 """Emits a stop signal to the test host and joins the thread. 197 198 Deletes a monitored file on the test host over ssh and waits for 199 the graphics_Power sub-test to end gracefully as a consequence. 200 """ 201 while True: 202 self._host.run('rm %s 2>/dev/null || true' % 203 self.signal_running_file) 204 self._test_thread.join(5) 205 if not self._test_thread.is_alive(): 206 break 207 208 while True: 209 time.sleep(1) 210 if not self._test_thread.is_alive(): 211 logging.debug('The graphics_Power subtest ended') 212 break 213 elif not self._running: 214 logging.debug( 215 'Sending stop signal to the graphics_Power subtest') 216 send_stop_signal_and_join() 217 break 218