1a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved.
2a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)# found in the LICENSE file.
4a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import collections
6a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import logging
7a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import os
8a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import plistlib
9a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import shutil
10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import tempfile
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import xml.parsers.expat
12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from telemetry import decorators
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from telemetry.core import util
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)from telemetry.core.platform import platform_backend
16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)from telemetry.core.platform import power_monitor
17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
19a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class PowerMetricsPowerMonitor(power_monitor.PowerMonitor):
20a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def __init__(self, backend):
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    super(PowerMetricsPowerMonitor, self).__init__()
22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._powermetrics_process = None
23a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._backend = backend
24a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._output_filename = None
25f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    self._output_directory = None
26a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
27a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def binary_path(self):
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return '/usr/bin/powermetrics'
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
31c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  def StartMonitoringPower(self, browser):
32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    assert not self._powermetrics_process, (
33c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        "Must call StopMonitoringPower().")
34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Empirically powermetrics creates an empty output file immediately upon
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # starting.  We detect file creation as a signal that measurement has
36a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # started.  In order to avoid various race conditions in tempfile creation
37a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # we create a temp directory and have powermetrics create it's output
38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # there rather than say, creating a tempfile, deleting it and reusing its
39a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # name.
40f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    self._output_directory = tempfile.mkdtemp()
41f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    self._output_filename = os.path.join(self._output_directory,
42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        'powermetrics.output')
43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    args = ['-f', 'plist',
44f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)            '-u', self._output_filename,
45f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)            '-i0',
46f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)            '--show-usage-summary']
47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._powermetrics_process = self._backend.LaunchApplication(
48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        self.binary_path, args, elevate_privilege=True)
49a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Block until output file is written to ensure this function call is
51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # synchronous in respect to powermetrics starting.
52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    def _OutputFileExists():
53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return os.path.isfile(self._output_filename)
54f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    util.WaitFor(_OutputFileExists, 1)
55a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @decorators.Cache
57c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  def CanMonitorPower(self):
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    mavericks_or_later = (
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        self._backend.GetOSVersionName() >= platform_backend.MAVERICKS)
60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    binary_path = self.binary_path
61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return mavericks_or_later and self._backend.CanLaunchApplication(
62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        binary_path)
63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @staticmethod
65a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def _ParsePlistString(plist_string):
66a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """Wrapper to parse a plist from a string and catch any errors.
67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    Sometimes powermetrics will exit in the middle of writing it's output,
69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    empirically it seems that it always writes at least one sample in it's
70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    entirety so we can safely ignore any errors in it's output.
71a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    Returns:
73a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        Parser output on succesful parse, None on parse error.
74a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """
75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    try:
76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return plistlib.readPlistFromString(plist_string)
77a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    except xml.parsers.expat.ExpatError:
78a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return None
79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
80a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @staticmethod
81a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def ParsePowerMetricsOutput(powermetrics_output):
82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """Parse output of powermetrics command line utility.
83a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    Returns:
85c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        Dictionary in the format returned by StopMonitoringPower() or None
86effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        if |powermetrics_output| is empty - crbug.com/353250 .
87a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """
88effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    if len(powermetrics_output) == 0:
89effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      logging.warning("powermetrics produced zero length output")
90effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      return None
91a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
92a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Container to collect samples for running averages.
93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # out_path - list containing the key path in the output dictionary.
94a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # src_path - list containing the key path to get the data from in
95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    #    powermetrics' output.
96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    def ConstructMetric(out_path, src_path):
97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      RunningAverage = collections.namedtuple('RunningAverage', [
98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        'out_path', 'src_path', 'samples'])
99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return RunningAverage(out_path, src_path, [])
100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # List of RunningAverage objects specifying metrics we want to aggregate.
102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    metrics = [
103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        ConstructMetric(
104c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch            ['component_utilization', 'whole_package', 'average_frequency_hz'],
105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            ['processor','freq_hz']),
106a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        ConstructMetric(
107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            ['component_utilization', 'whole_package', 'idle_percent'],
108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)            ['processor','packages', 0, 'c_state_ratio'])]
109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
110a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    def DataWithMetricKeyPath(metric, powermetrics_output):
111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      """Retrieve the sample from powermetrics' output for a given metric.
112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      Args:
114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          metric: The RunningAverage object we want to collect a new sample for.
115a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          powermetrics_output: Dictionary containing powermetrics output.
116a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
117a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      Returns:
118a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          The sample corresponding to |metric|'s keypath."""
119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      # Get actual data corresponding to key path.
120a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      out_data = powermetrics_output
121a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      for k in metric.src_path:
122a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        out_data = out_data[k]
123a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
124a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      assert type(out_data) in [int, float], (
125a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          "Was expecting a number: %s (%s)" % (type(out_data), out_data))
126a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return float(out_data)
127a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
128a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    sample_durations = []
129a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    total_energy_consumption_mwh = 0
130a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # powermetrics outputs multiple plists separated by null terminators.
131a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    raw_plists = powermetrics_output.split('\0')
132a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    raw_plists = [x for x in raw_plists if len(x) > 0]
133f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    assert(len(raw_plists) == 1)
134a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
135a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # -------- Examine contents of first plist for systems specs. --------
136a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    plist = PowerMetricsPowerMonitor._ParsePlistString(raw_plists[0])
137a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if not plist:
138a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      logging.warning("powermetrics produced invalid output, output length: "
139a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          "%d" % len(powermetrics_output))
140a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return {}
141a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
142a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if 'GPU' in plist:
143a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      metrics.extend([
144a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          ConstructMetric(
145c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch              ['component_utilization', 'gpu', 'average_frequency_hz'],
146a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              ['GPU', 0, 'freq_hz']),
147a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          ConstructMetric(
148a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              ['component_utilization', 'gpu', 'idle_percent'],
149a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              ['GPU', 0, 'c_state_ratio'])])
150a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
151a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
152a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # There's no way of knowing ahead of time how many cpus and packages the
153a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # current system has. Iterate over cores and cpus - construct metrics for
154a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # each one.
155a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if 'processor' in plist:
156a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      core_dict = plist['processor']['packages'][0]['cores']
157a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      num_cores = len(core_dict)
158a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      cpu_num = 0
159a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      for core_idx in xrange(num_cores):
160a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        num_cpus = len(core_dict[core_idx]['cpus'])
161a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        base_src_path = ['processor', 'packages', 0, 'cores', core_idx]
162a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        for cpu_idx in xrange(num_cpus):
163a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          base_out_path = ['component_utilization', 'cpu%d' % cpu_num]
164a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          # C State ratio is per-package, component CPUs of that package may
165a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          # have different frequencies.
166a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          metrics.append(ConstructMetric(
167c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch              base_out_path + ['average_frequency_hz'],
168a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              base_src_path + ['cpus', cpu_idx, 'freq_hz']))
169a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          metrics.append(ConstructMetric(
170a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              base_out_path + ['idle_percent'],
171a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              base_src_path + ['c_state_ratio']))
172a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          cpu_num += 1
173a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
174a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # -------- Parse Data Out of Plists --------
175f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    plist = PowerMetricsPowerMonitor._ParsePlistString(raw_plists[0])
176f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    if not plist:
177f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      logging.error("Error parsing plist.")
178f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      return {}
179a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
180f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    # Duration of this sample.
181f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    sample_duration_ms = int(plist['elapsed_ns']) / 10**6
182f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    sample_durations.append(sample_duration_ms)
183a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
184f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    if 'processor' not in plist:
185f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      logging.error("'processor' field not found in plist.")
186f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      return {}
187f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    processor = plist['processor']
188a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
189f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    total_energy_consumption_mwh = (
190f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        (float(processor.get('package_joules', 0)) / 3600.) * 10**3 )
191a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
192f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    for m in metrics:
193f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      m.samples.append(DataWithMetricKeyPath(m, plist))
194a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
195a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # -------- Collect and Process Data --------
196a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    out_dict = {}
197a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    out_dict['identifier'] = 'powermetrics'
198f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    out_dict['energy_consumption_mwh'] = total_energy_consumption_mwh
199a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
200a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    def StoreMetricAverage(metric, sample_durations, out):
201a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      """Calculate average value of samples in a metric and store in output
202a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)         path as specified by metric.
203a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
204a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      Args:
205a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          metric: A RunningAverage object containing samples to average.
206a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          sample_durations: A list which parallels the samples list containing
207a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              the time slice for each sample.
208a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          out: The output dicat, average is stored in the location specified by
209a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              metric.out_path.
210a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      """
211a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      if len(metric.samples) == 0:
212a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        return
213a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
214a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      assert len(metric.samples) == len(sample_durations)
215a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      avg = 0
216a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      for i in xrange(len(metric.samples)):
217a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        avg += metric.samples[i] * sample_durations[i]
218a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      avg /= sum(sample_durations)
219a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
220a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      # Store data in output, creating empty dictionaries as we go.
221a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      for k in metric.out_path[:-1]:
222a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        if not out.has_key(k):
223a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          out[k] = {}
224a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        out = out[k]
225a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      out[metric.out_path[-1]] = avg
226a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
227a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for m in metrics:
228a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      StoreMetricAverage(m, sample_durations, out_dict)
229a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return out_dict
230a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
231c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  def StopMonitoringPower(self):
232a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    assert self._powermetrics_process, (
233c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch        "StartMonitoringPower() not called.")
234a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Tell powermetrics to take an immediate sample.
235a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    try:
236f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      self._powermetrics_process.terminate()
237effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      (power_stdout, power_stderr) = self._powermetrics_process.communicate()
238effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      returncode = self._powermetrics_process.returncode
239a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      assert returncode in [0, -15], (
240effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          """powermetrics error
241effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          return code=%d
242effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          stdout=(%s)
243effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          stderr=(%s)""" % (returncode, power_stdout, power_stderr))
244a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
245a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      with open(self._output_filename, 'rb') as output_file:
246a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        powermetrics_output = output_file.read()
247a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return PowerMetricsPowerMonitor.ParsePowerMetricsOutput(
248a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          powermetrics_output)
249a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
250a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    finally:
251f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      shutil.rmtree(self._output_directory)
252f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      self._output_directory = None
253a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self._output_filename = None
254a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self._powermetrics_process = None
255