Bug 844204 - Report high Finder CPU usage when building; r=ted
authorGregory Szorc <gps@mozilla.com>
Thu, 14 Mar 2013 12:42:06 -0700
changeset 124824 c994692d1ea862ace947e81e36e173cfc6754713
parent 124823 8920d009778e2e639ffe5adb0262701e8b2d9215
child 124825 7dc016fc39bfa566c810ee4e242ae50cd46e18d0
push id24436
push userryanvm@gmail.com
push dateFri, 15 Mar 2013 11:52:55 +0000
treeherdermozilla-central@8f5b1f9f5804 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersted
bugs844204
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 844204 - Report high Finder CPU usage when building; r=ted
python/mozbuild/mozbuild/mach_commands.py
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -2,32 +2,48 @@
 # 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, unicode_literals
 
 import logging
 import operator
 import os
+import sys
+import time
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
 from mozbuild.base import MachCommandBase
 
 
 BUILD_WHAT_HELP = '''
 What to build. Can be a top-level make target or a relative directory. If
 multiple options are provided, they will be built serially. BUILDING ONLY PARTS
 OF THE TREE CAN RESULT IN BAD TREE STATE. USE AT YOUR OWN RISK.
 '''.strip()
 
+FINDER_SLOW_MESSAGE = '''
+===================
+PERFORMANCE WARNING
+
+The OS X Finder application (file indexing used by Spotlight) used a lot of CPU
+during the build - an average of %f%% (100%% is 1 core). This made your build
+slower.
+
+Consider adding ".noindex" to the end of your object directory name to have
+Finder ignore it. Or, add an indexing exclusion through the Spotlight System
+Preferences.
+===================
+'''.strip()
+
 
 @CommandProvider
 class Build(MachCommandBase):
     """Interface to build the tree."""
 
     @Command('build', help='Build the tree.')
     @CommandArgument('what', default=None, nargs='*', help=BUILD_WHAT_HELP)
     def build(self, what=None):
@@ -56,16 +72,19 @@ class Build(MachCommandBase):
                     self.log(logging.INFO, 'compiler_warning', warning,
                         'Warning: {flag} in {filename}: {message}')
             except:
                 # This will get logged in the more robust implementation.
                 pass
 
             self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
 
+        finder_start_cpu = self._get_finder_cpu_usage()
+        time_start = time.time()
+
         if what:
             top_make = os.path.join(self.topobjdir, 'Makefile')
             if not os.path.exists(top_make):
                 print('Your tree has not been configured yet. Please run '
                     '|mach build| with no arguments.')
                 return 1
 
             for target in what:
@@ -90,20 +109,75 @@ class Build(MachCommandBase):
 
             self.log(logging.WARNING, 'warning_summary',
                 {'count': len(warnings_collector.database)},
                 '{count} compiler warnings present.')
 
         warnings_database.prune()
         warnings_database.save_to_file(warnings_path)
 
+        time_end = time.time()
+        self._handle_finder_cpu_usage(time_end - time_start, finder_start_cpu)
+
         print('Finished building. Built files are in %s' % self.topobjdir)
 
         return status
 
+    def _get_finder_cpu_usage(self):
+        """Obtain the CPU usage of the Finder app on OS X.
+
+        This is used to detect high CPU usage.
+        """
+        if not sys.platform.startswith('darwin'):
+            return None
+
+        try:
+            import psutil
+        except ImportError:
+            return None
+
+        for proc in psutil.process_iter():
+            if proc.name != 'Finder':
+                continue
+
+            # Try to isolate system finder as opposed to other "Finder"
+            # processes.
+            if not proc.exe.endswith('CoreServices/Finder.app/Contents/MacOS/Finder'):
+                continue
+
+            return proc.get_cpu_times()
+
+        return None
+
+    def _handle_finder_cpu_usage(self, elapsed, start):
+        if not start:
+            return
+
+        # We only measure if the measured range is sufficiently long.
+        if elapsed < 15:
+            return
+
+        end = self._get_finder_cpu_usage()
+        if not end:
+            return
+
+        start_total = start.user + start.system
+        end_total = end.user + end.system
+
+        cpu_seconds = end_total - start_total
+
+        # If Finder used more than 25% of 1 core during the build, report an
+        # error.
+        finder_percent = cpu_seconds / elapsed * 100
+        if finder_percent < 25:
+            return
+
+        print(FINDER_SLOW_MESSAGE % finder_percent)
+
+
     @Command('clobber', help='Clobber the tree (delete the object directory).')
     def clobber(self):
         try:
             self.remove_objdir()
             return 0
         except WindowsError as e:
             if e.winerror in (5, 32):
                 self.log(logging.ERROR, 'file_access_error', {'error': e},