Title: [137706] trunk/Tools
Revision
137706
Author
[email protected]
Date
2012-12-13 20:30:20 -0800 (Thu, 13 Dec 2012)

Log Message

Use 'perf' to profile on linux instead of google-pprof by default
https://bugs.webkit.org/show_bug.cgi?id=104971

Reviewed by Dirk Pranke.

This makes Chromium Linux match Chromium Android and use the perf
tool by default.  Once https://bugs.webkit.org/show_bug.cgi?id=104891
lands it will be possible to use pprof again on Linux.

This is slightly more advanced than the AndroidPerf profiler as
instead of using a timeout on "perf record" I instead watch
for the termination of the target process and then control-C
the 'perf record' process.  This required me to add two new
methods to Executive, one to have a limited-time wait() and
the second to be able to send a control-C.  I chose to add
these to Executive to make them easier to mock/fix-for-win32
at a later time if needed.

* Scripts/webkitpy/common/system/executive.py:
(Executive.wait_limited):
(Executive.interrupt):
* Scripts/webkitpy/common/system/profiler.py:
(ProfilerFactory.create_profiler):
(Perf):
(Perf.__init__):
(Perf._perf_path):
(Perf.attach_to_pid):
(Perf._first_ten_lines_of_profile):
(Perf.profile_after_exit):

Modified Paths

Diff

Modified: trunk/Tools/ChangeLog (137705 => 137706)


--- trunk/Tools/ChangeLog	2012-12-14 04:19:09 UTC (rev 137705)
+++ trunk/Tools/ChangeLog	2012-12-14 04:30:20 UTC (rev 137706)
@@ -1,3 +1,35 @@
+2012-12-13  Eric Seidel  <[email protected]>
+
+        Use 'perf' to profile on linux instead of google-pprof by default
+        https://bugs.webkit.org/show_bug.cgi?id=104971
+
+        Reviewed by Dirk Pranke.
+
+        This makes Chromium Linux match Chromium Android and use the perf
+        tool by default.  Once https://bugs.webkit.org/show_bug.cgi?id=104891
+        lands it will be possible to use pprof again on Linux.
+
+        This is slightly more advanced than the AndroidPerf profiler as
+        instead of using a timeout on "perf record" I instead watch
+        for the termination of the target process and then control-C
+        the 'perf record' process.  This required me to add two new
+        methods to Executive, one to have a limited-time wait() and
+        the second to be able to send a control-C.  I chose to add
+        these to Executive to make them easier to mock/fix-for-win32
+        at a later time if needed.
+
+        * Scripts/webkitpy/common/system/executive.py:
+        (Executive.wait_limited):
+        (Executive.interrupt):
+        * Scripts/webkitpy/common/system/profiler.py:
+        (ProfilerFactory.create_profiler):
+        (Perf):
+        (Perf.__init__):
+        (Perf._perf_path):
+        (Perf.attach_to_pid):
+        (Perf._first_ten_lines_of_profile):
+        (Perf.profile_after_exit):
+
 2012-12-13  Julie Parent  <[email protected]>
 
         Dashboard cleanup: remove usage of global g_builders.

Modified: trunk/Tools/Scripts/webkitpy/common/system/executive.py (137705 => 137706)


--- trunk/Tools/Scripts/webkitpy/common/system/executive.py	2012-12-14 04:19:09 UTC (rev 137705)
+++ trunk/Tools/Scripts/webkitpy/common/system/executive.py	2012-12-14 04:30:20 UTC (rev 137706)
@@ -304,6 +304,13 @@
         while self.check_running_pid(pid):
             time.sleep(0.25)
 
+    def wait_limited(self, pid, limit_in_seconds=None, check_frequency_in_seconds=None):
+        seconds_left = limit_in_seconds or 10
+        sleep_length = check_frequency_in_seconds or 1
+        while seconds_left > 0 and self.check_running_pid(pid):
+            seconds_left -= sleep_length
+            time.sleep(sleep_length)
+
     def _windows_image_name(self, process_name):
         name, extension = os.path.splitext(process_name)
         if not extension:
@@ -312,6 +319,17 @@
             process_name = "%s.exe" % name
         return process_name
 
+    def interrupt(self, pid):
+        interrupt_signal = signal.SIGINT
+        # FIXME: The python docs seem to imply that platform == 'win32' may need to use signal.CTRL_C_EVENT
+        # http://docs.python.org/2/library/signal.html
+        try:
+            os.kill(pid, interrupt_signal)
+        except OSError:
+            # Silently ignore when the pid doesn't exist.
+            # It's impossible for callers to avoid race conditions with process shutdown.
+            pass
+
     def kill_all(self, process_name):
         """Attempts to kill processes matching process_name.
         Will fail silently if no process are found."""

Modified: trunk/Tools/Scripts/webkitpy/common/system/profiler.py (137705 => 137706)


--- trunk/Tools/Scripts/webkitpy/common/system/profiler.py	2012-12-14 04:19:09 UTC (rev 137705)
+++ trunk/Tools/Scripts/webkitpy/common/system/profiler.py	2012-12-14 04:30:20 UTC (rev 137706)
@@ -37,7 +37,7 @@
     def create_profiler(cls, host, executable_path, output_dir, identifier=None):
         if host.platform.is_mac():
             return IProfiler(host, executable_path, output_dir, identifier)
-        return GooglePProf(host, executable_path, output_dir, identifier)
+        return Perf(host, executable_path, output_dir, identifier)
 
 
 class Profiler(object):
@@ -97,6 +97,49 @@
         print ' '.join([self._pprof_path(), self._executable_path, self._output_path])
 
 
+class Perf(SingleFileOutputProfiler):
+    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", "-g", "-p", pid, "-o", self._output_path]
+        cmd = map(unicode, cmd)
+        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', '-g', 'none', '-i', self._output_path]
+        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])
+
+
 class IProfiler(SingleFileOutputProfiler):
     def __init__(self, host, executable_path, output_dir, identifier=None):
         super(IProfiler, self).__init__(host, executable_path, output_dir, "dtps", identifier)
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to