1 # SPDX-License-Identifier: GPL-2.0
2 # exported-sql-viewer.py: view data from sql database
3 # Copyright (c) 2014-2018, Intel Corporation.
5 # To use this script you will need to have exported data using either the
6 # export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
9 # Following on from the example in the export scripts, a
10 # call-graph can be displayed for the pt_example database like this:
12 # python tools/perf/scripts/python/exported-sql-viewer.py pt_example
14 # Note that for PostgreSQL, this script supports connecting to remote databases
15 # by setting hostname, port, username, password, and dbname e.g.
17 # python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
19 # The result is a GUI window with a tree representing a context-sensitive
20 # call-graph. Expanding a couple of levels of the tree and adjusting column
21 # widths to suit will display something like:
23 # Call Graph: pt_example
24 # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
27 # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
28 # |- unknown unknown 1 13198 0.1 1 0.0
29 # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
30 # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
31 # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
32 # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
33 # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
34 # >- __libc_csu_init ls 1 10354 0.1 10 0.0
35 # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
36 # v- main ls 1 8182043 99.6 180254 99.9
39 # The top level is a command name (comm)
40 # The next level is a thread (pid:tid)
41 # Subsequent levels are functions
42 # 'Count' is the number of calls
43 # 'Time' is the elapsed time until the function returns
44 # Percentages are relative to the level above
45 # 'Branch Count' is the total number of branches for that function and all
46 # functions that it calls
48 # There is also a "All branches" report, which displays branches and
49 # possibly disassembly. However, presently, the only supported disassembler is
50 # Intel XED, and additionally the object code must be present in perf build ID
51 # cache. To use Intel XED, libxed.so must be present. To build and install
53 # git clone https://github.com/intelxed/mbuild.git mbuild
54 # git clone https://github.com/intelxed/xed
57 # sudo ./mfile.py --prefix=/usr/local install
62 # Time CPU Command PID TID Branch Type In Tx Branch
63 # 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
64 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
65 # 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
66 # 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
67 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
68 # 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930
69 # 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
70 # 7fab593ea930 55 pushq %rbp
71 # 7fab593ea931 48 89 e5 mov %rsp, %rbp
72 # 7fab593ea934 41 57 pushq %r15
73 # 7fab593ea936 41 56 pushq %r14
74 # 7fab593ea938 41 55 pushq %r13
75 # 7fab593ea93a 41 54 pushq %r12
76 # 7fab593ea93c 53 pushq %rbx
77 # 7fab593ea93d 48 89 fb mov %rdi, %rbx
78 # 7fab593ea940 48 83 ec 68 sub $0x68, %rsp
79 # 7fab593ea944 0f 31 rdtsc
80 # 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx
81 # 7fab593ea94a 89 c0 mov %eax, %eax
82 # 7fab593ea94c 48 09 c2 or %rax, %rdx
83 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
84 # 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
85 # 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
86 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
87 # 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip)
88 # 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
97 from PySide.QtCore import *
98 from PySide.QtGui import *
99 from PySide.QtSql import *
100 from decimal import *
102 from multiprocessing import Process, Array, Value, Event
104 # Data formatting helpers
113 return "+0x%x" % offset
117 if name == "[kernel.kallsyms]":
121 def findnth(s, sub, n, offs=0):
127 return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
129 # Percent to one decimal place
131 def PercentToOneDP(n, d):
134 x = (n * Decimal(100)) / d
135 return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
137 # Helper for queries that must not fail
139 def QueryExec(query, stmt):
140 ret = query.exec_(stmt)
142 raise Exception("Query failed: " + query.lastError().text())
146 class Thread(QThread):
148 done = Signal(object)
150 def __init__(self, task, param=None, parent=None):
151 super(Thread, self).__init__(parent)
157 if self.param is None:
158 done, result = self.task()
160 done, result = self.task(self.param)
161 self.done.emit(result)
167 class TreeModel(QAbstractItemModel):
169 def __init__(self, root, parent=None):
170 super(TreeModel, self).__init__(parent)
172 self.last_row_read = 0
174 def Item(self, parent):
176 return parent.internalPointer()
180 def rowCount(self, parent):
181 result = self.Item(parent).childCount()
184 self.dataChanged.emit(parent, parent)
187 def hasChildren(self, parent):
188 return self.Item(parent).hasChildren()
190 def headerData(self, section, orientation, role):
191 if role == Qt.TextAlignmentRole:
192 return self.columnAlignment(section)
193 if role != Qt.DisplayRole:
195 if orientation != Qt.Horizontal:
197 return self.columnHeader(section)
199 def parent(self, child):
200 child_item = child.internalPointer()
201 if child_item is self.root:
203 parent_item = child_item.getParentItem()
204 return self.createIndex(parent_item.getRow(), 0, parent_item)
206 def index(self, row, column, parent):
207 child_item = self.Item(parent).getChildItem(row)
208 return self.createIndex(row, column, child_item)
210 def DisplayData(self, item, index):
211 return item.getData(index.column())
213 def FetchIfNeeded(self, row):
214 if row > self.last_row_read:
215 self.last_row_read = row
216 if row + 10 >= self.root.child_count:
217 self.fetcher.Fetch(glb_chunk_sz)
219 def columnAlignment(self, column):
222 def columnFont(self, column):
225 def data(self, index, role):
226 if role == Qt.TextAlignmentRole:
227 return self.columnAlignment(index.column())
228 if role == Qt.FontRole:
229 return self.columnFont(index.column())
230 if role != Qt.DisplayRole:
232 item = index.internalPointer()
233 return self.DisplayData(item, index)
237 class TableModel(QAbstractTableModel):
239 def __init__(self, parent=None):
240 super(TableModel, self).__init__(parent)
242 self.child_items = []
243 self.last_row_read = 0
245 def Item(self, parent):
247 return parent.internalPointer()
251 def rowCount(self, parent):
252 return self.child_count
254 def headerData(self, section, orientation, role):
255 if role == Qt.TextAlignmentRole:
256 return self.columnAlignment(section)
257 if role != Qt.DisplayRole:
259 if orientation != Qt.Horizontal:
261 return self.columnHeader(section)
263 def index(self, row, column, parent):
264 return self.createIndex(row, column, self.child_items[row])
266 def DisplayData(self, item, index):
267 return item.getData(index.column())
269 def FetchIfNeeded(self, row):
270 if row > self.last_row_read:
271 self.last_row_read = row
272 if row + 10 >= self.child_count:
273 self.fetcher.Fetch(glb_chunk_sz)
275 def columnAlignment(self, column):
278 def columnFont(self, column):
281 def data(self, index, role):
282 if role == Qt.TextAlignmentRole:
283 return self.columnAlignment(index.column())
284 if role == Qt.FontRole:
285 return self.columnFont(index.column())
286 if role != Qt.DisplayRole:
288 item = index.internalPointer()
289 return self.DisplayData(item, index)
293 model_cache = weakref.WeakValueDictionary()
294 model_cache_lock = threading.Lock()
296 def LookupCreateModel(model_name, create_fn):
297 model_cache_lock.acquire()
299 model = model_cache[model_name]
304 model_cache[model_name] = model
305 model_cache_lock.release()
312 def __init__(self, parent, finder, is_reg_expr=False):
315 self.last_value = None
316 self.last_pattern = None
318 label = QLabel("Find:")
319 label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
321 self.textbox = QComboBox()
322 self.textbox.setEditable(True)
323 self.textbox.currentIndexChanged.connect(self.ValueChanged)
325 self.progress = QProgressBar()
326 self.progress.setRange(0, 0)
330 self.pattern = QCheckBox("Regular Expression")
332 self.pattern = QCheckBox("Pattern")
333 self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
335 self.next_button = QToolButton()
336 self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
337 self.next_button.released.connect(lambda: self.NextPrev(1))
339 self.prev_button = QToolButton()
340 self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
341 self.prev_button.released.connect(lambda: self.NextPrev(-1))
343 self.close_button = QToolButton()
344 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
345 self.close_button.released.connect(self.Deactivate)
347 self.hbox = QHBoxLayout()
348 self.hbox.setContentsMargins(0, 0, 0, 0)
350 self.hbox.addWidget(label)
351 self.hbox.addWidget(self.textbox)
352 self.hbox.addWidget(self.progress)
353 self.hbox.addWidget(self.pattern)
354 self.hbox.addWidget(self.next_button)
355 self.hbox.addWidget(self.prev_button)
356 self.hbox.addWidget(self.close_button)
359 self.bar.setLayout(self.hbox);
367 self.textbox.setFocus()
369 def Deactivate(self):
373 self.textbox.setEnabled(False)
375 self.next_button.hide()
376 self.prev_button.hide()
380 self.textbox.setEnabled(True)
383 self.next_button.show()
384 self.prev_button.show()
386 def Find(self, direction):
387 value = self.textbox.currentText()
388 pattern = self.pattern.isChecked()
389 self.last_value = value
390 self.last_pattern = pattern
391 self.finder.Find(value, direction, pattern, self.context)
393 def ValueChanged(self):
394 value = self.textbox.currentText()
395 pattern = self.pattern.isChecked()
396 index = self.textbox.currentIndex()
397 data = self.textbox.itemData(index)
398 # Store the pattern in the combo box to keep it with the text value
400 self.textbox.setItemData(index, pattern)
402 self.pattern.setChecked(data)
405 def NextPrev(self, direction):
406 value = self.textbox.currentText()
407 pattern = self.pattern.isChecked()
408 if value != self.last_value:
409 index = self.textbox.findText(value)
410 # Allow for a button press before the value has been added to the combo box
412 index = self.textbox.count()
413 self.textbox.addItem(value, pattern)
414 self.textbox.setCurrentIndex(index)
417 self.textbox.setItemData(index, pattern)
418 elif pattern != self.last_pattern:
419 # Keep the pattern recorded in the combo box up to date
420 index = self.textbox.currentIndex()
421 self.textbox.setItemData(index, pattern)
425 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
427 # Context-sensitive call graph data model item base
429 class CallGraphLevelItemBase(object):
431 def __init__(self, glb, row, parent_item):
434 self.parent_item = parent_item
435 self.query_done = False;
437 self.child_items = []
439 def getChildItem(self, row):
440 return self.child_items[row]
442 def getParentItem(self):
443 return self.parent_item
448 def childCount(self):
449 if not self.query_done:
451 if not self.child_count:
453 return self.child_count
455 def hasChildren(self):
456 if not self.query_done:
458 return self.child_count > 0
460 def getData(self, column):
461 return self.data[column]
463 # Context-sensitive call graph data model level 2+ item base
465 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
467 def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
468 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
469 self.comm_id = comm_id
470 self.thread_id = thread_id
471 self.call_path_id = call_path_id
472 self.branch_count = branch_count
476 self.query_done = True;
477 query = QSqlQuery(self.glb.db)
478 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
480 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
481 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
482 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
483 " WHERE parent_call_path_id = " + str(self.call_path_id) +
484 " AND comm_id = " + str(self.comm_id) +
485 " AND thread_id = " + str(self.thread_id) +
486 " GROUP BY call_path_id, name, short_name"
487 " ORDER BY call_path_id")
489 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)
490 self.child_items.append(child_item)
491 self.child_count += 1
493 # Context-sensitive call graph data model level three item
495 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
497 def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
498 super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
500 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
501 self.dbid = call_path_id
503 # Context-sensitive call graph data model level two item
505 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
507 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
508 super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
509 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
510 self.dbid = thread_id
513 super(CallGraphLevelTwoItem, self).Select()
514 for child_item in self.child_items:
515 self.time += child_item.time
516 self.branch_count += child_item.branch_count
517 for child_item in self.child_items:
518 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
519 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
521 # Context-sensitive call graph data model level one item
523 class CallGraphLevelOneItem(CallGraphLevelItemBase):
525 def __init__(self, glb, row, comm_id, comm, parent_item):
526 super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
527 self.data = [comm, "", "", "", "", "", ""]
531 self.query_done = True;
532 query = QSqlQuery(self.glb.db)
533 QueryExec(query, "SELECT thread_id, pid, tid"
535 " INNER JOIN threads ON thread_id = threads.id"
536 " WHERE comm_id = " + str(self.dbid))
538 child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
539 self.child_items.append(child_item)
540 self.child_count += 1
542 # Context-sensitive call graph data model root item
544 class CallGraphRootItem(CallGraphLevelItemBase):
546 def __init__(self, glb):
547 super(CallGraphRootItem, self).__init__(glb, 0, None)
549 self.query_done = True;
550 query = QSqlQuery(glb.db)
551 QueryExec(query, "SELECT id, comm FROM comms")
553 if not query.value(0):
555 child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
556 self.child_items.append(child_item)
557 self.child_count += 1
559 # Context-sensitive call graph data model
561 class CallGraphModel(TreeModel):
563 def __init__(self, glb, parent=None):
564 super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
567 def columnCount(self, parent=None):
570 def columnHeader(self, column):
571 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
572 return headers[column]
574 def columnAlignment(self, column):
575 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
576 return alignment[column]
578 def FindSelect(self, value, pattern, query):
580 # postgresql and sqlite pattern patching differences:
581 # postgresql LIKE is case sensitive but sqlite LIKE is not
582 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
583 # postgresql supports ILIKE which is case insensitive
584 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
585 if not self.glb.dbref.is_sqlite3:
587 s = value.replace("%", "\%")
588 s = s.replace("_", "\_")
589 # Translate * and ? into SQL LIKE pattern characters % and _
590 trans = string.maketrans("*?", "%_")
591 match = " LIKE '" + str(s).translate(trans) + "'"
593 match = " GLOB '" + str(value) + "'"
595 match = " = '" + str(value) + "'"
596 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
598 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
599 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
600 " WHERE symbols.name" + match +
601 " GROUP BY comm_id, thread_id, call_path_id"
602 " ORDER BY comm_id, thread_id, call_path_id")
604 def FindPath(self, query):
605 # Turn the query result into a list of ids that the tree view can walk
606 # to open the tree at the right place.
608 parent_id = query.value(0)
610 ids.insert(0, parent_id)
611 q2 = QSqlQuery(self.glb.db)
612 QueryExec(q2, "SELECT parent_id"
614 " WHERE id = " + str(parent_id))
617 parent_id = q2.value(0)
618 # The call path root is not used
621 ids.insert(0, query.value(2))
622 ids.insert(0, query.value(1))
625 def Found(self, query, found):
627 return self.FindPath(query)
630 def FindValue(self, value, pattern, query, last_value, last_pattern):
631 if last_value == value and pattern == last_pattern:
632 found = query.first()
634 self.FindSelect(value, pattern, query)
636 return self.Found(query, found)
638 def FindNext(self, query):
641 found = query.first()
642 return self.Found(query, found)
644 def FindPrev(self, query):
645 found = query.previous()
648 return self.Found(query, found)
650 def FindThread(self, c):
651 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
652 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
653 elif c.direction > 0:
654 ids = self.FindNext(c.query)
656 ids = self.FindPrev(c.query)
659 def Find(self, value, direction, pattern, context, callback):
661 def __init__(self, *x):
662 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
663 def Update(self, *x):
664 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
666 context[0].Update(value, direction, pattern)
668 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
669 # Use a thread so the UI is not blocked during the SELECT
670 thread = Thread(self.FindThread, context[0])
671 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
674 def FindDone(self, thread, callback, ids):
677 # Vertical widget layout
681 def __init__(self, w1, w2, w3=None):
682 self.vbox = QWidget()
683 self.vbox.setLayout(QVBoxLayout());
685 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
687 self.vbox.layout().addWidget(w1)
688 self.vbox.layout().addWidget(w2)
690 self.vbox.layout().addWidget(w3)
695 # Context-sensitive call graph window
697 class CallGraphWindow(QMdiSubWindow):
699 def __init__(self, glb, parent=None):
700 super(CallGraphWindow, self).__init__(parent)
702 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
704 self.view = QTreeView()
705 self.view.setModel(self.model)
707 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
708 self.view.setColumnWidth(c, w)
710 self.find_bar = FindBar(self, self)
712 self.vbox = VBox(self.view, self.find_bar.Widget())
714 self.setWidget(self.vbox.Widget())
716 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
718 def DisplayFound(self, ids):
721 parent = QModelIndex()
724 n = self.model.rowCount(parent)
725 for row in xrange(n):
726 child = self.model.index(row, 0, parent)
727 if child.internalPointer().dbid == dbid:
729 self.view.setCurrentIndex(child)
736 def Find(self, value, direction, pattern, context):
739 self.model.Find(value, direction, pattern, context, self.FindDone)
741 def FindDone(self, ids):
743 if not self.DisplayFound(ids):
747 self.find_bar.NotFound()
749 # Child data item finder
751 class ChildDataItemFinder():
753 def __init__(self, root):
755 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
759 def FindSelect(self):
762 pattern = re.compile(self.value)
763 for child in self.root.child_items:
764 for column_data in child.data:
765 if re.search(pattern, str(column_data)) is not None:
766 self.rows.append(child.row)
769 for child in self.root.child_items:
770 for column_data in child.data:
771 if self.value in str(column_data):
772 self.rows.append(child.row)
777 if self.last_value != self.value or self.pattern != self.last_pattern:
779 if not len(self.rows):
781 return self.rows[self.pos]
783 def FindThread(self):
784 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
785 row = self.FindValue()
787 if self.direction > 0:
789 if self.pos >= len(self.rows):
794 self.pos = len(self.rows) - 1
795 row = self.rows[self.pos]
800 def Find(self, value, direction, pattern, context, callback):
801 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
802 # Use a thread so the UI is not blocked
803 thread = Thread(self.FindThread)
804 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
807 def FindDone(self, thread, callback, row):
810 # Number of database records to fetch in one go
814 # size of pickled integer big enough for record size
818 # Background process for SQL data fetcher
820 class SQLFetcherProcess():
822 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
823 # Need a unique connection name
824 conn_name = "SQLFetcher" + str(os.getpid())
825 self.db, dbname = dbref.Open(conn_name)
830 self.fetch_count = fetch_count
831 self.fetching_done = fetching_done
832 self.process_target = process_target
833 self.wait_event = wait_event
834 self.fetched_event = fetched_event
836 self.query = QSqlQuery(self.db)
837 self.query_limit = 0 if "$$last_id$$" in sql else 2
841 self.local_head = self.head.value
842 self.local_tail = self.tail.value
846 if self.query_limit == 1:
848 self.query_limit -= 1
849 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
850 QueryExec(self.query, stmt)
853 if not self.query.next():
855 if not self.query.next():
857 self.last_id = self.query.value(0)
858 return self.prep(self.query)
860 def WaitForTarget(self):
862 self.wait_event.clear()
863 target = self.process_target.value
864 if target > self.fetched or target < 0:
866 self.wait_event.wait()
869 def HasSpace(self, sz):
870 if self.local_tail <= self.local_head:
871 space = len(self.buffer) - self.local_head
875 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
876 nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL)
877 self.buffer[self.local_head : self.local_head + len(nd)] = nd
879 if self.local_tail - self.local_head > sz:
883 def WaitForSpace(self, sz):
884 if self.HasSpace(sz):
887 self.wait_event.clear()
888 self.local_tail = self.tail.value
889 if self.HasSpace(sz):
891 self.wait_event.wait()
893 def AddToBuffer(self, obj):
894 d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL)
896 nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL)
898 self.WaitForSpace(sz)
899 pos = self.local_head
900 self.buffer[pos : pos + len(nd)] = nd
901 self.buffer[pos + glb_nsz : pos + sz] = d
902 self.local_head += sz
904 def FetchBatch(self, batch_size):
906 while batch_size > fetched:
911 self.AddToBuffer(obj)
914 self.fetched += fetched
915 with self.fetch_count.get_lock():
916 self.fetch_count.value += fetched
917 self.head.value = self.local_head
918 self.fetched_event.set()
922 target = self.WaitForTarget()
925 batch_size = min(glb_chunk_sz, target - self.fetched)
926 self.FetchBatch(batch_size)
927 self.fetching_done.value = True
928 self.fetched_event.set()
930 def SQLFetcherFn(*x):
931 process = SQLFetcherProcess(*x)
936 class SQLFetcher(QObject):
938 done = Signal(object)
940 def __init__(self, glb, sql, prep, process_data, parent=None):
941 super(SQLFetcher, self).__init__(parent)
942 self.process_data = process_data
947 self.buffer_size = 16 * 1024 * 1024
948 self.buffer = Array(c_char, self.buffer_size, lock=False)
949 self.head = Value(c_longlong)
950 self.tail = Value(c_longlong)
952 self.fetch_count = Value(c_longlong)
953 self.fetching_done = Value(c_bool)
955 self.process_target = Value(c_longlong)
956 self.wait_event = Event()
957 self.fetched_event = Event()
958 glb.AddInstanceToShutdownOnExit(self)
959 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))
961 self.thread = Thread(self.Thread)
962 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
966 # Tell the thread and process to exit
967 self.process_target.value = -1
968 self.wait_event.set()
970 self.fetching_done.value = True
971 self.fetched_event.set()
977 self.fetched_event.clear()
978 fetch_count = self.fetch_count.value
979 if fetch_count != self.last_count:
981 if self.fetching_done.value:
984 self.fetched_event.wait()
985 count = fetch_count - self.last_count
986 self.last_count = fetch_count
987 self.fetched += count
992 # -1 inidcates there are no more
994 result = self.fetched
995 extra = result + nr - self.target
998 # process_target < 0 indicates shutting down
999 if self.process_target.value >= 0:
1000 self.process_target.value = self.target
1001 self.wait_event.set()
1004 def RemoveFromBuffer(self):
1005 pos = self.local_tail
1006 if len(self.buffer) - pos < glb_nsz:
1008 n = cPickle.loads(self.buffer[pos : pos + glb_nsz])
1011 n = cPickle.loads(self.buffer[0 : glb_nsz])
1013 obj = cPickle.loads(self.buffer[pos : pos + n])
1014 self.local_tail = pos + n
1017 def ProcessData(self, count):
1018 for i in xrange(count):
1019 obj = self.RemoveFromBuffer()
1020 self.process_data(obj)
1021 self.tail.value = self.local_tail
1022 self.wait_event.set()
1023 self.done.emit(count)
1025 # Fetch more records bar
1027 class FetchMoreRecordsBar():
1029 def __init__(self, model, parent):
1032 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1033 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1035 self.fetch_count = QSpinBox()
1036 self.fetch_count.setRange(1, 1000000)
1037 self.fetch_count.setValue(10)
1038 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1040 self.fetch = QPushButton("Go!")
1041 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1042 self.fetch.released.connect(self.FetchMoreRecords)
1044 self.progress = QProgressBar()
1045 self.progress.setRange(0, 100)
1046 self.progress.hide()
1048 self.done_label = QLabel("All records fetched")
1049 self.done_label.hide()
1051 self.spacer = QLabel("")
1053 self.close_button = QToolButton()
1054 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1055 self.close_button.released.connect(self.Deactivate)
1057 self.hbox = QHBoxLayout()
1058 self.hbox.setContentsMargins(0, 0, 0, 0)
1060 self.hbox.addWidget(self.label)
1061 self.hbox.addWidget(self.fetch_count)
1062 self.hbox.addWidget(self.fetch)
1063 self.hbox.addWidget(self.spacer)
1064 self.hbox.addWidget(self.progress)
1065 self.hbox.addWidget(self.done_label)
1066 self.hbox.addWidget(self.close_button)
1068 self.bar = QWidget()
1069 self.bar.setLayout(self.hbox);
1072 self.in_progress = False
1073 self.model.progress.connect(self.Progress)
1077 if not model.HasMoreRecords():
1085 self.fetch.setFocus()
1087 def Deactivate(self):
1090 def Enable(self, enable):
1091 self.fetch.setEnabled(enable)
1092 self.fetch_count.setEnabled(enable)
1098 self.progress.show()
1101 self.in_progress = False
1103 self.progress.hide()
1108 return self.fetch_count.value() * glb_chunk_sz
1114 self.fetch_count.hide()
1117 self.done_label.show()
1119 def Progress(self, count):
1120 if self.in_progress:
1122 percent = ((count - self.start) * 100) / self.Target()
1126 self.progress.setValue(percent)
1128 # Count value of zero means no more records
1131 def FetchMoreRecords(self):
1134 self.progress.setValue(0)
1136 self.in_progress = True
1137 self.start = self.model.FetchMoreRecords(self.Target())
1139 # Brance data model level two item
1141 class BranchLevelTwoItem():
1143 def __init__(self, row, text, parent_item):
1145 self.parent_item = parent_item
1146 self.data = [""] * 8
1150 def getParentItem(self):
1151 return self.parent_item
1156 def childCount(self):
1159 def hasChildren(self):
1162 def getData(self, column):
1163 return self.data[column]
1165 # Brance data model level one item
1167 class BranchLevelOneItem():
1169 def __init__(self, glb, row, data, parent_item):
1172 self.parent_item = parent_item
1173 self.child_count = 0
1174 self.child_items = []
1175 self.data = data[1:]
1178 self.query_done = False
1180 def getChildItem(self, row):
1181 return self.child_items[row]
1183 def getParentItem(self):
1184 return self.parent_item
1190 self.query_done = True
1192 if not self.glb.have_disassembler:
1195 query = QSqlQuery(self.glb.db)
1197 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1199 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1200 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1201 " WHERE samples.id = " + str(self.dbid))
1202 if not query.next():
1204 cpu = query.value(0)
1205 dso = query.value(1)
1206 sym = query.value(2)
1207 if dso == 0 or sym == 0:
1209 off = query.value(3)
1210 short_name = query.value(4)
1211 long_name = query.value(5)
1212 build_id = query.value(6)
1213 sym_start = query.value(7)
1216 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1218 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1219 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1220 " ORDER BY samples.id"
1222 if not query.next():
1224 if query.value(0) != dso:
1225 # Cannot disassemble from one dso to another
1227 bsym = query.value(1)
1228 boff = query.value(2)
1229 bsym_start = query.value(3)
1232 tot = bsym_start + boff + 1 - sym_start - off
1233 if tot <= 0 or tot > 16384:
1236 inst = self.glb.disassembler.Instruction()
1237 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1240 mode = 0 if Is64Bit(f) else 1
1241 self.glb.disassembler.SetMode(inst, mode)
1244 buf = create_string_buffer(tot + 16)
1245 f.seek(sym_start + off)
1246 buf.value = f.read(buf_sz)
1247 buf_ptr = addressof(buf)
1250 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1252 byte_str = tohex(ip).rjust(16)
1253 for k in xrange(cnt):
1254 byte_str += " %02x" % ord(buf[i])
1259 self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
1260 self.child_count += 1
1268 def childCount(self):
1269 if not self.query_done:
1271 if not self.child_count:
1273 return self.child_count
1275 def hasChildren(self):
1276 if not self.query_done:
1278 return self.child_count > 0
1280 def getData(self, column):
1281 return self.data[column]
1283 # Brance data model root item
1285 class BranchRootItem():
1288 self.child_count = 0
1289 self.child_items = []
1292 def getChildItem(self, row):
1293 return self.child_items[row]
1295 def getParentItem(self):
1301 def childCount(self):
1302 return self.child_count
1304 def hasChildren(self):
1305 return self.child_count > 0
1307 def getData(self, column):
1310 # Branch data preparation
1312 def BranchDataPrep(query):
1314 for i in xrange(0, 8):
1315 data.append(query.value(i))
1316 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1317 " (" + dsoname(query.value(11)) + ")" + " -> " +
1318 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1319 " (" + dsoname(query.value(15)) + ")")
1324 class BranchModel(TreeModel):
1326 progress = Signal(object)
1328 def __init__(self, glb, event_id, where_clause, parent=None):
1329 super(BranchModel, self).__init__(BranchRootItem(), parent)
1331 self.event_id = event_id
1334 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1335 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1336 " ip, symbols.name, sym_offset, dsos.short_name,"
1337 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1339 " INNER JOIN comms ON comm_id = comms.id"
1340 " INNER JOIN threads ON thread_id = threads.id"
1341 " INNER JOIN branch_types ON branch_type = branch_types.id"
1342 " INNER JOIN symbols ON symbol_id = symbols.id"
1343 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1344 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1345 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1346 " WHERE samples.id > $$last_id$$" + where_clause +
1347 " AND evsel_id = " + str(self.event_id) +
1348 " ORDER BY samples.id"
1349 " LIMIT " + str(glb_chunk_sz))
1350 self.fetcher = SQLFetcher(glb, sql, BranchDataPrep, self.AddSample)
1351 self.fetcher.done.connect(self.Update)
1352 self.fetcher.Fetch(glb_chunk_sz)
1354 def columnCount(self, parent=None):
1357 def columnHeader(self, column):
1358 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1360 def columnFont(self, column):
1363 return QFont("Monospace")
1365 def DisplayData(self, item, index):
1367 self.FetchIfNeeded(item.row)
1368 return item.getData(index.column())
1370 def AddSample(self, data):
1371 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1372 self.root.child_items.append(child)
1375 def Update(self, fetched):
1378 self.progress.emit(0)
1379 child_count = self.root.child_count
1380 count = self.populated - child_count
1382 parent = QModelIndex()
1383 self.beginInsertRows(parent, child_count, child_count + count - 1)
1384 self.insertRows(child_count, count, parent)
1385 self.root.child_count += count
1386 self.endInsertRows()
1387 self.progress.emit(self.root.child_count)
1389 def FetchMoreRecords(self, count):
1390 current = self.root.child_count
1392 self.fetcher.Fetch(count)
1394 self.progress.emit(0)
1397 def HasMoreRecords(self):
1402 class BranchWindow(QMdiSubWindow):
1404 def __init__(self, glb, event_id, name, where_clause, parent=None):
1405 super(BranchWindow, self).__init__(parent)
1407 model_name = "Branch Events " + str(event_id)
1408 if len(where_clause):
1409 model_name = where_clause + " " + model_name
1411 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, where_clause))
1413 self.view = QTreeView()
1414 self.view.setUniformRowHeights(True)
1415 self.view.setModel(self.model)
1417 self.ResizeColumnsToContents()
1419 self.find_bar = FindBar(self, self, True)
1421 self.finder = ChildDataItemFinder(self.model.root)
1423 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1425 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1427 self.setWidget(self.vbox.Widget())
1429 AddSubWindow(glb.mainwindow.mdi_area, self, name + " Branch Events")
1431 def ResizeColumnToContents(self, column, n):
1432 # Using the view's resizeColumnToContents() here is extrememly slow
1433 # so implement a crude alternative
1434 mm = "MM" if column else "MMMM"
1435 font = self.view.font()
1436 metrics = QFontMetrics(font)
1438 for row in xrange(n):
1439 val = self.model.root.child_items[row].data[column]
1440 len = metrics.width(str(val) + mm)
1441 max = len if len > max else max
1442 val = self.model.columnHeader(column)
1443 len = metrics.width(str(val) + mm)
1444 max = len if len > max else max
1445 self.view.setColumnWidth(column, max)
1447 def ResizeColumnsToContents(self):
1448 n = min(self.model.root.child_count, 100)
1450 # No data yet, so connect a signal to notify when there is
1451 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1453 columns = self.model.columnCount()
1454 for i in xrange(columns):
1455 self.ResizeColumnToContents(i, n)
1457 def UpdateColumnWidths(self, *x):
1458 # This only needs to be done once, so disconnect the signal now
1459 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1460 self.ResizeColumnsToContents()
1462 def Find(self, value, direction, pattern, context):
1463 self.view.setFocus()
1464 self.find_bar.Busy()
1465 self.finder.Find(value, direction, pattern, context, self.FindDone)
1467 def FindDone(self, row):
1468 self.find_bar.Idle()
1470 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1472 self.find_bar.NotFound()
1474 # Dialog data item converted and validated using a SQL table
1476 class SQLTableDialogDataItem():
1478 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1481 self.placeholder_text = placeholder_text
1482 self.table_name = table_name
1483 self.match_column = match_column
1484 self.column_name1 = column_name1
1485 self.column_name2 = column_name2
1486 self.parent = parent
1490 self.widget = QLineEdit()
1491 self.widget.editingFinished.connect(self.Validate)
1492 self.widget.textChanged.connect(self.Invalidate)
1495 self.validated = True
1499 self.last_time = 2 ** 64
1500 if self.table_name == "<timeranges>":
1501 query = QSqlQuery(self.glb.db)
1502 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1504 self.last_id = int(query.value(0))
1505 self.last_time = int(query.value(1))
1506 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1508 self.first_time = int(query.value(0))
1509 if placeholder_text:
1510 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1512 if placeholder_text:
1513 self.widget.setPlaceholderText(placeholder_text)
1515 def ValueToIds(self, value):
1517 query = QSqlQuery(self.glb.db)
1518 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1519 ret = query.exec_(stmt)
1522 ids.append(str(query.value(0)))
1525 def IdBetween(self, query, lower_id, higher_id, order):
1526 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1528 return True, int(query.value(0))
1532 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1533 query = QSqlQuery(self.glb.db)
1535 next_id = int((lower_id + higher_id) / 2)
1536 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1537 if not query.next():
1538 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1540 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1542 return str(higher_id)
1544 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1545 next_time = int(query.value(0))
1547 if target_time > next_time:
1551 if higher_id <= lower_id + 1:
1552 return str(higher_id)
1554 if target_time >= next_time:
1558 if higher_id <= lower_id + 1:
1559 return str(lower_id)
1561 def ConvertRelativeTime(self, val):
1567 elif suffix == "us":
1569 elif suffix == "ns":
1573 val = val[:-2].strip()
1574 if not self.IsNumber(val):
1576 val = int(val) * mult
1578 val += self.first_time
1580 val += self.last_time
1583 def ConvertTimeRange(self, vrange):
1584 print "vrange ", vrange
1586 vrange[0] = str(self.first_time)
1588 vrange[1] = str(self.last_time)
1589 vrange[0] = self.ConvertRelativeTime(vrange[0])
1590 vrange[1] = self.ConvertRelativeTime(vrange[1])
1591 print "vrange2 ", vrange
1592 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1595 beg_range = max(int(vrange[0]), self.first_time)
1596 end_range = min(int(vrange[1]), self.last_time)
1597 if beg_range > self.last_time or end_range < self.first_time:
1600 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
1601 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
1602 print "vrange3 ", vrange
1605 def AddTimeRange(self, value, ranges):
1606 print "value ", value
1607 n = value.count("-")
1611 if value.split("-")[1].strip() == "":
1617 pos = findnth(value, "-", n)
1618 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
1619 if self.ConvertTimeRange(vrange):
1620 ranges.append(vrange)
1624 def InvalidValue(self, value):
1626 palette = QPalette()
1627 palette.setColor(QPalette.Text,Qt.red)
1628 self.widget.setPalette(palette)
1630 self.error = self.label + " invalid value '" + value + "'"
1631 self.parent.ShowMessage(self.error)
1633 def IsNumber(self, value):
1638 return str(x) == value
1640 def Invalidate(self):
1641 self.validated = False
1644 input_string = self.widget.text()
1645 self.validated = True
1647 palette = QPalette()
1648 self.widget.setPalette(palette)
1650 if not len(input_string.strip()):
1654 if self.table_name == "<timeranges>":
1656 for value in [x.strip() for x in input_string.split(",")]:
1657 if not self.AddTimeRange(value, ranges):
1658 return self.InvalidValue(value)
1659 ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
1660 self.value = " OR ".join(ranges)
1661 elif self.table_name == "<ranges>":
1664 for value in [x.strip() for x in input_string.split(",")]:
1666 vrange = value.split("-")
1667 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1668 return self.InvalidValue(value)
1669 ranges.append(vrange)
1671 if not self.IsNumber(value):
1672 return self.InvalidValue(value)
1673 singles.append(value)
1674 ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
1676 ranges.append(self.column_name1 + " IN (" + ",".join(singles) + ")")
1677 self.value = " OR ".join(ranges)
1678 elif self.table_name:
1680 for value in [x.strip() for x in input_string.split(",")]:
1681 ids = self.ValueToIds(value)
1685 return self.InvalidValue(value)
1686 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1687 if self.column_name2:
1688 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1690 self.value = input_string.strip()
1692 self.parent.ClearMessage()
1695 if not self.validated:
1698 self.parent.ShowMessage(self.error)
1702 # Selected branch report creation dialog
1704 class SelectedBranchDialog(QDialog):
1706 def __init__(self, glb, parent=None):
1707 super(SelectedBranchDialog, self).__init__(parent)
1712 self.where_clause = ""
1714 self.setWindowTitle("Selected Branches")
1715 self.setMinimumWidth(600)
1718 ("Report name:", "Enter a name to appear in the window title bar", "", "", "", ""),
1719 ("Time ranges:", "Enter time ranges", "<timeranges>", "", "samples.id", ""),
1720 ("CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "<ranges>", "", "cpu", ""),
1721 ("Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", ""),
1722 ("PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", ""),
1723 ("TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", ""),
1724 ("DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id"),
1725 ("Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id"),
1726 ("Raw SQL clause: ", "Enter a raw SQL WHERE clause", "", "", "", ""),
1728 self.data_items = [SQLTableDialogDataItem(glb, *x, parent=self) for x in items]
1730 self.grid = QGridLayout()
1732 for row in xrange(len(self.data_items)):
1733 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
1734 self.grid.addWidget(self.data_items[row].widget, row, 1)
1736 self.status = QLabel()
1738 self.ok_button = QPushButton("Ok", self)
1739 self.ok_button.setDefault(True)
1740 self.ok_button.released.connect(self.Ok)
1741 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1743 self.cancel_button = QPushButton("Cancel", self)
1744 self.cancel_button.released.connect(self.reject)
1745 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1747 self.hbox = QHBoxLayout()
1748 #self.hbox.addStretch()
1749 self.hbox.addWidget(self.status)
1750 self.hbox.addWidget(self.ok_button)
1751 self.hbox.addWidget(self.cancel_button)
1753 self.vbox = QVBoxLayout()
1754 self.vbox.addLayout(self.grid)
1755 self.vbox.addLayout(self.hbox)
1757 self.setLayout(self.vbox);
1760 self.name = self.data_items[0].value
1762 self.ShowMessage("Report name is required")
1764 for d in self.data_items:
1767 for d in self.data_items[1:]:
1769 if len(self.where_clause):
1770 self.where_clause += " AND "
1771 self.where_clause += d.value
1772 if len(self.where_clause):
1773 self.where_clause = " AND ( " + self.where_clause + " ) "
1775 self.ShowMessage("No selection")
1779 def ShowMessage(self, msg):
1780 self.status.setText("<font color=#FF0000>" + msg)
1782 def ClearMessage(self):
1783 self.status.setText("")
1787 def GetEventList(db):
1789 query = QSqlQuery(db)
1790 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
1792 events.append(query.value(0))
1795 # SQL data preparation
1797 def SQLTableDataPrep(query, count):
1799 for i in xrange(count):
1800 data.append(query.value(i))
1803 # SQL table data model item
1805 class SQLTableItem():
1807 def __init__(self, row, data):
1811 def getData(self, column):
1812 return self.data[column]
1814 # SQL table data model
1816 class SQLTableModel(TableModel):
1818 progress = Signal(object)
1820 def __init__(self, glb, sql, column_count, parent=None):
1821 super(SQLTableModel, self).__init__(parent)
1825 self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample)
1826 self.fetcher.done.connect(self.Update)
1827 self.fetcher.Fetch(glb_chunk_sz)
1829 def DisplayData(self, item, index):
1830 self.FetchIfNeeded(item.row)
1831 return item.getData(index.column())
1833 def AddSample(self, data):
1834 child = SQLTableItem(self.populated, data)
1835 self.child_items.append(child)
1838 def Update(self, fetched):
1841 self.progress.emit(0)
1842 child_count = self.child_count
1843 count = self.populated - child_count
1845 parent = QModelIndex()
1846 self.beginInsertRows(parent, child_count, child_count + count - 1)
1847 self.insertRows(child_count, count, parent)
1848 self.child_count += count
1849 self.endInsertRows()
1850 self.progress.emit(self.child_count)
1852 def FetchMoreRecords(self, count):
1853 current = self.child_count
1855 self.fetcher.Fetch(count)
1857 self.progress.emit(0)
1860 def HasMoreRecords(self):
1863 # SQL automatic table data model
1865 class SQLAutoTableModel(SQLTableModel):
1867 def __init__(self, glb, table_name, parent=None):
1868 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
1869 if table_name == "comm_threads_view":
1870 # For now, comm_threads_view has no id column
1871 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
1872 self.column_headers = []
1873 query = QSqlQuery(glb.db)
1874 if glb.dbref.is_sqlite3:
1875 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
1877 self.column_headers.append(query.value(1))
1878 if table_name == "sqlite_master":
1879 sql = "SELECT * FROM " + table_name
1881 if table_name[:19] == "information_schema.":
1882 sql = "SELECT * FROM " + table_name
1883 select_table_name = table_name[19:]
1884 schema = "information_schema"
1886 select_table_name = table_name
1888 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
1890 self.column_headers.append(query.value(0))
1891 super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent)
1893 def columnCount(self, parent=None):
1894 return len(self.column_headers)
1896 def columnHeader(self, column):
1897 return self.column_headers[column]
1899 # Base class for custom ResizeColumnsToContents
1901 class ResizeColumnsToContentsBase(QObject):
1903 def __init__(self, parent=None):
1904 super(ResizeColumnsToContentsBase, self).__init__(parent)
1906 def ResizeColumnToContents(self, column, n):
1907 # Using the view's resizeColumnToContents() here is extrememly slow
1908 # so implement a crude alternative
1909 font = self.view.font()
1910 metrics = QFontMetrics(font)
1912 for row in xrange(n):
1913 val = self.data_model.child_items[row].data[column]
1914 len = metrics.width(str(val) + "MM")
1915 max = len if len > max else max
1916 val = self.data_model.columnHeader(column)
1917 len = metrics.width(str(val) + "MM")
1918 max = len if len > max else max
1919 self.view.setColumnWidth(column, max)
1921 def ResizeColumnsToContents(self):
1922 n = min(self.data_model.child_count, 100)
1924 # No data yet, so connect a signal to notify when there is
1925 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
1927 columns = self.data_model.columnCount()
1928 for i in xrange(columns):
1929 self.ResizeColumnToContents(i, n)
1931 def UpdateColumnWidths(self, *x):
1932 # This only needs to be done once, so disconnect the signal now
1933 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
1934 self.ResizeColumnsToContents()
1938 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
1940 def __init__(self, glb, table_name, parent=None):
1941 super(TableWindow, self).__init__(parent)
1943 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
1945 self.model = QSortFilterProxyModel()
1946 self.model.setSourceModel(self.data_model)
1948 self.view = QTableView()
1949 self.view.setModel(self.model)
1950 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
1951 self.view.verticalHeader().setVisible(False)
1952 self.view.sortByColumn(-1, Qt.AscendingOrder)
1953 self.view.setSortingEnabled(True)
1955 self.ResizeColumnsToContents()
1957 self.find_bar = FindBar(self, self, True)
1959 self.finder = ChildDataItemFinder(self.data_model)
1961 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
1963 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1965 self.setWidget(self.vbox.Widget())
1967 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
1969 def Find(self, value, direction, pattern, context):
1970 self.view.setFocus()
1971 self.find_bar.Busy()
1972 self.finder.Find(value, direction, pattern, context, self.FindDone)
1974 def FindDone(self, row):
1975 self.find_bar.Idle()
1977 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
1979 self.find_bar.NotFound()
1983 def GetTableList(glb):
1985 query = QSqlQuery(glb.db)
1986 if glb.dbref.is_sqlite3:
1987 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
1989 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
1991 tables.append(query.value(0))
1992 if glb.dbref.is_sqlite3:
1993 tables.append("sqlite_master")
1995 tables.append("information_schema.tables")
1996 tables.append("information_schema.views")
1997 tables.append("information_schema.columns")
2002 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2003 action = QAction(label, parent)
2004 if shortcut != None:
2005 action.setShortcuts(shortcut)
2006 action.setStatusTip(tip)
2007 action.triggered.connect(callback)
2010 # Typical application actions
2012 def CreateExitAction(app, parent=None):
2013 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2015 # Typical MDI actions
2017 def CreateCloseActiveWindowAction(mdi_area):
2018 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2020 def CreateCloseAllWindowsAction(mdi_area):
2021 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2023 def CreateTileWindowsAction(mdi_area):
2024 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2026 def CreateCascadeWindowsAction(mdi_area):
2027 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2029 def CreateNextWindowAction(mdi_area):
2030 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2032 def CreatePreviousWindowAction(mdi_area):
2033 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2035 # Typical MDI window menu
2039 def __init__(self, mdi_area, menu):
2040 self.mdi_area = mdi_area
2041 self.window_menu = menu.addMenu("&Windows")
2042 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2043 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2044 self.tile_windows = CreateTileWindowsAction(mdi_area)
2045 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2046 self.next_window = CreateNextWindowAction(mdi_area)
2047 self.previous_window = CreatePreviousWindowAction(mdi_area)
2048 self.window_menu.aboutToShow.connect(self.Update)
2051 self.window_menu.clear()
2052 sub_window_count = len(self.mdi_area.subWindowList())
2053 have_sub_windows = sub_window_count != 0
2054 self.close_active_window.setEnabled(have_sub_windows)
2055 self.close_all_windows.setEnabled(have_sub_windows)
2056 self.tile_windows.setEnabled(have_sub_windows)
2057 self.cascade_windows.setEnabled(have_sub_windows)
2058 self.next_window.setEnabled(have_sub_windows)
2059 self.previous_window.setEnabled(have_sub_windows)
2060 self.window_menu.addAction(self.close_active_window)
2061 self.window_menu.addAction(self.close_all_windows)
2062 self.window_menu.addSeparator()
2063 self.window_menu.addAction(self.tile_windows)
2064 self.window_menu.addAction(self.cascade_windows)
2065 self.window_menu.addSeparator()
2066 self.window_menu.addAction(self.next_window)
2067 self.window_menu.addAction(self.previous_window)
2068 if sub_window_count == 0:
2070 self.window_menu.addSeparator()
2072 for sub_window in self.mdi_area.subWindowList():
2073 label = str(nr) + " " + sub_window.name
2076 action = self.window_menu.addAction(label)
2077 action.setCheckable(True)
2078 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2079 action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2080 self.window_menu.addAction(action)
2083 def setActiveSubWindow(self, nr):
2084 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2099 <p class=c1><a href=#reports>1. Reports</a></p>
2100 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2101 <p class=c2><a href=#allbranches>1.2 All branches</a></p>
2102 <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p>
2103 <p class=c1><a href=#tables>2. Tables</a></p>
2104 <h1 id=reports>1. Reports</h1>
2105 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2106 The result is a GUI window with a tree representing a context-sensitive
2107 call-graph. Expanding a couple of levels of the tree and adjusting column
2108 widths to suit will display something like:
2110 Call Graph: pt_example
2111 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2114 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2115 |- unknown unknown 1 13198 0.1 1 0.0
2116 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2117 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2118 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2119 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2120 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2121 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2122 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2123 v- main ls 1 8182043 99.6 180254 99.9
2125 <h3>Points to note:</h3>
2127 <li>The top level is a command name (comm)</li>
2128 <li>The next level is a thread (pid:tid)</li>
2129 <li>Subsequent levels are functions</li>
2130 <li>'Count' is the number of calls</li>
2131 <li>'Time' is the elapsed time until the function returns</li>
2132 <li>Percentages are relative to the level above</li>
2133 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2136 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2137 The pattern matching symbols are ? for any character and * for zero or more characters.
2138 <h2 id=allbranches>1.2 All branches</h2>
2139 The All branches report displays all branches in chronological order.
2140 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2141 <h3>Disassembly</h3>
2142 Open a branch to display disassembly. This only works if:
2144 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2145 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2146 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2147 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2148 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2150 <h4 id=xed>Intel XED Setup</h4>
2151 To use Intel XED, libxed.so must be present. To build and install libxed.so:
2153 git clone https://github.com/intelxed/mbuild.git mbuild
2154 git clone https://github.com/intelxed/xed
2157 sudo ./mfile.py --prefix=/usr/local install
2161 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2162 Refer to Python documentation for the regular expression syntax.
2163 All columns are searched, but only currently fetched rows are searched.
2164 <h2 id=selectedbranches>1.3 Selected branches</h2>
2165 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2166 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2167 <h3>1.3.1 Time ranges</h3>
2168 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2169 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
2171 81073085947329-81073085958238 From 81073085947329 to 81073085958238
2172 100us-200us From 100us to 200us
2173 10ms- From 10ms to the end
2174 -100ns The first 100ns
2175 -10ms- The last 10ms
2177 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2178 <h1 id=tables>2. Tables</h1>
2179 The Tables menu shows all tables and views in the database. Most tables have an associated view
2180 which displays the information in a more friendly way. Not all data for large tables is fetched
2181 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2182 but that can be slow for large tables.
2183 <p>There are also tables of database meta-information.
2184 For SQLite3 databases, the sqlite_master table is included.
2185 For PostgreSQL databases, information_schema.tables/views/columns are included.
2187 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2188 Refer to Python documentation for the regular expression syntax.
2189 All columns are searched, but only currently fetched rows are searched.
2190 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2191 will go to the next/previous result in id order, instead of display order.
2196 class HelpWindow(QMdiSubWindow):
2198 def __init__(self, glb, parent=None):
2199 super(HelpWindow, self).__init__(parent)
2201 self.text = QTextBrowser()
2202 self.text.setHtml(glb_help_text)
2203 self.text.setReadOnly(True)
2204 self.text.setOpenExternalLinks(True)
2206 self.setWidget(self.text)
2208 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2210 # Main window that only displays the help text
2212 class HelpOnlyWindow(QMainWindow):
2214 def __init__(self, parent=None):
2215 super(HelpOnlyWindow, self).__init__(parent)
2217 self.setMinimumSize(200, 100)
2218 self.resize(800, 600)
2219 self.setWindowTitle("Exported SQL Viewer Help")
2220 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2222 self.text = QTextBrowser()
2223 self.text.setHtml(glb_help_text)
2224 self.text.setReadOnly(True)
2225 self.text.setOpenExternalLinks(True)
2227 self.setCentralWidget(self.text)
2231 def ResizeFont(widget, diff):
2232 font = widget.font()
2233 sz = font.pointSize()
2234 font.setPointSize(sz + diff)
2235 widget.setFont(font)
2237 def ShrinkFont(widget):
2238 ResizeFont(widget, -1)
2240 def EnlargeFont(widget):
2241 ResizeFont(widget, 1)
2243 # Unique name for sub-windows
2245 def NumberedWindowName(name, nr):
2247 name += " <" + str(nr) + ">"
2250 def UniqueSubWindowName(mdi_area, name):
2253 unique_name = NumberedWindowName(name, nr)
2255 for sub_window in mdi_area.subWindowList():
2256 if sub_window.name == unique_name:
2265 def AddSubWindow(mdi_area, sub_window, name):
2266 unique_name = UniqueSubWindowName(mdi_area, name)
2267 sub_window.setMinimumSize(200, 100)
2268 sub_window.resize(800, 600)
2269 sub_window.setWindowTitle(unique_name)
2270 sub_window.setAttribute(Qt.WA_DeleteOnClose)
2271 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
2272 sub_window.name = unique_name
2273 mdi_area.addSubWindow(sub_window)
2278 class MainWindow(QMainWindow):
2280 def __init__(self, glb, parent=None):
2281 super(MainWindow, self).__init__(parent)
2285 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
2286 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
2287 self.setMinimumSize(200, 100)
2289 self.mdi_area = QMdiArea()
2290 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2291 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2293 self.setCentralWidget(self.mdi_area)
2295 menu = self.menuBar()
2297 file_menu = menu.addMenu("&File")
2298 file_menu.addAction(CreateExitAction(glb.app, self))
2300 edit_menu = menu.addMenu("&Edit")
2301 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
2302 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
2303 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
2304 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
2306 reports_menu = menu.addMenu("&Reports")
2307 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
2309 self.EventMenu(GetEventList(glb.db), reports_menu)
2311 self.TableMenu(GetTableList(glb), menu)
2313 self.window_menu = WindowMenu(self.mdi_area, menu)
2315 help_menu = menu.addMenu("&Help")
2316 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
2319 win = self.mdi_area.activeSubWindow()
2322 win.find_bar.Activate()
2326 def FetchMoreRecords(self):
2327 win = self.mdi_area.activeSubWindow()
2330 win.fetch_bar.Activate()
2334 def ShrinkFont(self):
2335 win = self.mdi_area.activeSubWindow()
2336 ShrinkFont(win.view)
2338 def EnlargeFont(self):
2339 win = self.mdi_area.activeSubWindow()
2340 EnlargeFont(win.view)
2342 def EventMenu(self, events, reports_menu):
2344 for event in events:
2345 event = event.split(":")[0]
2346 if event == "branches":
2347 branches_events += 1
2349 for event in events:
2351 event = event.split(":")[0]
2352 if event == "branches":
2353 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
2354 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
2355 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
2356 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
2358 def TableMenu(self, tables, menu):
2359 table_menu = menu.addMenu("&Tables")
2360 for table in tables:
2361 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
2363 def NewCallGraph(self):
2364 CallGraphWindow(self.glb, self)
2366 def NewBranchView(self, event_id):
2367 BranchWindow(self.glb, event_id, "", "", self)
2369 def NewSelectedBranchView(self, event_id):
2370 dialog = SelectedBranchDialog(self.glb, self)
2371 ret = dialog.exec_()
2373 BranchWindow(self.glb, event_id, dialog.name, dialog.where_clause, self)
2375 def NewTableView(self, table_name):
2376 TableWindow(self.glb, table_name, self)
2379 HelpWindow(self.glb, self)
2383 class xed_state_t(Structure):
2390 class XEDInstruction():
2392 def __init__(self, libxed):
2393 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
2394 xedd_t = c_byte * 512
2395 self.xedd = xedd_t()
2396 self.xedp = addressof(self.xedd)
2397 libxed.xed_decoded_inst_zero(self.xedp)
2398 self.state = xed_state_t()
2399 self.statep = addressof(self.state)
2400 # Buffer for disassembled instruction text
2401 self.buffer = create_string_buffer(256)
2402 self.bufferp = addressof(self.buffer)
2408 self.libxed = CDLL("libxed.so")
2412 self.libxed = CDLL("/usr/local/lib/libxed.so")
2414 self.xed_tables_init = self.libxed.xed_tables_init
2415 self.xed_tables_init.restype = None
2416 self.xed_tables_init.argtypes = []
2418 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
2419 self.xed_decoded_inst_zero.restype = None
2420 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
2422 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
2423 self.xed_operand_values_set_mode.restype = None
2424 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
2426 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
2427 self.xed_decoded_inst_zero_keep_mode.restype = None
2428 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
2430 self.xed_decode = self.libxed.xed_decode
2431 self.xed_decode.restype = c_int
2432 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
2434 self.xed_format_context = self.libxed.xed_format_context
2435 self.xed_format_context.restype = c_uint
2436 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
2438 self.xed_tables_init()
2440 def Instruction(self):
2441 return XEDInstruction(self)
2443 def SetMode(self, inst, mode):
2445 inst.state.mode = 4 # 32-bit
2446 inst.state.width = 4 # 4 bytes
2448 inst.state.mode = 1 # 64-bit
2449 inst.state.width = 8 # 8 bytes
2450 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
2452 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
2453 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
2454 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
2457 # Use AT&T mode (2), alternative is Intel (3)
2458 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
2461 # Return instruction length and the disassembled instruction text
2462 # For now, assume the length is in byte 166
2463 return inst.xedd[166], inst.buffer.value
2465 def TryOpen(file_name):
2467 return open(file_name, "rb")
2472 result = sizeof(c_void_p)
2479 eclass = ord(header[4])
2480 encoding = ord(header[5])
2481 version = ord(header[6])
2482 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
2483 result = True if eclass == 2 else False
2490 def __init__(self, dbref, db, dbname):
2493 self.dbname = dbname
2494 self.home_dir = os.path.expanduser("~")
2495 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
2496 if self.buildid_dir:
2497 self.buildid_dir += "/.build-id/"
2499 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
2501 self.mainwindow = None
2502 self.instances_to_shutdown_on_exit = weakref.WeakSet()
2504 self.disassembler = LibXED()
2505 self.have_disassembler = True
2507 self.have_disassembler = False
2509 def FileFromBuildId(self, build_id):
2510 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
2511 return TryOpen(file_name)
2513 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
2514 # Assume current machine i.e. no support for virtualization
2515 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
2516 file_name = os.getenv("PERF_KCORE")
2517 f = TryOpen(file_name) if file_name else None
2520 # For now, no special handling if long_name is /proc/kcore
2521 f = TryOpen(long_name)
2524 f = self.FileFromBuildId(build_id)
2529 def AddInstanceToShutdownOnExit(self, instance):
2530 self.instances_to_shutdown_on_exit.add(instance)
2532 # Shutdown any background processes or threads
2533 def ShutdownInstances(self):
2534 for x in self.instances_to_shutdown_on_exit:
2540 # Database reference
2544 def __init__(self, is_sqlite3, dbname):
2545 self.is_sqlite3 = is_sqlite3
2546 self.dbname = dbname
2548 def Open(self, connection_name):
2549 dbname = self.dbname
2551 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
2553 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
2554 opts = dbname.split()
2557 opt = opt.split("=")
2558 if opt[0] == "hostname":
2559 db.setHostName(opt[1])
2560 elif opt[0] == "port":
2561 db.setPort(int(opt[1]))
2562 elif opt[0] == "username":
2563 db.setUserName(opt[1])
2564 elif opt[0] == "password":
2565 db.setPassword(opt[1])
2566 elif opt[0] == "dbname":
2571 db.setDatabaseName(dbname)
2573 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
2579 if (len(sys.argv) < 2):
2580 print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}"
2581 raise Exception("Too few arguments")
2583 dbname = sys.argv[1]
2584 if dbname == "--help-only":
2585 app = QApplication(sys.argv)
2586 mainwindow = HelpOnlyWindow()
2594 if f.read(15) == "SQLite format 3":
2600 dbref = DBRef(is_sqlite3, dbname)
2601 db, dbname = dbref.Open("main")
2602 glb = Glb(dbref, db, dbname)
2603 app = QApplication(sys.argv)
2605 mainwindow = MainWindow(glb)
2606 glb.mainwindow = mainwindow
2609 glb.ShutdownInstances()
2613 if __name__ == "__main__":