mirror of
				https://github.com/ARM-software/workload-automation.git
				synced 2025-10-30 22:54:18 +00:00 
			
		
		
		
	Merge pull request #144 from ep1cman/servo
servo_power: Added support for chromebook servo boards
This commit is contained in:
		
							
								
								
									
										228
									
								
								wlauto/instrumentation/servo_power_monitors/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								wlauto/instrumentation/servo_power_monitors/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,228 @@ | ||||
| #    Copyright 2016 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| # pylint: disable=W0613,E1101,attribute-defined-outside-init | ||||
| from __future__ import division | ||||
| import os | ||||
| import subprocess | ||||
| import signal | ||||
| import csv | ||||
| import threading | ||||
| import time | ||||
| import getpass | ||||
| import logging | ||||
| import xmlrpclib | ||||
| from datetime import datetime | ||||
|  | ||||
| from wlauto import Instrument, Parameter, Executable | ||||
| from wlauto.exceptions import InstrumentError, ConfigError | ||||
| from wlauto.utils.types import list_of_strings | ||||
| from wlauto.utils.misc import check_output | ||||
| from wlauto.utils.cros_sdk import CrosSdkSession | ||||
| from wlauto.utils.misc import which | ||||
|  | ||||
|  | ||||
| class ServoPowerMonitor(Instrument): | ||||
|  | ||||
|     name = 'servo_power' | ||||
|     description = """ | ||||
|     Collects power traces using the Chromium OS Servo Board. | ||||
|  | ||||
|     Servo is a debug board used for Chromium OS test and development. Among other uses, it allows | ||||
|     access to the built in power monitors (if present) of a Chrome OS device. More information on | ||||
|     Servo board can be found in the link bellow: | ||||
|  | ||||
|     https://www.chromium.org/chromium-os/servo | ||||
|  | ||||
|     In order to use this instrument you need to be a sudoer and you need a chroot environment. More | ||||
|     information on the chroot environment can be found on the link bellow: | ||||
|  | ||||
|     https://www.chromium.org/chromium-os/developer-guide | ||||
|  | ||||
|     If you wish to run servod on a remote machine you will need to allow it to accept external connections | ||||
|     using the `--host` command line option, like so: | ||||
|     `sudo servod -b some_board -c some_board.xml --host=''` | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     parameters = [ | ||||
|         Parameter('power_domains', kind=list_of_strings, default=[], | ||||
|                   description="""The names of power domains to be monitored by the | ||||
|                                  instrument using servod."""), | ||||
|         Parameter('labels', kind=list_of_strings, default=[], | ||||
|                   description="""Meaningful labels for each of the monitored domains."""), | ||||
|         Parameter('chroot_path', kind=str, | ||||
|                   description="""Path to chroot direcory on the host."""), | ||||
|         Parameter('sampling_rate', kind=int, default=10, | ||||
|                   description="""Samples per second."""), | ||||
|         Parameter('board_name', kind=str, mandatory=True, | ||||
|                   description="""The name of the board under test."""), | ||||
|         Parameter('autostart', kind=bool, default=True, | ||||
|                   description="""Automatically start `servod`. Set to `False` if you want to | ||||
|                                  use an already running `servod` instance or a remote servo"""), | ||||
|         Parameter('host', kind=str, default="localhost", | ||||
|                   description="""When `autostart` is set to `False` you can specify the host | ||||
|                                  on which `servod` is running allowing you to remotelly access | ||||
|                                  as servo board. | ||||
|  | ||||
|                                  if `autostart` is `True` this parameter is ignored and `localhost` | ||||
|                                  is used instead"""), | ||||
|         Parameter('port', kind=int, default=9999, | ||||
|                   description="""When `autostart` is set to false you must provide the port | ||||
|                                  that `servod` is running on | ||||
|  | ||||
|                                  If `autostart` is `True` this parameter is ignored and the port | ||||
|                                  output during the startup of `servod` will be used."""), | ||||
|         Parameter('vid', kind=str, | ||||
|                   description="""When more than one servo is plugged in, you must provide | ||||
|                                  a vid/pid pair to identify the servio you wish to use."""), | ||||
|         Parameter('pid', kind=str, | ||||
|                   description="""When more than one servo is plugged in, you must provide | ||||
|                                  a vid/pid pair to identify the servio you wish to use."""), | ||||
|     ] | ||||
|  | ||||
|     # When trying to initialize servod, it may take some time until the server is up | ||||
|     # Therefore we need to poll to identify when the sever has successfully started | ||||
|     # servod_max_tries specifies the maximum number of times we will check to see if the server has started | ||||
|     # while servod_delay_between_tries is the sleep time between checks. | ||||
|     servod_max_tries = 100 | ||||
|     servod_delay_between_tries = 0.1 | ||||
|  | ||||
|     def validate(self): | ||||
|         # pylint: disable=access-member-before-definition | ||||
|         if self.labels and len(self.power_domains) != len(self.labels): | ||||
|             raise ConfigError('There should be exactly one label per power domain') | ||||
|         if self.autostart: | ||||
|             if self.host != 'localhost':  # pylint: disable=access-member-before-definition | ||||
|                 self.logger.warning('Ignoring host "%s" since autostart is set to "True"', self.host) | ||||
|                 self.host = "localhost" | ||||
|         if (self.vid is None) != (self.pid is None): | ||||
|             raise ConfigError('`vid` and `pid` must both be specified') | ||||
|  | ||||
|     def initialize(self, context): | ||||
|         # pylint: disable=access-member-before-definition | ||||
|         self.poller = None | ||||
|         self.data = None | ||||
|         self.stopped = True | ||||
|         if not self.labels: | ||||
|             self.labels = ["PORT_{}".format(channel) for channel, _ in enumerate(self.power_domains)] | ||||
|  | ||||
|         self.power_domains = [channel if channel.endswith("_mw") else | ||||
|                               "{}_mw".format(channel) for channel in self.power_domains] | ||||
|         self.label_map = {pd: l for pd, l in zip(self.power_domains, self.labels)} | ||||
|  | ||||
|         if self.autostart: | ||||
|             self._start_servod() | ||||
|  | ||||
|     def setup(self, context): | ||||
|         # pylint: disable=access-member-before-definition | ||||
|         self.outfile = os.path.join(context.output_directory, 'servo.csv') | ||||
|         self.poller = PowerPoller(self.host, self.port, self.power_domains, self.sampling_rate) | ||||
|  | ||||
|     def start(self, context): | ||||
|         self.poller.start() | ||||
|         self.stopped = False | ||||
|  | ||||
|     def stop(self, context): | ||||
|         self.data = self.poller.stop() | ||||
|         self.poller.join() | ||||
|         self.stopped = True | ||||
|  | ||||
|         timestamps = self.data.pop("timestamp") | ||||
|         for channel, data in self.data.iteritems(): | ||||
|             label = self.label_map[channel] | ||||
|             data = [float(v) / 1000.0 for v in data] | ||||
|             sample_sum = sum(data) | ||||
|  | ||||
|             metric_name = '{}_power'.format(label) | ||||
|             power = sample_sum / len(data) | ||||
|             context.result.add_metric(metric_name, round(power, 3), 'Watts') | ||||
|  | ||||
|             metric_name = '{}_energy'.format(label) | ||||
|             energy = sample_sum * (1.0 / self.sampling_rate) | ||||
|             context.result.add_metric(metric_name, round(energy, 3), 'Joules') | ||||
|  | ||||
|         with open(self.outfile, 'wb') as f: | ||||
|             c = csv.writer(f) | ||||
|             headings = ['timestamp'] + ['{}_power'.format(label) for label in self.labels] | ||||
|             c.writerow(headings) | ||||
|             for row in zip(timestamps, *self.data.itervalues()): | ||||
|                 c.writerow(row) | ||||
|  | ||||
|     def teardown(self, context): | ||||
|         if not self.stopped: | ||||
|             self.stop(context) | ||||
|         if self.autostart: | ||||
|             self.server_session.kill_session() | ||||
|  | ||||
|     def _start_servod(self): | ||||
|         in_chroot = False if which('dut-control') is None else True | ||||
|         password = '' | ||||
|         if not in_chroot: | ||||
|             msg = 'Instrument %s requires sudo access on this machine to start `servod`' | ||||
|             self.logger.info(msg, self.name) | ||||
|             self.logger.info('You need to be sudoer to use it.') | ||||
|             password = getpass.getpass() | ||||
|             check = subprocess.call('echo {} | sudo -S ls > /dev/null'.format(password), shell=True) | ||||
|             if check: | ||||
|                 raise InstrumentError('Given password was either wrong or you are not a sudoer') | ||||
|         self.server_session = CrosSdkSession(self.chroot_path, password=password) | ||||
|         password = '' | ||||
|  | ||||
|         command = 'sudo servod -b {b} -c {b}.xml' | ||||
|         if self.vid and self.pid: | ||||
|             command += " -v " + self.vid | ||||
|             command += " -p " + self.pid | ||||
|         command += '&' | ||||
|         self.server_session.send_command(command.format(b=self.board_name)) | ||||
|         for _ in xrange(self.servod_max_tries): | ||||
|             server_lines = self.server_session.get_lines(timeout=1, from_stderr=True, | ||||
|                                                          timeout_only_for_first_line=False) | ||||
|             if server_lines: | ||||
|                 if 'Listening on' in server_lines[-1]: | ||||
|                     self.port = int(server_lines[-1].split()[-1]) | ||||
|                     break | ||||
|             time.sleep(self.servod_delay_between_tries) | ||||
|         else: | ||||
|             raise InstrumentError('Failed to start servod in cros_sdk environment') | ||||
|  | ||||
|  | ||||
| class PowerPoller(threading.Thread): | ||||
|  | ||||
|     def __init__(self, host, port, channels, sampling_rate): | ||||
|         super(PowerPoller, self).__init__() | ||||
|         self.proxy = xmlrpclib.ServerProxy("http://{}:{}/".format(host, port)) | ||||
|         self.proxy.get(channels[1])  # Testing connection | ||||
|         self.channels = channels | ||||
|         self.data = {channel: [] for channel in channels} | ||||
|         self.data['timestamp'] = [] | ||||
|         self.period = 1.0 / sampling_rate | ||||
|  | ||||
|         self.term_signal = threading.Event() | ||||
|         self.term_signal.set() | ||||
|         self.logger = logging.getLogger(self.__class__.__name__) | ||||
|  | ||||
|     def run(self): | ||||
|         while self.term_signal.is_set(): | ||||
|             self.data['timestamp'].append(str(datetime.now())) | ||||
|             for channel in self.channels: | ||||
|                 self.data[channel].append(float(self.proxy.get(channel))) | ||||
|             time.sleep(self.period) | ||||
|  | ||||
|     def stop(self): | ||||
|         self.term_signal.clear() | ||||
|         self.join() | ||||
|         return self.data | ||||
							
								
								
									
										132
									
								
								wlauto/utils/cros_sdk.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								wlauto/utils/cros_sdk.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| #    Copyright 2016 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| import sys | ||||
| import time | ||||
| import os | ||||
| import logging | ||||
|  | ||||
| from Queue import Queue, Empty | ||||
| from threading import Thread | ||||
| from subprocess import Popen, PIPE | ||||
| from wlauto.utils.misc import which | ||||
| from wlauto.exceptions import HostError | ||||
|  | ||||
|  | ||||
| class OutputPollingThread(Thread): | ||||
|  | ||||
|     def __init__(self, out, queue, name): | ||||
|         super(OutputPollingThread, self).__init__() | ||||
|         self.out = out | ||||
|         self.queue = queue | ||||
|         self.stop_signal = False | ||||
|         self.name = name | ||||
|  | ||||
|     def run(self): | ||||
|         for line in iter(self.out.readline, ''): | ||||
|             if self.stop_signal: | ||||
|                 break | ||||
|             self.queue.put(line) | ||||
|  | ||||
|     def set_stop(self): | ||||
|         self.stop_signal = True | ||||
|  | ||||
|  | ||||
| class CrosSdkSession(object): | ||||
|  | ||||
|     def __init__(self, cros_path, password=''): | ||||
|         self.logger = logging.getLogger(self.__class__.__name__) | ||||
|         self.in_chroot = True if which('dut-control') else False | ||||
|         ON_POSIX = 'posix' in sys.builtin_module_names | ||||
|         if self.in_chroot: | ||||
|             self.cros_sdk_session = Popen(['/bin/sh'], bufsize=1, stdin=PIPE, stdout=PIPE, stderr=PIPE, | ||||
|                                           cwd=cros_path, close_fds=ON_POSIX, shell=True) | ||||
|         else: | ||||
|             cros_sdk_bin_path = which('cros_sdk') | ||||
|             potential_path = os.path.join("cros_path", "chromium/tools/depot_tools/cros_sdk") | ||||
|             if not cros_sdk_bin_path and os.path.isfile(potential_path): | ||||
|                 cros_sdk_bin_path = potential_path | ||||
|             if not cros_sdk_bin_path: | ||||
|                 raise HostError("Failed to locate 'cros_sdk' make sure it is in your PATH") | ||||
|             self.cros_sdk_session = Popen(['sudo -Sk {}'.format(cros_sdk_bin_path)], bufsize=1, stdin=PIPE, | ||||
|                                           stdout=PIPE, stderr=PIPE, cwd=cros_path, close_fds=ON_POSIX, shell=True) | ||||
|             self.cros_sdk_session.stdin.write(password) | ||||
|             self.cros_sdk_session.stdin.write('\n') | ||||
|         self.stdout_queue = Queue() | ||||
|         self.stdout_thread = OutputPollingThread(self.cros_sdk_session.stdout, self.stdout_queue, 'stdout') | ||||
|         self.stdout_thread.daemon = True | ||||
|         self.stdout_thread.start() | ||||
|         self.stderr_queue = Queue() | ||||
|         self.stderr_thread = OutputPollingThread(self.cros_sdk_session.stderr, self.stderr_queue, 'stderr') | ||||
|         self.stderr_thread.daemon = True | ||||
|         self.stderr_thread.start() | ||||
|  | ||||
|     def kill_session(self): | ||||
|         self.stdout_thread.set_stop() | ||||
|         self.stderr_thread.set_stop() | ||||
|         self.send_command('echo TERMINATE >&1')  # send something into stdout to unblock it and close it properly | ||||
|         self.send_command('echo TERMINATE 1>&2')  # ditto for stderr | ||||
|         self.stdout_thread.join() | ||||
|         self.stderr_thread.join() | ||||
|         self.cros_sdk_session.kill() | ||||
|  | ||||
|     def send_command(self, cmd, flush=True): | ||||
|         if not cmd.endswith('\n'): | ||||
|             cmd = cmd + '\n' | ||||
|         self.logger.debug(cmd.strip()) | ||||
|         self.cros_sdk_session.stdin.write(cmd) | ||||
|         if flush: | ||||
|             self.cros_sdk_session.stdin.flush() | ||||
|  | ||||
|     def read_line(self, timeout=0): | ||||
|         return _read_line_from_queue(self.stdout_queue, timeout=timeout, logger=self.logger) | ||||
|  | ||||
|     def read_stderr_line(self, timeout=0): | ||||
|         return _read_line_from_queue(self.stderr_queue, timeout=timeout, logger=self.logger) | ||||
|  | ||||
|     def get_lines(self, timeout=0, timeout_only_for_first_line=True, from_stderr=False): | ||||
|         lines = [] | ||||
|         line = True | ||||
|         while line is not None: | ||||
|             if from_stderr: | ||||
|                 line = self.read_stderr_line(timeout) | ||||
|             else: | ||||
|                 line = self.read_line(timeout) | ||||
|             if line: | ||||
|                 lines.append(line) | ||||
|                 if timeout and timeout_only_for_first_line: | ||||
|                     timeout = 0  # after a line has been read, no further delay is required | ||||
|         return lines | ||||
|  | ||||
|  | ||||
| def _read_line_from_queue(queue, timeout=0, logger=None): | ||||
|     try: | ||||
|         line = queue.get_nowait() | ||||
|     except Empty: | ||||
|         line = None | ||||
|     if line is None and timeout: | ||||
|         sleep_time = timeout | ||||
|         time.sleep(sleep_time) | ||||
|         try: | ||||
|             line = queue.get_nowait() | ||||
|         except Empty: | ||||
|             line = None | ||||
|     if line is not None: | ||||
|         line = line.strip('\n') | ||||
|     if logger and line: | ||||
|         logger.debug(line) | ||||
|     return line | ||||
|  | ||||
		Reference in New Issue
	
	Block a user