mach_commands.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. from __future__ import print_function
  5. from distutils.version import StrictVersion
  6. from mach.decorators import (
  7. Command,
  8. CommandArgument,
  9. CommandProvider,
  10. )
  11. from mozbuild.base import (
  12. MachCommandBase,
  13. MachCommandConditions as conditions,
  14. )
  15. def is_osx_10_10_or_greater(cls):
  16. import platform
  17. release = platform.mac_ver()[0]
  18. return release and StrictVersion(release) >= StrictVersion('10.10')
  19. @CommandProvider
  20. class MachCommands(MachCommandBase):
  21. '''
  22. Get system power consumption and related measurements.
  23. '''
  24. def __init__(self, context):
  25. MachCommandBase.__init__(self, context)
  26. @Command('power', category='misc',
  27. conditions=[is_osx_10_10_or_greater],
  28. description='Get system power consumption and related measurements for '
  29. 'all running browsers. Available only on Mac OS X 10.10 and above. '
  30. 'Requires root access.')
  31. @CommandArgument('-i', '--interval', type=int, default=30000,
  32. help='The sample period, measured in milliseconds. Defaults to 30000.')
  33. def power(self, interval):
  34. import os
  35. import re
  36. import subprocess
  37. rapl = os.path.join(self.topobjdir, 'dist', 'bin', 'rapl')
  38. interval = str(interval)
  39. # Run a trivial command with |sudo| to gain temporary root privileges
  40. # before |rapl| and |powermetrics| are called. This ensures that |rapl|
  41. # doesn't start measuring while |powermetrics| is waiting for the root
  42. # password to be entered.
  43. try:
  44. subprocess.check_call(['sudo', 'true'])
  45. except:
  46. print('\nsudo failed; aborting')
  47. return 1
  48. # This runs rapl in the background because nothing in this script
  49. # depends on the output. This is good because we want |rapl| and
  50. # |powermetrics| to run at the same time.
  51. subprocess.Popen([rapl, '-n', '1', '-i', interval])
  52. lines = subprocess.check_output(['sudo', 'powermetrics',
  53. '--samplers', 'tasks',
  54. '--show-process-coalition',
  55. '--show-process-gpu',
  56. '-n', '1',
  57. '-i', interval])
  58. # When run with --show-process-coalition, |powermetrics| groups outputs
  59. # into process coalitions, each of which has a leader.
  60. #
  61. # For example, when Firefox runs from the dock, its coalition looks
  62. # like this:
  63. #
  64. # org.mozilla.firefox
  65. # firefox
  66. # plugin-container
  67. #
  68. # When Safari runs from the dock:
  69. #
  70. # com.apple.Safari
  71. # Safari
  72. # com.apple.WebKit.Networking
  73. # com.apple.WebKit.WebContent
  74. # com.apple.WebKit.WebContent
  75. #
  76. # When Chrome runs from the dock:
  77. #
  78. # com.google.Chrome
  79. # Google Chrome
  80. # Google Chrome Helper
  81. # Google Chrome Helper
  82. #
  83. # In these cases, we want to print the whole coalition.
  84. #
  85. # Also, when you run any of them from the command line, things are the
  86. # same except that the leader is com.apple.Terminal and there may be
  87. # non-browser processes in the coalition, e.g.:
  88. #
  89. # com.apple.Terminal
  90. # firefox
  91. # plugin-container
  92. # <and possibly other, non-browser processes>
  93. #
  94. # Also, the WindowServer and kernel coalitions and processes are often
  95. # relevant.
  96. #
  97. # We want to print all these but omit uninteresting coalitions. We
  98. # could do this by properly parsing powermetrics output, but it's
  99. # simpler and more robust to just grep for a handful of identifying
  100. # strings.
  101. print() # blank line between |rapl| output and |powermetrics| output
  102. for line in lines.splitlines():
  103. # Search for the following things.
  104. #
  105. # - '^Name' is for the columns headings line.
  106. #
  107. # - 'firefox' and 'plugin-container' are for Firefox
  108. #
  109. # - 'Safari\b' and 'WebKit' are for Safari. The '\b' excludes
  110. # SafariCloudHistoryPush, which is a process that always
  111. # runs, even when Safari isn't open.
  112. #
  113. # - 'Chrome' is for Chrome.
  114. #
  115. # - 'Terminal' is for the terminal. If no browser is running from
  116. # within the terminal, it will show up unnecessarily. This is a
  117. # minor disadvantage of this very simple parsing strategy.
  118. #
  119. # - 'WindowServer' is for the WindowServer.
  120. #
  121. # - 'kernel' is for the kernel.
  122. #
  123. if re.search(r'(^Name|firefox|plugin-container|Safari\b|WebKit|Chrome|Terminal|WindowServer|kernel)', line):
  124. print(line)
  125. return 0