blob: a966b4f07ed3e3c481164662ff8e74c197af950b [file] [log] [blame]
#!/usr/bin/env python2
# Copyright 2018 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.
"""Servod power measurement utility."""
from __future__ import print_function
import argparse
import logging
import os
import shutil
import signal
import sys
import tempfile
import threading
import client
# This module is just a wrapper around measure_power functionality
import measure_power
import servo_parsing
class ProgressPrinter(threading.Thread):
"""Print a marker every few seconds to indicate progress.
Public Attributes:
stop: Event object to signal end to printing.
"""
# Default progress marker.
DOT_MARKER = '.'
# Rate to print progress marker.
PROGRESS_UPDATE_RATE = 1.0
def __init__(self, marker=DOT_MARKER, rate=PROGRESS_UPDATE_RATE,
stop_signal=None):
"""Initialize constants & prepare thread to run."""
super(ProgressPrinter, self).__init__()
self._marker = marker
self._rate = rate
if not stop_signal:
stop_signal = threading.Event()
self.stop = stop_signal
def run(self):
"""Print |_marker| every |_rate| seconds until |stop| is set."""
while not self.stop.is_set():
sys.stdout.write(self._marker)
sys.stdout.flush()
self.stop.wait(self._rate)
def _AddMutuallyExclusiveAction(name, parser, default=True, action='save'):
"""Add both '--do-something' and '--no-do-something' pair to parser.
This adds a mutually exclusive switch for a boolean action into a parser.
Adds two flags:
--%{action}-%{name}
--no-%{action}-%{name}
Args:
name: object on which to perform the action
parser: parser to attach mutually exclusive group to
default: default value for boolean switch
action: action to perform on name
"""
saver = parser.add_mutually_exclusive_group()
argname = '--%s-%s' % (action, name)
noargname = '--no-%s-%s' % (action, name)
dest = '%s_%s' % (action, name.replace('-', '_'))
arghelp = '%s %s' % (action, name)
saver.add_argument(argname, default=default, dest=dest,
action='store_true', help=arghelp)
noarghelp = "don't %s %s" % (action, name)
saver.add_argument(noargname, default=argparse.SUPPRESS, dest=dest,
action='store_false', help=noarghelp)
# pylint: disable=dangerous-default-value
def main(cmdline=sys.argv[1:]):
description = 'Measure power using servod.'
# BaseServodParser provides port, host, debug arguments
parser = servo_parsing.BaseServodParser(description=description)
# power measurement logistics
parser.add_argument('-f', '--fast', default=False, action='store_true',
help='if fast no verification cmds are done')
parser.add_argument('-w', '--wait', default=0, type=float,
help='time (sec) to wait before measuring power')
parser.add_argument('-t', '--time', default=60, type=float,
help='time (sec) to measure power for')
parser.add_argument('--ina-rate', default=measure_power.DEFAULT_INA_RATE,
type=float, help='rate (sec) to query the INAs, if <= 0 '
'then INAs will not be queried')
parser.add_argument('--vbat-rate', default=measure_power.DEFAULT_VBAT_RATE,
type=float,
help='rate (sec) to query the ec vbat command, if <= 0 '
'then ec vbat will not be queried')
# output and logging logic
parser.add_argument('--no-output', default=False, action='store_true',
help='do not output anything into stdout')
parser.add_argument('-o', '--outdir', default=None,
help='directory to save data into')
parser.add_argument('-m', '--message', default=None,
help='message to append to each summary file stored')
_AddMutuallyExclusiveAction('raw-data', parser, default=False)
_AddMutuallyExclusiveAction('summary', parser)
# NOTE: if logging gets too verbose, turn default off
_AddMutuallyExclusiveAction('logs', parser)
parser.add_argument('--save-all', default=False, action='store_true',
help='Equivalent to --save-summary --save-logs '
'--save-raw-data. Overwrites any of those if specified.')
args = parser.parse_args(cmdline)
# Save all logic
if args.save_all:
args.save_logs = args.save_raw_data = args.save_summary = True
pm_logger = logging.getLogger('')
pm_logger.setLevel(logging.DEBUG)
servo_parsing.get_env_options(pm_logger, args)
if not args.port:
args.port = client.DEFAULT_PORT
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setLevel(logging.INFO)
if args.debug:
stdout_handler.setLevel(logging.DEBUG)
if not args.no_output:
pm_logger.addHandler(stdout_handler)
if args.save_logs:
tmplogfile = tempfile.NamedTemporaryFile()
logfilehandler = logging.StreamHandler(tmplogfile)
logfilehandler.setLevel(logging.DEBUG)
pm_logger.addHandler(logfilehandler)
pm = measure_power.PowerMeasurement(host=args.host, port=args.port,
ina_rate=args.ina_rate,
vbat_rate=args.vbat_rate,
fast=args.fast)
# pylint: disable=undefined-variable
# sleep is used as a preemptible way to sleep & allow for SIGTERM/SIGINT
sleep = threading.Event()
progress_printer = ProgressPrinter(stop_signal=sleep)
setup_done = pm.MeasurePower(wait=args.wait)
# pylint: disable=g-long-lambda
handler = lambda signal, _, pm=pm, sleep=sleep: (sleep.set(),
pm.FinishMeasurement())
# Ensure that SIGTERM and SIGNINT gracefully stop the measurement
signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGTERM, handler)
# Wait until measurement is is setup
setup_done.wait()
if not args.no_output:
# Start printing progress once power collection has started
progress_printer.start()
# Sleep for measurement time and wait time. Will wake on SIGINT & SIGTERM
sleep.wait(args.time + args.wait)
# To ensure the ProgressPrinter also stops printing.
sleep.set()
# Indicate that measurement should stop, as ProcessMeasurement sets
# stop_signal internally as well
pm.ProcessMeasurement()
pm.DisplaySummary()
if args.save_summary:
pm.SaveSummary(args.outdir, args.message)
if args.save_raw_data:
pm.SaveRawData(args.outdir)
if args.save_logs:
# pylint: disable=protected-access
outdir = pm._outdir
if args.outdir and os.path.isdir(args.outdir):
outdir = args.outdir
logfile = os.path.join(outdir, 'logs.txt')
pm_logger.info('Storing logs at:\n%s', logfile)
shutil.move(tmplogfile.name, logfile)
if __name__ == '__main__':
main(sys.argv[1:])