diff options
Diffstat (limited to 'Tools/Scripts/webkitpy/common/system/profiler.py')
-rw-r--r-- | Tools/Scripts/webkitpy/common/system/profiler.py | 210 |
1 files changed, 0 insertions, 210 deletions
diff --git a/Tools/Scripts/webkitpy/common/system/profiler.py b/Tools/Scripts/webkitpy/common/system/profiler.py deleted file mode 100644 index 0208cf898..000000000 --- a/Tools/Scripts/webkitpy/common/system/profiler.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright (C) 2012 Google Inc. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the Google name nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import logging -import re -import itertools - -_log = logging.getLogger(__name__) - - -class ProfilerFactory(object): - @classmethod - def create_profiler(cls, host, executable_path, output_dir, profiler_name=None, identifier=None): - profilers = cls.profilers_for_platform(host.platform) - if not profilers: - return None - profiler_name = profiler_name or cls.default_profiler_name(host.platform) - profiler_class = next(itertools.ifilter(lambda profiler: profiler.name == profiler_name, profilers), None) - if not profiler_class: - return None - return profilers[0](host, executable_path, output_dir, identifier) - - @classmethod - def default_profiler_name(cls, platform): - profilers = cls.profilers_for_platform(platform) - return profilers[0].name if profilers else None - - @classmethod - def profilers_for_platform(cls, platform): - # GooglePProf requires TCMalloc/google-perftools, but is available everywhere. - profilers_by_os_name = { - 'mac': [IProfiler, Sample, GooglePProf], - 'linux': [Perf, GooglePProf], - # Note: freebsd, win32 have no profilers defined yet, thus --profile will be ignored - # by default, but a profiler can be selected with --profiler=PROFILER explicitly. - } - return profilers_by_os_name.get(platform.os_name, []) - - -class Profiler(object): - # Used by ProfilerFactory to lookup a profiler from the --profiler=NAME option. - name = None - - def __init__(self, host, executable_path, output_dir, identifier=None): - self._host = host - self._executable_path = executable_path - self._output_dir = output_dir - self._identifier = "test" - self._host.filesystem.maybe_make_directory(self._output_dir) - - def adjusted_environment(self, env): - return env - - def attach_to_pid(self, pid): - pass - - def profile_after_exit(self): - pass - - -class SingleFileOutputProfiler(Profiler): - def __init__(self, host, executable_path, output_dir, output_suffix, identifier=None): - super(SingleFileOutputProfiler, self).__init__(host, executable_path, output_dir, identifier) - # FIXME: Currently all reports are kept as test.*, until we fix that, search up to 1000 names before giving up. - self._output_path = self._host.workspace.find_unused_filename(self._output_dir, self._identifier, output_suffix, search_limit=1000) - assert(self._output_path) - - -class GooglePProf(SingleFileOutputProfiler): - name = 'pprof' - - def __init__(self, host, executable_path, output_dir, identifier=None): - super(GooglePProf, self).__init__(host, executable_path, output_dir, "pprof", identifier) - - def adjusted_environment(self, env): - env['CPUPROFILE'] = self._output_path - return env - - def _first_ten_lines_of_profile(self, pprof_output): - match = re.search("^Total:[^\n]*\n((?:[^\n]*\n){0,10})", pprof_output, re.MULTILINE) - return match.group(1) if match else None - - def _pprof_path(self): - # FIXME: We should have code to find the right google-pprof executable, some Googlers have - # google-pprof installed as "pprof" on their machines for them. - return '/usr/bin/google-pprof' - - def profile_after_exit(self): - # google-pprof doesn't check its arguments, so we have to. - if not (self._host.filesystem.exists(self._output_path)): - print "Failed to gather profile, %s does not exist." % self._output_path - return - - pprof_args = [self._pprof_path(), '--text', self._executable_path, self._output_path] - profile_text = self._host.executive.run_command(pprof_args) - print "First 10 lines of pprof --text:" - print self._first_ten_lines_of_profile(profile_text) - print "http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html documents output." - print - print "To interact with the the full profile, including produce graphs:" - print ' '.join([self._pprof_path(), self._executable_path, self._output_path]) - - -class Perf(SingleFileOutputProfiler): - name = 'perf' - - def __init__(self, host, executable_path, output_dir, identifier=None): - super(Perf, self).__init__(host, executable_path, output_dir, "data", identifier) - self._perf_process = None - self._pid_being_profiled = None - - def _perf_path(self): - # FIXME: We may need to support finding the perf binary in other locations. - return 'perf' - - def attach_to_pid(self, pid): - assert(not self._perf_process and not self._pid_being_profiled) - self._pid_being_profiled = pid - cmd = [self._perf_path(), "record", "--call-graph", "--pid", pid, "--output", self._output_path] - self._perf_process = self._host.executive.popen(cmd) - - def _first_ten_lines_of_profile(self, perf_output): - match = re.search("^#[^\n]*\n((?: [^\n]*\n){1,10})", perf_output, re.MULTILINE) - return match.group(1) if match else None - - def profile_after_exit(self): - # Perf doesn't automatically watch the attached pid for death notifications, - # so we have to do it for it, and then tell it its time to stop sampling. :( - self._host.executive.wait_limited(self._pid_being_profiled, limit_in_seconds=10) - perf_exitcode = self._perf_process.poll() - if perf_exitcode is None: # This should always be the case, unless perf error'd out early. - self._host.executive.interrupt(self._perf_process.pid) - - perf_exitcode = self._perf_process.wait() - if perf_exitcode not in (0, -2): # The exit code should always be -2, as we're always interrupting perf. - print "'perf record' failed (exit code: %i), can't process results:" % perf_exitcode - return - - perf_args = [self._perf_path(), 'report', '--call-graph', 'none', '--input', self._output_path] - print "First 10 lines of 'perf report --call-graph=none':" - - print " ".join(perf_args) - perf_output = self._host.executive.run_command(perf_args) - print self._first_ten_lines_of_profile(perf_output) - - print "To view the full profile, run:" - print ' '.join([self._perf_path(), 'report', '-i', self._output_path]) - print # An extra line between tests looks nicer. - - -class Sample(SingleFileOutputProfiler): - name = 'sample' - - def __init__(self, host, executable_path, output_dir, identifier=None): - super(Sample, self).__init__(host, executable_path, output_dir, "txt", identifier) - self._profiler_process = None - - def attach_to_pid(self, pid): - cmd = ["sample", pid, "-mayDie", "-file", self._output_path] - self._profiler_process = self._host.executive.popen(cmd) - - def profile_after_exit(self): - self._profiler_process.wait() - - -class IProfiler(SingleFileOutputProfiler): - name = 'iprofiler' - - def __init__(self, host, executable_path, output_dir, identifier=None): - super(IProfiler, self).__init__(host, executable_path, output_dir, "dtps", identifier) - self._profiler_process = None - - def attach_to_pid(self, pid): - # FIXME: iprofiler requires us to pass the directory separately - # from the basename of the file, with no control over the extension. - fs = self._host.filesystem - cmd = ["iprofiler", "-timeprofiler", "-a", pid, - "-d", fs.dirname(self._output_path), "-o", fs.splitext(fs.basename(self._output_path))[0]] - # FIXME: Consider capturing instead of letting instruments spam to stderr directly. - self._profiler_process = self._host.executive.popen(cmd) - - def profile_after_exit(self): - # It seems like a nicer user experiance to wait on the profiler to exit to prevent - # it from spewing to stderr at odd times. - self._profiler_process.wait() |