[gimp/gimp-2-10] tools: in performance-log-viewer.py, add thread filter to profile



commit b5d628bec2614d62fc7ef90e43e3855820ffc254
Author: Ell <ell_se yahoo com>
Date:   Sun Sep 23 00:44:50 2018 -0400

    tools: in performance-log-viewer.py, add thread filter to profile
    
    In the performance-log viewer, add an option to filter which
    threads, and which states of each thread, are included in the
    profile.  By default, all threads in the RUNNING state are
    included.
    
    (cherry picked from commit 3f630378b0697b37d305165c0cc31065a65133c2)

 tools/performance-log-viewer.py | 176 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 175 insertions(+), 1 deletion(-)
---
diff --git a/tools/performance-log-viewer.py b/tools/performance-log-viewer.py
index 791fb6bc28..3cfbaa452e 100755
--- a/tools/performance-log-viewer.py
+++ b/tools/performance-log-viewer.py
@@ -1859,6 +1859,144 @@ class BacktraceViewer (Gtk.Box):
         return False
 
 class ProfileViewer (Gtk.ScrolledWindow):
+    class ThreadFilter (Gtk.TreeView):
+        class Store (Gtk.ListStore):
+            VISIBLE = 0
+            ID      = 1
+            NAME    = 2
+            STATE   = {list (ThreadState)[i]: 3 + i
+                       for i in range (len (ThreadState))}
+
+            def __init__ (self):
+                Gtk.ListStore.__init__ (self,
+                                        bool, int, str,
+                                        *(len (self.STATE) * (bool,)))
+
+                threads = list ({thread.id
+                                 for sample in samples
+                                 for thread in sample.backtrace or ()})
+                threads.sort ()
+
+                states = [state == ThreadState.RUNNING for state in self.STATE]
+
+                for id in threads:
+                    self.append ((False, id, None, *states))
+
+            def get_filter (self):
+                return {row[self.ID]: {state
+                                       for state, column in self.STATE.items ()
+                                       if row[column]}
+                        for row in self}
+
+        def __init__ (self, *args, **kwargs):
+            Gtk.TreeView.__init__ (self, *args, **kwargs)
+
+            self.needs_update = True
+
+            store = self.Store ()
+            self.store = store
+
+            filter = Gtk.TreeModelFilter (child_model = store)
+            filter.set_visible_column (store.VISIBLE)
+            self.set_model (filter)
+
+            col = Gtk.TreeViewColumn (title = "ID")
+            self.append_column (col)
+
+            cell = Gtk.CellRendererText (xalign = 1)
+            col.pack_start (cell, False)
+            col.add_attribute (cell, "text", store.ID)
+
+            col = Gtk.TreeViewColumn (title = "Name")
+            self.append_column (col)
+
+            cell = Gtk.CellRendererText ()
+            col.pack_start (cell, False)
+            col.add_attribute (cell, "text", store.NAME)
+
+            for state in store.STATE:
+                col = Gtk.TreeViewColumn (title = str (state))
+                col.column = store.STATE[state]
+                self.append_column (col)
+                col.set_alignment (0.5)
+                col.set_clickable (True)
+
+                def col_clicked (col):
+                    active = not all (row[col.column] for row in filter)
+
+                    for row in filter:
+                        row[col.column] = active
+
+                col.connect ("clicked", col_clicked)
+
+                cell = Gtk.CellRendererToggle ()
+                cell.column = store.STATE[state]
+                col.pack_start (cell, False)
+                col.add_attribute (cell, "active", store.STATE[state])
+
+                def cell_toggled (cell, path):
+                    store[path][cell.column] = not cell.get_property ("active")
+
+                cell.connect ("toggled", cell_toggled)
+
+            selection.connect ("change-complete",
+                               self.selection_change_complete)
+
+        def update (self):
+            if not self.needs_update:
+                return
+
+            self.needs_update = False
+
+            sel = selection.get_effective_selection ()
+
+            threads = {thread.id: thread.name
+                       for i in sel
+                       for thread in samples[i].backtrace or ()}
+
+            for row in self.store:
+                id = row[self.store.ID]
+
+                if id in threads:
+                    row[self.store.VISIBLE] = True
+                    row[self.store.NAME]    = threads[id]
+                else:
+                    row[self.store.VISIBLE] = False
+                    row[self.store.NAME]    = None
+
+        def do_map (self):
+            self.update ()
+
+            Gtk.TreeView.do_map (self)
+
+        def selection_change_complete (self, selection):
+            self.needs_update = True
+
+            if self.get_mapped ():
+                self.update ()
+
+    class ThreadPopover (Gtk.Popover):
+        def __init__ (self, *args, **kwargs):
+            Gtk.Popover.__init__ (self, *args, border_width = 4, **kwargs)
+
+            frame = Gtk.Frame (shadow_type = Gtk.ShadowType.IN)
+            self.add (frame)
+            frame.show ()
+
+            scrolled = Gtk.ScrolledWindow (
+                hscrollbar_policy        = Gtk.PolicyType.NEVER,
+                vscrollbar_policy        = Gtk.PolicyType.AUTOMATIC,
+                propagate_natural_height = True,
+                max_content_height       = 400
+            )
+            frame.add (scrolled)
+            scrolled.show ()
+
+            thread_filter = ProfileViewer.ThreadFilter ()
+            self.thread_filter = thread_filter
+            scrolled.add (thread_filter)
+            thread_filter.show ()
+
     class Profile (Gtk.Box):
         ProfileFrame = namedtuple ("ProfileFrame", ("sample", "stack", "i"))
 
@@ -1914,6 +2052,33 @@ class ProfileViewer (Gtk.ScrolledWindow):
             header.show ()
 
             if not id:
+                popover = ProfileViewer.ThreadPopover ()
+
+                thread_filter_store = popover.thread_filter.store
+
+                self.thread_filter_store = thread_filter_store
+                self.thread_filter       = thread_filter_store.get_filter ()
+
+                button = Gtk.MenuButton (popover = popover)
+                header.pack_end (button)
+                button.show ()
+
+                button.connect ("toggled", self.thread_filter_button_toggled)
+
+                hbox = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL,
+                                spacing     = 4)
+                button.add (hbox)
+                hbox.show ()
+
+                label = Gtk.Label ("Threads")
+                hbox.pack_start (label, False, False, 0)
+                label.show ()
+
+                image = Gtk.Image.new_from_icon_name ("pan-down-symbolic",
+                                                      Gtk.IconSize.BUTTON)
+                hbox.pack_start (image, False, False, 0)
+                image.show ()
+
                 button = Gtk.Button (tooltip_text = "Call-graph direction")
                 header.pack_end (button)
                 button.show ()
@@ -2020,7 +2185,7 @@ class ProfileViewer (Gtk.ScrolledWindow):
 
             for i in selection.get_effective_selection ():
                 for thread in samples[i].backtrace or []:
-                    if thread.state == ThreadState.RUNNING:
+                    if thread.state in self.thread_filter[thread.id]:
                         thread_frames = thread.frames
 
                         if self.direction == self.Direction.CALLERS:
@@ -2162,6 +2327,15 @@ class ProfileViewer (Gtk.ScrolledWindow):
 
                 self.emit ("subprofile-removed", subprofile)
 
+        def thread_filter_button_toggled (self, button):
+            if not button.get_active ():
+                thread_filter = self.thread_filter_store.get_filter ()
+
+                if thread_filter != self.thread_filter:
+                    self.thread_filter = thread_filter
+
+                    self.update ()
+
         def direction_button_clicked (self, button):
             if self.direction == self.Direction.CALLEES:
                 self.direction = self.Direction.CALLERS


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]