blob: 4429e02de2ef1230eb3ec77c0341499869aa75b5 [file] [log] [blame]
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides a management class for using graphics_Power in server tests."""
import logging
import os
import tempfile
import threading
import time
from autotest_lib.client.common_lib import error
from autotest_lib.server import autotest
ROOT_DIR = '/tmp/graphics_Power/'
DEFAULT_SIGNAL_RUNNING_FILE = os.path.join(ROOT_DIR, 'signal_running')
DEFAULT_SIGNAL_CHECKPOINT_FILE = os.path.join(ROOT_DIR, 'signal_checkpoint')
class GraphicsPowerThread(threading.Thread):
"""Thread for running the graphics_Power client test.
Provides a threaded management interface for the graphics_Power subtest.
This class can be used from an autotest server test to log system
performance metrics in the background on the test host.
"""
class Error(Exception):
"""Base error that can be inherited to define more specific errors."""
pass
class ThreadNotInitializedError(Error):
"""An error indicating that the thread was not properly initialized."""
pass
class InitTimeoutError(Error):
"""An error indicating that a timeout occurred during a blocking call."""
pass
def __init__(self,
host,
max_duration_minutes,
sample_rate_seconds=1,
test_tag=None,
pdash_note=None,
result_dir=None,
signal_running_file=DEFAULT_SIGNAL_RUNNING_FILE,
signal_checkpoint_file=DEFAULT_SIGNAL_CHECKPOINT_FILE):
"""Initializes the thread.
Args:
host: An autotest host instance.
max_duration_minutes: Float defining the maximum running time of the
managed sub-test.
sample_rate_seconds: Optional; Number defining seconds between data
point acquisition.
test_tag: Optional; String describing the test that initiated this
monitoring process; appended to the true test name.
pdash_note: Optional; A tag that is included as a filter field on
the ChromeOS power-dashboard.
result_dir: Optional; String defining the location on the test
target where post-processed results from this sub-test should be
saved for retrieval by the managing test process. Set to None if
results output is not be created.
signal_running_file: Optional; String defining the location of the
'running' RPC flag file on the test target. Removal of this file
triggers the subtest to finish logging and stop gracefully.
signal_checkpoint_file: Optional; String defining the location of
the 'checkpoint' RPC flag file on the test target. Modifying
this file triggers the subtest to create a checkpoint with name
equal to the utf-8-encoded contents of the first-line and
optional alternative start time (in seconds since the epoch)
equal to the second line of the file.
"""
super(GraphicsPowerThread, self).__init__(name=__name__)
self._running = False
self._autotest_client = autotest.Autotest(host)
self._host = host
self._test_thread = None
self.max_duration_minutes = max_duration_minutes
self.sample_rate_seconds = sample_rate_seconds
self.test_tag = test_tag
self.pdash_note = pdash_note
self.result_dir = result_dir
self.signal_running_file = signal_running_file
self.signal_checkpoint_file = signal_checkpoint_file
def is_running(self):
"""Return a bool indicating the 'running' state of the subtest.
This check can be used to ensure system logging is initialized and
running before beginning other subtests.
"""
try:
self._host.run('test -f %s' % self.signal_running_file)
return True
except (error.AutotestHostRunCmdError, error.AutoservRunError):
return False
def wait_until_running(self, timeout=120):
"""Block execution until the subtest reports it is logging properly.
Args:
timeout: Optional; Float that defines how long to block before
timeout occurs. If timeout=None, then block forever
Raises:
RuntimeError: The subtest ended unexpectedly before initialization
finished.
GraphicsPowerThread.ThreadNotInitializedError: The thread hasn't
been started by the managing server test yet.
GraphicsPowerThread.InitTimeoutError: A timeout occurred while
waiting for subtest to report itself as running.
"""
if timeout:
time_start = time.time()
time_end = time_start + timeout
while True:
if timeout and time.time() >= time_end:
self.stop()
raise self.InitTimeoutError(
'The graphics_Power subtest initialization timed out')
if not self.is_alive():
raise RuntimeError(
'The graphics_Power subtest failed to initialize')
if self.is_running():
break
time.sleep(1)
if not self._test_thread:
raise self.ThreadNotInitializedError
def stop(self, timeout=None):
"""Gracefully stop the subtest on the test host.
If timeout is None, then this is a blocking call that waits forever.
If timeout is a positive number, then it waits for 'timeout' seconds.
If timeout is 0, then it returns immediately.
Args:
timeout: Time (seconds) before giving up on joining the thread.
Returns:
A bool indicating if thread was stopped.
"""
self._running = False
self.join(timeout)
return not self.is_alive()
def checkpoint_measurements(self, name, start_time=None):
"""Save the current log buffers with an associated name.
The power-dashboard displays time series data in one or more
checkpoints that can be used to annotate different phases of a test.
By saving a checkpoint, the time series data collected since the end of
the most recently committed checkpoint (or the test start if no
checkpoints are saved yet) is annotated on the power-dashboard with the
specified name. The checkpoint start time can be adjusted with the
optional 'start_time' argument.
Args:
name: String defining the saved checkpoint's name.
start_time: Optional; Float indicating the time (in seconds since
the epoch) at which this checkpoint should actually start. This
functionally discards data from the beginning of the logged
duration until start_time.
"""
with tempfile.NamedTemporaryFile('w') as tf:
tf.write(str(name) + '\n')
if start_time:
tf.write(str(start_time))
tf.flush()
self._host.send_file(tf.name, self.signal_checkpoint_file)
def _run_test_async(self):
self._autotest_client.run_test(
'graphics_Power',
tag=self.test_tag,
max_duration_minutes=self.max_duration_minutes,
sample_rate_seconds=self.sample_rate_seconds,
pdash_note=self.pdash_note,
result_dir=self.result_dir,
signal_running_file=self.signal_running_file,
signal_checkpoint_file=self.signal_checkpoint_file)
def run(self):
self._running = True
self._test_thread = threading.Thread(target=self._run_test_async)
self._test_thread.start()
logging.info('Started thread: %s', self.__class__.__name__)
def send_stop_signal_and_join():
"""Emits a stop signal to the test host and joins the thread.
Deletes a monitored file on the test host over ssh and waits for
the graphics_Power sub-test to end gracefully as a consequence.
"""
while True:
self._host.run('rm %s 2>/dev/null || true' %
self.signal_running_file)
self._test_thread.join(5)
if not self._test_thread.is_alive():
break
while True:
time.sleep(1)
if not self._test_thread.is_alive():
logging.debug('The graphics_Power subtest ended')
break
elif not self._running:
logging.debug(
'Sending stop signal to the graphics_Power subtest')
send_stop_signal_and_join()
break