1# Lint as: python2, python3 2# Copyright 2020 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Client test for logging system performance metrics.""" 6 7from collections import namedtuple 8import logging 9import os 10import time 11 12from autotest_lib.client.common_lib import error 13from autotest_lib.client.cros.power import power_test 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 20def remove_file_if_exists(f): 21 """Attempt to delete the file only if it exists.""" 22 if os.path.exists(f): 23 os.remove(f) 24 25 26class MonitoredFile(): 27 """Watches a file and supports querying changes to its status. 28 29 Tracks a file's current and previous status based on its modified time and 30 existence. Provides convenience functions that test for the occurrence of 31 various changes, such as file creation, deletion, and modification. 32 """ 33 34 MonitoredFileStatus = namedtuple('monitored_file_status', 35 ('exists', 'mtime')) 36 37 def __init__(self, filename): 38 self.filename = filename 39 self._prev_status = self._get_file_status() 40 self._curr_status = self._prev_status 41 42 def _get_file_status(self): 43 exists = os.path.exists(self.filename) 44 if exists: 45 mtime = os.path.getmtime(self.filename) 46 else: 47 mtime = None 48 49 return self.MonitoredFileStatus(exists=exists, mtime=mtime) 50 51 def update(self): 52 """Check file to update its status. 53 54 This should be called once per an event loop iteration. 55 """ 56 self._prev_status = self._curr_status 57 self._curr_status = self._get_file_status() 58 59 def exists(self): 60 """Tests if the file exists.""" 61 return self._curr_status.exists 62 63 def deleted(self): 64 """Tests that the file was just deleted""" 65 return not self._curr_status.exists and self._prev_status.exists 66 67 def created(self): 68 """Tests that the file was just created""" 69 return self._curr_status.exists and not self._prev_status.exists 70 71 def modified(self): 72 """Tests that the file was just modified""" 73 return (self.deleted() or self.created() or 74 self._prev_status.mtime != self._curr_status.mtime) 75 76 77class graphics_Power(power_test.power_Test): 78 """Wrapper around power_Test client test for use in server tests. 79 80 Wraps the client power_Test for acquiring system metrics related to graphics 81 rendering performance (temperature, clock freqs, power states). 82 83 This class should only be instantiated from a server test. For background 84 logging, see 85 <autotest_lib.server.cros.graphics.graphics_power.GraphicsPowerThread()> 86 """ 87 version = 1 88 89 def __init__(self, *args, **kwargs): 90 super(graphics_Power, self).__init__(*args, **kwargs) 91 self._last_checkpoint_time = None 92 93 def initialize(self, sample_rate_seconds=1, pdash_note=''): 94 """Setup power_Test base class. 95 96 Args: 97 sample_rate_seconds: Optional; Number defining seconds between data 98 point acquisition. 99 pdash_note: Optional; A tag that is included as a filter field on 100 the ChromeOS power-dashboard. 101 """ 102 super(graphics_Power, self).initialize( 103 seconds_period=sample_rate_seconds, 104 pdash_note=pdash_note, 105 force_discharge=False) 106 107 @staticmethod 108 def _read_checkpoint_file(filename): 109 """Parses checkpoint signal file and returns name and start_time. 110 111 Args: 112 filename: String path to the checkpoint file to be read. 113 114 Returns: 115 A 2-tuple: (name, start_time) containing a checkpoint name (string) 116 and the checkpoint's start time (float; seconds since the epoch). 117 118 If the start time is not provided in the checkpoint file, start_time 119 is equal to None. 120 """ 121 with open(filename, 'r') as f: 122 name = f.readline().rstrip('\n') 123 if not name: 124 name = None 125 126 start_time = f.readline().rstrip('\n') 127 if start_time: 128 start_time = float(start_time) 129 else: 130 start_time = None 131 return name, start_time 132 133 def checkpoint_measurements(self, name, start_time=None): 134 """Save a power_Test measurement checkpoint. 135 136 Wraps power_Test.checkpoint_measurements to change behavior of default 137 start_time to continue from the end of the previous checkpoint, rather 138 than from the test start time. 139 140 Args: 141 name: String defining the saved checkpoint's name. 142 start_time: Optional; Float indicating the time (in seconds since 143 the epoch) at which this checkpoint should actually start. This 144 functionally discards data from the beginning of the logged 145 duration until start_time. 146 """ 147 # The default start_time is the test start time, but we want checkpoints 148 # to start from the end of the previous one. 149 if not start_time: 150 start_time = self._last_checkpoint_time 151 logging.debug('Saving measurements checkpoint "%s" with start time %f', 152 name, start_time) 153 super(graphics_Power, self).checkpoint_measurements(name, start_time) 154 self._last_checkpoint_time = time.time() 155 156 def run_once(self, 157 max_duration_minutes, 158 result_dir=None, 159 signal_running_file=DEFAULT_SIGNAL_RUNNING_FILE, 160 signal_checkpoint_file=DEFAULT_SIGNAL_CHECKPOINT_FILE): 161 """Run system performance loggers until stopped or timeout occurs. 162 163 Temporal data logs are written to 164 <test_results>/{power,cpu,temp,fan_rpm}_results_<timestamp>_raw.txt 165 166 If result_dir points to a valid filesystem path, post-processing of logs 167 will be performed and a more convenient temporal format will be saved in 168 the result_dir. 169 170 Args: 171 max_duration_minutes: Number defining the maximum running time of 172 the managed sub-test. 173 result_dir: Optional; String defining the location on the test 174 target where post-processed results from this sub-test should be 175 saved for retrieval by the managing test process. Set to None if 176 results output is not be created. 177 signal_running_file: Optional; String defining the location of the 178 'running' RPC flag file on the test target. Removal of this file 179 triggers the subtest to finish logging and stop gracefully. 180 signal_checkpoint_file: Optional; String defining the location of 181 the 'checkpoint' RPC flag file on the test target. Modifying 182 this file triggers the subtest to create a checkpoint with name 183 equal to the utf-8-encoded contents of the first-line and 184 optional alternative start time (in seconds since the epoch) 185 equal to the second line of the file. 186 """ 187 # Initiailize test state 188 for f in (signal_running_file, signal_checkpoint_file): 189 remove_file_if_exists(f) 190 191 # Indicate 'running' state by touching a mutually-monitored file 192 try: 193 open(signal_running_file, 'w').close() 194 if not os.path.exists(signal_running_file): 195 raise RuntimeError( 196 'Signal "running" file %s was not properly initiailized' % 197 signal_running_file) 198 except: 199 logging.exception('Failed to set "running" state.') 200 raise 201 202 signal_running = MonitoredFile(signal_running_file) 203 logging.info('Monitoring "running" signal file: %s', 204 signal_running_file) 205 signal_checkpoint = MonitoredFile(signal_checkpoint_file) 206 logging.info('Monitoring "checkpoint" signal file: %s', 207 signal_checkpoint_file) 208 209 self.start_measurements() # provided by power_Test class 210 time_start = time.time() 211 time_end = time_start + max_duration_minutes * 60.0 212 self._last_checkpoint_time = time_start 213 monitored_files = [signal_running, signal_checkpoint] 214 while time.time() < time_end: 215 for f in monitored_files: 216 f.update() 217 218 if signal_checkpoint.exists() and signal_checkpoint.modified(): 219 try: 220 checkpoint_name, checkpoint_start_time = \ 221 self._read_checkpoint_file(signal_checkpoint_file) 222 except ValueError as err: 223 logging.exception(err) 224 raise error.TestFail( 225 'Error while converting the checkpoint start time ' 226 'string to a float.' % signal_checkpoint_file) 227 self.checkpoint_measurements(checkpoint_name, 228 checkpoint_start_time) 229 230 if signal_running.deleted(): 231 logging.info('Signaled to stop by the managing test process') 232 break 233 234 time.sleep(1) 235 236 self.checkpoint_measurements('default') 237 238 # Rely on managing test to create/cleanup result_dir 239 if result_dir: 240 # TODO(ryanneph): Implement structured log output for raw power_Test 241 # log files 242 with open( 243 os.path.join(result_dir, 'graphics_Power_test_output.txt'), 244 'w') as f: 245 f.write('{}\n') 246 247 # Cleanup our test state from the filesystem. 248 for f in (signal_running_file, signal_checkpoint_file): 249 remove_file_if_exists(f) 250