summaryrefslogtreecommitdiff
path: root/tools/power/mach_commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/power/mach_commands.py')
-rw-r--r--tools/power/mach_commands.py142
1 files changed, 142 insertions, 0 deletions
diff --git a/tools/power/mach_commands.py b/tools/power/mach_commands.py
new file mode 100644
index 0000000000..281e7a868e
--- /dev/null
+++ b/tools/power/mach_commands.py
@@ -0,0 +1,142 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import print_function
+
+from distutils.version import StrictVersion
+
+from mach.decorators import (
+ Command,
+ CommandArgument,
+ CommandProvider,
+)
+from mozbuild.base import (
+ MachCommandBase,
+ MachCommandConditions as conditions,
+)
+
+
+def is_osx_10_10_or_greater(cls):
+ import platform
+ release = platform.mac_ver()[0]
+ return release and StrictVersion(release) >= StrictVersion('10.10')
+
+
+@CommandProvider
+class MachCommands(MachCommandBase):
+ '''
+ Get system power consumption and related measurements.
+ '''
+ def __init__(self, context):
+ MachCommandBase.__init__(self, context)
+
+ @Command('power', category='misc',
+ conditions=[is_osx_10_10_or_greater],
+ description='Get system power consumption and related measurements for '
+ 'all running browsers. Available only on Mac OS X 10.10 and above. '
+ 'Requires root access.')
+ @CommandArgument('-i', '--interval', type=int, default=30000,
+ help='The sample period, measured in milliseconds. Defaults to 30000.')
+ def power(self, interval):
+ import os
+ import re
+ import subprocess
+
+ rapl = os.path.join(self.topobjdir, 'dist', 'bin', 'rapl')
+
+ interval = str(interval)
+
+ # Run a trivial command with |sudo| to gain temporary root privileges
+ # before |rapl| and |powermetrics| are called. This ensures that |rapl|
+ # doesn't start measuring while |powermetrics| is waiting for the root
+ # password to be entered.
+ try:
+ subprocess.check_call(['sudo', 'true'])
+ except:
+ print('\nsudo failed; aborting')
+ return 1
+
+ # This runs rapl in the background because nothing in this script
+ # depends on the output. This is good because we want |rapl| and
+ # |powermetrics| to run at the same time.
+ subprocess.Popen([rapl, '-n', '1', '-i', interval])
+
+ lines = subprocess.check_output(['sudo', 'powermetrics',
+ '--samplers', 'tasks',
+ '--show-process-coalition',
+ '--show-process-gpu',
+ '-n', '1',
+ '-i', interval])
+
+ # When run with --show-process-coalition, |powermetrics| groups outputs
+ # into process coalitions, each of which has a leader.
+ #
+ # For example, when Firefox runs from the dock, its coalition looks
+ # like this:
+ #
+ # org.mozilla.firefox
+ # firefox
+ # plugin-container
+ #
+ # When Safari runs from the dock:
+ #
+ # com.apple.Safari
+ # Safari
+ # com.apple.WebKit.Networking
+ # com.apple.WebKit.WebContent
+ # com.apple.WebKit.WebContent
+ #
+ # When Chrome runs from the dock:
+ #
+ # com.google.Chrome
+ # Google Chrome
+ # Google Chrome Helper
+ # Google Chrome Helper
+ #
+ # In these cases, we want to print the whole coalition.
+ #
+ # Also, when you run any of them from the command line, things are the
+ # same except that the leader is com.apple.Terminal and there may be
+ # non-browser processes in the coalition, e.g.:
+ #
+ # com.apple.Terminal
+ # firefox
+ # plugin-container
+ # <and possibly other, non-browser processes>
+ #
+ # Also, the WindowServer and kernel coalitions and processes are often
+ # relevant.
+ #
+ # We want to print all these but omit uninteresting coalitions. We
+ # could do this by properly parsing powermetrics output, but it's
+ # simpler and more robust to just grep for a handful of identifying
+ # strings.
+
+ print() # blank line between |rapl| output and |powermetrics| output
+
+ for line in lines.splitlines():
+ # Search for the following things.
+ #
+ # - '^Name' is for the columns headings line.
+ #
+ # - 'firefox' and 'plugin-container' are for Firefox
+ #
+ # - 'Safari\b' and 'WebKit' are for Safari. The '\b' excludes
+ # SafariCloudHistoryPush, which is a process that always
+ # runs, even when Safari isn't open.
+ #
+ # - 'Chrome' is for Chrome.
+ #
+ # - 'Terminal' is for the terminal. If no browser is running from
+ # within the terminal, it will show up unnecessarily. This is a
+ # minor disadvantage of this very simple parsing strategy.
+ #
+ # - 'WindowServer' is for the WindowServer.
+ #
+ # - 'kernel' is for the kernel.
+ #
+ if re.search(r'(^Name|firefox|plugin-container|Safari\b|WebKit|Chrome|Terminal|WindowServer|kernel)', line):
+ print(line)
+
+ return 0