249 lines
9.7 KiB
Python
249 lines
9.7 KiB
Python
# 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.
|
|
"""Client test for logging system performance metrics."""
|
|
|
|
from collections import namedtuple
|
|
import logging
|
|
import os
|
|
import time
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.cros.power import power_test
|
|
|
|
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')
|
|
|
|
|
|
def remove_file_if_exists(f):
|
|
"""Attempt to delete the file only if it exists."""
|
|
if os.path.exists(f):
|
|
os.remove(f)
|
|
|
|
|
|
class MonitoredFile():
|
|
"""Watches a file and supports querying changes to its status.
|
|
|
|
Tracks a file's current and previous status based on its modified time and
|
|
existence. Provides convenience functions that test for the occurrence of
|
|
various changes, such as file creation, deletion, and modification.
|
|
"""
|
|
|
|
MonitoredFileStatus = namedtuple('monitored_file_status',
|
|
('exists', 'mtime'))
|
|
|
|
def __init__(self, filename):
|
|
self.filename = filename
|
|
self._prev_status = self._get_file_status()
|
|
self._curr_status = self._prev_status
|
|
|
|
def _get_file_status(self):
|
|
exists = os.path.exists(self.filename)
|
|
if exists:
|
|
mtime = os.path.getmtime(self.filename)
|
|
else:
|
|
mtime = None
|
|
|
|
return self.MonitoredFileStatus(exists=exists, mtime=mtime)
|
|
|
|
def update(self):
|
|
"""Check file to update its status.
|
|
|
|
This should be called once per an event loop iteration.
|
|
"""
|
|
self._prev_status = self._curr_status
|
|
self._curr_status = self._get_file_status()
|
|
|
|
def exists(self):
|
|
"""Tests if the file exists."""
|
|
return self._curr_status.exists
|
|
|
|
def deleted(self):
|
|
"""Tests that the file was just deleted"""
|
|
return not self._curr_status.exists and self._prev_status.exists
|
|
|
|
def created(self):
|
|
"""Tests that the file was just created"""
|
|
return self._curr_status.exists and not self._prev_status.exists
|
|
|
|
def modified(self):
|
|
"""Tests that the file was just modified"""
|
|
return (self.deleted() or self.created() or
|
|
self._prev_status.mtime != self._curr_status.mtime)
|
|
|
|
|
|
class graphics_Power(power_test.power_Test):
|
|
"""Wrapper around power_Test client test for use in server tests.
|
|
|
|
Wraps the client power_Test for acquiring system metrics related to graphics
|
|
rendering performance (temperature, clock freqs, power states).
|
|
|
|
This class should only be instantiated from a server test. For background
|
|
logging, see
|
|
<autotest_lib.server.cros.graphics.graphics_power.GraphicsPowerThread()>
|
|
"""
|
|
version = 1
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(graphics_Power, self).__init__(*args, **kwargs)
|
|
self._last_checkpoint_time = None
|
|
|
|
def initialize(self, sample_rate_seconds=1, pdash_note=''):
|
|
"""Setup power_Test base class.
|
|
|
|
Args:
|
|
sample_rate_seconds: Optional; Number defining seconds between data
|
|
point acquisition.
|
|
pdash_note: Optional; A tag that is included as a filter field on
|
|
the ChromeOS power-dashboard.
|
|
"""
|
|
super(graphics_Power, self).initialize(
|
|
seconds_period=sample_rate_seconds,
|
|
pdash_note=pdash_note,
|
|
force_discharge=False)
|
|
|
|
@staticmethod
|
|
def _read_checkpoint_file(filename):
|
|
"""Parses checkpoint signal file and returns name and start_time.
|
|
|
|
Args:
|
|
filename: String path to the checkpoint file to be read.
|
|
|
|
Returns:
|
|
A 2-tuple: (name, start_time) containing a checkpoint name (string)
|
|
and the checkpoint's start time (float; seconds since the epoch).
|
|
|
|
If the start time is not provided in the checkpoint file, start_time
|
|
is equal to None.
|
|
"""
|
|
with open(filename, 'r') as f:
|
|
name = f.readline().rstrip('\n')
|
|
if not name:
|
|
name = None
|
|
|
|
start_time = f.readline().rstrip('\n')
|
|
if start_time:
|
|
start_time = float(start_time)
|
|
else:
|
|
start_time = None
|
|
return name, start_time
|
|
|
|
def checkpoint_measurements(self, name, start_time=None):
|
|
"""Save a power_Test measurement checkpoint.
|
|
|
|
Wraps power_Test.checkpoint_measurements to change behavior of default
|
|
start_time to continue from the end of the previous checkpoint, rather
|
|
than from the test start time.
|
|
|
|
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.
|
|
"""
|
|
# The default start_time is the test start time, but we want checkpoints
|
|
# to start from the end of the previous one.
|
|
if not start_time:
|
|
start_time = self._last_checkpoint_time
|
|
logging.debug('Saving measurements checkpoint "%s" with start time %f',
|
|
name, start_time)
|
|
super(graphics_Power, self).checkpoint_measurements(name, start_time)
|
|
self._last_checkpoint_time = time.time()
|
|
|
|
def run_once(self,
|
|
max_duration_minutes,
|
|
result_dir=None,
|
|
signal_running_file=DEFAULT_SIGNAL_RUNNING_FILE,
|
|
signal_checkpoint_file=DEFAULT_SIGNAL_CHECKPOINT_FILE):
|
|
"""Run system performance loggers until stopped or timeout occurs.
|
|
|
|
Temporal data logs are written to
|
|
<test_results>/{power,cpu,temp,fan_rpm}_results_<timestamp>_raw.txt
|
|
|
|
If result_dir points to a valid filesystem path, post-processing of logs
|
|
will be performed and a more convenient temporal format will be saved in
|
|
the result_dir.
|
|
|
|
Args:
|
|
max_duration_minutes: Number defining the maximum running time of
|
|
the managed sub-test.
|
|
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.
|
|
"""
|
|
# Initiailize test state
|
|
for f in (signal_running_file, signal_checkpoint_file):
|
|
remove_file_if_exists(f)
|
|
|
|
# Indicate 'running' state by touching a mutually-monitored file
|
|
try:
|
|
open(signal_running_file, 'w').close()
|
|
if not os.path.exists(signal_running_file):
|
|
raise RuntimeError(
|
|
'Signal "running" file %s was not properly initiailized' %
|
|
signal_running_file)
|
|
except:
|
|
logging.exception('Failed to set "running" state.')
|
|
raise
|
|
|
|
signal_running = MonitoredFile(signal_running_file)
|
|
logging.info('Monitoring "running" signal file: %s',
|
|
signal_running_file)
|
|
signal_checkpoint = MonitoredFile(signal_checkpoint_file)
|
|
logging.info('Monitoring "checkpoint" signal file: %s',
|
|
signal_checkpoint_file)
|
|
|
|
self.start_measurements() # provided by power_Test class
|
|
time_start = time.time()
|
|
time_end = time_start + max_duration_minutes * 60.0
|
|
self._last_checkpoint_time = time_start
|
|
monitored_files = [signal_running, signal_checkpoint]
|
|
while time.time() < time_end:
|
|
for f in monitored_files:
|
|
f.update()
|
|
|
|
if signal_checkpoint.exists() and signal_checkpoint.modified():
|
|
try:
|
|
checkpoint_name, checkpoint_start_time = \
|
|
self._read_checkpoint_file(signal_checkpoint_file)
|
|
except ValueError as err:
|
|
logging.exception(err)
|
|
raise error.TestFail(
|
|
'Error while converting the checkpoint start time '
|
|
'string to a float.' % signal_checkpoint_file)
|
|
self.checkpoint_measurements(checkpoint_name,
|
|
checkpoint_start_time)
|
|
|
|
if signal_running.deleted():
|
|
logging.info('Signaled to stop by the managing test process')
|
|
break
|
|
|
|
time.sleep(1)
|
|
|
|
self.checkpoint_measurements('default')
|
|
|
|
# Rely on managing test to create/cleanup result_dir
|
|
if result_dir:
|
|
# TODO(ryanneph): Implement structured log output for raw power_Test
|
|
# log files
|
|
with open(
|
|
os.path.join(result_dir, 'graphics_Power_test_output.txt'),
|
|
'w') as f:
|
|
f.write('{}\n')
|
|
|
|
# Cleanup our test state from the filesystem.
|
|
for f in (signal_running_file, signal_checkpoint_file):
|
|
remove_file_if_exists(f)
|