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])
91 from __future__ import print_function
99 import cPickle as pickle
100 # size of pickled integer big enough for record size
107 from PySide.QtCore import *
108 from PySide.QtGui import *
109 from PySide.QtSql import *
110 pyside_version_1 = True
111 from decimal import *
113 from multiprocessing import Process, Array, Value, Event
115 # xrange is range in Python3
121 def printerr(*args, **keyword_args):
122 print(*args, file=sys.stderr, **keyword_args)
124 # Data formatting helpers
133 return "+0x%x" % offset
137 if name == "[kernel.kallsyms]":
141 def findnth(s, sub, n, offs=0):
147 return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
149 # Percent to one decimal place
151 def PercentToOneDP(n, d):
154 x = (n * Decimal(100)) / d
155 return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
157 # Helper for queries that must not fail
159 def QueryExec(query, stmt):
160 ret = query.exec_(stmt)
162 raise Exception("Query failed: " + query.lastError().text())
166 class Thread(QThread):
168 done = Signal(object)
170 def __init__(self, task, param=None, parent=None):
171 super(Thread, self).__init__(parent)
177 if self.param is None:
178 done, result = self.task()
180 done, result = self.task(self.param)
181 self.done.emit(result)
187 class TreeModel(QAbstractItemModel):
189 def __init__(self, glb, parent=None):
190 super(TreeModel, self).__init__(parent)
192 self.root = self.GetRoot()
193 self.last_row_read = 0
195 def Item(self, parent):
197 return parent.internalPointer()
201 def rowCount(self, parent):
202 result = self.Item(parent).childCount()
205 self.dataChanged.emit(parent, parent)
208 def hasChildren(self, parent):
209 return self.Item(parent).hasChildren()
211 def headerData(self, section, orientation, role):
212 if role == Qt.TextAlignmentRole:
213 return self.columnAlignment(section)
214 if role != Qt.DisplayRole:
216 if orientation != Qt.Horizontal:
218 return self.columnHeader(section)
220 def parent(self, child):
221 child_item = child.internalPointer()
222 if child_item is self.root:
224 parent_item = child_item.getParentItem()
225 return self.createIndex(parent_item.getRow(), 0, parent_item)
227 def index(self, row, column, parent):
228 child_item = self.Item(parent).getChildItem(row)
229 return self.createIndex(row, column, child_item)
231 def DisplayData(self, item, index):
232 return item.getData(index.column())
234 def FetchIfNeeded(self, row):
235 if row > self.last_row_read:
236 self.last_row_read = row
237 if row + 10 >= self.root.child_count:
238 self.fetcher.Fetch(glb_chunk_sz)
240 def columnAlignment(self, column):
243 def columnFont(self, column):
246 def data(self, index, role):
247 if role == Qt.TextAlignmentRole:
248 return self.columnAlignment(index.column())
249 if role == Qt.FontRole:
250 return self.columnFont(index.column())
251 if role != Qt.DisplayRole:
253 item = index.internalPointer()
254 return self.DisplayData(item, index)
258 class TableModel(QAbstractTableModel):
260 def __init__(self, parent=None):
261 super(TableModel, self).__init__(parent)
263 self.child_items = []
264 self.last_row_read = 0
266 def Item(self, parent):
268 return parent.internalPointer()
272 def rowCount(self, parent):
273 return self.child_count
275 def headerData(self, section, orientation, role):
276 if role == Qt.TextAlignmentRole:
277 return self.columnAlignment(section)
278 if role != Qt.DisplayRole:
280 if orientation != Qt.Horizontal:
282 return self.columnHeader(section)
284 def index(self, row, column, parent):
285 return self.createIndex(row, column, self.child_items[row])
287 def DisplayData(self, item, index):
288 return item.getData(index.column())
290 def FetchIfNeeded(self, row):
291 if row > self.last_row_read:
292 self.last_row_read = row
293 if row + 10 >= self.child_count:
294 self.fetcher.Fetch(glb_chunk_sz)
296 def columnAlignment(self, column):
299 def columnFont(self, column):
302 def data(self, index, role):
303 if role == Qt.TextAlignmentRole:
304 return self.columnAlignment(index.column())
305 if role == Qt.FontRole:
306 return self.columnFont(index.column())
307 if role != Qt.DisplayRole:
309 item = index.internalPointer()
310 return self.DisplayData(item, index)
314 model_cache = weakref.WeakValueDictionary()
315 model_cache_lock = threading.Lock()
317 def LookupCreateModel(model_name, create_fn):
318 model_cache_lock.acquire()
320 model = model_cache[model_name]
325 model_cache[model_name] = model
326 model_cache_lock.release()
333 def __init__(self, parent, finder, is_reg_expr=False):
336 self.last_value = None
337 self.last_pattern = None
339 label = QLabel("Find:")
340 label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
342 self.textbox = QComboBox()
343 self.textbox.setEditable(True)
344 self.textbox.currentIndexChanged.connect(self.ValueChanged)
346 self.progress = QProgressBar()
347 self.progress.setRange(0, 0)
351 self.pattern = QCheckBox("Regular Expression")
353 self.pattern = QCheckBox("Pattern")
354 self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
356 self.next_button = QToolButton()
357 self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
358 self.next_button.released.connect(lambda: self.NextPrev(1))
360 self.prev_button = QToolButton()
361 self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
362 self.prev_button.released.connect(lambda: self.NextPrev(-1))
364 self.close_button = QToolButton()
365 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
366 self.close_button.released.connect(self.Deactivate)
368 self.hbox = QHBoxLayout()
369 self.hbox.setContentsMargins(0, 0, 0, 0)
371 self.hbox.addWidget(label)
372 self.hbox.addWidget(self.textbox)
373 self.hbox.addWidget(self.progress)
374 self.hbox.addWidget(self.pattern)
375 self.hbox.addWidget(self.next_button)
376 self.hbox.addWidget(self.prev_button)
377 self.hbox.addWidget(self.close_button)
380 self.bar.setLayout(self.hbox);
388 self.textbox.setFocus()
390 def Deactivate(self):
394 self.textbox.setEnabled(False)
396 self.next_button.hide()
397 self.prev_button.hide()
401 self.textbox.setEnabled(True)
404 self.next_button.show()
405 self.prev_button.show()
407 def Find(self, direction):
408 value = self.textbox.currentText()
409 pattern = self.pattern.isChecked()
410 self.last_value = value
411 self.last_pattern = pattern
412 self.finder.Find(value, direction, pattern, self.context)
414 def ValueChanged(self):
415 value = self.textbox.currentText()
416 pattern = self.pattern.isChecked()
417 index = self.textbox.currentIndex()
418 data = self.textbox.itemData(index)
419 # Store the pattern in the combo box to keep it with the text value
421 self.textbox.setItemData(index, pattern)
423 self.pattern.setChecked(data)
426 def NextPrev(self, direction):
427 value = self.textbox.currentText()
428 pattern = self.pattern.isChecked()
429 if value != self.last_value:
430 index = self.textbox.findText(value)
431 # Allow for a button press before the value has been added to the combo box
433 index = self.textbox.count()
434 self.textbox.addItem(value, pattern)
435 self.textbox.setCurrentIndex(index)
438 self.textbox.setItemData(index, pattern)
439 elif pattern != self.last_pattern:
440 # Keep the pattern recorded in the combo box up to date
441 index = self.textbox.currentIndex()
442 self.textbox.setItemData(index, pattern)
446 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
448 # Context-sensitive call graph data model item base
450 class CallGraphLevelItemBase(object):
452 def __init__(self, glb, row, parent_item):
455 self.parent_item = parent_item
456 self.query_done = False;
458 self.child_items = []
460 self.level = parent_item.level + 1
464 def getChildItem(self, row):
465 return self.child_items[row]
467 def getParentItem(self):
468 return self.parent_item
473 def childCount(self):
474 if not self.query_done:
476 if not self.child_count:
478 return self.child_count
480 def hasChildren(self):
481 if not self.query_done:
483 return self.child_count > 0
485 def getData(self, column):
486 return self.data[column]
488 # Context-sensitive call graph data model level 2+ item base
490 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
492 def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
493 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
494 self.comm_id = comm_id
495 self.thread_id = thread_id
496 self.call_path_id = call_path_id
497 self.branch_count = branch_count
501 self.query_done = True;
502 query = QSqlQuery(self.glb.db)
503 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
505 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
506 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
507 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
508 " WHERE parent_call_path_id = " + str(self.call_path_id) +
509 " AND comm_id = " + str(self.comm_id) +
510 " AND thread_id = " + str(self.thread_id) +
511 " GROUP BY call_path_id, name, short_name"
512 " ORDER BY call_path_id")
514 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)
515 self.child_items.append(child_item)
516 self.child_count += 1
518 # Context-sensitive call graph data model level three item
520 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
522 def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
523 super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
525 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
526 self.dbid = call_path_id
528 # Context-sensitive call graph data model level two item
530 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
532 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
533 super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
534 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
535 self.dbid = thread_id
538 super(CallGraphLevelTwoItem, self).Select()
539 for child_item in self.child_items:
540 self.time += child_item.time
541 self.branch_count += child_item.branch_count
542 for child_item in self.child_items:
543 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
544 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
546 # Context-sensitive call graph data model level one item
548 class CallGraphLevelOneItem(CallGraphLevelItemBase):
550 def __init__(self, glb, row, comm_id, comm, parent_item):
551 super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
552 self.data = [comm, "", "", "", "", "", ""]
556 self.query_done = True;
557 query = QSqlQuery(self.glb.db)
558 QueryExec(query, "SELECT thread_id, pid, tid"
560 " INNER JOIN threads ON thread_id = threads.id"
561 " WHERE comm_id = " + str(self.dbid))
563 child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
564 self.child_items.append(child_item)
565 self.child_count += 1
567 # Context-sensitive call graph data model root item
569 class CallGraphRootItem(CallGraphLevelItemBase):
571 def __init__(self, glb):
572 super(CallGraphRootItem, self).__init__(glb, 0, None)
574 self.query_done = True;
575 query = QSqlQuery(glb.db)
576 QueryExec(query, "SELECT id, comm FROM comms")
578 if not query.value(0):
580 child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
581 self.child_items.append(child_item)
582 self.child_count += 1
584 # Context-sensitive call graph data model base
586 class CallGraphModelBase(TreeModel):
588 def __init__(self, glb, parent=None):
589 super(CallGraphModelBase, self).__init__(glb, parent)
591 def FindSelect(self, value, pattern, query):
593 # postgresql and sqlite pattern patching differences:
594 # postgresql LIKE is case sensitive but sqlite LIKE is not
595 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
596 # postgresql supports ILIKE which is case insensitive
597 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
598 if not self.glb.dbref.is_sqlite3:
600 s = value.replace("%", "\%")
601 s = s.replace("_", "\_")
602 # Translate * and ? into SQL LIKE pattern characters % and _
603 trans = string.maketrans("*?", "%_")
604 match = " LIKE '" + str(s).translate(trans) + "'"
606 match = " GLOB '" + str(value) + "'"
608 match = " = '" + str(value) + "'"
609 self.DoFindSelect(query, match)
611 def Found(self, query, found):
613 return self.FindPath(query)
616 def FindValue(self, value, pattern, query, last_value, last_pattern):
617 if last_value == value and pattern == last_pattern:
618 found = query.first()
620 self.FindSelect(value, pattern, query)
622 return self.Found(query, found)
624 def FindNext(self, query):
627 found = query.first()
628 return self.Found(query, found)
630 def FindPrev(self, query):
631 found = query.previous()
634 return self.Found(query, found)
636 def FindThread(self, c):
637 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
638 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
639 elif c.direction > 0:
640 ids = self.FindNext(c.query)
642 ids = self.FindPrev(c.query)
645 def Find(self, value, direction, pattern, context, callback):
647 def __init__(self, *x):
648 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
649 def Update(self, *x):
650 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
652 context[0].Update(value, direction, pattern)
654 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
655 # Use a thread so the UI is not blocked during the SELECT
656 thread = Thread(self.FindThread, context[0])
657 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
660 def FindDone(self, thread, callback, ids):
663 # Context-sensitive call graph data model
665 class CallGraphModel(CallGraphModelBase):
667 def __init__(self, glb, parent=None):
668 super(CallGraphModel, self).__init__(glb, parent)
671 return CallGraphRootItem(self.glb)
673 def columnCount(self, parent=None):
676 def columnHeader(self, column):
677 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
678 return headers[column]
680 def columnAlignment(self, column):
681 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
682 return alignment[column]
684 def DoFindSelect(self, query, match):
685 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
687 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
688 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
689 " WHERE symbols.name" + match +
690 " GROUP BY comm_id, thread_id, call_path_id"
691 " ORDER BY comm_id, thread_id, call_path_id")
693 def FindPath(self, query):
694 # Turn the query result into a list of ids that the tree view can walk
695 # to open the tree at the right place.
697 parent_id = query.value(0)
699 ids.insert(0, parent_id)
700 q2 = QSqlQuery(self.glb.db)
701 QueryExec(q2, "SELECT parent_id"
703 " WHERE id = " + str(parent_id))
706 parent_id = q2.value(0)
707 # The call path root is not used
710 ids.insert(0, query.value(2))
711 ids.insert(0, query.value(1))
714 # Call tree data model level 2+ item base
716 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
718 def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
719 super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
720 self.comm_id = comm_id
721 self.thread_id = thread_id
722 self.calls_id = calls_id
723 self.branch_count = branch_count
727 self.query_done = True;
728 if self.calls_id == 0:
729 comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
732 query = QSqlQuery(self.glb.db)
733 QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
735 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
736 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
737 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
738 " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
739 " ORDER BY call_time, calls.id")
741 child_item = CallTreeLevelThreeItem(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)
742 self.child_items.append(child_item)
743 self.child_count += 1
745 # Call tree data model level three item
747 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
749 def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
750 super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
752 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
755 # Call tree data model level two item
757 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
759 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
760 super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item)
761 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
762 self.dbid = thread_id
765 super(CallTreeLevelTwoItem, self).Select()
766 for child_item in self.child_items:
767 self.time += child_item.time
768 self.branch_count += child_item.branch_count
769 for child_item in self.child_items:
770 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
771 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
773 # Call tree data model level one item
775 class CallTreeLevelOneItem(CallGraphLevelItemBase):
777 def __init__(self, glb, row, comm_id, comm, parent_item):
778 super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item)
779 self.data = [comm, "", "", "", "", "", ""]
783 self.query_done = True;
784 query = QSqlQuery(self.glb.db)
785 QueryExec(query, "SELECT thread_id, pid, tid"
787 " INNER JOIN threads ON thread_id = threads.id"
788 " WHERE comm_id = " + str(self.dbid))
790 child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
791 self.child_items.append(child_item)
792 self.child_count += 1
794 # Call tree data model root item
796 class CallTreeRootItem(CallGraphLevelItemBase):
798 def __init__(self, glb):
799 super(CallTreeRootItem, self).__init__(glb, 0, None)
801 self.query_done = True;
802 query = QSqlQuery(glb.db)
803 QueryExec(query, "SELECT id, comm FROM comms")
805 if not query.value(0):
807 child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
808 self.child_items.append(child_item)
809 self.child_count += 1
811 # Call Tree data model
813 class CallTreeModel(CallGraphModelBase):
815 def __init__(self, glb, parent=None):
816 super(CallTreeModel, self).__init__(glb, parent)
819 return CallTreeRootItem(self.glb)
821 def columnCount(self, parent=None):
824 def columnHeader(self, column):
825 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
826 return headers[column]
828 def columnAlignment(self, column):
829 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
830 return alignment[column]
832 def DoFindSelect(self, query, match):
833 QueryExec(query, "SELECT calls.id, comm_id, thread_id"
835 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
836 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
837 " WHERE symbols.name" + match +
838 " ORDER BY comm_id, thread_id, call_time, calls.id")
840 def FindPath(self, query):
841 # Turn the query result into a list of ids that the tree view can walk
842 # to open the tree at the right place.
844 parent_id = query.value(0)
846 ids.insert(0, parent_id)
847 q2 = QSqlQuery(self.glb.db)
848 QueryExec(q2, "SELECT parent_id"
850 " WHERE id = " + str(parent_id))
853 parent_id = q2.value(0)
854 ids.insert(0, query.value(2))
855 ids.insert(0, query.value(1))
858 # Vertical widget layout
862 def __init__(self, w1, w2, w3=None):
863 self.vbox = QWidget()
864 self.vbox.setLayout(QVBoxLayout());
866 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
868 self.vbox.layout().addWidget(w1)
869 self.vbox.layout().addWidget(w2)
871 self.vbox.layout().addWidget(w3)
878 class TreeWindowBase(QMdiSubWindow):
880 def __init__(self, parent=None):
881 super(TreeWindowBase, self).__init__(parent)
886 self.view = QTreeView()
887 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
888 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
890 self.context_menu = TreeContextMenu(self.view)
892 def DisplayFound(self, ids):
895 parent = QModelIndex()
898 n = self.model.rowCount(parent)
899 for row in xrange(n):
900 child = self.model.index(row, 0, parent)
901 if child.internalPointer().dbid == dbid:
903 self.view.setCurrentIndex(child)
910 def Find(self, value, direction, pattern, context):
913 self.model.Find(value, direction, pattern, context, self.FindDone)
915 def FindDone(self, ids):
917 if not self.DisplayFound(ids):
921 self.find_bar.NotFound()
924 # Context-sensitive call graph window
926 class CallGraphWindow(TreeWindowBase):
928 def __init__(self, glb, parent=None):
929 super(CallGraphWindow, self).__init__(parent)
931 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
933 self.view.setModel(self.model)
935 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
936 self.view.setColumnWidth(c, w)
938 self.find_bar = FindBar(self, self)
940 self.vbox = VBox(self.view, self.find_bar.Widget())
942 self.setWidget(self.vbox.Widget())
944 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
948 class CallTreeWindow(TreeWindowBase):
950 def __init__(self, glb, parent=None):
951 super(CallTreeWindow, self).__init__(parent)
953 self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
955 self.view.setModel(self.model)
957 for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
958 self.view.setColumnWidth(c, w)
960 self.find_bar = FindBar(self, self)
962 self.vbox = VBox(self.view, self.find_bar.Widget())
964 self.setWidget(self.vbox.Widget())
966 AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
968 # Child data item finder
970 class ChildDataItemFinder():
972 def __init__(self, root):
974 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
978 def FindSelect(self):
981 pattern = re.compile(self.value)
982 for child in self.root.child_items:
983 for column_data in child.data:
984 if re.search(pattern, str(column_data)) is not None:
985 self.rows.append(child.row)
988 for child in self.root.child_items:
989 for column_data in child.data:
990 if self.value in str(column_data):
991 self.rows.append(child.row)
996 if self.last_value != self.value or self.pattern != self.last_pattern:
998 if not len(self.rows):
1000 return self.rows[self.pos]
1002 def FindThread(self):
1003 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1004 row = self.FindValue()
1005 elif len(self.rows):
1006 if self.direction > 0:
1008 if self.pos >= len(self.rows):
1013 self.pos = len(self.rows) - 1
1014 row = self.rows[self.pos]
1019 def Find(self, value, direction, pattern, context, callback):
1020 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1021 # Use a thread so the UI is not blocked
1022 thread = Thread(self.FindThread)
1023 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1026 def FindDone(self, thread, callback, row):
1029 # Number of database records to fetch in one go
1031 glb_chunk_sz = 10000
1033 # Background process for SQL data fetcher
1035 class SQLFetcherProcess():
1037 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1038 # Need a unique connection name
1039 conn_name = "SQLFetcher" + str(os.getpid())
1040 self.db, dbname = dbref.Open(conn_name)
1042 self.buffer = buffer
1045 self.fetch_count = fetch_count
1046 self.fetching_done = fetching_done
1047 self.process_target = process_target
1048 self.wait_event = wait_event
1049 self.fetched_event = fetched_event
1051 self.query = QSqlQuery(self.db)
1052 self.query_limit = 0 if "$$last_id$$" in sql else 2
1056 self.local_head = self.head.value
1057 self.local_tail = self.tail.value
1060 if self.query_limit:
1061 if self.query_limit == 1:
1063 self.query_limit -= 1
1064 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1065 QueryExec(self.query, stmt)
1068 if not self.query.next():
1070 if not self.query.next():
1072 self.last_id = self.query.value(0)
1073 return self.prep(self.query)
1075 def WaitForTarget(self):
1077 self.wait_event.clear()
1078 target = self.process_target.value
1079 if target > self.fetched or target < 0:
1081 self.wait_event.wait()
1084 def HasSpace(self, sz):
1085 if self.local_tail <= self.local_head:
1086 space = len(self.buffer) - self.local_head
1089 if space >= glb_nsz:
1090 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1091 nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
1092 self.buffer[self.local_head : self.local_head + len(nd)] = nd
1094 if self.local_tail - self.local_head > sz:
1098 def WaitForSpace(self, sz):
1099 if self.HasSpace(sz):
1102 self.wait_event.clear()
1103 self.local_tail = self.tail.value
1104 if self.HasSpace(sz):
1106 self.wait_event.wait()
1108 def AddToBuffer(self, obj):
1109 d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
1111 nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
1113 self.WaitForSpace(sz)
1114 pos = self.local_head
1115 self.buffer[pos : pos + len(nd)] = nd
1116 self.buffer[pos + glb_nsz : pos + sz] = d
1117 self.local_head += sz
1119 def FetchBatch(self, batch_size):
1121 while batch_size > fetched:
1126 self.AddToBuffer(obj)
1129 self.fetched += fetched
1130 with self.fetch_count.get_lock():
1131 self.fetch_count.value += fetched
1132 self.head.value = self.local_head
1133 self.fetched_event.set()
1137 target = self.WaitForTarget()
1140 batch_size = min(glb_chunk_sz, target - self.fetched)
1141 self.FetchBatch(batch_size)
1142 self.fetching_done.value = True
1143 self.fetched_event.set()
1145 def SQLFetcherFn(*x):
1146 process = SQLFetcherProcess(*x)
1151 class SQLFetcher(QObject):
1153 done = Signal(object)
1155 def __init__(self, glb, sql, prep, process_data, parent=None):
1156 super(SQLFetcher, self).__init__(parent)
1157 self.process_data = process_data
1160 self.last_target = 0
1162 self.buffer_size = 16 * 1024 * 1024
1163 self.buffer = Array(c_char, self.buffer_size, lock=False)
1164 self.head = Value(c_longlong)
1165 self.tail = Value(c_longlong)
1167 self.fetch_count = Value(c_longlong)
1168 self.fetching_done = Value(c_bool)
1170 self.process_target = Value(c_longlong)
1171 self.wait_event = Event()
1172 self.fetched_event = Event()
1173 glb.AddInstanceToShutdownOnExit(self)
1174 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))
1175 self.process.start()
1176 self.thread = Thread(self.Thread)
1177 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1181 # Tell the thread and process to exit
1182 self.process_target.value = -1
1183 self.wait_event.set()
1185 self.fetching_done.value = True
1186 self.fetched_event.set()
1192 self.fetched_event.clear()
1193 fetch_count = self.fetch_count.value
1194 if fetch_count != self.last_count:
1196 if self.fetching_done.value:
1199 self.fetched_event.wait()
1200 count = fetch_count - self.last_count
1201 self.last_count = fetch_count
1202 self.fetched += count
1205 def Fetch(self, nr):
1207 # -1 inidcates there are no more
1209 result = self.fetched
1210 extra = result + nr - self.target
1212 self.target += extra
1213 # process_target < 0 indicates shutting down
1214 if self.process_target.value >= 0:
1215 self.process_target.value = self.target
1216 self.wait_event.set()
1219 def RemoveFromBuffer(self):
1220 pos = self.local_tail
1221 if len(self.buffer) - pos < glb_nsz:
1223 n = pickle.loads(self.buffer[pos : pos + glb_nsz])
1226 n = pickle.loads(self.buffer[0 : glb_nsz])
1228 obj = pickle.loads(self.buffer[pos : pos + n])
1229 self.local_tail = pos + n
1232 def ProcessData(self, count):
1233 for i in xrange(count):
1234 obj = self.RemoveFromBuffer()
1235 self.process_data(obj)
1236 self.tail.value = self.local_tail
1237 self.wait_event.set()
1238 self.done.emit(count)
1240 # Fetch more records bar
1242 class FetchMoreRecordsBar():
1244 def __init__(self, model, parent):
1247 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1248 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1250 self.fetch_count = QSpinBox()
1251 self.fetch_count.setRange(1, 1000000)
1252 self.fetch_count.setValue(10)
1253 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1255 self.fetch = QPushButton("Go!")
1256 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1257 self.fetch.released.connect(self.FetchMoreRecords)
1259 self.progress = QProgressBar()
1260 self.progress.setRange(0, 100)
1261 self.progress.hide()
1263 self.done_label = QLabel("All records fetched")
1264 self.done_label.hide()
1266 self.spacer = QLabel("")
1268 self.close_button = QToolButton()
1269 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1270 self.close_button.released.connect(self.Deactivate)
1272 self.hbox = QHBoxLayout()
1273 self.hbox.setContentsMargins(0, 0, 0, 0)
1275 self.hbox.addWidget(self.label)
1276 self.hbox.addWidget(self.fetch_count)
1277 self.hbox.addWidget(self.fetch)
1278 self.hbox.addWidget(self.spacer)
1279 self.hbox.addWidget(self.progress)
1280 self.hbox.addWidget(self.done_label)
1281 self.hbox.addWidget(self.close_button)
1283 self.bar = QWidget()
1284 self.bar.setLayout(self.hbox);
1287 self.in_progress = False
1288 self.model.progress.connect(self.Progress)
1292 if not model.HasMoreRecords():
1300 self.fetch.setFocus()
1302 def Deactivate(self):
1305 def Enable(self, enable):
1306 self.fetch.setEnabled(enable)
1307 self.fetch_count.setEnabled(enable)
1313 self.progress.show()
1316 self.in_progress = False
1318 self.progress.hide()
1323 return self.fetch_count.value() * glb_chunk_sz
1329 self.fetch_count.hide()
1332 self.done_label.show()
1334 def Progress(self, count):
1335 if self.in_progress:
1337 percent = ((count - self.start) * 100) / self.Target()
1341 self.progress.setValue(percent)
1343 # Count value of zero means no more records
1346 def FetchMoreRecords(self):
1349 self.progress.setValue(0)
1351 self.in_progress = True
1352 self.start = self.model.FetchMoreRecords(self.Target())
1354 # Brance data model level two item
1356 class BranchLevelTwoItem():
1358 def __init__(self, row, text, parent_item):
1360 self.parent_item = parent_item
1361 self.data = [""] * 8
1365 def getParentItem(self):
1366 return self.parent_item
1371 def childCount(self):
1374 def hasChildren(self):
1377 def getData(self, column):
1378 return self.data[column]
1380 # Brance data model level one item
1382 class BranchLevelOneItem():
1384 def __init__(self, glb, row, data, parent_item):
1387 self.parent_item = parent_item
1388 self.child_count = 0
1389 self.child_items = []
1390 self.data = data[1:]
1393 self.query_done = False
1395 def getChildItem(self, row):
1396 return self.child_items[row]
1398 def getParentItem(self):
1399 return self.parent_item
1405 self.query_done = True
1407 if not self.glb.have_disassembler:
1410 query = QSqlQuery(self.glb.db)
1412 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1414 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1415 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1416 " WHERE samples.id = " + str(self.dbid))
1417 if not query.next():
1419 cpu = query.value(0)
1420 dso = query.value(1)
1421 sym = query.value(2)
1422 if dso == 0 or sym == 0:
1424 off = query.value(3)
1425 short_name = query.value(4)
1426 long_name = query.value(5)
1427 build_id = query.value(6)
1428 sym_start = query.value(7)
1431 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1433 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1434 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1435 " ORDER BY samples.id"
1437 if not query.next():
1439 if query.value(0) != dso:
1440 # Cannot disassemble from one dso to another
1442 bsym = query.value(1)
1443 boff = query.value(2)
1444 bsym_start = query.value(3)
1447 tot = bsym_start + boff + 1 - sym_start - off
1448 if tot <= 0 or tot > 16384:
1451 inst = self.glb.disassembler.Instruction()
1452 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1455 mode = 0 if Is64Bit(f) else 1
1456 self.glb.disassembler.SetMode(inst, mode)
1459 buf = create_string_buffer(tot + 16)
1460 f.seek(sym_start + off)
1461 buf.value = f.read(buf_sz)
1462 buf_ptr = addressof(buf)
1465 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1467 byte_str = tohex(ip).rjust(16)
1468 for k in xrange(cnt):
1469 byte_str += " %02x" % ord(buf[i])
1474 self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
1475 self.child_count += 1
1483 def childCount(self):
1484 if not self.query_done:
1486 if not self.child_count:
1488 return self.child_count
1490 def hasChildren(self):
1491 if not self.query_done:
1493 return self.child_count > 0
1495 def getData(self, column):
1496 return self.data[column]
1498 # Brance data model root item
1500 class BranchRootItem():
1503 self.child_count = 0
1504 self.child_items = []
1507 def getChildItem(self, row):
1508 return self.child_items[row]
1510 def getParentItem(self):
1516 def childCount(self):
1517 return self.child_count
1519 def hasChildren(self):
1520 return self.child_count > 0
1522 def getData(self, column):
1525 # Branch data preparation
1527 def BranchDataPrep(query):
1529 for i in xrange(0, 8):
1530 data.append(query.value(i))
1531 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1532 " (" + dsoname(query.value(11)) + ")" + " -> " +
1533 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1534 " (" + dsoname(query.value(15)) + ")")
1537 def BranchDataPrepWA(query):
1539 data.append(query.value(0))
1540 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1541 data.append("{:>19}".format(query.value(1)))
1542 for i in xrange(2, 8):
1543 data.append(query.value(i))
1544 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1545 " (" + dsoname(query.value(11)) + ")" + " -> " +
1546 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1547 " (" + dsoname(query.value(15)) + ")")
1552 class BranchModel(TreeModel):
1554 progress = Signal(object)
1556 def __init__(self, glb, event_id, where_clause, parent=None):
1557 super(BranchModel, self).__init__(glb, parent)
1558 self.event_id = event_id
1561 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1562 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1563 " ip, symbols.name, sym_offset, dsos.short_name,"
1564 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1566 " INNER JOIN comms ON comm_id = comms.id"
1567 " INNER JOIN threads ON thread_id = threads.id"
1568 " INNER JOIN branch_types ON branch_type = branch_types.id"
1569 " INNER JOIN symbols ON symbol_id = symbols.id"
1570 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1571 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1572 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1573 " WHERE samples.id > $$last_id$$" + where_clause +
1574 " AND evsel_id = " + str(self.event_id) +
1575 " ORDER BY samples.id"
1576 " LIMIT " + str(glb_chunk_sz))
1577 if pyside_version_1 and sys.version_info[0] == 3:
1578 prep = BranchDataPrepWA
1580 prep = BranchDataPrep
1581 self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
1582 self.fetcher.done.connect(self.Update)
1583 self.fetcher.Fetch(glb_chunk_sz)
1586 return BranchRootItem()
1588 def columnCount(self, parent=None):
1591 def columnHeader(self, column):
1592 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1594 def columnFont(self, column):
1597 return QFont("Monospace")
1599 def DisplayData(self, item, index):
1601 self.FetchIfNeeded(item.row)
1602 return item.getData(index.column())
1604 def AddSample(self, data):
1605 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1606 self.root.child_items.append(child)
1609 def Update(self, fetched):
1612 self.progress.emit(0)
1613 child_count = self.root.child_count
1614 count = self.populated - child_count
1616 parent = QModelIndex()
1617 self.beginInsertRows(parent, child_count, child_count + count - 1)
1618 self.insertRows(child_count, count, parent)
1619 self.root.child_count += count
1620 self.endInsertRows()
1621 self.progress.emit(self.root.child_count)
1623 def FetchMoreRecords(self, count):
1624 current = self.root.child_count
1626 self.fetcher.Fetch(count)
1628 self.progress.emit(0)
1631 def HasMoreRecords(self):
1638 def __init__(self, name = "", where_clause = "", limit = ""):
1640 self.where_clause = where_clause
1644 return str(self.where_clause + ";" + self.limit)
1648 class BranchWindow(QMdiSubWindow):
1650 def __init__(self, glb, event_id, report_vars, parent=None):
1651 super(BranchWindow, self).__init__(parent)
1653 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
1655 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1657 self.view = QTreeView()
1658 self.view.setUniformRowHeights(True)
1659 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1660 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1661 self.view.setModel(self.model)
1663 self.ResizeColumnsToContents()
1665 self.context_menu = TreeContextMenu(self.view)
1667 self.find_bar = FindBar(self, self, True)
1669 self.finder = ChildDataItemFinder(self.model.root)
1671 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1673 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1675 self.setWidget(self.vbox.Widget())
1677 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1679 def ResizeColumnToContents(self, column, n):
1680 # Using the view's resizeColumnToContents() here is extrememly slow
1681 # so implement a crude alternative
1682 mm = "MM" if column else "MMMM"
1683 font = self.view.font()
1684 metrics = QFontMetrics(font)
1686 for row in xrange(n):
1687 val = self.model.root.child_items[row].data[column]
1688 len = metrics.width(str(val) + mm)
1689 max = len if len > max else max
1690 val = self.model.columnHeader(column)
1691 len = metrics.width(str(val) + mm)
1692 max = len if len > max else max
1693 self.view.setColumnWidth(column, max)
1695 def ResizeColumnsToContents(self):
1696 n = min(self.model.root.child_count, 100)
1698 # No data yet, so connect a signal to notify when there is
1699 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1701 columns = self.model.columnCount()
1702 for i in xrange(columns):
1703 self.ResizeColumnToContents(i, n)
1705 def UpdateColumnWidths(self, *x):
1706 # This only needs to be done once, so disconnect the signal now
1707 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1708 self.ResizeColumnsToContents()
1710 def Find(self, value, direction, pattern, context):
1711 self.view.setFocus()
1712 self.find_bar.Busy()
1713 self.finder.Find(value, direction, pattern, context, self.FindDone)
1715 def FindDone(self, row):
1716 self.find_bar.Idle()
1718 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1720 self.find_bar.NotFound()
1722 # Line edit data item
1724 class LineEditDataItem(object):
1726 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1729 self.placeholder_text = placeholder_text
1730 self.parent = parent
1733 self.value = default
1735 self.widget = QLineEdit(default)
1736 self.widget.editingFinished.connect(self.Validate)
1737 self.widget.textChanged.connect(self.Invalidate)
1740 self.validated = True
1742 if placeholder_text:
1743 self.widget.setPlaceholderText(placeholder_text)
1745 def TurnTextRed(self):
1747 palette = QPalette()
1748 palette.setColor(QPalette.Text,Qt.red)
1749 self.widget.setPalette(palette)
1752 def TurnTextNormal(self):
1754 palette = QPalette()
1755 self.widget.setPalette(palette)
1758 def InvalidValue(self, value):
1761 self.error = self.label + " invalid value '" + value + "'"
1762 self.parent.ShowMessage(self.error)
1764 def Invalidate(self):
1765 self.validated = False
1767 def DoValidate(self, input_string):
1768 self.value = input_string.strip()
1771 self.validated = True
1773 self.TurnTextNormal()
1774 self.parent.ClearMessage()
1775 input_string = self.widget.text()
1776 if not len(input_string.strip()):
1779 self.DoValidate(input_string)
1782 if not self.validated:
1785 self.parent.ShowMessage(self.error)
1789 def IsNumber(self, value):
1794 return str(x) == value
1796 # Non-negative integer ranges dialog data item
1798 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1800 def __init__(self, glb, label, placeholder_text, column_name, parent):
1801 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1803 self.column_name = column_name
1805 def DoValidate(self, input_string):
1808 for value in [x.strip() for x in input_string.split(",")]:
1810 vrange = value.split("-")
1811 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1812 return self.InvalidValue(value)
1813 ranges.append(vrange)
1815 if not self.IsNumber(value):
1816 return self.InvalidValue(value)
1817 singles.append(value)
1818 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1820 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1821 self.value = " OR ".join(ranges)
1823 # Positive integer dialog data item
1825 class PositiveIntegerDataItem(LineEditDataItem):
1827 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1828 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1830 def DoValidate(self, input_string):
1831 if not self.IsNumber(input_string.strip()):
1832 return self.InvalidValue(input_string)
1833 value = int(input_string.strip())
1835 return self.InvalidValue(input_string)
1836 self.value = str(value)
1838 # Dialog data item converted and validated using a SQL table
1840 class SQLTableDataItem(LineEditDataItem):
1842 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1843 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
1845 self.table_name = table_name
1846 self.match_column = match_column
1847 self.column_name1 = column_name1
1848 self.column_name2 = column_name2
1850 def ValueToIds(self, value):
1852 query = QSqlQuery(self.glb.db)
1853 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1854 ret = query.exec_(stmt)
1857 ids.append(str(query.value(0)))
1860 def DoValidate(self, input_string):
1862 for value in [x.strip() for x in input_string.split(",")]:
1863 ids = self.ValueToIds(value)
1867 return self.InvalidValue(value)
1868 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1869 if self.column_name2:
1870 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1872 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
1874 class SampleTimeRangesDataItem(LineEditDataItem):
1876 def __init__(self, glb, label, placeholder_text, column_name, parent):
1877 self.column_name = column_name
1881 self.last_time = 2 ** 64
1883 query = QSqlQuery(glb.db)
1884 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1886 self.last_id = int(query.value(0))
1887 self.last_time = int(query.value(1))
1888 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1890 self.first_time = int(query.value(0))
1891 if placeholder_text:
1892 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1894 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1896 def IdBetween(self, query, lower_id, higher_id, order):
1897 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1899 return True, int(query.value(0))
1903 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1904 query = QSqlQuery(self.glb.db)
1906 next_id = int((lower_id + higher_id) / 2)
1907 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1908 if not query.next():
1909 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1911 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1913 return str(higher_id)
1915 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1916 next_time = int(query.value(0))
1918 if target_time > next_time:
1922 if higher_id <= lower_id + 1:
1923 return str(higher_id)
1925 if target_time >= next_time:
1929 if higher_id <= lower_id + 1:
1930 return str(lower_id)
1932 def ConvertRelativeTime(self, val):
1937 elif suffix == "us":
1939 elif suffix == "ns":
1943 val = val[:-2].strip()
1944 if not self.IsNumber(val):
1946 val = int(val) * mult
1948 val += self.first_time
1950 val += self.last_time
1953 def ConvertTimeRange(self, vrange):
1955 vrange[0] = str(self.first_time)
1957 vrange[1] = str(self.last_time)
1958 vrange[0] = self.ConvertRelativeTime(vrange[0])
1959 vrange[1] = self.ConvertRelativeTime(vrange[1])
1960 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1962 beg_range = max(int(vrange[0]), self.first_time)
1963 end_range = min(int(vrange[1]), self.last_time)
1964 if beg_range > self.last_time or end_range < self.first_time:
1966 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
1967 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
1970 def AddTimeRange(self, value, ranges):
1971 n = value.count("-")
1975 if value.split("-")[1].strip() == "":
1981 pos = findnth(value, "-", n)
1982 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
1983 if self.ConvertTimeRange(vrange):
1984 ranges.append(vrange)
1988 def DoValidate(self, input_string):
1990 for value in [x.strip() for x in input_string.split(",")]:
1991 if not self.AddTimeRange(value, ranges):
1992 return self.InvalidValue(value)
1993 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1994 self.value = " OR ".join(ranges)
1996 # Report Dialog Base
1998 class ReportDialogBase(QDialog):
2000 def __init__(self, glb, title, items, partial, parent=None):
2001 super(ReportDialogBase, self).__init__(parent)
2005 self.report_vars = ReportVars()
2007 self.setWindowTitle(title)
2008 self.setMinimumWidth(600)
2010 self.data_items = [x(glb, self) for x in items]
2012 self.partial = partial
2014 self.grid = QGridLayout()
2016 for row in xrange(len(self.data_items)):
2017 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2018 self.grid.addWidget(self.data_items[row].widget, row, 1)
2020 self.status = QLabel()
2022 self.ok_button = QPushButton("Ok", self)
2023 self.ok_button.setDefault(True)
2024 self.ok_button.released.connect(self.Ok)
2025 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2027 self.cancel_button = QPushButton("Cancel", self)
2028 self.cancel_button.released.connect(self.reject)
2029 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2031 self.hbox = QHBoxLayout()
2032 #self.hbox.addStretch()
2033 self.hbox.addWidget(self.status)
2034 self.hbox.addWidget(self.ok_button)
2035 self.hbox.addWidget(self.cancel_button)
2037 self.vbox = QVBoxLayout()
2038 self.vbox.addLayout(self.grid)
2039 self.vbox.addLayout(self.hbox)
2041 self.setLayout(self.vbox);
2044 vars = self.report_vars
2045 for d in self.data_items:
2046 if d.id == "REPORTNAME":
2049 self.ShowMessage("Report name is required")
2051 for d in self.data_items:
2054 for d in self.data_items[1:]:
2056 vars.limit = d.value
2058 if len(vars.where_clause):
2059 vars.where_clause += " AND "
2060 vars.where_clause += d.value
2061 if len(vars.where_clause):
2063 vars.where_clause = " AND ( " + vars.where_clause + " ) "
2065 vars.where_clause = " WHERE " + vars.where_clause + " "
2068 def ShowMessage(self, msg):
2069 self.status.setText("<font color=#FF0000>" + msg)
2071 def ClearMessage(self):
2072 self.status.setText("")
2074 # Selected branch report creation dialog
2076 class SelectedBranchDialog(ReportDialogBase):
2078 def __init__(self, glb, parent=None):
2079 title = "Selected Branches"
2080 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2081 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2082 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2083 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2084 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2085 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2086 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2087 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2088 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2089 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2093 def GetEventList(db):
2095 query = QSqlQuery(db)
2096 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2098 events.append(query.value(0))
2101 # Is a table selectable
2103 def IsSelectable(db, table, sql = ""):
2104 query = QSqlQuery(db)
2106 QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1")
2111 # SQL table data model item
2113 class SQLTableItem():
2115 def __init__(self, row, data):
2119 def getData(self, column):
2120 return self.data[column]
2122 # SQL table data model
2124 class SQLTableModel(TableModel):
2126 progress = Signal(object)
2128 def __init__(self, glb, sql, column_headers, parent=None):
2129 super(SQLTableModel, self).__init__(parent)
2133 self.column_headers = column_headers
2134 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
2135 self.fetcher.done.connect(self.Update)
2136 self.fetcher.Fetch(glb_chunk_sz)
2138 def DisplayData(self, item, index):
2139 self.FetchIfNeeded(item.row)
2140 return item.getData(index.column())
2142 def AddSample(self, data):
2143 child = SQLTableItem(self.populated, data)
2144 self.child_items.append(child)
2147 def Update(self, fetched):
2150 self.progress.emit(0)
2151 child_count = self.child_count
2152 count = self.populated - child_count
2154 parent = QModelIndex()
2155 self.beginInsertRows(parent, child_count, child_count + count - 1)
2156 self.insertRows(child_count, count, parent)
2157 self.child_count += count
2158 self.endInsertRows()
2159 self.progress.emit(self.child_count)
2161 def FetchMoreRecords(self, count):
2162 current = self.child_count
2164 self.fetcher.Fetch(count)
2166 self.progress.emit(0)
2169 def HasMoreRecords(self):
2172 def columnCount(self, parent=None):
2173 return len(self.column_headers)
2175 def columnHeader(self, column):
2176 return self.column_headers[column]
2178 def SQLTableDataPrep(self, query, count):
2180 for i in xrange(count):
2181 data.append(query.value(i))
2184 # SQL automatic table data model
2186 class SQLAutoTableModel(SQLTableModel):
2188 def __init__(self, glb, table_name, parent=None):
2189 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2190 if table_name == "comm_threads_view":
2191 # For now, comm_threads_view has no id column
2192 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2194 query = QSqlQuery(glb.db)
2195 if glb.dbref.is_sqlite3:
2196 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2198 column_headers.append(query.value(1))
2199 if table_name == "sqlite_master":
2200 sql = "SELECT * FROM " + table_name
2202 if table_name[:19] == "information_schema.":
2203 sql = "SELECT * FROM " + table_name
2204 select_table_name = table_name[19:]
2205 schema = "information_schema"
2207 select_table_name = table_name
2209 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2211 column_headers.append(query.value(0))
2212 if pyside_version_1 and sys.version_info[0] == 3:
2213 if table_name == "samples_view":
2214 self.SQLTableDataPrep = self.samples_view_DataPrep
2215 if table_name == "samples":
2216 self.SQLTableDataPrep = self.samples_DataPrep
2217 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2219 def samples_view_DataPrep(self, query, count):
2221 data.append(query.value(0))
2222 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2223 data.append("{:>19}".format(query.value(1)))
2224 for i in xrange(2, count):
2225 data.append(query.value(i))
2228 def samples_DataPrep(self, query, count):
2231 data.append(query.value(i))
2232 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2233 data.append("{:>19}".format(query.value(9)))
2234 for i in xrange(10, count):
2235 data.append(query.value(i))
2238 # Base class for custom ResizeColumnsToContents
2240 class ResizeColumnsToContentsBase(QObject):
2242 def __init__(self, parent=None):
2243 super(ResizeColumnsToContentsBase, self).__init__(parent)
2245 def ResizeColumnToContents(self, column, n):
2246 # Using the view's resizeColumnToContents() here is extrememly slow
2247 # so implement a crude alternative
2248 font = self.view.font()
2249 metrics = QFontMetrics(font)
2251 for row in xrange(n):
2252 val = self.data_model.child_items[row].data[column]
2253 len = metrics.width(str(val) + "MM")
2254 max = len if len > max else max
2255 val = self.data_model.columnHeader(column)
2256 len = metrics.width(str(val) + "MM")
2257 max = len if len > max else max
2258 self.view.setColumnWidth(column, max)
2260 def ResizeColumnsToContents(self):
2261 n = min(self.data_model.child_count, 100)
2263 # No data yet, so connect a signal to notify when there is
2264 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2266 columns = self.data_model.columnCount()
2267 for i in xrange(columns):
2268 self.ResizeColumnToContents(i, n)
2270 def UpdateColumnWidths(self, *x):
2271 # This only needs to be done once, so disconnect the signal now
2272 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2273 self.ResizeColumnsToContents()
2275 # Convert value to CSV
2279 val = val.replace('"', '""')
2280 if "," in val or '"' in val:
2281 val = '"' + val + '"'
2284 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2288 def RowColumnKey(a):
2289 return a.row() * glb_max_cols + a.column()
2291 # Copy selected table cells to clipboard
2293 def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2294 indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2295 idx_cnt = len(indexes)
2300 min_row = indexes[0].row()
2301 max_row = indexes[0].row()
2302 min_col = indexes[0].column()
2303 max_col = indexes[0].column()
2305 min_row = min(min_row, i.row())
2306 max_row = max(max_row, i.row())
2307 min_col = min(min_col, i.column())
2308 max_col = max(max_col, i.column())
2309 if max_col > glb_max_cols:
2310 raise RuntimeError("glb_max_cols is too low")
2311 max_width = [0] * (1 + max_col - min_col)
2313 c = i.column() - min_col
2314 max_width[c] = max(max_width[c], len(str(i.data())))
2319 model = indexes[0].model()
2320 for col in range(min_col, max_col + 1):
2321 val = model.headerData(col, Qt.Horizontal)
2323 text += sep + ToCSValue(val)
2327 max_width[c] = max(max_width[c], len(val))
2328 width = max_width[c]
2329 align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2330 if align & Qt.AlignRight:
2331 val = val.rjust(width)
2332 text += pad + sep + val
2333 pad = " " * (width - len(val))
2340 if i.row() > last_row:
2346 text += sep + ToCSValue(str(i.data()))
2349 width = max_width[i.column() - min_col]
2350 if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2351 val = str(i.data()).rjust(width)
2354 text += pad + sep + val
2355 pad = " " * (width - len(val))
2357 QApplication.clipboard().setText(text)
2359 def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2360 indexes = view.selectedIndexes()
2361 if not len(indexes):
2364 selection = view.selectionModel()
2368 above = view.indexAbove(i)
2369 if not selection.isSelected(above):
2374 raise RuntimeError("CopyTreeCellsToClipboard internal error")
2376 model = first.model()
2378 col_cnt = model.columnCount(first)
2379 max_width = [0] * col_cnt
2382 indent_str = " " * indent_sz
2384 expanded_mark_sz = 2
2385 if sys.version_info[0] == 3:
2386 expanded_mark = "\u25BC "
2387 not_expanded_mark = "\u25B6 "
2389 expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2390 not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2398 for c in range(col_cnt):
2399 i = pos.sibling(row, c)
2401 n = len(str(i.data()))
2403 n = len(str(i.data()).strip())
2404 n += (i.internalPointer().level - 1) * indent_sz
2405 n += expanded_mark_sz
2406 max_width[c] = max(max_width[c], n)
2407 pos = view.indexBelow(pos)
2408 if not selection.isSelected(pos):
2415 for c in range(col_cnt):
2416 val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2418 text += sep + ToCSValue(val)
2421 max_width[c] = max(max_width[c], len(val))
2422 width = max_width[c]
2423 align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2424 if align & Qt.AlignRight:
2425 val = val.rjust(width)
2426 text += pad + sep + val
2427 pad = " " * (width - len(val))
2436 for c in range(col_cnt):
2437 i = pos.sibling(row, c)
2440 if model.hasChildren(i):
2441 if view.isExpanded(i):
2442 mark = expanded_mark
2444 mark = not_expanded_mark
2447 val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2449 text += sep + ToCSValue(val)
2452 width = max_width[c]
2453 if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2454 val = val.rjust(width)
2455 text += pad + sep + val
2456 pad = " " * (width - len(val))
2458 pos = view.indexBelow(pos)
2459 if not selection.isSelected(pos):
2461 text = text.rstrip() + "\n"
2465 QApplication.clipboard().setText(text)
2467 def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2468 view.CopyCellsToClipboard(view, as_csv, with_hdr)
2470 def CopyCellsToClipboardHdr(view):
2471 CopyCellsToClipboard(view, False, True)
2473 def CopyCellsToClipboardCSV(view):
2474 CopyCellsToClipboard(view, True, True)
2478 class ContextMenu(object):
2480 def __init__(self, view):
2482 self.view.setContextMenuPolicy(Qt.CustomContextMenu)
2483 self.view.customContextMenuRequested.connect(self.ShowContextMenu)
2485 def ShowContextMenu(self, pos):
2486 menu = QMenu(self.view)
2487 self.AddActions(menu)
2488 menu.exec_(self.view.mapToGlobal(pos))
2490 def AddCopy(self, menu):
2491 menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
2492 menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
2494 def AddActions(self, menu):
2497 class TreeContextMenu(ContextMenu):
2499 def __init__(self, view):
2500 super(TreeContextMenu, self).__init__(view)
2502 def AddActions(self, menu):
2503 i = self.view.currentIndex()
2504 text = str(i.data()).strip()
2506 menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
2511 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2513 def __init__(self, glb, table_name, parent=None):
2514 super(TableWindow, self).__init__(parent)
2516 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2518 self.model = QSortFilterProxyModel()
2519 self.model.setSourceModel(self.data_model)
2521 self.view = QTableView()
2522 self.view.setModel(self.model)
2523 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2524 self.view.verticalHeader().setVisible(False)
2525 self.view.sortByColumn(-1, Qt.AscendingOrder)
2526 self.view.setSortingEnabled(True)
2527 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2528 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2530 self.ResizeColumnsToContents()
2532 self.context_menu = ContextMenu(self.view)
2534 self.find_bar = FindBar(self, self, True)
2536 self.finder = ChildDataItemFinder(self.data_model)
2538 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2540 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2542 self.setWidget(self.vbox.Widget())
2544 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2546 def Find(self, value, direction, pattern, context):
2547 self.view.setFocus()
2548 self.find_bar.Busy()
2549 self.finder.Find(value, direction, pattern, context, self.FindDone)
2551 def FindDone(self, row):
2552 self.find_bar.Idle()
2554 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2556 self.find_bar.NotFound()
2560 def GetTableList(glb):
2562 query = QSqlQuery(glb.db)
2563 if glb.dbref.is_sqlite3:
2564 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2566 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2568 tables.append(query.value(0))
2569 if glb.dbref.is_sqlite3:
2570 tables.append("sqlite_master")
2572 tables.append("information_schema.tables")
2573 tables.append("information_schema.views")
2574 tables.append("information_schema.columns")
2577 # Top Calls data model
2579 class TopCallsModel(SQLTableModel):
2581 def __init__(self, glb, report_vars, parent=None):
2583 if not glb.dbref.is_sqlite3:
2586 if len(report_vars.limit):
2587 limit = " LIMIT " + report_vars.limit
2588 sql = ("SELECT comm, pid, tid, name,"
2590 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2593 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2595 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2596 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2597 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2601 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2602 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2603 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2604 " INNER JOIN comms ON calls.comm_id = comms.id"
2605 " INNER JOIN threads ON calls.thread_id = threads.id" +
2606 report_vars.where_clause +
2607 " ORDER BY elapsed_time DESC" +
2610 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2611 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2612 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2614 def columnAlignment(self, column):
2615 return self.alignment[column]
2617 # Top Calls report creation dialog
2619 class TopCallsDialog(ReportDialogBase):
2621 def __init__(self, glb, parent=None):
2622 title = "Top Calls by Elapsed Time"
2623 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2624 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2625 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2626 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2627 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2628 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2629 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2630 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2631 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2635 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2637 def __init__(self, glb, report_vars, parent=None):
2638 super(TopCallsWindow, self).__init__(parent)
2640 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2641 self.model = self.data_model
2643 self.view = QTableView()
2644 self.view.setModel(self.model)
2645 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2646 self.view.verticalHeader().setVisible(False)
2647 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2648 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2650 self.context_menu = ContextMenu(self.view)
2652 self.ResizeColumnsToContents()
2654 self.find_bar = FindBar(self, self, True)
2656 self.finder = ChildDataItemFinder(self.model)
2658 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2660 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2662 self.setWidget(self.vbox.Widget())
2664 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2666 def Find(self, value, direction, pattern, context):
2667 self.view.setFocus()
2668 self.find_bar.Busy()
2669 self.finder.Find(value, direction, pattern, context, self.FindDone)
2671 def FindDone(self, row):
2672 self.find_bar.Idle()
2674 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2676 self.find_bar.NotFound()
2680 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2681 action = QAction(label, parent)
2682 if shortcut != None:
2683 action.setShortcuts(shortcut)
2684 action.setStatusTip(tip)
2685 action.triggered.connect(callback)
2688 # Typical application actions
2690 def CreateExitAction(app, parent=None):
2691 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2693 # Typical MDI actions
2695 def CreateCloseActiveWindowAction(mdi_area):
2696 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2698 def CreateCloseAllWindowsAction(mdi_area):
2699 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2701 def CreateTileWindowsAction(mdi_area):
2702 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2704 def CreateCascadeWindowsAction(mdi_area):
2705 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2707 def CreateNextWindowAction(mdi_area):
2708 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2710 def CreatePreviousWindowAction(mdi_area):
2711 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2713 # Typical MDI window menu
2717 def __init__(self, mdi_area, menu):
2718 self.mdi_area = mdi_area
2719 self.window_menu = menu.addMenu("&Windows")
2720 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2721 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2722 self.tile_windows = CreateTileWindowsAction(mdi_area)
2723 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2724 self.next_window = CreateNextWindowAction(mdi_area)
2725 self.previous_window = CreatePreviousWindowAction(mdi_area)
2726 self.window_menu.aboutToShow.connect(self.Update)
2729 self.window_menu.clear()
2730 sub_window_count = len(self.mdi_area.subWindowList())
2731 have_sub_windows = sub_window_count != 0
2732 self.close_active_window.setEnabled(have_sub_windows)
2733 self.close_all_windows.setEnabled(have_sub_windows)
2734 self.tile_windows.setEnabled(have_sub_windows)
2735 self.cascade_windows.setEnabled(have_sub_windows)
2736 self.next_window.setEnabled(have_sub_windows)
2737 self.previous_window.setEnabled(have_sub_windows)
2738 self.window_menu.addAction(self.close_active_window)
2739 self.window_menu.addAction(self.close_all_windows)
2740 self.window_menu.addSeparator()
2741 self.window_menu.addAction(self.tile_windows)
2742 self.window_menu.addAction(self.cascade_windows)
2743 self.window_menu.addSeparator()
2744 self.window_menu.addAction(self.next_window)
2745 self.window_menu.addAction(self.previous_window)
2746 if sub_window_count == 0:
2748 self.window_menu.addSeparator()
2750 for sub_window in self.mdi_area.subWindowList():
2751 label = str(nr) + " " + sub_window.name
2754 action = self.window_menu.addAction(label)
2755 action.setCheckable(True)
2756 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2757 action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2758 self.window_menu.addAction(action)
2761 def setActiveSubWindow(self, nr):
2762 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2777 <p class=c1><a href=#reports>1. Reports</a></p>
2778 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2779 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2780 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
2781 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2782 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2783 <p class=c1><a href=#tables>2. Tables</a></p>
2784 <h1 id=reports>1. Reports</h1>
2785 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2786 The result is a GUI window with a tree representing a context-sensitive
2787 call-graph. Expanding a couple of levels of the tree and adjusting column
2788 widths to suit will display something like:
2790 Call Graph: pt_example
2791 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2794 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2795 |- unknown unknown 1 13198 0.1 1 0.0
2796 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2797 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2798 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2799 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2800 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2801 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2802 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2803 v- main ls 1 8182043 99.6 180254 99.9
2805 <h3>Points to note:</h3>
2807 <li>The top level is a command name (comm)</li>
2808 <li>The next level is a thread (pid:tid)</li>
2809 <li>Subsequent levels are functions</li>
2810 <li>'Count' is the number of calls</li>
2811 <li>'Time' is the elapsed time until the function returns</li>
2812 <li>Percentages are relative to the level above</li>
2813 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2816 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2817 The pattern matching symbols are ? for any character and * for zero or more characters.
2818 <h2 id=calltree>1.2 Call Tree</h2>
2819 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2820 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2821 <h2 id=allbranches>1.3 All branches</h2>
2822 The All branches report displays all branches in chronological order.
2823 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2824 <h3>Disassembly</h3>
2825 Open a branch to display disassembly. This only works if:
2827 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2828 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2829 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2830 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2831 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2833 <h4 id=xed>Intel XED Setup</h4>
2834 To use Intel XED, libxed.so must be present. To build and install libxed.so:
2836 git clone https://github.com/intelxed/mbuild.git mbuild
2837 git clone https://github.com/intelxed/xed
2840 sudo ./mfile.py --prefix=/usr/local install
2844 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2845 Refer to Python documentation for the regular expression syntax.
2846 All columns are searched, but only currently fetched rows are searched.
2847 <h2 id=selectedbranches>1.4 Selected branches</h2>
2848 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2849 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2850 <h3>1.4.1 Time ranges</h3>
2851 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2852 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
2854 81073085947329-81073085958238 From 81073085947329 to 81073085958238
2855 100us-200us From 100us to 200us
2856 10ms- From 10ms to the end
2857 -100ns The first 100ns
2858 -10ms- The last 10ms
2860 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2861 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2862 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.
2863 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2864 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
2865 <h1 id=tables>2. Tables</h1>
2866 The Tables menu shows all tables and views in the database. Most tables have an associated view
2867 which displays the information in a more friendly way. Not all data for large tables is fetched
2868 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2869 but that can be slow for large tables.
2870 <p>There are also tables of database meta-information.
2871 For SQLite3 databases, the sqlite_master table is included.
2872 For PostgreSQL databases, information_schema.tables/views/columns are included.
2874 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2875 Refer to Python documentation for the regular expression syntax.
2876 All columns are searched, but only currently fetched rows are searched.
2877 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2878 will go to the next/previous result in id order, instead of display order.
2883 class HelpWindow(QMdiSubWindow):
2885 def __init__(self, glb, parent=None):
2886 super(HelpWindow, self).__init__(parent)
2888 self.text = QTextBrowser()
2889 self.text.setHtml(glb_help_text)
2890 self.text.setReadOnly(True)
2891 self.text.setOpenExternalLinks(True)
2893 self.setWidget(self.text)
2895 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2897 # Main window that only displays the help text
2899 class HelpOnlyWindow(QMainWindow):
2901 def __init__(self, parent=None):
2902 super(HelpOnlyWindow, self).__init__(parent)
2904 self.setMinimumSize(200, 100)
2905 self.resize(800, 600)
2906 self.setWindowTitle("Exported SQL Viewer Help")
2907 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2909 self.text = QTextBrowser()
2910 self.text.setHtml(glb_help_text)
2911 self.text.setReadOnly(True)
2912 self.text.setOpenExternalLinks(True)
2914 self.setCentralWidget(self.text)
2918 def ResizeFont(widget, diff):
2919 font = widget.font()
2920 sz = font.pointSize()
2921 font.setPointSize(sz + diff)
2922 widget.setFont(font)
2924 def ShrinkFont(widget):
2925 ResizeFont(widget, -1)
2927 def EnlargeFont(widget):
2928 ResizeFont(widget, 1)
2930 # Unique name for sub-windows
2932 def NumberedWindowName(name, nr):
2934 name += " <" + str(nr) + ">"
2937 def UniqueSubWindowName(mdi_area, name):
2940 unique_name = NumberedWindowName(name, nr)
2942 for sub_window in mdi_area.subWindowList():
2943 if sub_window.name == unique_name:
2952 def AddSubWindow(mdi_area, sub_window, name):
2953 unique_name = UniqueSubWindowName(mdi_area, name)
2954 sub_window.setMinimumSize(200, 100)
2955 sub_window.resize(800, 600)
2956 sub_window.setWindowTitle(unique_name)
2957 sub_window.setAttribute(Qt.WA_DeleteOnClose)
2958 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
2959 sub_window.name = unique_name
2960 mdi_area.addSubWindow(sub_window)
2965 class MainWindow(QMainWindow):
2967 def __init__(self, glb, parent=None):
2968 super(MainWindow, self).__init__(parent)
2972 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
2973 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
2974 self.setMinimumSize(200, 100)
2976 self.mdi_area = QMdiArea()
2977 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2978 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2980 self.setCentralWidget(self.mdi_area)
2982 menu = self.menuBar()
2984 file_menu = menu.addMenu("&File")
2985 file_menu.addAction(CreateExitAction(glb.app, self))
2987 edit_menu = menu.addMenu("&Edit")
2988 edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
2989 edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
2990 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
2991 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
2992 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
2993 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
2995 reports_menu = menu.addMenu("&Reports")
2996 if IsSelectable(glb.db, "calls"):
2997 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
2999 if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3000 reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3002 self.EventMenu(GetEventList(glb.db), reports_menu)
3004 if IsSelectable(glb.db, "calls"):
3005 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3007 self.TableMenu(GetTableList(glb), menu)
3009 self.window_menu = WindowMenu(self.mdi_area, menu)
3011 help_menu = menu.addMenu("&Help")
3012 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3015 win = self.mdi_area.activeSubWindow()
3022 def CopyToClipboard(self):
3023 self.Try(CopyCellsToClipboardHdr)
3025 def CopyToClipboardCSV(self):
3026 self.Try(CopyCellsToClipboardCSV)
3029 win = self.mdi_area.activeSubWindow()
3032 win.find_bar.Activate()
3036 def FetchMoreRecords(self):
3037 win = self.mdi_area.activeSubWindow()
3040 win.fetch_bar.Activate()
3044 def ShrinkFont(self):
3045 self.Try(ShrinkFont)
3047 def EnlargeFont(self):
3048 self.Try(EnlargeFont)
3050 def EventMenu(self, events, reports_menu):
3052 for event in events:
3053 event = event.split(":")[0]
3054 if event == "branches":
3055 branches_events += 1
3057 for event in events:
3059 event = event.split(":")[0]
3060 if event == "branches":
3061 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3062 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
3063 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3064 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
3066 def TableMenu(self, tables, menu):
3067 table_menu = menu.addMenu("&Tables")
3068 for table in tables:
3069 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
3071 def NewCallGraph(self):
3072 CallGraphWindow(self.glb, self)
3074 def NewCallTree(self):
3075 CallTreeWindow(self.glb, self)
3077 def NewTopCalls(self):
3078 dialog = TopCallsDialog(self.glb, self)
3079 ret = dialog.exec_()
3081 TopCallsWindow(self.glb, dialog.report_vars, self)
3083 def NewBranchView(self, event_id):
3084 BranchWindow(self.glb, event_id, ReportVars(), self)
3086 def NewSelectedBranchView(self, event_id):
3087 dialog = SelectedBranchDialog(self.glb, self)
3088 ret = dialog.exec_()
3090 BranchWindow(self.glb, event_id, dialog.report_vars, self)
3092 def NewTableView(self, table_name):
3093 TableWindow(self.glb, table_name, self)
3096 HelpWindow(self.glb, self)
3100 class xed_state_t(Structure):
3107 class XEDInstruction():
3109 def __init__(self, libxed):
3110 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3111 xedd_t = c_byte * 512
3112 self.xedd = xedd_t()
3113 self.xedp = addressof(self.xedd)
3114 libxed.xed_decoded_inst_zero(self.xedp)
3115 self.state = xed_state_t()
3116 self.statep = addressof(self.state)
3117 # Buffer for disassembled instruction text
3118 self.buffer = create_string_buffer(256)
3119 self.bufferp = addressof(self.buffer)
3125 self.libxed = CDLL("libxed.so")
3129 self.libxed = CDLL("/usr/local/lib/libxed.so")
3131 self.xed_tables_init = self.libxed.xed_tables_init
3132 self.xed_tables_init.restype = None
3133 self.xed_tables_init.argtypes = []
3135 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3136 self.xed_decoded_inst_zero.restype = None
3137 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3139 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3140 self.xed_operand_values_set_mode.restype = None
3141 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3143 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3144 self.xed_decoded_inst_zero_keep_mode.restype = None
3145 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3147 self.xed_decode = self.libxed.xed_decode
3148 self.xed_decode.restype = c_int
3149 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3151 self.xed_format_context = self.libxed.xed_format_context
3152 self.xed_format_context.restype = c_uint
3153 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3155 self.xed_tables_init()
3157 def Instruction(self):
3158 return XEDInstruction(self)
3160 def SetMode(self, inst, mode):
3162 inst.state.mode = 4 # 32-bit
3163 inst.state.width = 4 # 4 bytes
3165 inst.state.mode = 1 # 64-bit
3166 inst.state.width = 8 # 8 bytes
3167 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3169 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3170 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3171 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3174 # Use AT&T mode (2), alternative is Intel (3)
3175 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3178 if sys.version_info[0] == 2:
3179 result = inst.buffer.value
3181 result = inst.buffer.value.decode()
3182 # Return instruction length and the disassembled instruction text
3183 # For now, assume the length is in byte 166
3184 return inst.xedd[166], result
3186 def TryOpen(file_name):
3188 return open(file_name, "rb")
3193 result = sizeof(c_void_p)
3200 if sys.version_info[0] == 2:
3201 eclass = ord(header[4])
3202 encoding = ord(header[5])
3203 version = ord(header[6])
3206 encoding = header[5]
3208 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3209 result = True if eclass == 2 else False
3216 def __init__(self, dbref, db, dbname):
3219 self.dbname = dbname
3220 self.home_dir = os.path.expanduser("~")
3221 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3222 if self.buildid_dir:
3223 self.buildid_dir += "/.build-id/"
3225 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3227 self.mainwindow = None
3228 self.instances_to_shutdown_on_exit = weakref.WeakSet()
3230 self.disassembler = LibXED()
3231 self.have_disassembler = True
3233 self.have_disassembler = False
3235 def FileFromBuildId(self, build_id):
3236 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3237 return TryOpen(file_name)
3239 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3240 # Assume current machine i.e. no support for virtualization
3241 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3242 file_name = os.getenv("PERF_KCORE")
3243 f = TryOpen(file_name) if file_name else None
3246 # For now, no special handling if long_name is /proc/kcore
3247 f = TryOpen(long_name)
3250 f = self.FileFromBuildId(build_id)
3255 def AddInstanceToShutdownOnExit(self, instance):
3256 self.instances_to_shutdown_on_exit.add(instance)
3258 # Shutdown any background processes or threads
3259 def ShutdownInstances(self):
3260 for x in self.instances_to_shutdown_on_exit:
3266 # Database reference
3270 def __init__(self, is_sqlite3, dbname):
3271 self.is_sqlite3 = is_sqlite3
3272 self.dbname = dbname
3274 def Open(self, connection_name):
3275 dbname = self.dbname
3277 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3279 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3280 opts = dbname.split()
3283 opt = opt.split("=")
3284 if opt[0] == "hostname":
3285 db.setHostName(opt[1])
3286 elif opt[0] == "port":
3287 db.setPort(int(opt[1]))
3288 elif opt[0] == "username":
3289 db.setUserName(opt[1])
3290 elif opt[0] == "password":
3291 db.setPassword(opt[1])
3292 elif opt[0] == "dbname":
3297 db.setDatabaseName(dbname)
3299 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3305 if (len(sys.argv) < 2):
3306 printerr("Usage is: exported-sql-viewer.py {<database name> | --help-only}");
3307 raise Exception("Too few arguments")
3309 dbname = sys.argv[1]
3310 if dbname == "--help-only":
3311 app = QApplication(sys.argv)
3312 mainwindow = HelpOnlyWindow()
3319 f = open(dbname, "rb")
3320 if f.read(15) == b'SQLite format 3':
3326 dbref = DBRef(is_sqlite3, dbname)
3327 db, dbname = dbref.Open("main")
3328 glb = Glb(dbref, db, dbname)
3329 app = QApplication(sys.argv)
3331 mainwindow = MainWindow(glb)
3332 glb.mainwindow = mainwindow
3335 glb.ShutdownInstances()
3339 if __name__ == "__main__":