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, glb, parent=None):
171 super(TreeModel, self).__init__(parent)
173 self.root = self.GetRoot()
174 self.last_row_read = 0
176 def Item(self, parent):
178 return parent.internalPointer()
182 def rowCount(self, parent):
183 result = self.Item(parent).childCount()
186 self.dataChanged.emit(parent, parent)
189 def hasChildren(self, parent):
190 return self.Item(parent).hasChildren()
192 def headerData(self, section, orientation, role):
193 if role == Qt.TextAlignmentRole:
194 return self.columnAlignment(section)
195 if role != Qt.DisplayRole:
197 if orientation != Qt.Horizontal:
199 return self.columnHeader(section)
201 def parent(self, child):
202 child_item = child.internalPointer()
203 if child_item is self.root:
205 parent_item = child_item.getParentItem()
206 return self.createIndex(parent_item.getRow(), 0, parent_item)
208 def index(self, row, column, parent):
209 child_item = self.Item(parent).getChildItem(row)
210 return self.createIndex(row, column, child_item)
212 def DisplayData(self, item, index):
213 return item.getData(index.column())
215 def FetchIfNeeded(self, row):
216 if row > self.last_row_read:
217 self.last_row_read = row
218 if row + 10 >= self.root.child_count:
219 self.fetcher.Fetch(glb_chunk_sz)
221 def columnAlignment(self, column):
224 def columnFont(self, column):
227 def data(self, index, role):
228 if role == Qt.TextAlignmentRole:
229 return self.columnAlignment(index.column())
230 if role == Qt.FontRole:
231 return self.columnFont(index.column())
232 if role != Qt.DisplayRole:
234 item = index.internalPointer()
235 return self.DisplayData(item, index)
239 class TableModel(QAbstractTableModel):
241 def __init__(self, parent=None):
242 super(TableModel, self).__init__(parent)
244 self.child_items = []
245 self.last_row_read = 0
247 def Item(self, parent):
249 return parent.internalPointer()
253 def rowCount(self, parent):
254 return self.child_count
256 def headerData(self, section, orientation, role):
257 if role == Qt.TextAlignmentRole:
258 return self.columnAlignment(section)
259 if role != Qt.DisplayRole:
261 if orientation != Qt.Horizontal:
263 return self.columnHeader(section)
265 def index(self, row, column, parent):
266 return self.createIndex(row, column, self.child_items[row])
268 def DisplayData(self, item, index):
269 return item.getData(index.column())
271 def FetchIfNeeded(self, row):
272 if row > self.last_row_read:
273 self.last_row_read = row
274 if row + 10 >= self.child_count:
275 self.fetcher.Fetch(glb_chunk_sz)
277 def columnAlignment(self, column):
280 def columnFont(self, column):
283 def data(self, index, role):
284 if role == Qt.TextAlignmentRole:
285 return self.columnAlignment(index.column())
286 if role == Qt.FontRole:
287 return self.columnFont(index.column())
288 if role != Qt.DisplayRole:
290 item = index.internalPointer()
291 return self.DisplayData(item, index)
295 model_cache = weakref.WeakValueDictionary()
296 model_cache_lock = threading.Lock()
298 def LookupCreateModel(model_name, create_fn):
299 model_cache_lock.acquire()
301 model = model_cache[model_name]
306 model_cache[model_name] = model
307 model_cache_lock.release()
314 def __init__(self, parent, finder, is_reg_expr=False):
317 self.last_value = None
318 self.last_pattern = None
320 label = QLabel("Find:")
321 label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
323 self.textbox = QComboBox()
324 self.textbox.setEditable(True)
325 self.textbox.currentIndexChanged.connect(self.ValueChanged)
327 self.progress = QProgressBar()
328 self.progress.setRange(0, 0)
332 self.pattern = QCheckBox("Regular Expression")
334 self.pattern = QCheckBox("Pattern")
335 self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
337 self.next_button = QToolButton()
338 self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
339 self.next_button.released.connect(lambda: self.NextPrev(1))
341 self.prev_button = QToolButton()
342 self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
343 self.prev_button.released.connect(lambda: self.NextPrev(-1))
345 self.close_button = QToolButton()
346 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
347 self.close_button.released.connect(self.Deactivate)
349 self.hbox = QHBoxLayout()
350 self.hbox.setContentsMargins(0, 0, 0, 0)
352 self.hbox.addWidget(label)
353 self.hbox.addWidget(self.textbox)
354 self.hbox.addWidget(self.progress)
355 self.hbox.addWidget(self.pattern)
356 self.hbox.addWidget(self.next_button)
357 self.hbox.addWidget(self.prev_button)
358 self.hbox.addWidget(self.close_button)
361 self.bar.setLayout(self.hbox);
369 self.textbox.setFocus()
371 def Deactivate(self):
375 self.textbox.setEnabled(False)
377 self.next_button.hide()
378 self.prev_button.hide()
382 self.textbox.setEnabled(True)
385 self.next_button.show()
386 self.prev_button.show()
388 def Find(self, direction):
389 value = self.textbox.currentText()
390 pattern = self.pattern.isChecked()
391 self.last_value = value
392 self.last_pattern = pattern
393 self.finder.Find(value, direction, pattern, self.context)
395 def ValueChanged(self):
396 value = self.textbox.currentText()
397 pattern = self.pattern.isChecked()
398 index = self.textbox.currentIndex()
399 data = self.textbox.itemData(index)
400 # Store the pattern in the combo box to keep it with the text value
402 self.textbox.setItemData(index, pattern)
404 self.pattern.setChecked(data)
407 def NextPrev(self, direction):
408 value = self.textbox.currentText()
409 pattern = self.pattern.isChecked()
410 if value != self.last_value:
411 index = self.textbox.findText(value)
412 # Allow for a button press before the value has been added to the combo box
414 index = self.textbox.count()
415 self.textbox.addItem(value, pattern)
416 self.textbox.setCurrentIndex(index)
419 self.textbox.setItemData(index, pattern)
420 elif pattern != self.last_pattern:
421 # Keep the pattern recorded in the combo box up to date
422 index = self.textbox.currentIndex()
423 self.textbox.setItemData(index, pattern)
427 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
429 # Context-sensitive call graph data model item base
431 class CallGraphLevelItemBase(object):
433 def __init__(self, glb, row, parent_item):
436 self.parent_item = parent_item
437 self.query_done = False;
439 self.child_items = []
441 def getChildItem(self, row):
442 return self.child_items[row]
444 def getParentItem(self):
445 return self.parent_item
450 def childCount(self):
451 if not self.query_done:
453 if not self.child_count:
455 return self.child_count
457 def hasChildren(self):
458 if not self.query_done:
460 return self.child_count > 0
462 def getData(self, column):
463 return self.data[column]
465 # Context-sensitive call graph data model level 2+ item base
467 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
469 def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
470 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
471 self.comm_id = comm_id
472 self.thread_id = thread_id
473 self.call_path_id = call_path_id
474 self.branch_count = branch_count
478 self.query_done = True;
479 query = QSqlQuery(self.glb.db)
480 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
482 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
483 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
484 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
485 " WHERE parent_call_path_id = " + str(self.call_path_id) +
486 " AND comm_id = " + str(self.comm_id) +
487 " AND thread_id = " + str(self.thread_id) +
488 " GROUP BY call_path_id, name, short_name"
489 " ORDER BY call_path_id")
491 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)
492 self.child_items.append(child_item)
493 self.child_count += 1
495 # Context-sensitive call graph data model level three item
497 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
499 def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
500 super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
502 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
503 self.dbid = call_path_id
505 # Context-sensitive call graph data model level two item
507 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
509 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
510 super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
511 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
512 self.dbid = thread_id
515 super(CallGraphLevelTwoItem, self).Select()
516 for child_item in self.child_items:
517 self.time += child_item.time
518 self.branch_count += child_item.branch_count
519 for child_item in self.child_items:
520 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
521 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
523 # Context-sensitive call graph data model level one item
525 class CallGraphLevelOneItem(CallGraphLevelItemBase):
527 def __init__(self, glb, row, comm_id, comm, parent_item):
528 super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
529 self.data = [comm, "", "", "", "", "", ""]
533 self.query_done = True;
534 query = QSqlQuery(self.glb.db)
535 QueryExec(query, "SELECT thread_id, pid, tid"
537 " INNER JOIN threads ON thread_id = threads.id"
538 " WHERE comm_id = " + str(self.dbid))
540 child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
541 self.child_items.append(child_item)
542 self.child_count += 1
544 # Context-sensitive call graph data model root item
546 class CallGraphRootItem(CallGraphLevelItemBase):
548 def __init__(self, glb):
549 super(CallGraphRootItem, self).__init__(glb, 0, None)
551 self.query_done = True;
552 query = QSqlQuery(glb.db)
553 QueryExec(query, "SELECT id, comm FROM comms")
555 if not query.value(0):
557 child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
558 self.child_items.append(child_item)
559 self.child_count += 1
561 # Context-sensitive call graph data model base
563 class CallGraphModelBase(TreeModel):
565 def __init__(self, glb, parent=None):
566 super(CallGraphModelBase, self).__init__(glb, parent)
568 def FindSelect(self, value, pattern, query):
570 # postgresql and sqlite pattern patching differences:
571 # postgresql LIKE is case sensitive but sqlite LIKE is not
572 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
573 # postgresql supports ILIKE which is case insensitive
574 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
575 if not self.glb.dbref.is_sqlite3:
577 s = value.replace("%", "\%")
578 s = s.replace("_", "\_")
579 # Translate * and ? into SQL LIKE pattern characters % and _
580 trans = string.maketrans("*?", "%_")
581 match = " LIKE '" + str(s).translate(trans) + "'"
583 match = " GLOB '" + str(value) + "'"
585 match = " = '" + str(value) + "'"
586 self.DoFindSelect(query, match)
588 def Found(self, query, found):
590 return self.FindPath(query)
593 def FindValue(self, value, pattern, query, last_value, last_pattern):
594 if last_value == value and pattern == last_pattern:
595 found = query.first()
597 self.FindSelect(value, pattern, query)
599 return self.Found(query, found)
601 def FindNext(self, query):
604 found = query.first()
605 return self.Found(query, found)
607 def FindPrev(self, query):
608 found = query.previous()
611 return self.Found(query, found)
613 def FindThread(self, c):
614 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
615 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
616 elif c.direction > 0:
617 ids = self.FindNext(c.query)
619 ids = self.FindPrev(c.query)
622 def Find(self, value, direction, pattern, context, callback):
624 def __init__(self, *x):
625 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
626 def Update(self, *x):
627 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
629 context[0].Update(value, direction, pattern)
631 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
632 # Use a thread so the UI is not blocked during the SELECT
633 thread = Thread(self.FindThread, context[0])
634 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
637 def FindDone(self, thread, callback, ids):
640 # Context-sensitive call graph data model
642 class CallGraphModel(CallGraphModelBase):
644 def __init__(self, glb, parent=None):
645 super(CallGraphModel, self).__init__(glb, parent)
648 return CallGraphRootItem(self.glb)
650 def columnCount(self, parent=None):
653 def columnHeader(self, column):
654 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
655 return headers[column]
657 def columnAlignment(self, column):
658 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
659 return alignment[column]
661 def DoFindSelect(self, query, match):
662 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
664 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
665 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
666 " WHERE symbols.name" + match +
667 " GROUP BY comm_id, thread_id, call_path_id"
668 " ORDER BY comm_id, thread_id, call_path_id")
670 def FindPath(self, query):
671 # Turn the query result into a list of ids that the tree view can walk
672 # to open the tree at the right place.
674 parent_id = query.value(0)
676 ids.insert(0, parent_id)
677 q2 = QSqlQuery(self.glb.db)
678 QueryExec(q2, "SELECT parent_id"
680 " WHERE id = " + str(parent_id))
683 parent_id = q2.value(0)
684 # The call path root is not used
687 ids.insert(0, query.value(2))
688 ids.insert(0, query.value(1))
691 # Vertical widget layout
695 def __init__(self, w1, w2, w3=None):
696 self.vbox = QWidget()
697 self.vbox.setLayout(QVBoxLayout());
699 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
701 self.vbox.layout().addWidget(w1)
702 self.vbox.layout().addWidget(w2)
704 self.vbox.layout().addWidget(w3)
711 class TreeWindowBase(QMdiSubWindow):
713 def __init__(self, parent=None):
714 super(TreeWindowBase, self).__init__(parent)
720 def DisplayFound(self, ids):
723 parent = QModelIndex()
726 n = self.model.rowCount(parent)
727 for row in xrange(n):
728 child = self.model.index(row, 0, parent)
729 if child.internalPointer().dbid == dbid:
731 self.view.setCurrentIndex(child)
738 def Find(self, value, direction, pattern, context):
741 self.model.Find(value, direction, pattern, context, self.FindDone)
743 def FindDone(self, ids):
745 if not self.DisplayFound(ids):
749 self.find_bar.NotFound()
752 # Context-sensitive call graph window
754 class CallGraphWindow(TreeWindowBase):
756 def __init__(self, glb, parent=None):
757 super(CallGraphWindow, self).__init__(parent)
759 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
761 self.view = QTreeView()
762 self.view.setModel(self.model)
764 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
765 self.view.setColumnWidth(c, w)
767 self.find_bar = FindBar(self, self)
769 self.vbox = VBox(self.view, self.find_bar.Widget())
771 self.setWidget(self.vbox.Widget())
773 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
775 # Child data item finder
777 class ChildDataItemFinder():
779 def __init__(self, root):
781 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
785 def FindSelect(self):
788 pattern = re.compile(self.value)
789 for child in self.root.child_items:
790 for column_data in child.data:
791 if re.search(pattern, str(column_data)) is not None:
792 self.rows.append(child.row)
795 for child in self.root.child_items:
796 for column_data in child.data:
797 if self.value in str(column_data):
798 self.rows.append(child.row)
803 if self.last_value != self.value or self.pattern != self.last_pattern:
805 if not len(self.rows):
807 return self.rows[self.pos]
809 def FindThread(self):
810 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
811 row = self.FindValue()
813 if self.direction > 0:
815 if self.pos >= len(self.rows):
820 self.pos = len(self.rows) - 1
821 row = self.rows[self.pos]
826 def Find(self, value, direction, pattern, context, callback):
827 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
828 # Use a thread so the UI is not blocked
829 thread = Thread(self.FindThread)
830 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
833 def FindDone(self, thread, callback, row):
836 # Number of database records to fetch in one go
840 # size of pickled integer big enough for record size
844 # Background process for SQL data fetcher
846 class SQLFetcherProcess():
848 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
849 # Need a unique connection name
850 conn_name = "SQLFetcher" + str(os.getpid())
851 self.db, dbname = dbref.Open(conn_name)
856 self.fetch_count = fetch_count
857 self.fetching_done = fetching_done
858 self.process_target = process_target
859 self.wait_event = wait_event
860 self.fetched_event = fetched_event
862 self.query = QSqlQuery(self.db)
863 self.query_limit = 0 if "$$last_id$$" in sql else 2
867 self.local_head = self.head.value
868 self.local_tail = self.tail.value
872 if self.query_limit == 1:
874 self.query_limit -= 1
875 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
876 QueryExec(self.query, stmt)
879 if not self.query.next():
881 if not self.query.next():
883 self.last_id = self.query.value(0)
884 return self.prep(self.query)
886 def WaitForTarget(self):
888 self.wait_event.clear()
889 target = self.process_target.value
890 if target > self.fetched or target < 0:
892 self.wait_event.wait()
895 def HasSpace(self, sz):
896 if self.local_tail <= self.local_head:
897 space = len(self.buffer) - self.local_head
901 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
902 nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL)
903 self.buffer[self.local_head : self.local_head + len(nd)] = nd
905 if self.local_tail - self.local_head > sz:
909 def WaitForSpace(self, sz):
910 if self.HasSpace(sz):
913 self.wait_event.clear()
914 self.local_tail = self.tail.value
915 if self.HasSpace(sz):
917 self.wait_event.wait()
919 def AddToBuffer(self, obj):
920 d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL)
922 nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL)
924 self.WaitForSpace(sz)
925 pos = self.local_head
926 self.buffer[pos : pos + len(nd)] = nd
927 self.buffer[pos + glb_nsz : pos + sz] = d
928 self.local_head += sz
930 def FetchBatch(self, batch_size):
932 while batch_size > fetched:
937 self.AddToBuffer(obj)
940 self.fetched += fetched
941 with self.fetch_count.get_lock():
942 self.fetch_count.value += fetched
943 self.head.value = self.local_head
944 self.fetched_event.set()
948 target = self.WaitForTarget()
951 batch_size = min(glb_chunk_sz, target - self.fetched)
952 self.FetchBatch(batch_size)
953 self.fetching_done.value = True
954 self.fetched_event.set()
956 def SQLFetcherFn(*x):
957 process = SQLFetcherProcess(*x)
962 class SQLFetcher(QObject):
964 done = Signal(object)
966 def __init__(self, glb, sql, prep, process_data, parent=None):
967 super(SQLFetcher, self).__init__(parent)
968 self.process_data = process_data
973 self.buffer_size = 16 * 1024 * 1024
974 self.buffer = Array(c_char, self.buffer_size, lock=False)
975 self.head = Value(c_longlong)
976 self.tail = Value(c_longlong)
978 self.fetch_count = Value(c_longlong)
979 self.fetching_done = Value(c_bool)
981 self.process_target = Value(c_longlong)
982 self.wait_event = Event()
983 self.fetched_event = Event()
984 glb.AddInstanceToShutdownOnExit(self)
985 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))
987 self.thread = Thread(self.Thread)
988 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
992 # Tell the thread and process to exit
993 self.process_target.value = -1
994 self.wait_event.set()
996 self.fetching_done.value = True
997 self.fetched_event.set()
1003 self.fetched_event.clear()
1004 fetch_count = self.fetch_count.value
1005 if fetch_count != self.last_count:
1007 if self.fetching_done.value:
1010 self.fetched_event.wait()
1011 count = fetch_count - self.last_count
1012 self.last_count = fetch_count
1013 self.fetched += count
1016 def Fetch(self, nr):
1018 # -1 inidcates there are no more
1020 result = self.fetched
1021 extra = result + nr - self.target
1023 self.target += extra
1024 # process_target < 0 indicates shutting down
1025 if self.process_target.value >= 0:
1026 self.process_target.value = self.target
1027 self.wait_event.set()
1030 def RemoveFromBuffer(self):
1031 pos = self.local_tail
1032 if len(self.buffer) - pos < glb_nsz:
1034 n = cPickle.loads(self.buffer[pos : pos + glb_nsz])
1037 n = cPickle.loads(self.buffer[0 : glb_nsz])
1039 obj = cPickle.loads(self.buffer[pos : pos + n])
1040 self.local_tail = pos + n
1043 def ProcessData(self, count):
1044 for i in xrange(count):
1045 obj = self.RemoveFromBuffer()
1046 self.process_data(obj)
1047 self.tail.value = self.local_tail
1048 self.wait_event.set()
1049 self.done.emit(count)
1051 # Fetch more records bar
1053 class FetchMoreRecordsBar():
1055 def __init__(self, model, parent):
1058 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1059 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1061 self.fetch_count = QSpinBox()
1062 self.fetch_count.setRange(1, 1000000)
1063 self.fetch_count.setValue(10)
1064 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1066 self.fetch = QPushButton("Go!")
1067 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1068 self.fetch.released.connect(self.FetchMoreRecords)
1070 self.progress = QProgressBar()
1071 self.progress.setRange(0, 100)
1072 self.progress.hide()
1074 self.done_label = QLabel("All records fetched")
1075 self.done_label.hide()
1077 self.spacer = QLabel("")
1079 self.close_button = QToolButton()
1080 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1081 self.close_button.released.connect(self.Deactivate)
1083 self.hbox = QHBoxLayout()
1084 self.hbox.setContentsMargins(0, 0, 0, 0)
1086 self.hbox.addWidget(self.label)
1087 self.hbox.addWidget(self.fetch_count)
1088 self.hbox.addWidget(self.fetch)
1089 self.hbox.addWidget(self.spacer)
1090 self.hbox.addWidget(self.progress)
1091 self.hbox.addWidget(self.done_label)
1092 self.hbox.addWidget(self.close_button)
1094 self.bar = QWidget()
1095 self.bar.setLayout(self.hbox);
1098 self.in_progress = False
1099 self.model.progress.connect(self.Progress)
1103 if not model.HasMoreRecords():
1111 self.fetch.setFocus()
1113 def Deactivate(self):
1116 def Enable(self, enable):
1117 self.fetch.setEnabled(enable)
1118 self.fetch_count.setEnabled(enable)
1124 self.progress.show()
1127 self.in_progress = False
1129 self.progress.hide()
1134 return self.fetch_count.value() * glb_chunk_sz
1140 self.fetch_count.hide()
1143 self.done_label.show()
1145 def Progress(self, count):
1146 if self.in_progress:
1148 percent = ((count - self.start) * 100) / self.Target()
1152 self.progress.setValue(percent)
1154 # Count value of zero means no more records
1157 def FetchMoreRecords(self):
1160 self.progress.setValue(0)
1162 self.in_progress = True
1163 self.start = self.model.FetchMoreRecords(self.Target())
1165 # Brance data model level two item
1167 class BranchLevelTwoItem():
1169 def __init__(self, row, text, parent_item):
1171 self.parent_item = parent_item
1172 self.data = [""] * 8
1176 def getParentItem(self):
1177 return self.parent_item
1182 def childCount(self):
1185 def hasChildren(self):
1188 def getData(self, column):
1189 return self.data[column]
1191 # Brance data model level one item
1193 class BranchLevelOneItem():
1195 def __init__(self, glb, row, data, parent_item):
1198 self.parent_item = parent_item
1199 self.child_count = 0
1200 self.child_items = []
1201 self.data = data[1:]
1204 self.query_done = False
1206 def getChildItem(self, row):
1207 return self.child_items[row]
1209 def getParentItem(self):
1210 return self.parent_item
1216 self.query_done = True
1218 if not self.glb.have_disassembler:
1221 query = QSqlQuery(self.glb.db)
1223 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1225 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1226 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1227 " WHERE samples.id = " + str(self.dbid))
1228 if not query.next():
1230 cpu = query.value(0)
1231 dso = query.value(1)
1232 sym = query.value(2)
1233 if dso == 0 or sym == 0:
1235 off = query.value(3)
1236 short_name = query.value(4)
1237 long_name = query.value(5)
1238 build_id = query.value(6)
1239 sym_start = query.value(7)
1242 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1244 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1245 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1246 " ORDER BY samples.id"
1248 if not query.next():
1250 if query.value(0) != dso:
1251 # Cannot disassemble from one dso to another
1253 bsym = query.value(1)
1254 boff = query.value(2)
1255 bsym_start = query.value(3)
1258 tot = bsym_start + boff + 1 - sym_start - off
1259 if tot <= 0 or tot > 16384:
1262 inst = self.glb.disassembler.Instruction()
1263 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1266 mode = 0 if Is64Bit(f) else 1
1267 self.glb.disassembler.SetMode(inst, mode)
1270 buf = create_string_buffer(tot + 16)
1271 f.seek(sym_start + off)
1272 buf.value = f.read(buf_sz)
1273 buf_ptr = addressof(buf)
1276 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1278 byte_str = tohex(ip).rjust(16)
1279 for k in xrange(cnt):
1280 byte_str += " %02x" % ord(buf[i])
1285 self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
1286 self.child_count += 1
1294 def childCount(self):
1295 if not self.query_done:
1297 if not self.child_count:
1299 return self.child_count
1301 def hasChildren(self):
1302 if not self.query_done:
1304 return self.child_count > 0
1306 def getData(self, column):
1307 return self.data[column]
1309 # Brance data model root item
1311 class BranchRootItem():
1314 self.child_count = 0
1315 self.child_items = []
1318 def getChildItem(self, row):
1319 return self.child_items[row]
1321 def getParentItem(self):
1327 def childCount(self):
1328 return self.child_count
1330 def hasChildren(self):
1331 return self.child_count > 0
1333 def getData(self, column):
1336 # Branch data preparation
1338 def BranchDataPrep(query):
1340 for i in xrange(0, 8):
1341 data.append(query.value(i))
1342 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1343 " (" + dsoname(query.value(11)) + ")" + " -> " +
1344 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1345 " (" + dsoname(query.value(15)) + ")")
1350 class BranchModel(TreeModel):
1352 progress = Signal(object)
1354 def __init__(self, glb, event_id, where_clause, parent=None):
1355 super(BranchModel, self).__init__(glb, parent)
1356 self.event_id = event_id
1359 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1360 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1361 " ip, symbols.name, sym_offset, dsos.short_name,"
1362 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1364 " INNER JOIN comms ON comm_id = comms.id"
1365 " INNER JOIN threads ON thread_id = threads.id"
1366 " INNER JOIN branch_types ON branch_type = branch_types.id"
1367 " INNER JOIN symbols ON symbol_id = symbols.id"
1368 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1369 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1370 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1371 " WHERE samples.id > $$last_id$$" + where_clause +
1372 " AND evsel_id = " + str(self.event_id) +
1373 " ORDER BY samples.id"
1374 " LIMIT " + str(glb_chunk_sz))
1375 self.fetcher = SQLFetcher(glb, sql, BranchDataPrep, self.AddSample)
1376 self.fetcher.done.connect(self.Update)
1377 self.fetcher.Fetch(glb_chunk_sz)
1380 return BranchRootItem()
1382 def columnCount(self, parent=None):
1385 def columnHeader(self, column):
1386 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1388 def columnFont(self, column):
1391 return QFont("Monospace")
1393 def DisplayData(self, item, index):
1395 self.FetchIfNeeded(item.row)
1396 return item.getData(index.column())
1398 def AddSample(self, data):
1399 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1400 self.root.child_items.append(child)
1403 def Update(self, fetched):
1406 self.progress.emit(0)
1407 child_count = self.root.child_count
1408 count = self.populated - child_count
1410 parent = QModelIndex()
1411 self.beginInsertRows(parent, child_count, child_count + count - 1)
1412 self.insertRows(child_count, count, parent)
1413 self.root.child_count += count
1414 self.endInsertRows()
1415 self.progress.emit(self.root.child_count)
1417 def FetchMoreRecords(self, count):
1418 current = self.root.child_count
1420 self.fetcher.Fetch(count)
1422 self.progress.emit(0)
1425 def HasMoreRecords(self):
1432 def __init__(self, name = "", where_clause = "", limit = ""):
1434 self.where_clause = where_clause
1438 return str(self.where_clause + ";" + self.limit)
1442 class BranchWindow(QMdiSubWindow):
1444 def __init__(self, glb, event_id, report_vars, parent=None):
1445 super(BranchWindow, self).__init__(parent)
1447 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
1449 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1451 self.view = QTreeView()
1452 self.view.setUniformRowHeights(True)
1453 self.view.setModel(self.model)
1455 self.ResizeColumnsToContents()
1457 self.find_bar = FindBar(self, self, True)
1459 self.finder = ChildDataItemFinder(self.model.root)
1461 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1463 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1465 self.setWidget(self.vbox.Widget())
1467 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1469 def ResizeColumnToContents(self, column, n):
1470 # Using the view's resizeColumnToContents() here is extrememly slow
1471 # so implement a crude alternative
1472 mm = "MM" if column else "MMMM"
1473 font = self.view.font()
1474 metrics = QFontMetrics(font)
1476 for row in xrange(n):
1477 val = self.model.root.child_items[row].data[column]
1478 len = metrics.width(str(val) + mm)
1479 max = len if len > max else max
1480 val = self.model.columnHeader(column)
1481 len = metrics.width(str(val) + mm)
1482 max = len if len > max else max
1483 self.view.setColumnWidth(column, max)
1485 def ResizeColumnsToContents(self):
1486 n = min(self.model.root.child_count, 100)
1488 # No data yet, so connect a signal to notify when there is
1489 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1491 columns = self.model.columnCount()
1492 for i in xrange(columns):
1493 self.ResizeColumnToContents(i, n)
1495 def UpdateColumnWidths(self, *x):
1496 # This only needs to be done once, so disconnect the signal now
1497 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1498 self.ResizeColumnsToContents()
1500 def Find(self, value, direction, pattern, context):
1501 self.view.setFocus()
1502 self.find_bar.Busy()
1503 self.finder.Find(value, direction, pattern, context, self.FindDone)
1505 def FindDone(self, row):
1506 self.find_bar.Idle()
1508 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1510 self.find_bar.NotFound()
1512 # Line edit data item
1514 class LineEditDataItem(object):
1516 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1519 self.placeholder_text = placeholder_text
1520 self.parent = parent
1523 self.value = default
1525 self.widget = QLineEdit(default)
1526 self.widget.editingFinished.connect(self.Validate)
1527 self.widget.textChanged.connect(self.Invalidate)
1530 self.validated = True
1532 if placeholder_text:
1533 self.widget.setPlaceholderText(placeholder_text)
1535 def TurnTextRed(self):
1537 palette = QPalette()
1538 palette.setColor(QPalette.Text,Qt.red)
1539 self.widget.setPalette(palette)
1542 def TurnTextNormal(self):
1544 palette = QPalette()
1545 self.widget.setPalette(palette)
1548 def InvalidValue(self, value):
1551 self.error = self.label + " invalid value '" + value + "'"
1552 self.parent.ShowMessage(self.error)
1554 def Invalidate(self):
1555 self.validated = False
1557 def DoValidate(self, input_string):
1558 self.value = input_string.strip()
1561 self.validated = True
1563 self.TurnTextNormal()
1564 self.parent.ClearMessage()
1565 input_string = self.widget.text()
1566 if not len(input_string.strip()):
1569 self.DoValidate(input_string)
1572 if not self.validated:
1575 self.parent.ShowMessage(self.error)
1579 def IsNumber(self, value):
1584 return str(x) == value
1586 # Non-negative integer ranges dialog data item
1588 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1590 def __init__(self, glb, label, placeholder_text, column_name, parent):
1591 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1593 self.column_name = column_name
1595 def DoValidate(self, input_string):
1598 for value in [x.strip() for x in input_string.split(",")]:
1600 vrange = value.split("-")
1601 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1602 return self.InvalidValue(value)
1603 ranges.append(vrange)
1605 if not self.IsNumber(value):
1606 return self.InvalidValue(value)
1607 singles.append(value)
1608 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1610 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1611 self.value = " OR ".join(ranges)
1613 # Positive integer dialog data item
1615 class PositiveIntegerDataItem(LineEditDataItem):
1617 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1618 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1620 def DoValidate(self, input_string):
1621 if not self.IsNumber(input_string.strip()):
1622 return self.InvalidValue(input_string)
1623 value = int(input_string.strip())
1625 return self.InvalidValue(input_string)
1626 self.value = str(value)
1628 # Dialog data item converted and validated using a SQL table
1630 class SQLTableDataItem(LineEditDataItem):
1632 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1633 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
1635 self.table_name = table_name
1636 self.match_column = match_column
1637 self.column_name1 = column_name1
1638 self.column_name2 = column_name2
1640 def ValueToIds(self, value):
1642 query = QSqlQuery(self.glb.db)
1643 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1644 ret = query.exec_(stmt)
1647 ids.append(str(query.value(0)))
1650 def DoValidate(self, input_string):
1652 for value in [x.strip() for x in input_string.split(",")]:
1653 ids = self.ValueToIds(value)
1657 return self.InvalidValue(value)
1658 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1659 if self.column_name2:
1660 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1662 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
1664 class SampleTimeRangesDataItem(LineEditDataItem):
1666 def __init__(self, glb, label, placeholder_text, column_name, parent):
1667 self.column_name = column_name
1671 self.last_time = 2 ** 64
1673 query = QSqlQuery(glb.db)
1674 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1676 self.last_id = int(query.value(0))
1677 self.last_time = int(query.value(1))
1678 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1680 self.first_time = int(query.value(0))
1681 if placeholder_text:
1682 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1684 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1686 def IdBetween(self, query, lower_id, higher_id, order):
1687 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1689 return True, int(query.value(0))
1693 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1694 query = QSqlQuery(self.glb.db)
1696 next_id = int((lower_id + higher_id) / 2)
1697 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1698 if not query.next():
1699 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1701 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1703 return str(higher_id)
1705 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1706 next_time = int(query.value(0))
1708 if target_time > next_time:
1712 if higher_id <= lower_id + 1:
1713 return str(higher_id)
1715 if target_time >= next_time:
1719 if higher_id <= lower_id + 1:
1720 return str(lower_id)
1722 def ConvertRelativeTime(self, val):
1727 elif suffix == "us":
1729 elif suffix == "ns":
1733 val = val[:-2].strip()
1734 if not self.IsNumber(val):
1736 val = int(val) * mult
1738 val += self.first_time
1740 val += self.last_time
1743 def ConvertTimeRange(self, vrange):
1745 vrange[0] = str(self.first_time)
1747 vrange[1] = str(self.last_time)
1748 vrange[0] = self.ConvertRelativeTime(vrange[0])
1749 vrange[1] = self.ConvertRelativeTime(vrange[1])
1750 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1752 beg_range = max(int(vrange[0]), self.first_time)
1753 end_range = min(int(vrange[1]), self.last_time)
1754 if beg_range > self.last_time or end_range < self.first_time:
1756 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
1757 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
1760 def AddTimeRange(self, value, ranges):
1761 n = value.count("-")
1765 if value.split("-")[1].strip() == "":
1771 pos = findnth(value, "-", n)
1772 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
1773 if self.ConvertTimeRange(vrange):
1774 ranges.append(vrange)
1778 def DoValidate(self, input_string):
1780 for value in [x.strip() for x in input_string.split(",")]:
1781 if not self.AddTimeRange(value, ranges):
1782 return self.InvalidValue(value)
1783 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1784 self.value = " OR ".join(ranges)
1786 # Report Dialog Base
1788 class ReportDialogBase(QDialog):
1790 def __init__(self, glb, title, items, partial, parent=None):
1791 super(ReportDialogBase, self).__init__(parent)
1795 self.report_vars = ReportVars()
1797 self.setWindowTitle(title)
1798 self.setMinimumWidth(600)
1800 self.data_items = [x(glb, self) for x in items]
1802 self.partial = partial
1804 self.grid = QGridLayout()
1806 for row in xrange(len(self.data_items)):
1807 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
1808 self.grid.addWidget(self.data_items[row].widget, row, 1)
1810 self.status = QLabel()
1812 self.ok_button = QPushButton("Ok", self)
1813 self.ok_button.setDefault(True)
1814 self.ok_button.released.connect(self.Ok)
1815 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1817 self.cancel_button = QPushButton("Cancel", self)
1818 self.cancel_button.released.connect(self.reject)
1819 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1821 self.hbox = QHBoxLayout()
1822 #self.hbox.addStretch()
1823 self.hbox.addWidget(self.status)
1824 self.hbox.addWidget(self.ok_button)
1825 self.hbox.addWidget(self.cancel_button)
1827 self.vbox = QVBoxLayout()
1828 self.vbox.addLayout(self.grid)
1829 self.vbox.addLayout(self.hbox)
1831 self.setLayout(self.vbox);
1834 vars = self.report_vars
1835 for d in self.data_items:
1836 if d.id == "REPORTNAME":
1839 self.ShowMessage("Report name is required")
1841 for d in self.data_items:
1844 for d in self.data_items[1:]:
1846 vars.limit = d.value
1848 if len(vars.where_clause):
1849 vars.where_clause += " AND "
1850 vars.where_clause += d.value
1851 if len(vars.where_clause):
1853 vars.where_clause = " AND ( " + vars.where_clause + " ) "
1855 vars.where_clause = " WHERE " + vars.where_clause + " "
1858 def ShowMessage(self, msg):
1859 self.status.setText("<font color=#FF0000>" + msg)
1861 def ClearMessage(self):
1862 self.status.setText("")
1864 # Selected branch report creation dialog
1866 class SelectedBranchDialog(ReportDialogBase):
1868 def __init__(self, glb, parent=None):
1869 title = "Selected Branches"
1870 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
1871 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
1872 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
1873 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
1874 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
1875 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
1876 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
1877 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
1878 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
1879 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
1883 def GetEventList(db):
1885 query = QSqlQuery(db)
1886 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
1888 events.append(query.value(0))
1891 # Is a table selectable
1893 def IsSelectable(db, table):
1894 query = QSqlQuery(db)
1896 QueryExec(query, "SELECT * FROM " + table + " LIMIT 1")
1901 # SQL data preparation
1903 def SQLTableDataPrep(query, count):
1905 for i in xrange(count):
1906 data.append(query.value(i))
1909 # SQL table data model item
1911 class SQLTableItem():
1913 def __init__(self, row, data):
1917 def getData(self, column):
1918 return self.data[column]
1920 # SQL table data model
1922 class SQLTableModel(TableModel):
1924 progress = Signal(object)
1926 def __init__(self, glb, sql, column_headers, parent=None):
1927 super(SQLTableModel, self).__init__(parent)
1931 self.column_headers = column_headers
1932 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): SQLTableDataPrep(x, y), self.AddSample)
1933 self.fetcher.done.connect(self.Update)
1934 self.fetcher.Fetch(glb_chunk_sz)
1936 def DisplayData(self, item, index):
1937 self.FetchIfNeeded(item.row)
1938 return item.getData(index.column())
1940 def AddSample(self, data):
1941 child = SQLTableItem(self.populated, data)
1942 self.child_items.append(child)
1945 def Update(self, fetched):
1948 self.progress.emit(0)
1949 child_count = self.child_count
1950 count = self.populated - child_count
1952 parent = QModelIndex()
1953 self.beginInsertRows(parent, child_count, child_count + count - 1)
1954 self.insertRows(child_count, count, parent)
1955 self.child_count += count
1956 self.endInsertRows()
1957 self.progress.emit(self.child_count)
1959 def FetchMoreRecords(self, count):
1960 current = self.child_count
1962 self.fetcher.Fetch(count)
1964 self.progress.emit(0)
1967 def HasMoreRecords(self):
1970 def columnCount(self, parent=None):
1971 return len(self.column_headers)
1973 def columnHeader(self, column):
1974 return self.column_headers[column]
1976 # SQL automatic table data model
1978 class SQLAutoTableModel(SQLTableModel):
1980 def __init__(self, glb, table_name, parent=None):
1981 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
1982 if table_name == "comm_threads_view":
1983 # For now, comm_threads_view has no id column
1984 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
1986 query = QSqlQuery(glb.db)
1987 if glb.dbref.is_sqlite3:
1988 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
1990 column_headers.append(query.value(1))
1991 if table_name == "sqlite_master":
1992 sql = "SELECT * FROM " + table_name
1994 if table_name[:19] == "information_schema.":
1995 sql = "SELECT * FROM " + table_name
1996 select_table_name = table_name[19:]
1997 schema = "information_schema"
1999 select_table_name = table_name
2001 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2003 column_headers.append(query.value(0))
2004 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2006 # Base class for custom ResizeColumnsToContents
2008 class ResizeColumnsToContentsBase(QObject):
2010 def __init__(self, parent=None):
2011 super(ResizeColumnsToContentsBase, self).__init__(parent)
2013 def ResizeColumnToContents(self, column, n):
2014 # Using the view's resizeColumnToContents() here is extrememly slow
2015 # so implement a crude alternative
2016 font = self.view.font()
2017 metrics = QFontMetrics(font)
2019 for row in xrange(n):
2020 val = self.data_model.child_items[row].data[column]
2021 len = metrics.width(str(val) + "MM")
2022 max = len if len > max else max
2023 val = self.data_model.columnHeader(column)
2024 len = metrics.width(str(val) + "MM")
2025 max = len if len > max else max
2026 self.view.setColumnWidth(column, max)
2028 def ResizeColumnsToContents(self):
2029 n = min(self.data_model.child_count, 100)
2031 # No data yet, so connect a signal to notify when there is
2032 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2034 columns = self.data_model.columnCount()
2035 for i in xrange(columns):
2036 self.ResizeColumnToContents(i, n)
2038 def UpdateColumnWidths(self, *x):
2039 # This only needs to be done once, so disconnect the signal now
2040 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2041 self.ResizeColumnsToContents()
2045 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2047 def __init__(self, glb, table_name, parent=None):
2048 super(TableWindow, self).__init__(parent)
2050 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2052 self.model = QSortFilterProxyModel()
2053 self.model.setSourceModel(self.data_model)
2055 self.view = QTableView()
2056 self.view.setModel(self.model)
2057 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2058 self.view.verticalHeader().setVisible(False)
2059 self.view.sortByColumn(-1, Qt.AscendingOrder)
2060 self.view.setSortingEnabled(True)
2062 self.ResizeColumnsToContents()
2064 self.find_bar = FindBar(self, self, True)
2066 self.finder = ChildDataItemFinder(self.data_model)
2068 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2070 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2072 self.setWidget(self.vbox.Widget())
2074 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2076 def Find(self, value, direction, pattern, context):
2077 self.view.setFocus()
2078 self.find_bar.Busy()
2079 self.finder.Find(value, direction, pattern, context, self.FindDone)
2081 def FindDone(self, row):
2082 self.find_bar.Idle()
2084 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2086 self.find_bar.NotFound()
2090 def GetTableList(glb):
2092 query = QSqlQuery(glb.db)
2093 if glb.dbref.is_sqlite3:
2094 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2096 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2098 tables.append(query.value(0))
2099 if glb.dbref.is_sqlite3:
2100 tables.append("sqlite_master")
2102 tables.append("information_schema.tables")
2103 tables.append("information_schema.views")
2104 tables.append("information_schema.columns")
2107 # Top Calls data model
2109 class TopCallsModel(SQLTableModel):
2111 def __init__(self, glb, report_vars, parent=None):
2113 if not glb.dbref.is_sqlite3:
2116 if len(report_vars.limit):
2117 limit = " LIMIT " + report_vars.limit
2118 sql = ("SELECT comm, pid, tid, name,"
2120 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2123 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2125 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2126 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2127 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2131 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2132 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2133 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2134 " INNER JOIN comms ON calls.comm_id = comms.id"
2135 " INNER JOIN threads ON calls.thread_id = threads.id" +
2136 report_vars.where_clause +
2137 " ORDER BY elapsed_time DESC" +
2140 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2141 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2142 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2144 def columnAlignment(self, column):
2145 return self.alignment[column]
2147 # Top Calls report creation dialog
2149 class TopCallsDialog(ReportDialogBase):
2151 def __init__(self, glb, parent=None):
2152 title = "Top Calls by Elapsed Time"
2153 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2154 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2155 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2156 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2157 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2158 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2159 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2160 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2161 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2165 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2167 def __init__(self, glb, report_vars, parent=None):
2168 super(TopCallsWindow, self).__init__(parent)
2170 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2171 self.model = self.data_model
2173 self.view = QTableView()
2174 self.view.setModel(self.model)
2175 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2176 self.view.verticalHeader().setVisible(False)
2178 self.ResizeColumnsToContents()
2180 self.find_bar = FindBar(self, self, True)
2182 self.finder = ChildDataItemFinder(self.model)
2184 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2186 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2188 self.setWidget(self.vbox.Widget())
2190 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2192 def Find(self, value, direction, pattern, context):
2193 self.view.setFocus()
2194 self.find_bar.Busy()
2195 self.finder.Find(value, direction, pattern, context, self.FindDone)
2197 def FindDone(self, row):
2198 self.find_bar.Idle()
2200 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2202 self.find_bar.NotFound()
2206 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2207 action = QAction(label, parent)
2208 if shortcut != None:
2209 action.setShortcuts(shortcut)
2210 action.setStatusTip(tip)
2211 action.triggered.connect(callback)
2214 # Typical application actions
2216 def CreateExitAction(app, parent=None):
2217 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2219 # Typical MDI actions
2221 def CreateCloseActiveWindowAction(mdi_area):
2222 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2224 def CreateCloseAllWindowsAction(mdi_area):
2225 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2227 def CreateTileWindowsAction(mdi_area):
2228 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2230 def CreateCascadeWindowsAction(mdi_area):
2231 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2233 def CreateNextWindowAction(mdi_area):
2234 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2236 def CreatePreviousWindowAction(mdi_area):
2237 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2239 # Typical MDI window menu
2243 def __init__(self, mdi_area, menu):
2244 self.mdi_area = mdi_area
2245 self.window_menu = menu.addMenu("&Windows")
2246 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2247 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2248 self.tile_windows = CreateTileWindowsAction(mdi_area)
2249 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2250 self.next_window = CreateNextWindowAction(mdi_area)
2251 self.previous_window = CreatePreviousWindowAction(mdi_area)
2252 self.window_menu.aboutToShow.connect(self.Update)
2255 self.window_menu.clear()
2256 sub_window_count = len(self.mdi_area.subWindowList())
2257 have_sub_windows = sub_window_count != 0
2258 self.close_active_window.setEnabled(have_sub_windows)
2259 self.close_all_windows.setEnabled(have_sub_windows)
2260 self.tile_windows.setEnabled(have_sub_windows)
2261 self.cascade_windows.setEnabled(have_sub_windows)
2262 self.next_window.setEnabled(have_sub_windows)
2263 self.previous_window.setEnabled(have_sub_windows)
2264 self.window_menu.addAction(self.close_active_window)
2265 self.window_menu.addAction(self.close_all_windows)
2266 self.window_menu.addSeparator()
2267 self.window_menu.addAction(self.tile_windows)
2268 self.window_menu.addAction(self.cascade_windows)
2269 self.window_menu.addSeparator()
2270 self.window_menu.addAction(self.next_window)
2271 self.window_menu.addAction(self.previous_window)
2272 if sub_window_count == 0:
2274 self.window_menu.addSeparator()
2276 for sub_window in self.mdi_area.subWindowList():
2277 label = str(nr) + " " + sub_window.name
2280 action = self.window_menu.addAction(label)
2281 action.setCheckable(True)
2282 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2283 action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2284 self.window_menu.addAction(action)
2287 def setActiveSubWindow(self, nr):
2288 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2303 <p class=c1><a href=#reports>1. Reports</a></p>
2304 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2305 <p class=c2><a href=#allbranches>1.2 All branches</a></p>
2306 <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p>
2307 <p class=c2><a href=#topcallsbyelapsedtime>1.4 Top calls by elapsed time</a></p>
2308 <p class=c1><a href=#tables>2. Tables</a></p>
2309 <h1 id=reports>1. Reports</h1>
2310 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2311 The result is a GUI window with a tree representing a context-sensitive
2312 call-graph. Expanding a couple of levels of the tree and adjusting column
2313 widths to suit will display something like:
2315 Call Graph: pt_example
2316 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2319 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2320 |- unknown unknown 1 13198 0.1 1 0.0
2321 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2322 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2323 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2324 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2325 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2326 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2327 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2328 v- main ls 1 8182043 99.6 180254 99.9
2330 <h3>Points to note:</h3>
2332 <li>The top level is a command name (comm)</li>
2333 <li>The next level is a thread (pid:tid)</li>
2334 <li>Subsequent levels are functions</li>
2335 <li>'Count' is the number of calls</li>
2336 <li>'Time' is the elapsed time until the function returns</li>
2337 <li>Percentages are relative to the level above</li>
2338 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2341 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2342 The pattern matching symbols are ? for any character and * for zero or more characters.
2343 <h2 id=allbranches>1.2 All branches</h2>
2344 The All branches report displays all branches in chronological order.
2345 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2346 <h3>Disassembly</h3>
2347 Open a branch to display disassembly. This only works if:
2349 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2350 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2351 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2352 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2353 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2355 <h4 id=xed>Intel XED Setup</h4>
2356 To use Intel XED, libxed.so must be present. To build and install libxed.so:
2358 git clone https://github.com/intelxed/mbuild.git mbuild
2359 git clone https://github.com/intelxed/xed
2362 sudo ./mfile.py --prefix=/usr/local install
2366 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2367 Refer to Python documentation for the regular expression syntax.
2368 All columns are searched, but only currently fetched rows are searched.
2369 <h2 id=selectedbranches>1.3 Selected branches</h2>
2370 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2371 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2372 <h3>1.3.1 Time ranges</h3>
2373 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2374 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
2376 81073085947329-81073085958238 From 81073085947329 to 81073085958238
2377 100us-200us From 100us to 200us
2378 10ms- From 10ms to the end
2379 -100ns The first 100ns
2380 -10ms- The last 10ms
2382 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2383 <h2 id=topcallsbyelapsedtime>1.4 Top calls by elapsed time</h2>
2384 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.
2385 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2386 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
2387 <h1 id=tables>2. Tables</h1>
2388 The Tables menu shows all tables and views in the database. Most tables have an associated view
2389 which displays the information in a more friendly way. Not all data for large tables is fetched
2390 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2391 but that can be slow for large tables.
2392 <p>There are also tables of database meta-information.
2393 For SQLite3 databases, the sqlite_master table is included.
2394 For PostgreSQL databases, information_schema.tables/views/columns are included.
2396 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2397 Refer to Python documentation for the regular expression syntax.
2398 All columns are searched, but only currently fetched rows are searched.
2399 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2400 will go to the next/previous result in id order, instead of display order.
2405 class HelpWindow(QMdiSubWindow):
2407 def __init__(self, glb, parent=None):
2408 super(HelpWindow, self).__init__(parent)
2410 self.text = QTextBrowser()
2411 self.text.setHtml(glb_help_text)
2412 self.text.setReadOnly(True)
2413 self.text.setOpenExternalLinks(True)
2415 self.setWidget(self.text)
2417 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2419 # Main window that only displays the help text
2421 class HelpOnlyWindow(QMainWindow):
2423 def __init__(self, parent=None):
2424 super(HelpOnlyWindow, self).__init__(parent)
2426 self.setMinimumSize(200, 100)
2427 self.resize(800, 600)
2428 self.setWindowTitle("Exported SQL Viewer Help")
2429 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2431 self.text = QTextBrowser()
2432 self.text.setHtml(glb_help_text)
2433 self.text.setReadOnly(True)
2434 self.text.setOpenExternalLinks(True)
2436 self.setCentralWidget(self.text)
2440 def ResizeFont(widget, diff):
2441 font = widget.font()
2442 sz = font.pointSize()
2443 font.setPointSize(sz + diff)
2444 widget.setFont(font)
2446 def ShrinkFont(widget):
2447 ResizeFont(widget, -1)
2449 def EnlargeFont(widget):
2450 ResizeFont(widget, 1)
2452 # Unique name for sub-windows
2454 def NumberedWindowName(name, nr):
2456 name += " <" + str(nr) + ">"
2459 def UniqueSubWindowName(mdi_area, name):
2462 unique_name = NumberedWindowName(name, nr)
2464 for sub_window in mdi_area.subWindowList():
2465 if sub_window.name == unique_name:
2474 def AddSubWindow(mdi_area, sub_window, name):
2475 unique_name = UniqueSubWindowName(mdi_area, name)
2476 sub_window.setMinimumSize(200, 100)
2477 sub_window.resize(800, 600)
2478 sub_window.setWindowTitle(unique_name)
2479 sub_window.setAttribute(Qt.WA_DeleteOnClose)
2480 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
2481 sub_window.name = unique_name
2482 mdi_area.addSubWindow(sub_window)
2487 class MainWindow(QMainWindow):
2489 def __init__(self, glb, parent=None):
2490 super(MainWindow, self).__init__(parent)
2494 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
2495 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
2496 self.setMinimumSize(200, 100)
2498 self.mdi_area = QMdiArea()
2499 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2500 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2502 self.setCentralWidget(self.mdi_area)
2504 menu = self.menuBar()
2506 file_menu = menu.addMenu("&File")
2507 file_menu.addAction(CreateExitAction(glb.app, self))
2509 edit_menu = menu.addMenu("&Edit")
2510 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
2511 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
2512 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
2513 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
2515 reports_menu = menu.addMenu("&Reports")
2516 if IsSelectable(glb.db, "calls"):
2517 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
2519 self.EventMenu(GetEventList(glb.db), reports_menu)
2521 if IsSelectable(glb.db, "calls"):
2522 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
2524 self.TableMenu(GetTableList(glb), menu)
2526 self.window_menu = WindowMenu(self.mdi_area, menu)
2528 help_menu = menu.addMenu("&Help")
2529 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
2532 win = self.mdi_area.activeSubWindow()
2535 win.find_bar.Activate()
2539 def FetchMoreRecords(self):
2540 win = self.mdi_area.activeSubWindow()
2543 win.fetch_bar.Activate()
2547 def ShrinkFont(self):
2548 win = self.mdi_area.activeSubWindow()
2549 ShrinkFont(win.view)
2551 def EnlargeFont(self):
2552 win = self.mdi_area.activeSubWindow()
2553 EnlargeFont(win.view)
2555 def EventMenu(self, events, reports_menu):
2557 for event in events:
2558 event = event.split(":")[0]
2559 if event == "branches":
2560 branches_events += 1
2562 for event in events:
2564 event = event.split(":")[0]
2565 if event == "branches":
2566 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
2567 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
2568 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
2569 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
2571 def TableMenu(self, tables, menu):
2572 table_menu = menu.addMenu("&Tables")
2573 for table in tables:
2574 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
2576 def NewCallGraph(self):
2577 CallGraphWindow(self.glb, self)
2579 def NewTopCalls(self):
2580 dialog = TopCallsDialog(self.glb, self)
2581 ret = dialog.exec_()
2583 TopCallsWindow(self.glb, dialog.report_vars, self)
2585 def NewBranchView(self, event_id):
2586 BranchWindow(self.glb, event_id, ReportVars(), self)
2588 def NewSelectedBranchView(self, event_id):
2589 dialog = SelectedBranchDialog(self.glb, self)
2590 ret = dialog.exec_()
2592 BranchWindow(self.glb, event_id, dialog.report_vars, self)
2594 def NewTableView(self, table_name):
2595 TableWindow(self.glb, table_name, self)
2598 HelpWindow(self.glb, self)
2602 class xed_state_t(Structure):
2609 class XEDInstruction():
2611 def __init__(self, libxed):
2612 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
2613 xedd_t = c_byte * 512
2614 self.xedd = xedd_t()
2615 self.xedp = addressof(self.xedd)
2616 libxed.xed_decoded_inst_zero(self.xedp)
2617 self.state = xed_state_t()
2618 self.statep = addressof(self.state)
2619 # Buffer for disassembled instruction text
2620 self.buffer = create_string_buffer(256)
2621 self.bufferp = addressof(self.buffer)
2627 self.libxed = CDLL("libxed.so")
2631 self.libxed = CDLL("/usr/local/lib/libxed.so")
2633 self.xed_tables_init = self.libxed.xed_tables_init
2634 self.xed_tables_init.restype = None
2635 self.xed_tables_init.argtypes = []
2637 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
2638 self.xed_decoded_inst_zero.restype = None
2639 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
2641 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
2642 self.xed_operand_values_set_mode.restype = None
2643 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
2645 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
2646 self.xed_decoded_inst_zero_keep_mode.restype = None
2647 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
2649 self.xed_decode = self.libxed.xed_decode
2650 self.xed_decode.restype = c_int
2651 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
2653 self.xed_format_context = self.libxed.xed_format_context
2654 self.xed_format_context.restype = c_uint
2655 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
2657 self.xed_tables_init()
2659 def Instruction(self):
2660 return XEDInstruction(self)
2662 def SetMode(self, inst, mode):
2664 inst.state.mode = 4 # 32-bit
2665 inst.state.width = 4 # 4 bytes
2667 inst.state.mode = 1 # 64-bit
2668 inst.state.width = 8 # 8 bytes
2669 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
2671 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
2672 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
2673 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
2676 # Use AT&T mode (2), alternative is Intel (3)
2677 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
2680 # Return instruction length and the disassembled instruction text
2681 # For now, assume the length is in byte 166
2682 return inst.xedd[166], inst.buffer.value
2684 def TryOpen(file_name):
2686 return open(file_name, "rb")
2691 result = sizeof(c_void_p)
2698 eclass = ord(header[4])
2699 encoding = ord(header[5])
2700 version = ord(header[6])
2701 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
2702 result = True if eclass == 2 else False
2709 def __init__(self, dbref, db, dbname):
2712 self.dbname = dbname
2713 self.home_dir = os.path.expanduser("~")
2714 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
2715 if self.buildid_dir:
2716 self.buildid_dir += "/.build-id/"
2718 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
2720 self.mainwindow = None
2721 self.instances_to_shutdown_on_exit = weakref.WeakSet()
2723 self.disassembler = LibXED()
2724 self.have_disassembler = True
2726 self.have_disassembler = False
2728 def FileFromBuildId(self, build_id):
2729 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
2730 return TryOpen(file_name)
2732 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
2733 # Assume current machine i.e. no support for virtualization
2734 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
2735 file_name = os.getenv("PERF_KCORE")
2736 f = TryOpen(file_name) if file_name else None
2739 # For now, no special handling if long_name is /proc/kcore
2740 f = TryOpen(long_name)
2743 f = self.FileFromBuildId(build_id)
2748 def AddInstanceToShutdownOnExit(self, instance):
2749 self.instances_to_shutdown_on_exit.add(instance)
2751 # Shutdown any background processes or threads
2752 def ShutdownInstances(self):
2753 for x in self.instances_to_shutdown_on_exit:
2759 # Database reference
2763 def __init__(self, is_sqlite3, dbname):
2764 self.is_sqlite3 = is_sqlite3
2765 self.dbname = dbname
2767 def Open(self, connection_name):
2768 dbname = self.dbname
2770 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
2772 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
2773 opts = dbname.split()
2776 opt = opt.split("=")
2777 if opt[0] == "hostname":
2778 db.setHostName(opt[1])
2779 elif opt[0] == "port":
2780 db.setPort(int(opt[1]))
2781 elif opt[0] == "username":
2782 db.setUserName(opt[1])
2783 elif opt[0] == "password":
2784 db.setPassword(opt[1])
2785 elif opt[0] == "dbname":
2790 db.setDatabaseName(dbname)
2792 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
2798 if (len(sys.argv) < 2):
2799 print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}"
2800 raise Exception("Too few arguments")
2802 dbname = sys.argv[1]
2803 if dbname == "--help-only":
2804 app = QApplication(sys.argv)
2805 mainwindow = HelpOnlyWindow()
2813 if f.read(15) == "SQLite format 3":
2819 dbref = DBRef(is_sqlite3, dbname)
2820 db, dbname = dbref.Open("main")
2821 glb = Glb(dbref, db, dbname)
2822 app = QApplication(sys.argv)
2824 mainwindow = MainWindow(glb)
2825 glb.mainwindow = mainwindow
2828 glb.ShutdownInstances()
2832 if __name__ == "__main__":