History

perf scripts python: exported-sql-viewer.py: Add ability to find symbols in the call-graph

Add a Find bar that appears at the bottom of the call-graph window. Committer testing: Using: python tools/perf/scripts/python/exported-sql-viewer.py pt_example branches calls Using the database built in the first "Committer Testing" section in this patch series I was able to: "Reports" "Context-Sensitive Call Graphs" Control+F or select "Edit" in the top menu then "Find" __poll<ENTER> and find the first place where the "__poll" function appears, then press the down arrow in the lower right corner and go to the next, etc.

Adrian Hunter committed 8 years ago

Commit ebd70c71 parent 1beb5c71 file · +305 −1Browse files

Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all

Unified · Split

tools/perf/scripts/python/exported-sql-viewer.py+305−1
@@ -49,6 +49,7 @@ · 48 unmodified lines
4949 import sys
5050 import weakref
5151 import threading
52+import string
5253 from PySide.QtCore import *
5354 from PySide.QtGui import *
5455 from PySide.QtSql import *
@@ -76,6 +77,27 @@ def QueryExec(query, stmt): · 21 unmodified lines
7677 if not ret:
7778 raise Exception("Query failed: " + query.lastError().text())
7879
80+# Background thread
81+
82+class Thread(QThread):
83+
84+ done = Signal(object)
85+
86+ def __init__(self, task, param=None, parent=None):
87+ super(Thread, self).__init__(parent)
88+ self.task = task
89+ self.param = param
90+
91+ def run(self):
92+ while True:
93+ if self.param is None:
94+ done, result = self.task()
95+ else:
96+ done, result = self.task(self.param)
97+ self.done.emit(result)
98+ if done:
99+ break
100+
79101 # Tree data model
80102
81103 class TreeModel(QAbstractItemModel):
@@ -157,6 +179,125 @@ def LookupCreateModel(model_name, create_fn): · 75 unmodified lines
157179 model_cache_lock.release()
158180 return model
159181
182+# Find bar
183+
184+class FindBar():
185+
186+ def __init__(self, parent, finder, is_reg_expr=False):
187+ self.finder = finder
188+ self.context = []
189+ self.last_value = None
190+ self.last_pattern = None
191+
192+ label = QLabel("Find:")
193+ label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
194+
195+ self.textbox = QComboBox()
196+ self.textbox.setEditable(True)
197+ self.textbox.currentIndexChanged.connect(self.ValueChanged)
198+
199+ self.progress = QProgressBar()
200+ self.progress.setRange(0, 0)
201+ self.progress.hide()
202+
203+ if is_reg_expr:
204+ self.pattern = QCheckBox("Regular Expression")
205+ else:
206+ self.pattern = QCheckBox("Pattern")
207+ self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
208+
209+ self.next_button = QToolButton()
210+ self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
211+ self.next_button.released.connect(lambda: self.NextPrev(1))
212+
213+ self.prev_button = QToolButton()
214+ self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
215+ self.prev_button.released.connect(lambda: self.NextPrev(-1))
216+
217+ self.close_button = QToolButton()
218+ self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
219+ self.close_button.released.connect(self.Deactivate)
220+
221+ self.hbox = QHBoxLayout()
222+ self.hbox.setContentsMargins(0, 0, 0, 0)
223+
224+ self.hbox.addWidget(label)
225+ self.hbox.addWidget(self.textbox)
226+ self.hbox.addWidget(self.progress)
227+ self.hbox.addWidget(self.pattern)
228+ self.hbox.addWidget(self.next_button)
229+ self.hbox.addWidget(self.prev_button)
230+ self.hbox.addWidget(self.close_button)
231+
232+ self.bar = QWidget()
233+ self.bar.setLayout(self.hbox);
234+ self.bar.hide()
235+
236+ def Widget(self):
237+ return self.bar
238+
239+ def Activate(self):
240+ self.bar.show()
241+ self.textbox.setFocus()
242+
243+ def Deactivate(self):
244+ self.bar.hide()
245+
246+ def Busy(self):
247+ self.textbox.setEnabled(False)
248+ self.pattern.hide()
249+ self.next_button.hide()
250+ self.prev_button.hide()
251+ self.progress.show()
252+
253+ def Idle(self):
254+ self.textbox.setEnabled(True)
255+ self.progress.hide()
256+ self.pattern.show()
257+ self.next_button.show()
258+ self.prev_button.show()
259+
260+ def Find(self, direction):
261+ value = self.textbox.currentText()
262+ pattern = self.pattern.isChecked()
263+ self.last_value = value
264+ self.last_pattern = pattern
265+ self.finder.Find(value, direction, pattern, self.context)
266+
267+ def ValueChanged(self):
268+ value = self.textbox.currentText()
269+ pattern = self.pattern.isChecked()
270+ index = self.textbox.currentIndex()
271+ data = self.textbox.itemData(index)
272+ # Store the pattern in the combo box to keep it with the text value
273+ if data == None:
274+ self.textbox.setItemData(index, pattern)
275+ else:
276+ self.pattern.setChecked(data)
277+ self.Find(0)
278+
279+ def NextPrev(self, direction):
280+ value = self.textbox.currentText()
281+ pattern = self.pattern.isChecked()
282+ if value != self.last_value:
283+ index = self.textbox.findText(value)
284+ # Allow for a button press before the value has been added to the combo box
285+ if index < 0:
286+ index = self.textbox.count()
287+ self.textbox.addItem(value, pattern)
288+ self.textbox.setCurrentIndex(index)
289+ return
290+ else:
291+ self.textbox.setItemData(index, pattern)
292+ elif pattern != self.last_pattern:
293+ # Keep the pattern recorded in the combo box up to date
294+ index = self.textbox.currentIndex()
295+ self.textbox.setItemData(index, pattern)
296+ self.Find(direction)
297+
298+ def NotFound(self):
299+ QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
300+
160301 # Context-sensitive call graph data model item base
161302
162303 class CallGraphLevelItemBase(object):
@@ -308,6 +449,123 @@ class CallGraphModel(TreeModel): · 145 unmodified lines
308449 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
309450 return alignment[column]
310451
452+ def FindSelect(self, value, pattern, query):
453+ if pattern:
454+ # postgresql and sqlite pattern patching differences:
455+ # postgresql LIKE is case sensitive but sqlite LIKE is not
456+ # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
457+ # postgresql supports ILIKE which is case insensitive
458+ # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
459+ if not self.glb.dbref.is_sqlite3:
460+ # Escape % and _
461+ s = value.replace("%", "\%")
462+ s = s.replace("_", "\_")
463+ # Translate * and ? into SQL LIKE pattern characters % and _
464+ trans = string.maketrans("*?", "%_")
465+ match = " LIKE '" + str(s).translate(trans) + "'"
466+ else:
467+ match = " GLOB '" + str(value) + "'"
468+ else:
469+ match = " = '" + str(value) + "'"
470+ QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
471+ " FROM calls"
472+ " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
473+ " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
474+ " WHERE symbols.name" + match +
475+ " GROUP BY comm_id, thread_id, call_path_id"
476+ " ORDER BY comm_id, thread_id, call_path_id")
477+
478+ def FindPath(self, query):
479+ # Turn the query result into a list of ids that the tree view can walk
480+ # to open the tree at the right place.
481+ ids = []
482+ parent_id = query.value(0)
483+ while parent_id:
484+ ids.insert(0, parent_id)
485+ q2 = QSqlQuery(self.glb.db)
486+ QueryExec(q2, "SELECT parent_id"
487+ " FROM call_paths"
488+ " WHERE id = " + str(parent_id))
489+ if not q2.next():
490+ break
491+ parent_id = q2.value(0)
492+ # The call path root is not used
493+ if ids[0] == 1:
494+ del ids[0]
495+ ids.insert(0, query.value(2))
496+ ids.insert(0, query.value(1))
497+ return ids
498+
499+ def Found(self, query, found):
500+ if found:
501+ return self.FindPath(query)
502+ return []
503+
504+ def FindValue(self, value, pattern, query, last_value, last_pattern):
505+ if last_value == value and pattern == last_pattern:
506+ found = query.first()
507+ else:
508+ self.FindSelect(value, pattern, query)
509+ found = query.next()
510+ return self.Found(query, found)
511+
512+ def FindNext(self, query):
513+ found = query.next()
514+ if not found:
515+ found = query.first()
516+ return self.Found(query, found)
517+
518+ def FindPrev(self, query):
519+ found = query.previous()
520+ if not found:
521+ found = query.last()
522+ return self.Found(query, found)
523+
524+ def FindThread(self, c):
525+ if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
526+ ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
527+ elif c.direction > 0:
528+ ids = self.FindNext(c.query)
529+ else:
530+ ids = self.FindPrev(c.query)
531+ return (True, ids)
532+
533+ def Find(self, value, direction, pattern, context, callback):
534+ class Context():
535+ def __init__(self, *x):
536+ self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
537+ def Update(self, *x):
538+ self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
539+ if len(context):
540+ context[0].Update(value, direction, pattern)
541+ else:
542+ context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
543+ # Use a thread so the UI is not blocked during the SELECT
544+ thread = Thread(self.FindThread, context[0])
545+ thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
546+ thread.start()
547+
548+ def FindDone(self, thread, callback, ids):
549+ callback(ids)
550+
551+# Vertical widget layout
552+
553+class VBox():
554+
555+ def __init__(self, w1, w2, w3=None):
556+ self.vbox = QWidget()
557+ self.vbox.setLayout(QVBoxLayout());
558+
559+ self.vbox.layout().setContentsMargins(0, 0, 0, 0)
560+
561+ self.vbox.layout().addWidget(w1)
562+ self.vbox.layout().addWidget(w2)
563+ if w3:
564+ self.vbox.layout().addWidget(w3)
565+
566+ def Widget(self):
567+ return self.vbox
568+
311569 # Context-sensitive call graph window
312570
313571 class CallGraphWindow(QMdiSubWindow):
@@ -323,10 +581,45 @@ class CallGraphWindow(QMdiSubWindow): · 9 unmodified lines
323581 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
324582 self.view.setColumnWidth(c, w)
325583
326− self.setWidget(self.view)
584+ self.find_bar = FindBar(self, self)
585+
586+ self.vbox = VBox(self.view, self.find_bar.Widget())
587+
588+ self.setWidget(self.vbox.Widget())
327589
328590 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
329591
592+ def DisplayFound(self, ids):
593+ if not len(ids):
594+ return False
595+ parent = QModelIndex()
596+ for dbid in ids:
597+ found = False
598+ n = self.model.rowCount(parent)
599+ for row in xrange(n):
600+ child = self.model.index(row, 0, parent)
601+ if child.internalPointer().dbid == dbid:
602+ found = True
603+ self.view.setCurrentIndex(child)
604+ parent = child
605+ break
606+ if not found:
607+ break
608+ return found
609+
610+ def Find(self, value, direction, pattern, context):
611+ self.view.setFocus()
612+ self.find_bar.Busy()
613+ self.model.Find(value, direction, pattern, context, self.FindDone)
614+
615+ def FindDone(self, ids):
616+ found = True
617+ if not self.DisplayFound(ids):
618+ found = False
619+ self.find_bar.Idle()
620+ if not found:
621+ self.find_bar.NotFound()
622+
330623 # Action Definition
331624
332625 def CreateAction(label, tip, callback, parent=None, shortcut=None):
@@ -470,11 +763,22 @@ class MainWindow(QMainWindow): · 137 unmodified lines
470763 file_menu = menu.addMenu("&File")
471764 file_menu.addAction(CreateExitAction(glb.app, self))
472765
766+ edit_menu = menu.addMenu("&Edit")
767+ edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
768+
473769 reports_menu = menu.addMenu("&Reports")
474770 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
475771
476772 self.window_menu = WindowMenu(self.mdi_area, menu)
477773
774+ def Find(self):
775+ win = self.mdi_area.activeSubWindow()
776+ if win:
777+ try:
778+ win.find_bar.Activate()
779+ except:
780+ pass
781+
478782 def NewCallGraph(self):
479783 CallGraphWindow(self.glb, self)
480784