Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(169)

Unified Diff: tools/telemetry/telemetry/core/platform/mac_platform_backend.py

Issue 145353012: [Telemetry] Improvements to OS X powermetrics integration (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tools/telemetry/telemetry/core/platform/platform_backend_unittest.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/telemetry/telemetry/core/platform/mac_platform_backend.py
diff --git a/tools/telemetry/telemetry/core/platform/mac_platform_backend.py b/tools/telemetry/telemetry/core/platform/mac_platform_backend.py
index 21122a72843d5974eccc5507bbc09e92ac2f265d..f62c54797959fc7b80823c50f61f665925e45b9c 100644
--- a/tools/telemetry/telemetry/core/platform/mac_platform_backend.py
+++ b/tools/telemetry/telemetry/core/platform/mac_platform_backend.py
@@ -4,17 +4,21 @@
import collections
import ctypes
+import logging
import os
import plistlib
+import shutil
import signal
import tempfile
import time
+import xml.parsers.expat
try:
import resource # pylint: disable=F0401
except ImportError:
resource = None # Not available on all platforms
from ctypes import util
+from telemetry.core import util
from telemetry.core.platform import platform_backend
from telemetry.core.platform import posix_platform_backend
@@ -31,8 +35,9 @@ class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
class PowerMetricsUtility(object):
def __init__(self, backend):
self._powermetrics_process = None
- self._powermetrics_output_file = None
self._backend = backend
+ self._output_filename = None
+ self._ouput_directory = None
@property
def binary_path(self):
@@ -42,13 +47,28 @@ class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
assert not self._powermetrics_process, (
"Must call StopMonitoringPowerAsync().")
SAMPLE_INTERVAL_MS = 1000 / 20 # 20 Hz, arbitrary.
- self._powermetrics_output_file = tempfile.NamedTemporaryFile()
+ # Empirically powermetrics creates an empty output file immediately upon
+ # starting. We detect file creation as a signal that measurement has
+ # started. In order to avoid various race conditions in tempfile creation
+ # we create a temp directory and have powermetrics create it's output
+ # there rather than say, creating a tempfile, deleting it and reusing its
+ # name.
+ self._ouput_directory = tempfile.mkdtemp()
+ self._output_filename = os.path.join(self._ouput_directory,
+ 'powermetrics.output')
args = ['-f', 'plist',
'-i', '%d' % SAMPLE_INTERVAL_MS,
- '-u', self._powermetrics_output_file.name]
+ '-u', self._output_filename]
self._powermetrics_process = self._backend.LaunchApplication(
self.binary_path, args, elevate_privilege=True)
+ # Block until output file is written to ensure this function call is
+ # synchronous in respect to powermetrics starting.
+ def _OutputFileExists():
+ return os.path.isfile(self._output_filename)
+ timeout_sec = 2 * (SAMPLE_INTERVAL_MS / 1000.)
+ util.WaitFor(_OutputFileExists, timeout_sec)
+
def StopMonitoringPowerAsync(self):
assert self._powermetrics_process, (
"StartMonitoringPowerAsync() not called.")
@@ -59,10 +79,12 @@ class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
returncode = self._powermetrics_process.wait()
assert returncode in [0, -15], (
"powermetrics return code: %d" % returncode)
- return open(self._powermetrics_output_file.name, 'r').read()
+
+ return open(self._output_filename, 'rb').read()
finally:
- self._powermetrics_output_file.close()
- self._powermetrics_output_file = None
+ shutil.rmtree(self._ouput_directory)
+ self._ouput_directory = None
+ self._output_filename = None
self._powermetrics_process = None
def __init__(self):
@@ -187,6 +209,21 @@ class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
def StartMonitoringPowerAsync(self):
self.powermetrics_tool_.StartMonitoringPowerAsync()
+ def _ParsePlistString(self, plist_string):
+ """Wrapper to parse a plist from a string and catch any errors.
+
+ Sometimes powermetrics will exit in the middle of writing it's output,
+ empirically it seems that it always writes at least one sample in it's
+ entirety so we can safely ignore any errors in it's output.
+
+ Returns:
+ Parser output on succesful parse, None on parse error.
+ """
+ try:
+ return plistlib.readPlistFromString(plist_string)
+ except xml.parsers.expat.ExpatError:
+ return None
+
def _ParsePowerMetricsOutput(self, powermetrics_output):
"""Parse output of powermetrics command line utility.
@@ -238,7 +275,11 @@ class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
raw_plists = [x for x in raw_plists if len(x) > 0]
# -------- Examine contents of first plist for systems specs. --------
- plist = plistlib.readPlistFromString(raw_plists[0])
+ plist = self._ParsePlistString(raw_plists[0])
+ if not plist:
+ logging.warning("powermetrics produced invalid output, output length: "
+ "%d" % len(powermetrics_output))
+ return {}
if 'GPU' in plist:
metrics.extend([
@@ -274,7 +315,9 @@ class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
# -------- Parse Data Out of Plists --------
for raw_plist in raw_plists:
- plist = plistlib.readPlistFromString(raw_plist)
+ plist = self._ParsePlistString(raw_plist)
+ if not plist:
+ continue
# Duration of this sample.
sample_duration_ms = int(plist['elapsed_ns']) / 10**6
« no previous file with comments | « no previous file | tools/telemetry/telemetry/core/platform/platform_backend_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698