2 # SPDX-License-Identifier: GPL-2.0
3 # exported-sql-viewer.py: view data from sql database
4 # Copyright (c) 2014-2018, Intel Corporation.
6 # To use this script you will need to have exported data using either the
7 # export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
10 # Following on from the example in the export scripts, a
11 # call-graph can be displayed for the pt_example database like this:
13 # python tools/perf/scripts/python/exported-sql-viewer.py pt_example
15 # Note that for PostgreSQL, this script supports connecting to remote databases
16 # by setting hostname, port, username, password, and dbname e.g.
18 # python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
20 # The result is a GUI window with a tree representing a context-sensitive
21 # call-graph. Expanding a couple of levels of the tree and adjusting column
22 # widths to suit will display something like:
24 # Call Graph: pt_example
25 # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
28 # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
29 # |- unknown unknown 1 13198 0.1 1 0.0
30 # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
31 # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
32 # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
33 # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
34 # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
35 # >- __libc_csu_init ls 1 10354 0.1 10 0.0
36 # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
37 # v- main ls 1 8182043 99.6 180254 99.9
40 # The top level is a command name (comm)
41 # The next level is a thread (pid:tid)
42 # Subsequent levels are functions
43 # 'Count' is the number of calls
44 # 'Time' is the elapsed time until the function returns
45 # Percentages are relative to the level above
46 # 'Branch Count' is the total number of branches for that function and all
47 # functions that it calls
49 # There is also a "All branches" report, which displays branches and
50 # possibly disassembly. However, presently, the only supported disassembler is
51 # Intel XED, and additionally the object code must be present in perf build ID
52 # cache. To use Intel XED, libxed.so must be present. To build and install
54 # git clone https://github.com/intelxed/mbuild.git mbuild
55 # git clone https://github.com/intelxed/xed
58 # sudo ./mfile.py --prefix=/usr/local install
63 # Time CPU Command PID TID Branch Type In Tx Branch
64 # 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
65 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
66 # 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
67 # 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
68 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
69 # 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930
70 # 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
71 # 7fab593ea930 55 pushq %rbp
72 # 7fab593ea931 48 89 e5 mov %rsp, %rbp
73 # 7fab593ea934 41 57 pushq %r15
74 # 7fab593ea936 41 56 pushq %r14
75 # 7fab593ea938 41 55 pushq %r13
76 # 7fab593ea93a 41 54 pushq %r12
77 # 7fab593ea93c 53 pushq %rbx
78 # 7fab593ea93d 48 89 fb mov %rdi, %rbx
79 # 7fab593ea940 48 83 ec 68 sub $0x68, %rsp
80 # 7fab593ea944 0f 31 rdtsc
81 # 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx
82 # 7fab593ea94a 89 c0 mov %eax, %eax
83 # 7fab593ea94c 48 09 c2 or %rax, %rdx
84 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
85 # 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
86 # 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
87 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
88 # 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip)
89 # 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
98 from PySide.QtCore import *
99 from PySide.QtGui import *
100 from PySide.QtSql import *
101 from decimal import *
103 from multiprocessing import Process, Array, Value, Event
105 # Data formatting helpers
114 return "+0x%x" % offset
118 if name == "[kernel.kallsyms]":
122 def findnth(s, sub, n, offs=0):
128 return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
130 # Percent to one decimal place
132 def PercentToOneDP(n, d):
135 x = (n * Decimal(100)) / d
136 return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
138 # Helper for queries that must not fail
140 def QueryExec(query, stmt):
141 ret = query.exec_(stmt)
143 raise Exception("Query failed: " + query.lastError().text())
147 class Thread(QThread):
149 done = Signal(object)
151 def __init__(self, task, param=None, parent=None):
152 super(Thread, self).__init__(parent)
158 if self.param is None:
159 done, result = self.task()
161 done, result = self.task(self.param)
162 self.done.emit(result)
168 class TreeModel(QAbstractItemModel):
170 def __init__(self, root, parent=None):
171 super(TreeModel, self).__init__(parent)
173 self.last_row_read = 0
175 def Item(self, parent):
177 return parent.internalPointer()
181 def rowCount(self, parent):
182 result = self.Item(parent).childCount()
185 self.dataChanged.emit(parent, parent)
188 def hasChildren(self, parent):
189 return self.Item(parent).hasChildren()
191 def headerData(self, section, orientation, role):
192 if role == Qt.TextAlignmentRole:
193 return self.columnAlignment(section)
194 if role != Qt.DisplayRole:
196 if orientation != Qt.Horizontal:
198 return self.columnHeader(section)
200 def parent(self, child):
201 child_item = child.internalPointer()
202 if child_item is self.root:
204 parent_item = child_item.getParentItem()
205 return self.createIndex(parent_item.getRow(), 0, parent_item)
207 def index(self, row, column, parent):
208 child_item = self.Item(parent).getChildItem(row)
209 return self.createIndex(row, column, child_item)
211 def DisplayData(self, item, index):
212 return item.getData(index.column())
214 def FetchIfNeeded(self, row):
215 if row > self.last_row_read:
216 self.last_row_read = row
217 if row + 10 >= self.root.child_count:
218 self.fetcher.Fetch(glb_chunk_sz)
220 def columnAlignment(self, column):
223 def columnFont(self, column):
226 def data(self, index, role):
227 if role == Qt.TextAlignmentRole:
228 return self.columnAlignment(index.column())
229 if role == Qt.FontRole:
230 return self.columnFont(index.column())
231 if role != Qt.DisplayRole:
233 item = index.internalPointer()
234 return self.DisplayData(item, index)
238 class TableModel(QAbstractTableModel):
240 def __init__(self, parent=None):
241 super(TableModel, self).__init__(parent)
243 self.child_items = []
244 self.last_row_read = 0
246 def Item(self, parent):
248 return parent.internalPointer()
252 def rowCount(self, parent):
253 return self.child_count
255 def headerData(self, section, orientation, role):
256 if role == Qt.TextAlignmentRole:
257 return self.columnAlignment(section)
258 if role != Qt.DisplayRole:
260 if orientation != Qt.Horizontal:
262 return self.columnHeader(section)
264 def index(self, row, column, parent):
265 return self.createIndex(row, column, self.child_items[row])
267 def DisplayData(self, item, index):
268 return item.getData(index.column())
270 def FetchIfNeeded(self, row):
271 if row > self.last_row_read:
272 self.last_row_read = row
273 if row + 10 >= self.child_count:
274 self.fetcher.Fetch(glb_chunk_sz)
276 def columnAlignment(self, column):
279 def columnFont(self, column):
282 def data(self, index, role):
283 if role == Qt.TextAlignmentRole:
284 return self.columnAlignment(index.column())
285 if role == Qt.FontRole:
286 return self.columnFont(index.column())
287 if role != Qt.DisplayRole:
289 item = index.internalPointer()
290 return self.DisplayData(item, index)
294 model_cache = weakref.WeakValueDictionary()
295 model_cache_lock = threading.Lock()
297 def LookupCreateModel(model_name, create_fn):
298 model_cache_lock.acquire()
300 model = model_cache[model_name]
305 model_cache[model_name] = model
306 model_cache_lock.release()
313 def __init__(self, parent, finder, is_reg_expr=False):
316 self.last_value = None
317 self.last_pattern = None
319 label = QLabel("Find:")
320 label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
322 self.textbox = QComboBox()
323 self.textbox.setEditable(True)
324 self.textbox.currentIndexChanged.connect(self.ValueChanged)
326 self.progress = QProgressBar()
327 self.progress.setRange(0, 0)
331 self.pattern = QCheckBox("Regular Expression")
333 self.pattern = QCheckBox("Pattern")
334 self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
336 self.next_button = QToolButton()
337 self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
338 self.next_button.released.connect(lambda: self.NextPrev(1))
340 self.prev_button = QToolButton()
341 self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
342 self.prev_button.released.connect(lambda: self.NextPrev(-1))
344 self.close_button = QToolButton()
345 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
346 self.close_button.released.connect(self.Deactivate)
348 self.hbox = QHBoxLayout()
349 self.hbox.setContentsMargins(0, 0, 0, 0)
351 self.hbox.addWidget(label)
352 self.hbox.addWidget(self.textbox)
353 self.hbox.addWidget(self.progress)
354 self.hbox.addWidget(self.pattern)
355 self.hbox.addWidget(self.next_button)
356 self.hbox.addWidget(self.prev_button)
357 self.hbox.addWidget(self.close_button)
360 self.bar.setLayout(self.hbox);
368 self.textbox.setFocus()
370 def Deactivate(self):
374 self.textbox.setEnabled(False)
376 self.next_button.hide()
377 self.prev_button.hide()
381 self.textbox.setEnabled(True)
384 self.next_button.show()
385 self.prev_button.show()
387 def Find(self, direction):
388 value = self.textbox.currentText()
389 pattern = self.pattern.isChecked()
390 self.last_value = value
391 self.last_pattern = pattern
392 self.finder.Find(value, direction, pattern, self.context)
394 def ValueChanged(self):
395 value = self.textbox.currentText()
396 pattern = self.pattern.isChecked()
397 index = self.textbox.currentIndex()
398 data = self.textbox.itemData(index)
399 # Store the pattern in the combo box to keep it with the text value
401 self.textbox.setItemData(index, pattern)
403 self.pattern.setChecked(data)
406 def NextPrev(self, direction):
407 value = self.textbox.currentText()
408 pattern = self.pattern.isChecked()
409 if value != self.last_value:
410 index = self.textbox.findText(value)
411 # Allow for a button press before the value has been added to the combo box
413 index = self.textbox.count()
414 self.textbox.addItem(value, pattern)
415 self.textbox.setCurrentIndex(index)
418 self.textbox.setItemData(index, pattern)
419 elif pattern != self.last_pattern:
420 # Keep the pattern recorded in the combo box up to date
421 index = self.textbox.currentIndex()
422 self.textbox.setItemData(index, pattern)
426 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
428 # Context-sensitive call graph data model item base
430 class CallGraphLevelItemBase(object):
432 def __init__(self, glb, row, parent_item):
435 self.parent_item = parent_item
436 self.query_done = False;
438 self.child_items = []
440 def getChildItem(self, row):
441 return self.child_items[row]
443 def getParentItem(self):
444 return self.parent_item
449 def childCount(self):
450 if not self.query_done:
452 if not self.child_count:
454 return self.child_count
456 def hasChildren(self):
457 if not self.query_done:
459 return self.child_count > 0
461 def getData(self, column):
462 return self.data[column]
464 # Context-sensitive call graph data model level 2+ item base
466 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
468 def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
469 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
470 self.comm_id = comm_id
471 self.thread_id = thread_id
472 self.call_path_id = call_path_id
473 self.branch_count = branch_count
477 self.query_done = True;
478 query = QSqlQuery(self.glb.db)
479 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
481 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
482 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
483 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
484 " WHERE parent_call_path_id = " + str(self.call_path_id) +
485 " AND comm_id = " + str(self.comm_id) +
486 " AND thread_id = " + str(self.thread_id) +
487 " GROUP BY call_path_id, name, short_name"
488 " ORDER BY call_path_id")
490 child_item = CallGraphLevelThreeItem(self.glb, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), int(query.value(5)), self)
491 self.child_items.append(child_item)
492 self.child_count += 1
494 # Context-sensitive call graph data model level three item
496 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
498 def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
499 super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
501 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
502 self.dbid = call_path_id
504 # Context-sensitive call graph data model level two item
506 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
508 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
509 super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
510 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
511 self.dbid = thread_id
514 super(CallGraphLevelTwoItem, self).Select()
515 for child_item in self.child_items:
516 self.time += child_item.time
517 self.branch_count += child_item.branch_count
518 for child_item in self.child_items:
519 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
520 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
522 # Context-sensitive call graph data model level one item
524 class CallGraphLevelOneItem(CallGraphLevelItemBase):
526 def __init__(self, glb, row, comm_id, comm, parent_item):
527 super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
528 self.data = [comm, "", "", "", "", "", ""]
532 self.query_done = True;
533 query = QSqlQuery(self.glb.db)
534 QueryExec(query, "SELECT thread_id, pid, tid"
536 " INNER JOIN threads ON thread_id = threads.id"
537 " WHERE comm_id = " + str(self.dbid))
539 child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
540 self.child_items.append(child_item)
541 self.child_count += 1
543 # Context-sensitive call graph data model root item
545 class CallGraphRootItem(CallGraphLevelItemBase):
547 def __init__(self, glb):
548 super(CallGraphRootItem, self).__init__(glb, 0, None)
550 self.query_done = True;
551 query = QSqlQuery(glb.db)
552 QueryExec(query, "SELECT id, comm FROM comms")
554 if not query.value(0):
556 child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
557 self.child_items.append(child_item)
558 self.child_count += 1
560 # Context-sensitive call graph data model
562 class CallGraphModel(TreeModel):
564 def __init__(self, glb, parent=None):
565 super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
568 def columnCount(self, parent=None):
571 def columnHeader(self, column):
572 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
573 return headers[column]
575 def columnAlignment(self, column):
576 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
577 return alignment[column]
579 def FindSelect(self, value, pattern, query):
581 # postgresql and sqlite pattern patching differences:
582 # postgresql LIKE is case sensitive but sqlite LIKE is not
583 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
584 # postgresql supports ILIKE which is case insensitive
585 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
586 if not self.glb.dbref.is_sqlite3:
588 s = value.replace("%", "\%")
589 s = s.replace("_", "\_")
590 # Translate * and ? into SQL LIKE pattern characters % and _
591 trans = string.maketrans("*?", "%_")
592 match = " LIKE '" + str(s).translate(trans) + "'"
594 match = " GLOB '" + str(value) + "'"
596 match = " = '" + str(value) + "'"
597 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
599 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
600 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
601 " WHERE symbols.name" + match +
602 " GROUP BY comm_id, thread_id, call_path_id"
603 " ORDER BY comm_id, thread_id, call_path_id")
605 def FindPath(self, query):
606 # Turn the query result into a list of ids that the tree view can walk
607 # to open the tree at the right place.
609 parent_id = query.value(0)
611 ids.insert(0, parent_id)
612 q2 = QSqlQuery(self.glb.db)
613 QueryExec(q2, "SELECT parent_id"
615 " WHERE id = " + str(parent_id))
618 parent_id = q2.value(0)
619 # The call path root is not used
622 ids.insert(0, query.value(2))
623 ids.insert(0, query.value(1))
626 def Found(self, query, found):
628 return self.FindPath(query)
631 def FindValue(self, value, pattern, query, last_value, last_pattern):
632 if last_value == value and pattern == last_pattern:
633 found = query.first()
635 self.FindSelect(value, pattern, query)
637 return self.Found(query, found)
639 def FindNext(self, query):
642 found = query.first()
643 return self.Found(query, found)
645 def FindPrev(self, query):
646 found = query.previous()
649 return self.Found(query, found)
651 def FindThread(self, c):
652 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
653 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
654 elif c.direction > 0:
655 ids = self.FindNext(c.query)
657 ids = self.FindPrev(c.query)
660 def Find(self, value, direction, pattern, context, callback):
662 def __init__(self, *x):
663 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
664 def Update(self, *x):
665 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
667 context[0].Update(value, direction, pattern)
669 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
670 # Use a thread so the UI is not blocked during the SELECT
671 thread = Thread(self.FindThread, context[0])
672 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
675 def FindDone(self, thread, callback, ids):
678 # Vertical widget layout
682 def __init__(self, w1, w2, w3=None):
683 self.vbox = QWidget()
684 self.vbox.setLayout(QVBoxLayout());
686 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
688 self.vbox.layout().addWidget(w1)
689 self.vbox.layout().addWidget(w2)
691 self.vbox.layout().addWidget(w3)
696 # Context-sensitive call graph window
698 class CallGraphWindow(QMdiSubWindow):
700 def __init__(self, glb, parent=None):
701 super(CallGraphWindow, self).__init__(parent)
703 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
705 self.view = QTreeView()
706 self.view.setModel(self.model)
708 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
709 self.view.setColumnWidth(c, w)
711 self.find_bar = FindBar(self, self)
713 self.vbox = VBox(self.view, self.find_bar.Widget())
715 self.setWidget(self.vbox.Widget())
717 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
719 def DisplayFound(self, ids):
722 parent = QModelIndex()
725 n = self.model.rowCount(parent)
726 for row in xrange(n):
727 child = self.model.index(row, 0, parent)
728 if child.internalPointer().dbid == dbid:
730 self.view.setCurrentIndex(child)
737 def Find(self, value, direction, pattern, context):
740 self.model.Find(value, direction, pattern, context, self.FindDone)
742 def FindDone(self, ids):
744 if not self.DisplayFound(ids):
748 self.find_bar.NotFound()
750 # Child data item finder
752 class ChildDataItemFinder():
754 def __init__(self, root):
756 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
760 def FindSelect(self):
763 pattern = re.compile(self.value)
764 for child in self.root.child_items:
765 for column_data in child.data:
766 if re.search(pattern, str(column_data)) is not None:
767 self.rows.append(child.row)
770 for child in self.root.child_items:
771 for column_data in child.data:
772 if self.value in str(column_data):
773 self.rows.append(child.row)
778 if self.last_value != self.value or self.pattern != self.last_pattern:
780 if not len(self.rows):
782 return self.rows[self.pos]
784 def FindThread(self):
785 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
786 row = self.FindValue()
788 if self.direction > 0:
790 if self.pos >= len(self.rows):
795 self.pos = len(self.rows) - 1
796 row = self.rows[self.pos]
801 def Find(self, value, direction, pattern, context, callback):
802 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
803 # Use a thread so the UI is not blocked
804 thread = Thread(self.FindThread)
805 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
808 def FindDone(self, thread, callback, row):
811 # Number of database records to fetch in one go
815 # size of pickled integer big enough for record size
819 # Background process for SQL data fetcher
821 class SQLFetcherProcess():
823 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
824 # Need a unique connection name
825 conn_name = "SQLFetcher" + str(os.getpid())
826 self.db, dbname = dbref.Open(conn_name)
831 self.fetch_count = fetch_count
832 self.fetching_done = fetching_done
833 self.process_target = process_target
834 self.wait_event = wait_event
835 self.fetched_event = fetched_event
837 self.query = QSqlQuery(self.db)
838 self.query_limit = 0 if "$$last_id$$" in sql else 2
842 self.local_head = self.head.value
843 self.local_tail = self.tail.value
847 if self.query_limit == 1:
849 self.query_limit -= 1
850 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
851 QueryExec(self.query, stmt)
854 if not self.query.next():
856 if not self.query.next():
858 self.last_id = self.query.value(0)
859 return self.prep(self.query)
861 def WaitForTarget(self):
863 self.wait_event.clear()
864 target = self.process_target.value
865 if target > self.fetched or target < 0:
867 self.wait_event.wait()
870 def HasSpace(self, sz):
871 if self.local_tail <= self.local_head:
872 space = len(self.buffer) - self.local_head
876 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
877 nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL)
878 self.buffer[self.local_head : self.local_head + len(nd)] = nd
880 if self.local_tail - self.local_head > sz:
884 def WaitForSpace(self, sz):
885 if self.HasSpace(sz):
888 self.wait_event.clear()
889 self.local_tail = self.tail.value
890 if self.HasSpace(sz):
892 self.wait_event.wait()
894 def AddToBuffer(self, obj):
895 d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL)
897 nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL)
899 self.WaitForSpace(sz)
900 pos = self.local_head
901 self.buffer[pos : pos + len(nd)] = nd
902 self.buffer[pos + glb_nsz : pos + sz] = d
903 self.local_head += sz
905 def FetchBatch(self, batch_size):
907 while batch_size > fetched:
912 self.AddToBuffer(obj)
915 self.fetched += fetched
916 with self.fetch_count.get_lock():
917 self.fetch_count.value += fetched
918 self.head.value = self.local_head
919 self.fetched_event.set()
923 target = self.WaitForTarget()
926 batch_size = min(glb_chunk_sz, target - self.fetched)
927 self.FetchBatch(batch_size)
928 self.fetching_done.value = True
929 self.fetched_event.set()
931 def SQLFetcherFn(*x):
932 process = SQLFetcherProcess(*x)
937 class SQLFetcher(QObject):
939 done = Signal(object)
941 def __init__(self, glb, sql, prep, process_data, parent=None):
942 super(SQLFetcher, self).__init__(parent)
943 self.process_data = process_data
948 self.buffer_size = 16 * 1024 * 1024
949 self.buffer = Array(c_char, self.buffer_size, lock=False)
950 self.head = Value(c_longlong)
951 self.tail = Value(c_longlong)
953 self.fetch_count = Value(c_longlong)
954 self.fetching_done = Value(c_bool)
956 self.process_target = Value(c_longlong)
957 self.wait_event = Event()
958 self.fetched_event = Event()
959 glb.AddInstanceToShutdownOnExit(self)
960 self.process = Process(target=SQLFetcherFn, args=(glb.dbref, sql, self.buffer, self.head, self.tail, self.fetch_count, self.fetching_done, self.process_target, self.wait_event, self.fetched_event, prep))
962 self.thread = Thread(self.Thread)
963 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
967 # Tell the thread and process to exit
968 self.process_target.value = -1
969 self.wait_event.set()
971 self.fetching_done.value = True
972 self.fetched_event.set()
978 self.fetched_event.clear()
979 fetch_count = self.fetch_count.value
980 if fetch_count != self.last_count:
982 if self.fetching_done.value:
985 self.fetched_event.wait()
986 count = fetch_count - self.last_count
987 self.last_count = fetch_count
988 self.fetched += count
993 # -1 inidcates there are no more
995 result = self.fetched
996 extra = result + nr - self.target
999 # process_target < 0 indicates shutting down
1000 if self.process_target.value >= 0:
1001 self.process_target.value = self.target
1002 self.wait_event.set()
1005 def RemoveFromBuffer(self):
1006 pos = self.local_tail
1007 if len(self.buffer) - pos < glb_nsz:
1009 n = cPickle.loads(self.buffer[pos : pos + glb_nsz])
1012 n = cPickle.loads(self.buffer[0 : glb_nsz])
1014 obj = cPickle.loads(self.buffer[pos : pos + n])
1015 self.local_tail = pos + n
1018 def ProcessData(self, count):
1019 for i in xrange(count):
1020 obj = self.RemoveFromBuffer()
1021 self.process_data(obj)
1022 self.tail.value = self.local_tail
1023 self.wait_event.set()
1024 self.done.emit(count)
1026 # Fetch more records bar
1028 class FetchMoreRecordsBar():
1030 def __init__(self, model, parent):
1033 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1034 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1036 self.fetch_count = QSpinBox()
1037 self.fetch_count.setRange(1, 1000000)
1038 self.fetch_count.setValue(10)
1039 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1041 self.fetch = QPushButton("Go!")
1042 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1043 self.fetch.released.connect(self.FetchMoreRecords)
1045 self.progress = QProgressBar()
1046 self.progress.setRange(0, 100)
1047 self.progress.hide()
1049 self.done_label = QLabel("All records fetched")
1050 self.done_label.hide()
1052 self.spacer = QLabel("")
1054 self.close_button = QToolButton()
1055 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1056 self.close_button.released.connect(self.Deactivate)
1058 self.hbox = QHBoxLayout()
1059 self.hbox.setContentsMargins(0, 0, 0, 0)
1061 self.hbox.addWidget(self.label)
1062 self.hbox.addWidget(self.fetch_count)
1063 self.hbox.addWidget(self.fetch)
1064 self.hbox.addWidget(self.spacer)
1065 self.hbox.addWidget(self.progress)
1066 self.hbox.addWidget(self.done_label)
1067 self.hbox.addWidget(self.close_button)
1069 self.bar = QWidget()
1070 self.bar.setLayout(self.hbox);
1073 self.in_progress = False
1074 self.model.progress.connect(self.Progress)
1078 if not model.HasMoreRecords():
1086 self.fetch.setFocus()
1088 def Deactivate(self):
1091 def Enable(self, enable):
1092 self.fetch.setEnabled(enable)
1093 self.fetch_count.setEnabled(enable)
1099 self.progress.show()
1102 self.in_progress = False
1104 self.progress.hide()
1109 return self.fetch_count.value() * glb_chunk_sz
1115 self.fetch_count.hide()
1118 self.done_label.show()
1120 def Progress(self, count):
1121 if self.in_progress:
1123 percent = ((count - self.start) * 100) / self.Target()
1127 self.progress.setValue(percent)
1129 # Count value of zero means no more records
1132 def FetchMoreRecords(self):
1135 self.progress.setValue(0)
1137 self.in_progress = True
1138 self.start = self.model.FetchMoreRecords(self.Target())
1140 # Brance data model level two item
1142 class BranchLevelTwoItem():
1144 def __init__(self, row, text, parent_item):
1146 self.parent_item = parent_item
1147 self.data = [""] * 8
1151 def getParentItem(self):
1152 return self.parent_item
1157 def childCount(self):
1160 def hasChildren(self):
1163 def getData(self, column):
1164 return self.data[column]
1166 # Brance data model level one item
1168 class BranchLevelOneItem():
1170 def __init__(self, glb, row, data, parent_item):
1173 self.parent_item = parent_item
1174 self.child_count = 0
1175 self.child_items = []
1176 self.data = data[1:]
1179 self.query_done = False
1181 def getChildItem(self, row):
1182 return self.child_items[row]
1184 def getParentItem(self):
1185 return self.parent_item
1191 self.query_done = True
1193 if not self.glb.have_disassembler:
1196 query = QSqlQuery(self.glb.db)
1198 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1200 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1201 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1202 " WHERE samples.id = " + str(self.dbid))
1203 if not query.next():
1205 cpu = query.value(0)
1206 dso = query.value(1)
1207 sym = query.value(2)
1208 if dso == 0 or sym == 0:
1210 off = query.value(3)
1211 short_name = query.value(4)
1212 long_name = query.value(5)
1213 build_id = query.value(6)
1214 sym_start = query.value(7)
1217 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1219 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1220 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1221 " ORDER BY samples.id"
1223 if not query.next():
1225 if query.value(0) != dso:
1226 # Cannot disassemble from one dso to another
1228 bsym = query.value(1)
1229 boff = query.value(2)
1230 bsym_start = query.value(3)
1233 tot = bsym_start + boff + 1 - sym_start - off
1234 if tot <= 0 or tot > 16384:
1237 inst = self.glb.disassembler.Instruction()
1238 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1241 mode = 0 if Is64Bit(f) else 1
1242 self.glb.disassembler.SetMode(inst, mode)
1245 buf = create_string_buffer(tot + 16)
1246 f.seek(sym_start + off)
1247 buf.value = f.read(buf_sz)
1248 buf_ptr = addressof(buf)
1251 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1253 byte_str = tohex(ip).rjust(16)
1254 for k in xrange(cnt):
1255 byte_str += " %02x" % ord(buf[i])
1260 self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
1261 self.child_count += 1
1269 def childCount(self):
1270 if not self.query_done:
1272 if not self.child_count:
1274 return self.child_count
1276 def hasChildren(self):
1277 if not self.query_done:
1279 return self.child_count > 0
1281 def getData(self, column):
1282 return self.data[column]
1284 # Brance data model root item
1286 class BranchRootItem():
1289 self.child_count = 0
1290 self.child_items = []
1293 def getChildItem(self, row):
1294 return self.child_items[row]
1296 def getParentItem(self):
1302 def childCount(self):
1303 return self.child_count
1305 def hasChildren(self):
1306 return self.child_count > 0
1308 def getData(self, column):
1311 # Branch data preparation
1313 def BranchDataPrep(query):
1315 for i in xrange(0, 8):
1316 data.append(query.value(i))
1317 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1318 " (" + dsoname(query.value(11)) + ")" + " -> " +
1319 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1320 " (" + dsoname(query.value(15)) + ")")
1325 class BranchModel(TreeModel):
1327 progress = Signal(object)
1329 def __init__(self, glb, event_id, where_clause, parent=None):
1330 super(BranchModel, self).__init__(BranchRootItem(), parent)
1332 self.event_id = event_id
1335 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1336 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1337 " ip, symbols.name, sym_offset, dsos.short_name,"
1338 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1340 " INNER JOIN comms ON comm_id = comms.id"
1341 " INNER JOIN threads ON thread_id = threads.id"
1342 " INNER JOIN branch_types ON branch_type = branch_types.id"
1343 " INNER JOIN symbols ON symbol_id = symbols.id"
1344 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1345 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1346 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1347 " WHERE samples.id > $$last_id$$" + where_clause +
1348 " AND evsel_id = " + str(self.event_id) +
1349 " ORDER BY samples.id"
1350 " LIMIT " + str(glb_chunk_sz))
1351 self.fetcher = SQLFetcher(glb, sql, BranchDataPrep, self.AddSample)
1352 self.fetcher.done.connect(self.Update)
1353 self.fetcher.Fetch(glb_chunk_sz)
1355 def columnCount(self, parent=None):
1358 def columnHeader(self, column):
1359 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1361 def columnFont(self, column):
1364 return QFont("Monospace")
1366 def DisplayData(self, item, index):
1368 self.FetchIfNeeded(item.row)
1369 return item.getData(index.column())
1371 def AddSample(self, data):
1372 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1373 self.root.child_items.append(child)
1376 def Update(self, fetched):
1379 self.progress.emit(0)
1380 child_count = self.root.child_count
1381 count = self.populated - child_count
1383 parent = QModelIndex()
1384 self.beginInsertRows(parent, child_count, child_count + count - 1)
1385 self.insertRows(child_count, count, parent)
1386 self.root.child_count += count
1387 self.endInsertRows()
1388 self.progress.emit(self.root.child_count)
1390 def FetchMoreRecords(self, count):
1391 current = self.root.child_count
1393 self.fetcher.Fetch(count)
1395 self.progress.emit(0)
1398 def HasMoreRecords(self):
1405 def __init__(self, name = "", where_clause = "", limit = ""):
1407 self.where_clause = where_clause
1411 return str(self.where_clause + ";" + self.limit)
1415 class BranchWindow(QMdiSubWindow):
1417 def __init__(self, glb, event_id, report_vars, parent=None):
1418 super(BranchWindow, self).__init__(parent)
1420 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
1422 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1424 self.view = QTreeView()
1425 self.view.setUniformRowHeights(True)
1426 self.view.setModel(self.model)
1428 self.ResizeColumnsToContents()
1430 self.find_bar = FindBar(self, self, True)
1432 self.finder = ChildDataItemFinder(self.model.root)
1434 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1436 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1438 self.setWidget(self.vbox.Widget())
1440 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1442 def ResizeColumnToContents(self, column, n):
1443 # Using the view's resizeColumnToContents() here is extrememly slow
1444 # so implement a crude alternative
1445 mm = "MM" if column else "MMMM"
1446 font = self.view.font()
1447 metrics = QFontMetrics(font)
1449 for row in xrange(n):
1450 val = self.model.root.child_items[row].data[column]
1451 len = metrics.width(str(val) + mm)
1452 max = len if len > max else max
1453 val = self.model.columnHeader(column)
1454 len = metrics.width(str(val) + mm)
1455 max = len if len > max else max
1456 self.view.setColumnWidth(column, max)
1458 def ResizeColumnsToContents(self):
1459 n = min(self.model.root.child_count, 100)
1461 # No data yet, so connect a signal to notify when there is
1462 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1464 columns = self.model.columnCount()
1465 for i in xrange(columns):
1466 self.ResizeColumnToContents(i, n)
1468 def UpdateColumnWidths(self, *x):
1469 # This only needs to be done once, so disconnect the signal now
1470 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1471 self.ResizeColumnsToContents()
1473 def Find(self, value, direction, pattern, context):
1474 self.view.setFocus()
1475 self.find_bar.Busy()
1476 self.finder.Find(value, direction, pattern, context, self.FindDone)
1478 def FindDone(self, row):
1479 self.find_bar.Idle()
1481 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1483 self.find_bar.NotFound()
1485 # Line edit data item
1487 class LineEditDataItem(object):
1489 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1492 self.placeholder_text = placeholder_text
1493 self.parent = parent
1496 self.value = default
1498 self.widget = QLineEdit(default)
1499 self.widget.editingFinished.connect(self.Validate)
1500 self.widget.textChanged.connect(self.Invalidate)
1503 self.validated = True
1505 if placeholder_text:
1506 self.widget.setPlaceholderText(placeholder_text)
1508 def TurnTextRed(self):
1510 palette = QPalette()
1511 palette.setColor(QPalette.Text,Qt.red)
1512 self.widget.setPalette(palette)
1515 def TurnTextNormal(self):
1517 palette = QPalette()
1518 self.widget.setPalette(palette)
1521 def InvalidValue(self, value):
1524 self.error = self.label + " invalid value '" + value + "'"
1525 self.parent.ShowMessage(self.error)
1527 def Invalidate(self):
1528 self.validated = False
1530 def DoValidate(self, input_string):
1531 self.value = input_string.strip()
1534 self.validated = True
1536 self.TurnTextNormal()
1537 self.parent.ClearMessage()
1538 input_string = self.widget.text()
1539 if not len(input_string.strip()):
1542 self.DoValidate(input_string)
1545 if not self.validated:
1548 self.parent.ShowMessage(self.error)
1552 def IsNumber(self, value):
1557 return str(x) == value
1559 # Non-negative integer ranges dialog data item
1561 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1563 def __init__(self, glb, label, placeholder_text, column_name, parent):
1564 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1566 self.column_name = column_name
1568 def DoValidate(self, input_string):
1571 for value in [x.strip() for x in input_string.split(",")]:
1573 vrange = value.split("-")
1574 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1575 return self.InvalidValue(value)
1576 ranges.append(vrange)
1578 if not self.IsNumber(value):
1579 return self.InvalidValue(value)
1580 singles.append(value)
1581 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1583 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1584 self.value = " OR ".join(ranges)
1586 # Positive integer dialog data item
1588 class PositiveIntegerDataItem(LineEditDataItem):
1590 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1591 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1593 def DoValidate(self, input_string):
1594 if not self.IsNumber(input_string.strip()):
1595 return self.InvalidValue(input_string)
1596 value = int(input_string.strip())
1598 return self.InvalidValue(input_string)
1599 self.value = str(value)
1601 # Dialog data item converted and validated using a SQL table
1603 class SQLTableDataItem(LineEditDataItem):
1605 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1606 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
1608 self.table_name = table_name
1609 self.match_column = match_column
1610 self.column_name1 = column_name1
1611 self.column_name2 = column_name2
1613 def ValueToIds(self, value):
1615 query = QSqlQuery(self.glb.db)
1616 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1617 ret = query.exec_(stmt)
1620 ids.append(str(query.value(0)))
1623 def DoValidate(self, input_string):
1625 for value in [x.strip() for x in input_string.split(",")]:
1626 ids = self.ValueToIds(value)
1630 return self.InvalidValue(value)
1631 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1632 if self.column_name2:
1633 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1635 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
1637 class SampleTimeRangesDataItem(LineEditDataItem):
1639 def __init__(self, glb, label, placeholder_text, column_name, parent):
1640 self.column_name = column_name
1644 self.last_time = 2 ** 64
1646 query = QSqlQuery(glb.db)
1647 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1649 self.last_id = int(query.value(0))
1650 self.last_time = int(query.value(1))
1651 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1653 self.first_time = int(query.value(0))
1654 if placeholder_text:
1655 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1657 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1659 def IdBetween(self, query, lower_id, higher_id, order):
1660 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1662 return True, int(query.value(0))
1666 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1667 query = QSqlQuery(self.glb.db)
1669 next_id = int((lower_id + higher_id) / 2)
1670 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1671 if not query.next():
1672 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1674 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1676 return str(higher_id)
1678 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1679 next_time = int(query.value(0))
1681 if target_time > next_time:
1685 if higher_id <= lower_id + 1:
1686 return str(higher_id)
1688 if target_time >= next_time:
1692 if higher_id <= lower_id + 1:
1693 return str(lower_id)
1695 def ConvertRelativeTime(self, val):
1700 elif suffix == "us":
1702 elif suffix == "ns":
1706 val = val[:-2].strip()
1707 if not self.IsNumber(val):
1709 val = int(val) * mult
1711 val += self.first_time
1713 val += self.last_time
1716 def ConvertTimeRange(self, vrange):
1718 vrange[0] = str(self.first_time)
1720 vrange[1] = str(self.last_time)
1721 vrange[0] = self.ConvertRelativeTime(vrange[0])
1722 vrange[1] = self.ConvertRelativeTime(vrange[1])
1723 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1725 beg_range = max(int(vrange[0]), self.first_time)
1726 end_range = min(int(vrange[1]), self.last_time)
1727 if beg_range > self.last_time or end_range < self.first_time:
1729 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
1730 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
1733 def AddTimeRange(self, value, ranges):
1734 n = value.count("-")
1738 if value.split("-")[1].strip() == "":
1744 pos = findnth(value, "-", n)
1745 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
1746 if self.ConvertTimeRange(vrange):
1747 ranges.append(vrange)
1751 def DoValidate(self, input_string):
1753 for value in [x.strip() for x in input_string.split(",")]:
1754 if not self.AddTimeRange(value, ranges):
1755 return self.InvalidValue(value)
1756 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1757 self.value = " OR ".join(ranges)
1759 # Report Dialog Base
1761 class ReportDialogBase(QDialog):
1763 def __init__(self, glb, title, items, partial, parent=None):
1764 super(ReportDialogBase, self).__init__(parent)
1768 self.report_vars = ReportVars()
1770 self.setWindowTitle(title)
1771 self.setMinimumWidth(600)
1773 self.data_items = [x(glb, self) for x in items]
1775 self.partial = partial
1777 self.grid = QGridLayout()
1779 for row in xrange(len(self.data_items)):
1780 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
1781 self.grid.addWidget(self.data_items[row].widget, row, 1)
1783 self.status = QLabel()
1785 self.ok_button = QPushButton("Ok", self)
1786 self.ok_button.setDefault(True)
1787 self.ok_button.released.connect(self.Ok)
1788 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1790 self.cancel_button = QPushButton("Cancel", self)
1791 self.cancel_button.released.connect(self.reject)
1792 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1794 self.hbox = QHBoxLayout()
1795 #self.hbox.addStretch()
1796 self.hbox.addWidget(self.status)
1797 self.hbox.addWidget(self.ok_button)
1798 self.hbox.addWidget(self.cancel_button)
1800 self.vbox = QVBoxLayout()
1801 self.vbox.addLayout(self.grid)
1802 self.vbox.addLayout(self.hbox)
1804 self.setLayout(self.vbox);
1807 vars = self.report_vars
1808 for d in self.data_items:
1809 if d.id == "REPORTNAME":
1812 self.ShowMessage("Report name is required")
1814 for d in self.data_items:
1817 for d in self.data_items[1:]:
1819 vars.limit = d.value
1821 if len(vars.where_clause):
1822 vars.where_clause += " AND "
1823 vars.where_clause += d.value
1824 if len(vars.where_clause):
1826 vars.where_clause = " AND ( " + vars.where_clause + " ) "
1828 vars.where_clause = " WHERE " + vars.where_clause + " "
1831 def ShowMessage(self, msg):
1832 self.status.setText("<font color=#FF0000>" + msg)
1834 def ClearMessage(self):
1835 self.status.setText("")
1837 # Selected branch report creation dialog
1839 class SelectedBranchDialog(ReportDialogBase):
1841 def __init__(self, glb, parent=None):
1842 title = "Selected Branches"
1843 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
1844 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
1845 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
1846 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
1847 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
1848 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
1849 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
1850 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
1851 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
1852 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
1856 def GetEventList(db):
1858 query = QSqlQuery(db)
1859 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
1861 events.append(query.value(0))
1864 # Is a table selectable
1866 def IsSelectable(db, table):
1867 query = QSqlQuery(db)
1869 QueryExec(query, "SELECT * FROM " + table + " LIMIT 1")
1874 # SQL data preparation
1876 def SQLTableDataPrep(query, count):
1878 for i in xrange(count):
1879 data.append(query.value(i))
1882 # SQL table data model item
1884 class SQLTableItem():
1886 def __init__(self, row, data):
1890 def getData(self, column):
1891 return self.data[column]
1893 # SQL table data model
1895 class SQLTableModel(TableModel):
1897 progress = Signal(object)
1899 def __init__(self, glb, sql, column_headers, parent=None):
1900 super(SQLTableModel, self).__init__(parent)
1904 self.column_headers = column_headers
1905 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): SQLTableDataPrep(x, y), self.AddSample)
1906 self.fetcher.done.connect(self.Update)
1907 self.fetcher.Fetch(glb_chunk_sz)
1909 def DisplayData(self, item, index):
1910 self.FetchIfNeeded(item.row)
1911 return item.getData(index.column())
1913 def AddSample(self, data):
1914 child = SQLTableItem(self.populated, data)
1915 self.child_items.append(child)
1918 def Update(self, fetched):
1921 self.progress.emit(0)
1922 child_count = self.child_count
1923 count = self.populated - child_count
1925 parent = QModelIndex()
1926 self.beginInsertRows(parent, child_count, child_count + count - 1)
1927 self.insertRows(child_count, count, parent)
1928 self.child_count += count
1929 self.endInsertRows()
1930 self.progress.emit(self.child_count)
1932 def FetchMoreRecords(self, count):
1933 current = self.child_count
1935 self.fetcher.Fetch(count)
1937 self.progress.emit(0)
1940 def HasMoreRecords(self):
1943 def columnCount(self, parent=None):
1944 return len(self.column_headers)
1946 def columnHeader(self, column):
1947 return self.column_headers[column]
1949 # SQL automatic table data model
1951 class SQLAutoTableModel(SQLTableModel):
1953 def __init__(self, glb, table_name, parent=None):
1954 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
1955 if table_name == "comm_threads_view":
1956 # For now, comm_threads_view has no id column
1957 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
1959 query = QSqlQuery(glb.db)
1960 if glb.dbref.is_sqlite3:
1961 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
1963 column_headers.append(query.value(1))
1964 if table_name == "sqlite_master":
1965 sql = "SELECT * FROM " + table_name
1967 if table_name[:19] == "information_schema.":
1968 sql = "SELECT * FROM " + table_name
1969 select_table_name = table_name[19:]
1970 schema = "information_schema"
1972 select_table_name = table_name
1974 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
1976 column_headers.append(query.value(0))
1977 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
1979 # Base class for custom ResizeColumnsToContents
1981 class ResizeColumnsToContentsBase(QObject):
1983 def __init__(self, parent=None):
1984 super(ResizeColumnsToContentsBase, self).__init__(parent)
1986 def ResizeColumnToContents(self, column, n):
1987 # Using the view's resizeColumnToContents() here is extrememly slow
1988 # so implement a crude alternative
1989 font = self.view.font()
1990 metrics = QFontMetrics(font)
1992 for row in xrange(n):
1993 val = self.data_model.child_items[row].data[column]
1994 len = metrics.width(str(val) + "MM")
1995 max = len if len > max else max
1996 val = self.data_model.columnHeader(column)
1997 len = metrics.width(str(val) + "MM")
1998 max = len if len > max else max
1999 self.view.setColumnWidth(column, max)
2001 def ResizeColumnsToContents(self):
2002 n = min(self.data_model.child_count, 100)
2004 # No data yet, so connect a signal to notify when there is
2005 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2007 columns = self.data_model.columnCount()
2008 for i in xrange(columns):
2009 self.ResizeColumnToContents(i, n)
2011 def UpdateColumnWidths(self, *x):
2012 # This only needs to be done once, so disconnect the signal now
2013 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2014 self.ResizeColumnsToContents()
2018 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2020 def __init__(self, glb, table_name, parent=None):
2021 super(TableWindow, self).__init__(parent)
2023 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2025 self.model = QSortFilterProxyModel()
2026 self.model.setSourceModel(self.data_model)
2028 self.view = QTableView()
2029 self.view.setModel(self.model)
2030 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2031 self.view.verticalHeader().setVisible(False)
2032 self.view.sortByColumn(-1, Qt.AscendingOrder)
2033 self.view.setSortingEnabled(True)
2035 self.ResizeColumnsToContents()
2037 self.find_bar = FindBar(self, self, True)
2039 self.finder = ChildDataItemFinder(self.data_model)
2041 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2043 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2045 self.setWidget(self.vbox.Widget())
2047 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2049 def Find(self, value, direction, pattern, context):
2050 self.view.setFocus()
2051 self.find_bar.Busy()
2052 self.finder.Find(value, direction, pattern, context, self.FindDone)
2054 def FindDone(self, row):
2055 self.find_bar.Idle()
2057 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2059 self.find_bar.NotFound()
2063 def GetTableList(glb):
2065 query = QSqlQuery(glb.db)
2066 if glb.dbref.is_sqlite3:
2067 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2069 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2071 tables.append(query.value(0))
2072 if glb.dbref.is_sqlite3:
2073 tables.append("sqlite_master")
2075 tables.append("information_schema.tables")
2076 tables.append("information_schema.views")
2077 tables.append("information_schema.columns")
2080 # Top Calls data model
2082 class TopCallsModel(SQLTableModel):
2084 def __init__(self, glb, report_vars, parent=None):
2086 if not glb.dbref.is_sqlite3:
2089 if len(report_vars.limit):
2090 limit = " LIMIT " + report_vars.limit
2091 sql = ("SELECT comm, pid, tid, name,"
2093 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2096 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2098 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2099 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2100 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2104 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2105 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2106 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2107 " INNER JOIN comms ON calls.comm_id = comms.id"
2108 " INNER JOIN threads ON calls.thread_id = threads.id" +
2109 report_vars.where_clause +
2110 " ORDER BY elapsed_time DESC" +
2113 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2114 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2115 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2117 def columnAlignment(self, column):
2118 return self.alignment[column]
2120 # Top Calls report creation dialog
2122 class TopCallsDialog(ReportDialogBase):
2124 def __init__(self, glb, parent=None):
2125 title = "Top Calls by Elapsed Time"
2126 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2127 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2128 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2129 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2130 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2131 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2132 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2133 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2134 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2138 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2140 def __init__(self, glb, report_vars, parent=None):
2141 super(TopCallsWindow, self).__init__(parent)
2143 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2144 self.model = self.data_model
2146 self.view = QTableView()
2147 self.view.setModel(self.model)
2148 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2149 self.view.verticalHeader().setVisible(False)
2151 self.ResizeColumnsToContents()
2153 self.find_bar = FindBar(self, self, True)
2155 self.finder = ChildDataItemFinder(self.model)
2157 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2159 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2161 self.setWidget(self.vbox.Widget())
2163 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2165 def Find(self, value, direction, pattern, context):
2166 self.view.setFocus()
2167 self.find_bar.Busy()
2168 self.finder.Find(value, direction, pattern, context, self.FindDone)
2170 def FindDone(self, row):
2171 self.find_bar.Idle()
2173 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2175 self.find_bar.NotFound()
2179 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2180 action = QAction(label, parent)
2181 if shortcut != None:
2182 action.setShortcuts(shortcut)
2183 action.setStatusTip(tip)
2184 action.triggered.connect(callback)
2187 # Typical application actions
2189 def CreateExitAction(app, parent=None):
2190 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2192 # Typical MDI actions
2194 def CreateCloseActiveWindowAction(mdi_area):
2195 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2197 def CreateCloseAllWindowsAction(mdi_area):
2198 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2200 def CreateTileWindowsAction(mdi_area):
2201 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2203 def CreateCascadeWindowsAction(mdi_area):
2204 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2206 def CreateNextWindowAction(mdi_area):
2207 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2209 def CreatePreviousWindowAction(mdi_area):
2210 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2212 # Typical MDI window menu
2216 def __init__(self, mdi_area, menu):
2217 self.mdi_area = mdi_area
2218 self.window_menu = menu.addMenu("&Windows")
2219 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2220 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2221 self.tile_windows = CreateTileWindowsAction(mdi_area)
2222 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2223 self.next_window = CreateNextWindowAction(mdi_area)
2224 self.previous_window = CreatePreviousWindowAction(mdi_area)
2225 self.window_menu.aboutToShow.connect(self.Update)
2228 self.window_menu.clear()
2229 sub_window_count = len(self.mdi_area.subWindowList())
2230 have_sub_windows = sub_window_count != 0
2231 self.close_active_window.setEnabled(have_sub_windows)
2232 self.close_all_windows.setEnabled(have_sub_windows)
2233 self.tile_windows.setEnabled(have_sub_windows)
2234 self.cascade_windows.setEnabled(have_sub_windows)
2235 self.next_window.setEnabled(have_sub_windows)
2236 self.previous_window.setEnabled(have_sub_windows)
2237 self.window_menu.addAction(self.close_active_window)
2238 self.window_menu.addAction(self.close_all_windows)
2239 self.window_menu.addSeparator()
2240 self.window_menu.addAction(self.tile_windows)
2241 self.window_menu.addAction(self.cascade_windows)
2242 self.window_menu.addSeparator()
2243 self.window_menu.addAction(self.next_window)
2244 self.window_menu.addAction(self.previous_window)
2245 if sub_window_count == 0:
2247 self.window_menu.addSeparator()
2249 for sub_window in self.mdi_area.subWindowList():
2250 label = str(nr) + " " + sub_window.name
2253 action = self.window_menu.addAction(label)
2254 action.setCheckable(True)
2255 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2256 action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2257 self.window_menu.addAction(action)
2260 def setActiveSubWindow(self, nr):
2261 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2276 <p class=c1><a href=#reports>1. Reports</a></p>
2277 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2278 <p class=c2><a href=#allbranches>1.2 All branches</a></p>
2279 <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p>
2280 <p class=c2><a href=#topcallsbyelapsedtime>1.4 Top calls by elapsed time</a></p>
2281 <p class=c1><a href=#tables>2. Tables</a></p>
2282 <h1 id=reports>1. Reports</h1>
2283 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2284 The result is a GUI window with a tree representing a context-sensitive
2285 call-graph. Expanding a couple of levels of the tree and adjusting column
2286 widths to suit will display something like:
2288 Call Graph: pt_example
2289 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2292 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2293 |- unknown unknown 1 13198 0.1 1 0.0
2294 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2295 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2296 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2297 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2298 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2299 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2300 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2301 v- main ls 1 8182043 99.6 180254 99.9
2303 <h3>Points to note:</h3>
2305 <li>The top level is a command name (comm)</li>
2306 <li>The next level is a thread (pid:tid)</li>
2307 <li>Subsequent levels are functions</li>
2308 <li>'Count' is the number of calls</li>
2309 <li>'Time' is the elapsed time until the function returns</li>
2310 <li>Percentages are relative to the level above</li>
2311 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2314 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2315 The pattern matching symbols are ? for any character and * for zero or more characters.
2316 <h2 id=allbranches>1.2 All branches</h2>
2317 The All branches report displays all branches in chronological order.
2318 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2319 <h3>Disassembly</h3>
2320 Open a branch to display disassembly. This only works if:
2322 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2323 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2324 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2325 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2326 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2328 <h4 id=xed>Intel XED Setup</h4>
2329 To use Intel XED, libxed.so must be present. To build and install libxed.so:
2331 git clone https://github.com/intelxed/mbuild.git mbuild
2332 git clone https://github.com/intelxed/xed
2335 sudo ./mfile.py --prefix=/usr/local install
2339 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2340 Refer to Python documentation for the regular expression syntax.
2341 All columns are searched, but only currently fetched rows are searched.
2342 <h2 id=selectedbranches>1.3 Selected branches</h2>
2343 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2344 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2345 <h3>1.3.1 Time ranges</h3>
2346 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2347 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
2349 81073085947329-81073085958238 From 81073085947329 to 81073085958238
2350 100us-200us From 100us to 200us
2351 10ms- From 10ms to the end
2352 -100ns The first 100ns
2353 -10ms- The last 10ms
2355 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2356 <h2 id=topcallsbyelapsedtime>1.4 Top calls by elapsed time</h2>
2357 The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
2358 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2359 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
2360 <h1 id=tables>2. Tables</h1>
2361 The Tables menu shows all tables and views in the database. Most tables have an associated view
2362 which displays the information in a more friendly way. Not all data for large tables is fetched
2363 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2364 but that can be slow for large tables.
2365 <p>There are also tables of database meta-information.
2366 For SQLite3 databases, the sqlite_master table is included.
2367 For PostgreSQL databases, information_schema.tables/views/columns are included.
2369 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2370 Refer to Python documentation for the regular expression syntax.
2371 All columns are searched, but only currently fetched rows are searched.
2372 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2373 will go to the next/previous result in id order, instead of display order.
2378 class HelpWindow(QMdiSubWindow):
2380 def __init__(self, glb, parent=None):
2381 super(HelpWindow, self).__init__(parent)
2383 self.text = QTextBrowser()
2384 self.text.setHtml(glb_help_text)
2385 self.text.setReadOnly(True)
2386 self.text.setOpenExternalLinks(True)
2388 self.setWidget(self.text)
2390 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2392 # Main window that only displays the help text
2394 class HelpOnlyWindow(QMainWindow):
2396 def __init__(self, parent=None):
2397 super(HelpOnlyWindow, self).__init__(parent)
2399 self.setMinimumSize(200, 100)
2400 self.resize(800, 600)
2401 self.setWindowTitle("Exported SQL Viewer Help")
2402 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2404 self.text = QTextBrowser()
2405 self.text.setHtml(glb_help_text)
2406 self.text.setReadOnly(True)
2407 self.text.setOpenExternalLinks(True)
2409 self.setCentralWidget(self.text)
2413 def ResizeFont(widget, diff):
2414 font = widget.font()
2415 sz = font.pointSize()
2416 font.setPointSize(sz + diff)
2417 widget.setFont(font)
2419 def ShrinkFont(widget):
2420 ResizeFont(widget, -1)
2422 def EnlargeFont(widget):
2423 ResizeFont(widget, 1)
2425 # Unique name for sub-windows
2427 def NumberedWindowName(name, nr):
2429 name += " <" + str(nr) + ">"
2432 def UniqueSubWindowName(mdi_area, name):
2435 unique_name = NumberedWindowName(name, nr)
2437 for sub_window in mdi_area.subWindowList():
2438 if sub_window.name == unique_name:
2447 def AddSubWindow(mdi_area, sub_window, name):
2448 unique_name = UniqueSubWindowName(mdi_area, name)
2449 sub_window.setMinimumSize(200, 100)
2450 sub_window.resize(800, 600)
2451 sub_window.setWindowTitle(unique_name)
2452 sub_window.setAttribute(Qt.WA_DeleteOnClose)
2453 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
2454 sub_window.name = unique_name
2455 mdi_area.addSubWindow(sub_window)
2460 class MainWindow(QMainWindow):
2462 def __init__(self, glb, parent=None):
2463 super(MainWindow, self).__init__(parent)
2467 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
2468 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
2469 self.setMinimumSize(200, 100)
2471 self.mdi_area = QMdiArea()
2472 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2473 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2475 self.setCentralWidget(self.mdi_area)
2477 menu = self.menuBar()
2479 file_menu = menu.addMenu("&File")
2480 file_menu.addAction(CreateExitAction(glb.app, self))
2482 edit_menu = menu.addMenu("&Edit")
2483 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
2484 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
2485 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
2486 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
2488 reports_menu = menu.addMenu("&Reports")
2489 if IsSelectable(glb.db, "calls"):
2490 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
2492 self.EventMenu(GetEventList(glb.db), reports_menu)
2494 if IsSelectable(glb.db, "calls"):
2495 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
2497 self.TableMenu(GetTableList(glb), menu)
2499 self.window_menu = WindowMenu(self.mdi_area, menu)
2501 help_menu = menu.addMenu("&Help")
2502 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
2505 win = self.mdi_area.activeSubWindow()
2508 win.find_bar.Activate()
2512 def FetchMoreRecords(self):
2513 win = self.mdi_area.activeSubWindow()
2516 win.fetch_bar.Activate()
2520 def ShrinkFont(self):
2521 win = self.mdi_area.activeSubWindow()
2522 ShrinkFont(win.view)
2524 def EnlargeFont(self):
2525 win = self.mdi_area.activeSubWindow()
2526 EnlargeFont(win.view)
2528 def EventMenu(self, events, reports_menu):
2530 for event in events:
2531 event = event.split(":")[0]
2532 if event == "branches":
2533 branches_events += 1
2535 for event in events:
2537 event = event.split(":")[0]
2538 if event == "branches":
2539 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
2540 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
2541 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
2542 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
2544 def TableMenu(self, tables, menu):
2545 table_menu = menu.addMenu("&Tables")
2546 for table in tables:
2547 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
2549 def NewCallGraph(self):
2550 CallGraphWindow(self.glb, self)
2552 def NewTopCalls(self):
2553 dialog = TopCallsDialog(self.glb, self)
2554 ret = dialog.exec_()
2556 TopCallsWindow(self.glb, dialog.report_vars, self)
2558 def NewBranchView(self, event_id):
2559 BranchWindow(self.glb, event_id, ReportVars(), self)
2561 def NewSelectedBranchView(self, event_id):
2562 dialog = SelectedBranchDialog(self.glb, self)
2563 ret = dialog.exec_()
2565 BranchWindow(self.glb, event_id, dialog.report_vars, self)
2567 def NewTableView(self, table_name):
2568 TableWindow(self.glb, table_name, self)
2571 HelpWindow(self.glb, self)
2575 class xed_state_t(Structure):
2582 class XEDInstruction():
2584 def __init__(self, libxed):
2585 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
2586 xedd_t = c_byte * 512
2587 self.xedd = xedd_t()
2588 self.xedp = addressof(self.xedd)
2589 libxed.xed_decoded_inst_zero(self.xedp)
2590 self.state = xed_state_t()
2591 self.statep = addressof(self.state)
2592 # Buffer for disassembled instruction text
2593 self.buffer = create_string_buffer(256)
2594 self.bufferp = addressof(self.buffer)
2600 self.libxed = CDLL("libxed.so")
2604 self.libxed = CDLL("/usr/local/lib/libxed.so")
2606 self.xed_tables_init = self.libxed.xed_tables_init
2607 self.xed_tables_init.restype = None
2608 self.xed_tables_init.argtypes = []
2610 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
2611 self.xed_decoded_inst_zero.restype = None
2612 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
2614 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
2615 self.xed_operand_values_set_mode.restype = None
2616 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
2618 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
2619 self.xed_decoded_inst_zero_keep_mode.restype = None
2620 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
2622 self.xed_decode = self.libxed.xed_decode
2623 self.xed_decode.restype = c_int
2624 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
2626 self.xed_format_context = self.libxed.xed_format_context
2627 self.xed_format_context.restype = c_uint
2628 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
2630 self.xed_tables_init()
2632 def Instruction(self):
2633 return XEDInstruction(self)
2635 def SetMode(self, inst, mode):
2637 inst.state.mode = 4 # 32-bit
2638 inst.state.width = 4 # 4 bytes
2640 inst.state.mode = 1 # 64-bit
2641 inst.state.width = 8 # 8 bytes
2642 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
2644 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
2645 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
2646 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
2649 # Use AT&T mode (2), alternative is Intel (3)
2650 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
2653 # Return instruction length and the disassembled instruction text
2654 # For now, assume the length is in byte 166
2655 return inst.xedd[166], inst.buffer.value
2657 def TryOpen(file_name):
2659 return open(file_name, "rb")
2664 result = sizeof(c_void_p)
2671 eclass = ord(header[4])
2672 encoding = ord(header[5])
2673 version = ord(header[6])
2674 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
2675 result = True if eclass == 2 else False
2682 def __init__(self, dbref, db, dbname):
2685 self.dbname = dbname
2686 self.home_dir = os.path.expanduser("~")
2687 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
2688 if self.buildid_dir:
2689 self.buildid_dir += "/.build-id/"
2691 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
2693 self.mainwindow = None
2694 self.instances_to_shutdown_on_exit = weakref.WeakSet()
2696 self.disassembler = LibXED()
2697 self.have_disassembler = True
2699 self.have_disassembler = False
2701 def FileFromBuildId(self, build_id):
2702 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
2703 return TryOpen(file_name)
2705 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
2706 # Assume current machine i.e. no support for virtualization
2707 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
2708 file_name = os.getenv("PERF_KCORE")
2709 f = TryOpen(file_name) if file_name else None
2712 # For now, no special handling if long_name is /proc/kcore
2713 f = TryOpen(long_name)
2716 f = self.FileFromBuildId(build_id)
2721 def AddInstanceToShutdownOnExit(self, instance):
2722 self.instances_to_shutdown_on_exit.add(instance)
2724 # Shutdown any background processes or threads
2725 def ShutdownInstances(self):
2726 for x in self.instances_to_shutdown_on_exit:
2732 # Database reference
2736 def __init__(self, is_sqlite3, dbname):
2737 self.is_sqlite3 = is_sqlite3
2738 self.dbname = dbname
2740 def Open(self, connection_name):
2741 dbname = self.dbname
2743 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
2745 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
2746 opts = dbname.split()
2749 opt = opt.split("=")
2750 if opt[0] == "hostname":
2751 db.setHostName(opt[1])
2752 elif opt[0] == "port":
2753 db.setPort(int(opt[1]))
2754 elif opt[0] == "username":
2755 db.setUserName(opt[1])
2756 elif opt[0] == "password":
2757 db.setPassword(opt[1])
2758 elif opt[0] == "dbname":
2763 db.setDatabaseName(dbname)
2765 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
2771 if (len(sys.argv) < 2):
2772 print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}"
2773 raise Exception("Too few arguments")
2775 dbname = sys.argv[1]
2776 if dbname == "--help-only":
2777 app = QApplication(sys.argv)
2778 mainwindow = HelpOnlyWindow()
2786 if f.read(15) == "SQLite format 3":
2792 dbref = DBRef(is_sqlite3, dbname)
2793 db, dbname = dbref.Open("main")
2794 glb = Glb(dbref, db, dbname)
2795 app = QApplication(sys.argv)
2797 mainwindow = MainWindow(glb)
2798 glb.mainwindow = mainwindow
2801 glb.ShutdownInstances()
2805 if __name__ == "__main__":