DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (55f382781e14)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
# 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


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 Exception:
            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):  # NOQA: E501
                print(line)

        return 0