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"""Implementation of the graphics_TraceReplayExtended server test.""" 5 6import logging 7import os 8import threading 9import time 10 11from autotest_lib.client.common_lib import error 12from autotest_lib.server import test 13from autotest_lib.server.cros.graphics import graphics_power 14from autotest_lib.server.site_tests.tast import tast 15 16 17class TastManagerThread(threading.Thread): 18 """Thread for running a local tast test from an autotest server test.""" 19 20 def __init__(self, 21 host, 22 tast_instance, 23 client_test, 24 max_duration_minutes, 25 build_bundle, 26 varslist=None, 27 command_args=None): 28 """Initializes the thread. 29 30 Args: 31 host: An autotest host instance. 32 tast_instance: An instance of the tast.tast() class. 33 client_test: String identifying which tast test to run. 34 max_duration_minutes: Float defining the maximum running time of the 35 managed sub-test. 36 build_bundle: String defining which tast test bundle to build and 37 query for the client_test. 38 varslist: list of strings that define dynamic variables made 39 available to tast tests at runtime via `tast run -var=name=value 40 ...`. Each string should be formatted as 'name=value'. 41 command_args: list of strings that are passed as args to the `tast 42 run` command. 43 """ 44 super(TastManagerThread, self).__init__(name=__name__) 45 self.tast = tast_instance 46 self.tast.initialize( 47 host=host, 48 test_exprs=[client_test], 49 ignore_test_failures=True, 50 max_run_sec=max_duration_minutes * 60, 51 command_args=command_args if command_args else [], 52 build_bundle=build_bundle, 53 varslist=varslist) 54 55 def run(self): 56 logging.info('Started thread: %s', self.__class__.__name__) 57 self.tast.run_once() 58 59 60class graphics_TraceReplayExtended(test.test): 61 """Autotest server test for running repeated trace replays. 62 63 This test simultaneously initiates system performance logging and extended 64 trace replay processes on a target host, and parses their test results for 65 combined analysis and reporting. 66 """ 67 version = 1 68 69 @staticmethod 70 def _initialize_dir_on_host(host, directory): 71 """Initialize a directory to a consistent (empty) state on the host. 72 73 Args: 74 host: An autotest host instance. 75 directory: String defining the location of the directory to 76 initialize. 77 78 Raises: 79 TestFail: If the directory cannot be initialized. 80 """ 81 try: 82 host.run('rm -r %(0)s 2>/dev/null || true; ! test -d %(0)s' % 83 {'0': directory}) 84 host.run('mkdir -p %s' % directory) 85 except (error.AutotestHostRunCmdError, error.AutoservRunError) as err: 86 logging.exception(err) 87 raise error.TestFail( 88 'Failed to initialize directory "%s" on the test host' % 89 directory) 90 91 @staticmethod 92 def _cleanup_dir_on_host(host, directory): 93 """Ensure that a directory and its contents are deleted on the host. 94 95 Args: 96 host: An autotest host instance. 97 directory: String defining the location of the directory to delete. 98 99 Raises: 100 TestFail: If the directory remains on the host. 101 """ 102 try: 103 host.run('rm -r %(0)s || true; ! test -d %(0)s' % {'0': directory}) 104 except (error.AutotestHostRunCmdError, error.AutoservRunError) as err: 105 logging.exception(err) 106 raise error.TestFail( 107 'Failed to cleanup directory "%s" on the test host' % directory) 108 109 def run_once(self, 110 host, 111 client_tast_test, 112 max_duration_minutes, 113 tast_build_bundle='cros', 114 tast_varslist=None, 115 tast_command_args=None): 116 """Runs the test. 117 118 Args: 119 host: An autotest host instance. 120 client_tast_test: String defining which tast test to run. 121 max_duration_minutes: Float defining the maximum running time of the 122 managed sub-test. 123 tast_build_bundle: String defining which tast test bundle to build 124 and query for the client_test. 125 tast_varslist: list of strings that define dynamic variables made 126 available to tast tests at runtime via `tast run -var=name=value 127 ...`. Each string should be formatted as 'name=value'. 128 tast_command_args: list of strings that are passed as args to the 129 `tast run` command. 130 """ 131 # Construct a suffix tag indicating which managing test is using logged 132 # data from the graphics_Power subtest. 133 trace_name = client_tast_test.split('.')[-1] 134 135 # workaround for running test locally since crrev/c/2374267 and 136 # crrev/i/2374267 137 if not tast_command_args: 138 tast_command_args = [] 139 tast_command_args.extend([ 140 'extraallowedbuckets=termina-component-testing,cros-containers-staging' 141 ]) 142 143 # Define paths of signal files for basic RPC/IPC between sub-tests. 144 temp_io_root = '/tmp/%s/' % self.__class__.__name__ 145 result_dir = os.path.join(temp_io_root, 'results') 146 signal_running_file = os.path.join(temp_io_root, 'signal_running') 147 signal_checkpoint_file = os.path.join(temp_io_root, 'signal_checkpoint') 148 149 # This test is responsible for creating/deleting root and resultdir. 150 logging.debug('Creating temporary IPC/RPC dir: %s', temp_io_root) 151 self._initialize_dir_on_host(host, temp_io_root) 152 self._initialize_dir_on_host(host, result_dir) 153 154 # Start background system performance monitoring process on the test 155 # target (via an autotest client 'power_Test'). 156 logging.debug('Connecting to autotest client on host') 157 graphics_power_thread = graphics_power.GraphicsPowerThread( 158 host=host, 159 max_duration_minutes=max_duration_minutes, 160 test_tag='Trace' + '.' + trace_name, 161 pdash_note='', 162 result_dir=result_dir, 163 signal_running_file=signal_running_file, 164 signal_checkpoint_file=signal_checkpoint_file) 165 graphics_power_thread.start() 166 167 logging.info('Waiting for graphics_Power subtest to initialize...') 168 try: 169 graphics_power_thread.wait_until_running(timeout=120) 170 except Exception as err: 171 logging.exception(err) 172 raise error.TestFail( 173 'An error occured during graphics_Power subtest initialization') 174 logging.info('The graphics_Power subtest was properly initialized') 175 176 # Start repeated trace replay process on the test target (via a tast 177 # local test). 178 logging.info('Running Tast test: %s', client_tast_test) 179 tast_outputdir = os.path.join(self.outputdir, 'tast') 180 if not os.path.exists(tast_outputdir): 181 logging.debug('Creating tast outputdir: %s', tast_outputdir) 182 os.makedirs(tast_outputdir) 183 184 if not tast_varslist: 185 tast_varslist = [] 186 tast_varslist.extend([ 187 'graphics.TraceReplayExtended.resultDir=' + result_dir, 188 'graphics.TraceReplayExtended.signalRunningFile=' + 189 signal_running_file, 190 'graphics.TraceReplayExtended.signalCheckpointFile=' + 191 signal_checkpoint_file, 192 ]) 193 194 tast_instance = tast.tast( 195 job=self.job, bindir=self.bindir, outputdir=tast_outputdir) 196 tast_manager_thread = TastManagerThread( 197 host, 198 tast_instance, 199 client_tast_test, 200 max_duration_minutes, 201 tast_build_bundle, 202 varslist=tast_varslist, 203 command_args=tast_command_args) 204 tast_manager_thread.start() 205 206 # Block until both subtests finish. 207 threads = [graphics_power_thread, tast_manager_thread] 208 stop_attempts = 0 209 while threads: 210 # TODO(ryanneph): Move stop signal emission to tast test instance. 211 if (not tast_manager_thread.is_alive() and 212 graphics_power_thread.is_alive() and stop_attempts < 1): 213 logging.info('Attempting to stop graphics_Power thread') 214 graphics_power_thread.stop(timeout=0) 215 stop_attempts += 1 216 217 # Raise test failure if graphics_Power thread ends before tast test. 218 if (not graphics_power_thread.is_alive() and 219 tast_manager_thread.is_alive()): 220 raise error.TestFail( 221 'The graphics_Power subtest ended too soon.') 222 223 for thread in list(threads): 224 if not thread.is_alive(): 225 logging.info('Thread "%s" has ended', 226 thread.__class__.__name__) 227 threads.remove(thread) 228 time.sleep(1) 229 230 client_result_dir = os.path.join(self.outputdir, 'client_results') 231 logging.info('Saving client results to %s', client_result_dir) 232 host.get_file(result_dir, client_result_dir) 233 234 # Ensure the host filesystem is clean for the next test. 235 self._cleanup_dir_on_host(host, result_dir) 236 self._cleanup_dir_on_host(host, temp_io_root) 237 238 # TODO(ryanneph): Implement results parsing/analysis/reporting 239