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
100 import cPickle as pickle
101 # size of pickled integer big enough for record size
109 pyside_version_1 = True
110 if not "--pyside-version-1" in sys.argv:
112 from PySide2.QtCore import *
113 from PySide2.QtGui import *
114 from PySide2.QtSql import *
115 from PySide2.QtWidgets import *
116 pyside_version_1 = False
121 from PySide.QtCore import *
122 from PySide.QtGui import *
123 from PySide.QtSql import *
125 from decimal import *
127 from multiprocessing import Process, Array, Value, Event
129 # xrange is range in Python3
135 def printerr(*args, **keyword_args):
136 print(*args, file=sys.stderr, **keyword_args)
138 # Data formatting helpers
147 return "+0x%x" % offset
151 if name == "[kernel.kallsyms]":
155 def findnth(s, sub, n, offs=0):
161 return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
163 # Percent to one decimal place
165 def PercentToOneDP(n, d):
168 x = (n * Decimal(100)) / d
169 return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
171 # Helper for queries that must not fail
173 def QueryExec(query, stmt):
174 ret = query.exec_(stmt)
176 raise Exception("Query failed: " + query.lastError().text())
180 class Thread(QThread):
182 done = Signal(object)
184 def __init__(self, task, param=None, parent=None):
185 super(Thread, self).__init__(parent)
191 if self.param is None:
192 done, result = self.task()
194 done, result = self.task(self.param)
195 self.done.emit(result)
201 class TreeModel(QAbstractItemModel):
203 def __init__(self, glb, params, parent=None):
204 super(TreeModel, self).__init__(parent)
207 self.root = self.GetRoot()
208 self.last_row_read = 0
210 def Item(self, parent):
212 return parent.internalPointer()
216 def rowCount(self, parent):
217 result = self.Item(parent).childCount()
220 self.dataChanged.emit(parent, parent)
223 def hasChildren(self, parent):
224 return self.Item(parent).hasChildren()
226 def headerData(self, section, orientation, role):
227 if role == Qt.TextAlignmentRole:
228 return self.columnAlignment(section)
229 if role != Qt.DisplayRole:
231 if orientation != Qt.Horizontal:
233 return self.columnHeader(section)
235 def parent(self, child):
236 child_item = child.internalPointer()
237 if child_item is self.root:
239 parent_item = child_item.getParentItem()
240 return self.createIndex(parent_item.getRow(), 0, parent_item)
242 def index(self, row, column, parent):
243 child_item = self.Item(parent).getChildItem(row)
244 return self.createIndex(row, column, child_item)
246 def DisplayData(self, item, index):
247 return item.getData(index.column())
249 def FetchIfNeeded(self, row):
250 if row > self.last_row_read:
251 self.last_row_read = row
252 if row + 10 >= self.root.child_count:
253 self.fetcher.Fetch(glb_chunk_sz)
255 def columnAlignment(self, column):
258 def columnFont(self, column):
261 def data(self, index, role):
262 if role == Qt.TextAlignmentRole:
263 return self.columnAlignment(index.column())
264 if role == Qt.FontRole:
265 return self.columnFont(index.column())
266 if role != Qt.DisplayRole:
268 item = index.internalPointer()
269 return self.DisplayData(item, index)
273 class TableModel(QAbstractTableModel):
275 def __init__(self, parent=None):
276 super(TableModel, self).__init__(parent)
278 self.child_items = []
279 self.last_row_read = 0
281 def Item(self, parent):
283 return parent.internalPointer()
287 def rowCount(self, parent):
288 return self.child_count
290 def headerData(self, section, orientation, role):
291 if role == Qt.TextAlignmentRole:
292 return self.columnAlignment(section)
293 if role != Qt.DisplayRole:
295 if orientation != Qt.Horizontal:
297 return self.columnHeader(section)
299 def index(self, row, column, parent):
300 return self.createIndex(row, column, self.child_items[row])
302 def DisplayData(self, item, index):
303 return item.getData(index.column())
305 def FetchIfNeeded(self, row):
306 if row > self.last_row_read:
307 self.last_row_read = row
308 if row + 10 >= self.child_count:
309 self.fetcher.Fetch(glb_chunk_sz)
311 def columnAlignment(self, column):
314 def columnFont(self, column):
317 def data(self, index, role):
318 if role == Qt.TextAlignmentRole:
319 return self.columnAlignment(index.column())
320 if role == Qt.FontRole:
321 return self.columnFont(index.column())
322 if role != Qt.DisplayRole:
324 item = index.internalPointer()
325 return self.DisplayData(item, index)
329 model_cache = weakref.WeakValueDictionary()
330 model_cache_lock = threading.Lock()
332 def LookupCreateModel(model_name, create_fn):
333 model_cache_lock.acquire()
335 model = model_cache[model_name]
340 model_cache[model_name] = model
341 model_cache_lock.release()
344 def LookupModel(model_name):
345 model_cache_lock.acquire()
347 model = model_cache[model_name]
350 model_cache_lock.release()
357 def __init__(self, parent, finder, is_reg_expr=False):
360 self.last_value = None
361 self.last_pattern = None
363 label = QLabel("Find:")
364 label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
366 self.textbox = QComboBox()
367 self.textbox.setEditable(True)
368 self.textbox.currentIndexChanged.connect(self.ValueChanged)
370 self.progress = QProgressBar()
371 self.progress.setRange(0, 0)
375 self.pattern = QCheckBox("Regular Expression")
377 self.pattern = QCheckBox("Pattern")
378 self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
380 self.next_button = QToolButton()
381 self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
382 self.next_button.released.connect(lambda: self.NextPrev(1))
384 self.prev_button = QToolButton()
385 self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
386 self.prev_button.released.connect(lambda: self.NextPrev(-1))
388 self.close_button = QToolButton()
389 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
390 self.close_button.released.connect(self.Deactivate)
392 self.hbox = QHBoxLayout()
393 self.hbox.setContentsMargins(0, 0, 0, 0)
395 self.hbox.addWidget(label)
396 self.hbox.addWidget(self.textbox)
397 self.hbox.addWidget(self.progress)
398 self.hbox.addWidget(self.pattern)
399 self.hbox.addWidget(self.next_button)
400 self.hbox.addWidget(self.prev_button)
401 self.hbox.addWidget(self.close_button)
404 self.bar.setLayout(self.hbox)
412 self.textbox.lineEdit().selectAll()
413 self.textbox.setFocus()
415 def Deactivate(self):
419 self.textbox.setEnabled(False)
421 self.next_button.hide()
422 self.prev_button.hide()
426 self.textbox.setEnabled(True)
429 self.next_button.show()
430 self.prev_button.show()
432 def Find(self, direction):
433 value = self.textbox.currentText()
434 pattern = self.pattern.isChecked()
435 self.last_value = value
436 self.last_pattern = pattern
437 self.finder.Find(value, direction, pattern, self.context)
439 def ValueChanged(self):
440 value = self.textbox.currentText()
441 pattern = self.pattern.isChecked()
442 index = self.textbox.currentIndex()
443 data = self.textbox.itemData(index)
444 # Store the pattern in the combo box to keep it with the text value
446 self.textbox.setItemData(index, pattern)
448 self.pattern.setChecked(data)
451 def NextPrev(self, direction):
452 value = self.textbox.currentText()
453 pattern = self.pattern.isChecked()
454 if value != self.last_value:
455 index = self.textbox.findText(value)
456 # Allow for a button press before the value has been added to the combo box
458 index = self.textbox.count()
459 self.textbox.addItem(value, pattern)
460 self.textbox.setCurrentIndex(index)
463 self.textbox.setItemData(index, pattern)
464 elif pattern != self.last_pattern:
465 # Keep the pattern recorded in the combo box up to date
466 index = self.textbox.currentIndex()
467 self.textbox.setItemData(index, pattern)
471 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
473 # Context-sensitive call graph data model item base
475 class CallGraphLevelItemBase(object):
477 def __init__(self, glb, params, row, parent_item):
481 self.parent_item = parent_item
482 self.query_done = False
484 self.child_items = []
486 self.level = parent_item.level + 1
490 def getChildItem(self, row):
491 return self.child_items[row]
493 def getParentItem(self):
494 return self.parent_item
499 def childCount(self):
500 if not self.query_done:
502 if not self.child_count:
504 return self.child_count
506 def hasChildren(self):
507 if not self.query_done:
509 return self.child_count > 0
511 def getData(self, column):
512 return self.data[column]
514 # Context-sensitive call graph data model level 2+ item base
516 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
518 def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
519 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
520 self.comm_id = comm_id
521 self.thread_id = thread_id
522 self.call_path_id = call_path_id
523 self.insn_cnt = insn_cnt
524 self.cyc_cnt = cyc_cnt
525 self.branch_count = branch_count
529 self.query_done = True
530 query = QSqlQuery(self.glb.db)
531 if self.params.have_ipc:
532 ipc_str = ", SUM(insn_count), SUM(cyc_count)"
535 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
537 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
538 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
539 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
540 " WHERE parent_call_path_id = " + str(self.call_path_id) +
541 " AND comm_id = " + str(self.comm_id) +
542 " AND thread_id = " + str(self.thread_id) +
543 " GROUP BY call_path_id, name, short_name"
544 " ORDER BY call_path_id")
546 if self.params.have_ipc:
547 insn_cnt = int(query.value(5))
548 cyc_cnt = int(query.value(6))
549 branch_count = int(query.value(7))
553 branch_count = int(query.value(5))
554 child_item = CallGraphLevelThreeItem(self.glb, self.params, 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)), insn_cnt, cyc_cnt, branch_count, self)
555 self.child_items.append(child_item)
556 self.child_count += 1
558 # Context-sensitive call graph data model level three item
560 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
562 def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item):
563 super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
565 if self.params.have_ipc:
566 insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
567 cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
568 br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
569 ipc = CalcIPC(cyc_cnt, insn_cnt)
570 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
572 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
573 self.dbid = call_path_id
575 # Context-sensitive call graph data model level two item
577 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
579 def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
580 super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
581 if self.params.have_ipc:
582 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
584 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
585 self.dbid = thread_id
588 super(CallGraphLevelTwoItem, self).Select()
589 for child_item in self.child_items:
590 self.time += child_item.time
591 self.insn_cnt += child_item.insn_cnt
592 self.cyc_cnt += child_item.cyc_cnt
593 self.branch_count += child_item.branch_count
594 for child_item in self.child_items:
595 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
596 if self.params.have_ipc:
597 child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
598 child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
599 child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
601 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
603 # Context-sensitive call graph data model level one item
605 class CallGraphLevelOneItem(CallGraphLevelItemBase):
607 def __init__(self, glb, params, row, comm_id, comm, parent_item):
608 super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
609 if self.params.have_ipc:
610 self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
612 self.data = [comm, "", "", "", "", "", ""]
616 self.query_done = True
617 query = QSqlQuery(self.glb.db)
618 QueryExec(query, "SELECT thread_id, pid, tid"
620 " INNER JOIN threads ON thread_id = threads.id"
621 " WHERE comm_id = " + str(self.dbid))
623 child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
624 self.child_items.append(child_item)
625 self.child_count += 1
627 # Context-sensitive call graph data model root item
629 class CallGraphRootItem(CallGraphLevelItemBase):
631 def __init__(self, glb, params):
632 super(CallGraphRootItem, self).__init__(glb, params, 0, None)
634 self.query_done = True
636 if IsSelectable(glb.db, "comms", columns = "has_calls"):
637 if_has_calls = " WHERE has_calls = TRUE"
638 query = QSqlQuery(glb.db)
639 QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
641 if not query.value(0):
643 child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
644 self.child_items.append(child_item)
645 self.child_count += 1
647 # Call graph model parameters
649 class CallGraphModelParams():
651 def __init__(self, glb, parent=None):
652 self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
654 # Context-sensitive call graph data model base
656 class CallGraphModelBase(TreeModel):
658 def __init__(self, glb, parent=None):
659 super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
661 def FindSelect(self, value, pattern, query):
663 # postgresql and sqlite pattern patching differences:
664 # postgresql LIKE is case sensitive but sqlite LIKE is not
665 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
666 # postgresql supports ILIKE which is case insensitive
667 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
668 if not self.glb.dbref.is_sqlite3:
670 s = value.replace("%", "\%")
671 s = s.replace("_", "\_")
672 # Translate * and ? into SQL LIKE pattern characters % and _
673 trans = string.maketrans("*?", "%_")
674 match = " LIKE '" + str(s).translate(trans) + "'"
676 match = " GLOB '" + str(value) + "'"
678 match = " = '" + str(value) + "'"
679 self.DoFindSelect(query, match)
681 def Found(self, query, found):
683 return self.FindPath(query)
686 def FindValue(self, value, pattern, query, last_value, last_pattern):
687 if last_value == value and pattern == last_pattern:
688 found = query.first()
690 self.FindSelect(value, pattern, query)
692 return self.Found(query, found)
694 def FindNext(self, query):
697 found = query.first()
698 return self.Found(query, found)
700 def FindPrev(self, query):
701 found = query.previous()
704 return self.Found(query, found)
706 def FindThread(self, c):
707 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
708 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
709 elif c.direction > 0:
710 ids = self.FindNext(c.query)
712 ids = self.FindPrev(c.query)
715 def Find(self, value, direction, pattern, context, callback):
717 def __init__(self, *x):
718 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
719 def Update(self, *x):
720 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
722 context[0].Update(value, direction, pattern)
724 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
725 # Use a thread so the UI is not blocked during the SELECT
726 thread = Thread(self.FindThread, context[0])
727 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
730 def FindDone(self, thread, callback, ids):
733 # Context-sensitive call graph data model
735 class CallGraphModel(CallGraphModelBase):
737 def __init__(self, glb, parent=None):
738 super(CallGraphModel, self).__init__(glb, parent)
741 return CallGraphRootItem(self.glb, self.params)
743 def columnCount(self, parent=None):
744 if self.params.have_ipc:
749 def columnHeader(self, column):
750 if self.params.have_ipc:
751 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
753 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
754 return headers[column]
756 def columnAlignment(self, column):
757 if self.params.have_ipc:
758 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
760 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
761 return alignment[column]
763 def DoFindSelect(self, query, match):
764 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
766 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
767 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
768 " WHERE symbols.name" + match +
769 " GROUP BY comm_id, thread_id, call_path_id"
770 " ORDER BY comm_id, thread_id, call_path_id")
772 def FindPath(self, query):
773 # Turn the query result into a list of ids that the tree view can walk
774 # to open the tree at the right place.
776 parent_id = query.value(0)
778 ids.insert(0, parent_id)
779 q2 = QSqlQuery(self.glb.db)
780 QueryExec(q2, "SELECT parent_id"
782 " WHERE id = " + str(parent_id))
785 parent_id = q2.value(0)
786 # The call path root is not used
789 ids.insert(0, query.value(2))
790 ids.insert(0, query.value(1))
793 # Call tree data model level 2+ item base
795 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
797 def __init__(self, glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
798 super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
799 self.comm_id = comm_id
800 self.thread_id = thread_id
801 self.calls_id = calls_id
802 self.call_time = call_time
804 self.insn_cnt = insn_cnt
805 self.cyc_cnt = cyc_cnt
806 self.branch_count = branch_count
809 self.query_done = True
810 if self.calls_id == 0:
811 comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
814 if self.params.have_ipc:
815 ipc_str = ", insn_count, cyc_count"
818 query = QSqlQuery(self.glb.db)
819 QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
821 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
822 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
823 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
824 " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
825 " ORDER BY call_time, calls.id")
827 if self.params.have_ipc:
828 insn_cnt = int(query.value(5))
829 cyc_cnt = int(query.value(6))
830 branch_count = int(query.value(7))
834 branch_count = int(query.value(5))
835 child_item = CallTreeLevelThreeItem(self.glb, self.params, 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)), insn_cnt, cyc_cnt, branch_count, self)
836 self.child_items.append(child_item)
837 self.child_count += 1
839 # Call tree data model level three item
841 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
843 def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
844 super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item)
846 if self.params.have_ipc:
847 insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
848 cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
849 br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
850 ipc = CalcIPC(cyc_cnt, insn_cnt)
851 self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
853 self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
856 # Call tree data model level two item
858 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
860 def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
861 super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, 0, parent_item)
862 if self.params.have_ipc:
863 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
865 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
866 self.dbid = thread_id
869 super(CallTreeLevelTwoItem, self).Select()
870 for child_item in self.child_items:
871 self.time += child_item.time
872 self.insn_cnt += child_item.insn_cnt
873 self.cyc_cnt += child_item.cyc_cnt
874 self.branch_count += child_item.branch_count
875 for child_item in self.child_items:
876 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
877 if self.params.have_ipc:
878 child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
879 child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
880 child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
882 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
884 # Call tree data model level one item
886 class CallTreeLevelOneItem(CallGraphLevelItemBase):
888 def __init__(self, glb, params, row, comm_id, comm, parent_item):
889 super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
890 if self.params.have_ipc:
891 self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
893 self.data = [comm, "", "", "", "", "", ""]
897 self.query_done = True
898 query = QSqlQuery(self.glb.db)
899 QueryExec(query, "SELECT thread_id, pid, tid"
901 " INNER JOIN threads ON thread_id = threads.id"
902 " WHERE comm_id = " + str(self.dbid))
904 child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
905 self.child_items.append(child_item)
906 self.child_count += 1
908 # Call tree data model root item
910 class CallTreeRootItem(CallGraphLevelItemBase):
912 def __init__(self, glb, params):
913 super(CallTreeRootItem, self).__init__(glb, params, 0, None)
915 self.query_done = True
917 if IsSelectable(glb.db, "comms", columns = "has_calls"):
918 if_has_calls = " WHERE has_calls = TRUE"
919 query = QSqlQuery(glb.db)
920 QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
922 if not query.value(0):
924 child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
925 self.child_items.append(child_item)
926 self.child_count += 1
928 # Call Tree data model
930 class CallTreeModel(CallGraphModelBase):
932 def __init__(self, glb, parent=None):
933 super(CallTreeModel, self).__init__(glb, parent)
936 return CallTreeRootItem(self.glb, self.params)
938 def columnCount(self, parent=None):
939 if self.params.have_ipc:
944 def columnHeader(self, column):
945 if self.params.have_ipc:
946 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
948 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
949 return headers[column]
951 def columnAlignment(self, column):
952 if self.params.have_ipc:
953 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
955 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
956 return alignment[column]
958 def DoFindSelect(self, query, match):
959 QueryExec(query, "SELECT calls.id, comm_id, thread_id"
961 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
962 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
963 " WHERE symbols.name" + match +
964 " ORDER BY comm_id, thread_id, call_time, calls.id")
966 def FindPath(self, query):
967 # Turn the query result into a list of ids that the tree view can walk
968 # to open the tree at the right place.
970 parent_id = query.value(0)
972 ids.insert(0, parent_id)
973 q2 = QSqlQuery(self.glb.db)
974 QueryExec(q2, "SELECT parent_id"
976 " WHERE id = " + str(parent_id))
979 parent_id = q2.value(0)
980 ids.insert(0, query.value(2))
981 ids.insert(0, query.value(1))
986 class HBoxLayout(QHBoxLayout):
988 def __init__(self, *children):
989 super(HBoxLayout, self).__init__()
991 self.layout().setContentsMargins(0, 0, 0, 0)
992 for child in children:
993 if child.isWidgetType():
994 self.layout().addWidget(child)
996 self.layout().addLayout(child)
1000 class VBoxLayout(QVBoxLayout):
1002 def __init__(self, *children):
1003 super(VBoxLayout, self).__init__()
1005 self.layout().setContentsMargins(0, 0, 0, 0)
1006 for child in children:
1007 if child.isWidgetType():
1008 self.layout().addWidget(child)
1010 self.layout().addLayout(child)
1012 # Vertical layout widget
1016 def __init__(self, *children):
1017 self.vbox = QWidget()
1018 self.vbox.setLayout(VBoxLayout(*children))
1025 class TreeWindowBase(QMdiSubWindow):
1027 def __init__(self, parent=None):
1028 super(TreeWindowBase, self).__init__(parent)
1031 self.find_bar = None
1033 self.view = QTreeView()
1034 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1035 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1037 self.context_menu = TreeContextMenu(self.view)
1039 def DisplayFound(self, ids):
1042 parent = QModelIndex()
1045 n = self.model.rowCount(parent)
1046 for row in xrange(n):
1047 child = self.model.index(row, 0, parent)
1048 if child.internalPointer().dbid == dbid:
1050 self.view.setCurrentIndex(child)
1057 def Find(self, value, direction, pattern, context):
1058 self.view.setFocus()
1059 self.find_bar.Busy()
1060 self.model.Find(value, direction, pattern, context, self.FindDone)
1062 def FindDone(self, ids):
1064 if not self.DisplayFound(ids):
1066 self.find_bar.Idle()
1068 self.find_bar.NotFound()
1071 # Context-sensitive call graph window
1073 class CallGraphWindow(TreeWindowBase):
1075 def __init__(self, glb, parent=None):
1076 super(CallGraphWindow, self).__init__(parent)
1078 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
1080 self.view.setModel(self.model)
1082 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1083 self.view.setColumnWidth(c, w)
1085 self.find_bar = FindBar(self, self)
1087 self.vbox = VBox(self.view, self.find_bar.Widget())
1089 self.setWidget(self.vbox.Widget())
1091 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
1095 class CallTreeWindow(TreeWindowBase):
1097 def __init__(self, glb, parent=None, thread_at_time=None):
1098 super(CallTreeWindow, self).__init__(parent)
1100 self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
1102 self.view.setModel(self.model)
1104 for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1105 self.view.setColumnWidth(c, w)
1107 self.find_bar = FindBar(self, self)
1109 self.vbox = VBox(self.view, self.find_bar.Widget())
1111 self.setWidget(self.vbox.Widget())
1113 AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
1116 self.DisplayThreadAtTime(*thread_at_time)
1118 def DisplayThreadAtTime(self, comm_id, thread_id, time):
1119 parent = QModelIndex()
1120 for dbid in (comm_id, thread_id):
1122 n = self.model.rowCount(parent)
1123 for row in xrange(n):
1124 child = self.model.index(row, 0, parent)
1125 if child.internalPointer().dbid == dbid:
1127 self.view.setCurrentIndex(child)
1134 n = self.model.rowCount(parent)
1138 for row in xrange(n):
1139 child = self.model.index(row, 0, parent)
1140 child_call_time = child.internalPointer().call_time
1141 if child_call_time < time:
1143 elif child_call_time == time:
1144 self.view.setCurrentIndex(child)
1146 elif child_call_time > time:
1150 child = self.model.index(0, 0, parent)
1151 self.view.setCurrentIndex(child)
1154 self.view.setCurrentIndex(last_child)
1157 # Child data item finder
1159 class ChildDataItemFinder():
1161 def __init__(self, root):
1163 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
1167 def FindSelect(self):
1170 pattern = re.compile(self.value)
1171 for child in self.root.child_items:
1172 for column_data in child.data:
1173 if re.search(pattern, str(column_data)) is not None:
1174 self.rows.append(child.row)
1177 for child in self.root.child_items:
1178 for column_data in child.data:
1179 if self.value in str(column_data):
1180 self.rows.append(child.row)
1183 def FindValue(self):
1185 if self.last_value != self.value or self.pattern != self.last_pattern:
1187 if not len(self.rows):
1189 return self.rows[self.pos]
1191 def FindThread(self):
1192 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1193 row = self.FindValue()
1194 elif len(self.rows):
1195 if self.direction > 0:
1197 if self.pos >= len(self.rows):
1202 self.pos = len(self.rows) - 1
1203 row = self.rows[self.pos]
1208 def Find(self, value, direction, pattern, context, callback):
1209 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1210 # Use a thread so the UI is not blocked
1211 thread = Thread(self.FindThread)
1212 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1215 def FindDone(self, thread, callback, row):
1218 # Number of database records to fetch in one go
1220 glb_chunk_sz = 10000
1222 # Background process for SQL data fetcher
1224 class SQLFetcherProcess():
1226 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1227 # Need a unique connection name
1228 conn_name = "SQLFetcher" + str(os.getpid())
1229 self.db, dbname = dbref.Open(conn_name)
1231 self.buffer = buffer
1234 self.fetch_count = fetch_count
1235 self.fetching_done = fetching_done
1236 self.process_target = process_target
1237 self.wait_event = wait_event
1238 self.fetched_event = fetched_event
1240 self.query = QSqlQuery(self.db)
1241 self.query_limit = 0 if "$$last_id$$" in sql else 2
1245 self.local_head = self.head.value
1246 self.local_tail = self.tail.value
1249 if self.query_limit:
1250 if self.query_limit == 1:
1252 self.query_limit -= 1
1253 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1254 QueryExec(self.query, stmt)
1257 if not self.query.next():
1259 if not self.query.next():
1261 self.last_id = self.query.value(0)
1262 return self.prep(self.query)
1264 def WaitForTarget(self):
1266 self.wait_event.clear()
1267 target = self.process_target.value
1268 if target > self.fetched or target < 0:
1270 self.wait_event.wait()
1273 def HasSpace(self, sz):
1274 if self.local_tail <= self.local_head:
1275 space = len(self.buffer) - self.local_head
1278 if space >= glb_nsz:
1279 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1280 nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
1281 self.buffer[self.local_head : self.local_head + len(nd)] = nd
1283 if self.local_tail - self.local_head > sz:
1287 def WaitForSpace(self, sz):
1288 if self.HasSpace(sz):
1291 self.wait_event.clear()
1292 self.local_tail = self.tail.value
1293 if self.HasSpace(sz):
1295 self.wait_event.wait()
1297 def AddToBuffer(self, obj):
1298 d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
1300 nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
1302 self.WaitForSpace(sz)
1303 pos = self.local_head
1304 self.buffer[pos : pos + len(nd)] = nd
1305 self.buffer[pos + glb_nsz : pos + sz] = d
1306 self.local_head += sz
1308 def FetchBatch(self, batch_size):
1310 while batch_size > fetched:
1315 self.AddToBuffer(obj)
1318 self.fetched += fetched
1319 with self.fetch_count.get_lock():
1320 self.fetch_count.value += fetched
1321 self.head.value = self.local_head
1322 self.fetched_event.set()
1326 target = self.WaitForTarget()
1329 batch_size = min(glb_chunk_sz, target - self.fetched)
1330 self.FetchBatch(batch_size)
1331 self.fetching_done.value = True
1332 self.fetched_event.set()
1334 def SQLFetcherFn(*x):
1335 process = SQLFetcherProcess(*x)
1340 class SQLFetcher(QObject):
1342 done = Signal(object)
1344 def __init__(self, glb, sql, prep, process_data, parent=None):
1345 super(SQLFetcher, self).__init__(parent)
1346 self.process_data = process_data
1349 self.last_target = 0
1351 self.buffer_size = 16 * 1024 * 1024
1352 self.buffer = Array(c_char, self.buffer_size, lock=False)
1353 self.head = Value(c_longlong)
1354 self.tail = Value(c_longlong)
1356 self.fetch_count = Value(c_longlong)
1357 self.fetching_done = Value(c_bool)
1359 self.process_target = Value(c_longlong)
1360 self.wait_event = Event()
1361 self.fetched_event = Event()
1362 glb.AddInstanceToShutdownOnExit(self)
1363 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))
1364 self.process.start()
1365 self.thread = Thread(self.Thread)
1366 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1370 # Tell the thread and process to exit
1371 self.process_target.value = -1
1372 self.wait_event.set()
1374 self.fetching_done.value = True
1375 self.fetched_event.set()
1381 self.fetched_event.clear()
1382 fetch_count = self.fetch_count.value
1383 if fetch_count != self.last_count:
1385 if self.fetching_done.value:
1388 self.fetched_event.wait()
1389 count = fetch_count - self.last_count
1390 self.last_count = fetch_count
1391 self.fetched += count
1394 def Fetch(self, nr):
1396 # -1 inidcates there are no more
1398 result = self.fetched
1399 extra = result + nr - self.target
1401 self.target += extra
1402 # process_target < 0 indicates shutting down
1403 if self.process_target.value >= 0:
1404 self.process_target.value = self.target
1405 self.wait_event.set()
1408 def RemoveFromBuffer(self):
1409 pos = self.local_tail
1410 if len(self.buffer) - pos < glb_nsz:
1412 n = pickle.loads(self.buffer[pos : pos + glb_nsz])
1415 n = pickle.loads(self.buffer[0 : glb_nsz])
1417 obj = pickle.loads(self.buffer[pos : pos + n])
1418 self.local_tail = pos + n
1421 def ProcessData(self, count):
1422 for i in xrange(count):
1423 obj = self.RemoveFromBuffer()
1424 self.process_data(obj)
1425 self.tail.value = self.local_tail
1426 self.wait_event.set()
1427 self.done.emit(count)
1429 # Fetch more records bar
1431 class FetchMoreRecordsBar():
1433 def __init__(self, model, parent):
1436 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1437 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1439 self.fetch_count = QSpinBox()
1440 self.fetch_count.setRange(1, 1000000)
1441 self.fetch_count.setValue(10)
1442 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1444 self.fetch = QPushButton("Go!")
1445 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1446 self.fetch.released.connect(self.FetchMoreRecords)
1448 self.progress = QProgressBar()
1449 self.progress.setRange(0, 100)
1450 self.progress.hide()
1452 self.done_label = QLabel("All records fetched")
1453 self.done_label.hide()
1455 self.spacer = QLabel("")
1457 self.close_button = QToolButton()
1458 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1459 self.close_button.released.connect(self.Deactivate)
1461 self.hbox = QHBoxLayout()
1462 self.hbox.setContentsMargins(0, 0, 0, 0)
1464 self.hbox.addWidget(self.label)
1465 self.hbox.addWidget(self.fetch_count)
1466 self.hbox.addWidget(self.fetch)
1467 self.hbox.addWidget(self.spacer)
1468 self.hbox.addWidget(self.progress)
1469 self.hbox.addWidget(self.done_label)
1470 self.hbox.addWidget(self.close_button)
1472 self.bar = QWidget()
1473 self.bar.setLayout(self.hbox)
1476 self.in_progress = False
1477 self.model.progress.connect(self.Progress)
1481 if not model.HasMoreRecords():
1489 self.fetch.setFocus()
1491 def Deactivate(self):
1494 def Enable(self, enable):
1495 self.fetch.setEnabled(enable)
1496 self.fetch_count.setEnabled(enable)
1502 self.progress.show()
1505 self.in_progress = False
1507 self.progress.hide()
1512 return self.fetch_count.value() * glb_chunk_sz
1518 self.fetch_count.hide()
1521 self.done_label.show()
1523 def Progress(self, count):
1524 if self.in_progress:
1526 percent = ((count - self.start) * 100) / self.Target()
1530 self.progress.setValue(percent)
1532 # Count value of zero means no more records
1535 def FetchMoreRecords(self):
1538 self.progress.setValue(0)
1540 self.in_progress = True
1541 self.start = self.model.FetchMoreRecords(self.Target())
1543 # Brance data model level two item
1545 class BranchLevelTwoItem():
1547 def __init__(self, row, col, text, parent_item):
1549 self.parent_item = parent_item
1550 self.data = [""] * (col + 1)
1551 self.data[col] = text
1554 def getParentItem(self):
1555 return self.parent_item
1560 def childCount(self):
1563 def hasChildren(self):
1566 def getData(self, column):
1567 return self.data[column]
1569 # Brance data model level one item
1571 class BranchLevelOneItem():
1573 def __init__(self, glb, row, data, parent_item):
1576 self.parent_item = parent_item
1577 self.child_count = 0
1578 self.child_items = []
1579 self.data = data[1:]
1582 self.query_done = False
1583 self.br_col = len(self.data) - 1
1585 def getChildItem(self, row):
1586 return self.child_items[row]
1588 def getParentItem(self):
1589 return self.parent_item
1595 self.query_done = True
1597 if not self.glb.have_disassembler:
1600 query = QSqlQuery(self.glb.db)
1602 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1604 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1605 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1606 " WHERE samples.id = " + str(self.dbid))
1607 if not query.next():
1609 cpu = query.value(0)
1610 dso = query.value(1)
1611 sym = query.value(2)
1612 if dso == 0 or sym == 0:
1614 off = query.value(3)
1615 short_name = query.value(4)
1616 long_name = query.value(5)
1617 build_id = query.value(6)
1618 sym_start = query.value(7)
1621 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1623 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1624 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1625 " ORDER BY samples.id"
1627 if not query.next():
1629 if query.value(0) != dso:
1630 # Cannot disassemble from one dso to another
1632 bsym = query.value(1)
1633 boff = query.value(2)
1634 bsym_start = query.value(3)
1637 tot = bsym_start + boff + 1 - sym_start - off
1638 if tot <= 0 or tot > 16384:
1641 inst = self.glb.disassembler.Instruction()
1642 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1645 mode = 0 if Is64Bit(f) else 1
1646 self.glb.disassembler.SetMode(inst, mode)
1649 buf = create_string_buffer(tot + 16)
1650 f.seek(sym_start + off)
1651 buf.value = f.read(buf_sz)
1652 buf_ptr = addressof(buf)
1655 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1657 byte_str = tohex(ip).rjust(16)
1658 for k in xrange(cnt):
1659 byte_str += " %02x" % ord(buf[i])
1664 self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
1665 self.child_count += 1
1673 def childCount(self):
1674 if not self.query_done:
1676 if not self.child_count:
1678 return self.child_count
1680 def hasChildren(self):
1681 if not self.query_done:
1683 return self.child_count > 0
1685 def getData(self, column):
1686 return self.data[column]
1688 # Brance data model root item
1690 class BranchRootItem():
1693 self.child_count = 0
1694 self.child_items = []
1697 def getChildItem(self, row):
1698 return self.child_items[row]
1700 def getParentItem(self):
1706 def childCount(self):
1707 return self.child_count
1709 def hasChildren(self):
1710 return self.child_count > 0
1712 def getData(self, column):
1715 # Calculate instructions per cycle
1717 def CalcIPC(cyc_cnt, insn_cnt):
1718 if cyc_cnt and insn_cnt:
1719 ipc = Decimal(float(insn_cnt) / cyc_cnt)
1720 ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
1725 # Branch data preparation
1727 def BranchDataPrepBr(query, data):
1728 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1729 " (" + dsoname(query.value(11)) + ")" + " -> " +
1730 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1731 " (" + dsoname(query.value(15)) + ")")
1733 def BranchDataPrepIPC(query, data):
1734 insn_cnt = query.value(16)
1735 cyc_cnt = query.value(17)
1736 ipc = CalcIPC(cyc_cnt, insn_cnt)
1737 data.append(insn_cnt)
1738 data.append(cyc_cnt)
1741 def BranchDataPrep(query):
1743 for i in xrange(0, 8):
1744 data.append(query.value(i))
1745 BranchDataPrepBr(query, data)
1748 def BranchDataPrepWA(query):
1750 data.append(query.value(0))
1751 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1752 data.append("{:>19}".format(query.value(1)))
1753 for i in xrange(2, 8):
1754 data.append(query.value(i))
1755 BranchDataPrepBr(query, data)
1758 def BranchDataWithIPCPrep(query):
1760 for i in xrange(0, 8):
1761 data.append(query.value(i))
1762 BranchDataPrepIPC(query, data)
1763 BranchDataPrepBr(query, data)
1766 def BranchDataWithIPCPrepWA(query):
1768 data.append(query.value(0))
1769 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1770 data.append("{:>19}".format(query.value(1)))
1771 for i in xrange(2, 8):
1772 data.append(query.value(i))
1773 BranchDataPrepIPC(query, data)
1774 BranchDataPrepBr(query, data)
1779 class BranchModel(TreeModel):
1781 progress = Signal(object)
1783 def __init__(self, glb, event_id, where_clause, parent=None):
1784 super(BranchModel, self).__init__(glb, None, parent)
1785 self.event_id = event_id
1788 self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
1790 select_ipc = ", insn_count, cyc_count"
1791 prep_fn = BranchDataWithIPCPrep
1792 prep_wa_fn = BranchDataWithIPCPrepWA
1795 prep_fn = BranchDataPrep
1796 prep_wa_fn = BranchDataPrepWA
1797 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1798 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1799 " ip, symbols.name, sym_offset, dsos.short_name,"
1800 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1803 " INNER JOIN comms ON comm_id = comms.id"
1804 " INNER JOIN threads ON thread_id = threads.id"
1805 " INNER JOIN branch_types ON branch_type = branch_types.id"
1806 " INNER JOIN symbols ON symbol_id = symbols.id"
1807 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1808 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1809 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1810 " WHERE samples.id > $$last_id$$" + where_clause +
1811 " AND evsel_id = " + str(self.event_id) +
1812 " ORDER BY samples.id"
1813 " LIMIT " + str(glb_chunk_sz))
1814 if pyside_version_1 and sys.version_info[0] == 3:
1818 self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
1819 self.fetcher.done.connect(self.Update)
1820 self.fetcher.Fetch(glb_chunk_sz)
1823 return BranchRootItem()
1825 def columnCount(self, parent=None):
1831 def columnHeader(self, column):
1833 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
1835 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1837 def columnFont(self, column):
1842 if column != br_col:
1844 return QFont("Monospace")
1846 def DisplayData(self, item, index):
1848 self.FetchIfNeeded(item.row)
1849 return item.getData(index.column())
1851 def AddSample(self, data):
1852 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1853 self.root.child_items.append(child)
1856 def Update(self, fetched):
1859 self.progress.emit(0)
1860 child_count = self.root.child_count
1861 count = self.populated - child_count
1863 parent = QModelIndex()
1864 self.beginInsertRows(parent, child_count, child_count + count - 1)
1865 self.insertRows(child_count, count, parent)
1866 self.root.child_count += count
1867 self.endInsertRows()
1868 self.progress.emit(self.root.child_count)
1870 def FetchMoreRecords(self, count):
1871 current = self.root.child_count
1873 self.fetcher.Fetch(count)
1875 self.progress.emit(0)
1878 def HasMoreRecords(self):
1885 def __init__(self, name = "", where_clause = "", limit = ""):
1887 self.where_clause = where_clause
1891 return str(self.where_clause + ";" + self.limit)
1895 class BranchWindow(QMdiSubWindow):
1897 def __init__(self, glb, event_id, report_vars, parent=None):
1898 super(BranchWindow, self).__init__(parent)
1900 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
1902 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1904 self.view = QTreeView()
1905 self.view.setUniformRowHeights(True)
1906 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1907 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1908 self.view.setModel(self.model)
1910 self.ResizeColumnsToContents()
1912 self.context_menu = TreeContextMenu(self.view)
1914 self.find_bar = FindBar(self, self, True)
1916 self.finder = ChildDataItemFinder(self.model.root)
1918 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1920 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1922 self.setWidget(self.vbox.Widget())
1924 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1926 def ResizeColumnToContents(self, column, n):
1927 # Using the view's resizeColumnToContents() here is extrememly slow
1928 # so implement a crude alternative
1929 mm = "MM" if column else "MMMM"
1930 font = self.view.font()
1931 metrics = QFontMetrics(font)
1933 for row in xrange(n):
1934 val = self.model.root.child_items[row].data[column]
1935 len = metrics.width(str(val) + mm)
1936 max = len if len > max else max
1937 val = self.model.columnHeader(column)
1938 len = metrics.width(str(val) + mm)
1939 max = len if len > max else max
1940 self.view.setColumnWidth(column, max)
1942 def ResizeColumnsToContents(self):
1943 n = min(self.model.root.child_count, 100)
1945 # No data yet, so connect a signal to notify when there is
1946 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1948 columns = self.model.columnCount()
1949 for i in xrange(columns):
1950 self.ResizeColumnToContents(i, n)
1952 def UpdateColumnWidths(self, *x):
1953 # This only needs to be done once, so disconnect the signal now
1954 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1955 self.ResizeColumnsToContents()
1957 def Find(self, value, direction, pattern, context):
1958 self.view.setFocus()
1959 self.find_bar.Busy()
1960 self.finder.Find(value, direction, pattern, context, self.FindDone)
1962 def FindDone(self, row):
1963 self.find_bar.Idle()
1965 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1967 self.find_bar.NotFound()
1969 # Line edit data item
1971 class LineEditDataItem(object):
1973 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1976 self.placeholder_text = placeholder_text
1977 self.parent = parent
1980 self.value = default
1982 self.widget = QLineEdit(default)
1983 self.widget.editingFinished.connect(self.Validate)
1984 self.widget.textChanged.connect(self.Invalidate)
1987 self.validated = True
1989 if placeholder_text:
1990 self.widget.setPlaceholderText(placeholder_text)
1992 def TurnTextRed(self):
1994 palette = QPalette()
1995 palette.setColor(QPalette.Text,Qt.red)
1996 self.widget.setPalette(palette)
1999 def TurnTextNormal(self):
2001 palette = QPalette()
2002 self.widget.setPalette(palette)
2005 def InvalidValue(self, value):
2008 self.error = self.label + " invalid value '" + value + "'"
2009 self.parent.ShowMessage(self.error)
2011 def Invalidate(self):
2012 self.validated = False
2014 def DoValidate(self, input_string):
2015 self.value = input_string.strip()
2018 self.validated = True
2020 self.TurnTextNormal()
2021 self.parent.ClearMessage()
2022 input_string = self.widget.text()
2023 if not len(input_string.strip()):
2026 self.DoValidate(input_string)
2029 if not self.validated:
2032 self.parent.ShowMessage(self.error)
2036 def IsNumber(self, value):
2041 return str(x) == value
2043 # Non-negative integer ranges dialog data item
2045 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
2047 def __init__(self, glb, label, placeholder_text, column_name, parent):
2048 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
2050 self.column_name = column_name
2052 def DoValidate(self, input_string):
2055 for value in [x.strip() for x in input_string.split(",")]:
2057 vrange = value.split("-")
2058 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
2059 return self.InvalidValue(value)
2060 ranges.append(vrange)
2062 if not self.IsNumber(value):
2063 return self.InvalidValue(value)
2064 singles.append(value)
2065 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
2067 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
2068 self.value = " OR ".join(ranges)
2070 # Positive integer dialog data item
2072 class PositiveIntegerDataItem(LineEditDataItem):
2074 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
2075 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
2077 def DoValidate(self, input_string):
2078 if not self.IsNumber(input_string.strip()):
2079 return self.InvalidValue(input_string)
2080 value = int(input_string.strip())
2082 return self.InvalidValue(input_string)
2083 self.value = str(value)
2085 # Dialog data item converted and validated using a SQL table
2087 class SQLTableDataItem(LineEditDataItem):
2089 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
2090 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
2092 self.table_name = table_name
2093 self.match_column = match_column
2094 self.column_name1 = column_name1
2095 self.column_name2 = column_name2
2097 def ValueToIds(self, value):
2099 query = QSqlQuery(self.glb.db)
2100 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
2101 ret = query.exec_(stmt)
2104 ids.append(str(query.value(0)))
2107 def DoValidate(self, input_string):
2109 for value in [x.strip() for x in input_string.split(",")]:
2110 ids = self.ValueToIds(value)
2114 return self.InvalidValue(value)
2115 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
2116 if self.column_name2:
2117 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
2119 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
2121 class SampleTimeRangesDataItem(LineEditDataItem):
2123 def __init__(self, glb, label, placeholder_text, column_name, parent):
2124 self.column_name = column_name
2128 self.last_time = 2 ** 64
2130 query = QSqlQuery(glb.db)
2131 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
2133 self.last_id = int(query.value(0))
2134 self.first_time = int(glb.HostStartTime())
2135 self.last_time = int(glb.HostFinishTime())
2136 if placeholder_text:
2137 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
2139 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
2141 def IdBetween(self, query, lower_id, higher_id, order):
2142 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
2144 return True, int(query.value(0))
2148 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
2149 query = QSqlQuery(self.glb.db)
2151 next_id = int((lower_id + higher_id) / 2)
2152 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2153 if not query.next():
2154 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
2156 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
2158 return str(higher_id)
2160 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2161 next_time = int(query.value(0))
2163 if target_time > next_time:
2167 if higher_id <= lower_id + 1:
2168 return str(higher_id)
2170 if target_time >= next_time:
2174 if higher_id <= lower_id + 1:
2175 return str(lower_id)
2177 def ConvertRelativeTime(self, val):
2182 elif suffix == "us":
2184 elif suffix == "ns":
2188 val = val[:-2].strip()
2189 if not self.IsNumber(val):
2191 val = int(val) * mult
2193 val += self.first_time
2195 val += self.last_time
2198 def ConvertTimeRange(self, vrange):
2200 vrange[0] = str(self.first_time)
2202 vrange[1] = str(self.last_time)
2203 vrange[0] = self.ConvertRelativeTime(vrange[0])
2204 vrange[1] = self.ConvertRelativeTime(vrange[1])
2205 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
2207 beg_range = max(int(vrange[0]), self.first_time)
2208 end_range = min(int(vrange[1]), self.last_time)
2209 if beg_range > self.last_time or end_range < self.first_time:
2211 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
2212 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
2215 def AddTimeRange(self, value, ranges):
2216 n = value.count("-")
2220 if value.split("-")[1].strip() == "":
2226 pos = findnth(value, "-", n)
2227 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
2228 if self.ConvertTimeRange(vrange):
2229 ranges.append(vrange)
2233 def DoValidate(self, input_string):
2235 for value in [x.strip() for x in input_string.split(",")]:
2236 if not self.AddTimeRange(value, ranges):
2237 return self.InvalidValue(value)
2238 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
2239 self.value = " OR ".join(ranges)
2241 # Report Dialog Base
2243 class ReportDialogBase(QDialog):
2245 def __init__(self, glb, title, items, partial, parent=None):
2246 super(ReportDialogBase, self).__init__(parent)
2250 self.report_vars = ReportVars()
2252 self.setWindowTitle(title)
2253 self.setMinimumWidth(600)
2255 self.data_items = [x(glb, self) for x in items]
2257 self.partial = partial
2259 self.grid = QGridLayout()
2261 for row in xrange(len(self.data_items)):
2262 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2263 self.grid.addWidget(self.data_items[row].widget, row, 1)
2265 self.status = QLabel()
2267 self.ok_button = QPushButton("Ok", self)
2268 self.ok_button.setDefault(True)
2269 self.ok_button.released.connect(self.Ok)
2270 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2272 self.cancel_button = QPushButton("Cancel", self)
2273 self.cancel_button.released.connect(self.reject)
2274 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2276 self.hbox = QHBoxLayout()
2277 #self.hbox.addStretch()
2278 self.hbox.addWidget(self.status)
2279 self.hbox.addWidget(self.ok_button)
2280 self.hbox.addWidget(self.cancel_button)
2282 self.vbox = QVBoxLayout()
2283 self.vbox.addLayout(self.grid)
2284 self.vbox.addLayout(self.hbox)
2286 self.setLayout(self.vbox)
2289 vars = self.report_vars
2290 for d in self.data_items:
2291 if d.id == "REPORTNAME":
2294 self.ShowMessage("Report name is required")
2296 for d in self.data_items:
2299 for d in self.data_items[1:]:
2301 vars.limit = d.value
2303 if len(vars.where_clause):
2304 vars.where_clause += " AND "
2305 vars.where_clause += d.value
2306 if len(vars.where_clause):
2308 vars.where_clause = " AND ( " + vars.where_clause + " ) "
2310 vars.where_clause = " WHERE " + vars.where_clause + " "
2313 def ShowMessage(self, msg):
2314 self.status.setText("<font color=#FF0000>" + msg)
2316 def ClearMessage(self):
2317 self.status.setText("")
2319 # Selected branch report creation dialog
2321 class SelectedBranchDialog(ReportDialogBase):
2323 def __init__(self, glb, parent=None):
2324 title = "Selected Branches"
2325 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2326 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2327 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2328 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2329 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2330 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2331 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2332 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2333 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2334 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2338 def GetEventList(db):
2340 query = QSqlQuery(db)
2341 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2343 events.append(query.value(0))
2346 # Is a table selectable
2348 def IsSelectable(db, table, sql = "", columns = "*"):
2349 query = QSqlQuery(db)
2351 QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
2356 # SQL table data model item
2358 class SQLTableItem():
2360 def __init__(self, row, data):
2364 def getData(self, column):
2365 return self.data[column]
2367 # SQL table data model
2369 class SQLTableModel(TableModel):
2371 progress = Signal(object)
2373 def __init__(self, glb, sql, column_headers, parent=None):
2374 super(SQLTableModel, self).__init__(parent)
2378 self.column_headers = column_headers
2379 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
2380 self.fetcher.done.connect(self.Update)
2381 self.fetcher.Fetch(glb_chunk_sz)
2383 def DisplayData(self, item, index):
2384 self.FetchIfNeeded(item.row)
2385 return item.getData(index.column())
2387 def AddSample(self, data):
2388 child = SQLTableItem(self.populated, data)
2389 self.child_items.append(child)
2392 def Update(self, fetched):
2395 self.progress.emit(0)
2396 child_count = self.child_count
2397 count = self.populated - child_count
2399 parent = QModelIndex()
2400 self.beginInsertRows(parent, child_count, child_count + count - 1)
2401 self.insertRows(child_count, count, parent)
2402 self.child_count += count
2403 self.endInsertRows()
2404 self.progress.emit(self.child_count)
2406 def FetchMoreRecords(self, count):
2407 current = self.child_count
2409 self.fetcher.Fetch(count)
2411 self.progress.emit(0)
2414 def HasMoreRecords(self):
2417 def columnCount(self, parent=None):
2418 return len(self.column_headers)
2420 def columnHeader(self, column):
2421 return self.column_headers[column]
2423 def SQLTableDataPrep(self, query, count):
2425 for i in xrange(count):
2426 data.append(query.value(i))
2429 # SQL automatic table data model
2431 class SQLAutoTableModel(SQLTableModel):
2433 def __init__(self, glb, table_name, parent=None):
2434 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2435 if table_name == "comm_threads_view":
2436 # For now, comm_threads_view has no id column
2437 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2439 query = QSqlQuery(glb.db)
2440 if glb.dbref.is_sqlite3:
2441 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2443 column_headers.append(query.value(1))
2444 if table_name == "sqlite_master":
2445 sql = "SELECT * FROM " + table_name
2447 if table_name[:19] == "information_schema.":
2448 sql = "SELECT * FROM " + table_name
2449 select_table_name = table_name[19:]
2450 schema = "information_schema"
2452 select_table_name = table_name
2454 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2456 column_headers.append(query.value(0))
2457 if pyside_version_1 and sys.version_info[0] == 3:
2458 if table_name == "samples_view":
2459 self.SQLTableDataPrep = self.samples_view_DataPrep
2460 if table_name == "samples":
2461 self.SQLTableDataPrep = self.samples_DataPrep
2462 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2464 def samples_view_DataPrep(self, query, count):
2466 data.append(query.value(0))
2467 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2468 data.append("{:>19}".format(query.value(1)))
2469 for i in xrange(2, count):
2470 data.append(query.value(i))
2473 def samples_DataPrep(self, query, count):
2476 data.append(query.value(i))
2477 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2478 data.append("{:>19}".format(query.value(9)))
2479 for i in xrange(10, count):
2480 data.append(query.value(i))
2483 # Base class for custom ResizeColumnsToContents
2485 class ResizeColumnsToContentsBase(QObject):
2487 def __init__(self, parent=None):
2488 super(ResizeColumnsToContentsBase, self).__init__(parent)
2490 def ResizeColumnToContents(self, column, n):
2491 # Using the view's resizeColumnToContents() here is extrememly slow
2492 # so implement a crude alternative
2493 font = self.view.font()
2494 metrics = QFontMetrics(font)
2496 for row in xrange(n):
2497 val = self.data_model.child_items[row].data[column]
2498 len = metrics.width(str(val) + "MM")
2499 max = len if len > max else max
2500 val = self.data_model.columnHeader(column)
2501 len = metrics.width(str(val) + "MM")
2502 max = len if len > max else max
2503 self.view.setColumnWidth(column, max)
2505 def ResizeColumnsToContents(self):
2506 n = min(self.data_model.child_count, 100)
2508 # No data yet, so connect a signal to notify when there is
2509 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2511 columns = self.data_model.columnCount()
2512 for i in xrange(columns):
2513 self.ResizeColumnToContents(i, n)
2515 def UpdateColumnWidths(self, *x):
2516 # This only needs to be done once, so disconnect the signal now
2517 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2518 self.ResizeColumnsToContents()
2520 # Convert value to CSV
2524 val = val.replace('"', '""')
2525 if "," in val or '"' in val:
2526 val = '"' + val + '"'
2529 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2533 def RowColumnKey(a):
2534 return a.row() * glb_max_cols + a.column()
2536 # Copy selected table cells to clipboard
2538 def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2539 indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2540 idx_cnt = len(indexes)
2545 min_row = indexes[0].row()
2546 max_row = indexes[0].row()
2547 min_col = indexes[0].column()
2548 max_col = indexes[0].column()
2550 min_row = min(min_row, i.row())
2551 max_row = max(max_row, i.row())
2552 min_col = min(min_col, i.column())
2553 max_col = max(max_col, i.column())
2554 if max_col > glb_max_cols:
2555 raise RuntimeError("glb_max_cols is too low")
2556 max_width = [0] * (1 + max_col - min_col)
2558 c = i.column() - min_col
2559 max_width[c] = max(max_width[c], len(str(i.data())))
2564 model = indexes[0].model()
2565 for col in range(min_col, max_col + 1):
2566 val = model.headerData(col, Qt.Horizontal)
2568 text += sep + ToCSValue(val)
2572 max_width[c] = max(max_width[c], len(val))
2573 width = max_width[c]
2574 align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2575 if align & Qt.AlignRight:
2576 val = val.rjust(width)
2577 text += pad + sep + val
2578 pad = " " * (width - len(val))
2585 if i.row() > last_row:
2591 text += sep + ToCSValue(str(i.data()))
2594 width = max_width[i.column() - min_col]
2595 if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2596 val = str(i.data()).rjust(width)
2599 text += pad + sep + val
2600 pad = " " * (width - len(val))
2602 QApplication.clipboard().setText(text)
2604 def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2605 indexes = view.selectedIndexes()
2606 if not len(indexes):
2609 selection = view.selectionModel()
2613 above = view.indexAbove(i)
2614 if not selection.isSelected(above):
2619 raise RuntimeError("CopyTreeCellsToClipboard internal error")
2621 model = first.model()
2623 col_cnt = model.columnCount(first)
2624 max_width = [0] * col_cnt
2627 indent_str = " " * indent_sz
2629 expanded_mark_sz = 2
2630 if sys.version_info[0] == 3:
2631 expanded_mark = "\u25BC "
2632 not_expanded_mark = "\u25B6 "
2634 expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2635 not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2643 for c in range(col_cnt):
2644 i = pos.sibling(row, c)
2646 n = len(str(i.data()))
2648 n = len(str(i.data()).strip())
2649 n += (i.internalPointer().level - 1) * indent_sz
2650 n += expanded_mark_sz
2651 max_width[c] = max(max_width[c], n)
2652 pos = view.indexBelow(pos)
2653 if not selection.isSelected(pos):
2660 for c in range(col_cnt):
2661 val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2663 text += sep + ToCSValue(val)
2666 max_width[c] = max(max_width[c], len(val))
2667 width = max_width[c]
2668 align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2669 if align & Qt.AlignRight:
2670 val = val.rjust(width)
2671 text += pad + sep + val
2672 pad = " " * (width - len(val))
2681 for c in range(col_cnt):
2682 i = pos.sibling(row, c)
2685 if model.hasChildren(i):
2686 if view.isExpanded(i):
2687 mark = expanded_mark
2689 mark = not_expanded_mark
2692 val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2694 text += sep + ToCSValue(val)
2697 width = max_width[c]
2698 if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2699 val = val.rjust(width)
2700 text += pad + sep + val
2701 pad = " " * (width - len(val))
2703 pos = view.indexBelow(pos)
2704 if not selection.isSelected(pos):
2706 text = text.rstrip() + "\n"
2710 QApplication.clipboard().setText(text)
2712 def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2713 view.CopyCellsToClipboard(view, as_csv, with_hdr)
2715 def CopyCellsToClipboardHdr(view):
2716 CopyCellsToClipboard(view, False, True)
2718 def CopyCellsToClipboardCSV(view):
2719 CopyCellsToClipboard(view, True, True)
2723 class ContextMenu(object):
2725 def __init__(self, view):
2727 self.view.setContextMenuPolicy(Qt.CustomContextMenu)
2728 self.view.customContextMenuRequested.connect(self.ShowContextMenu)
2730 def ShowContextMenu(self, pos):
2731 menu = QMenu(self.view)
2732 self.AddActions(menu)
2733 menu.exec_(self.view.mapToGlobal(pos))
2735 def AddCopy(self, menu):
2736 menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
2737 menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
2739 def AddActions(self, menu):
2742 class TreeContextMenu(ContextMenu):
2744 def __init__(self, view):
2745 super(TreeContextMenu, self).__init__(view)
2747 def AddActions(self, menu):
2748 i = self.view.currentIndex()
2749 text = str(i.data()).strip()
2751 menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
2756 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2758 def __init__(self, glb, table_name, parent=None):
2759 super(TableWindow, self).__init__(parent)
2761 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2763 self.model = QSortFilterProxyModel()
2764 self.model.setSourceModel(self.data_model)
2766 self.view = QTableView()
2767 self.view.setModel(self.model)
2768 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2769 self.view.verticalHeader().setVisible(False)
2770 self.view.sortByColumn(-1, Qt.AscendingOrder)
2771 self.view.setSortingEnabled(True)
2772 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2773 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2775 self.ResizeColumnsToContents()
2777 self.context_menu = ContextMenu(self.view)
2779 self.find_bar = FindBar(self, self, True)
2781 self.finder = ChildDataItemFinder(self.data_model)
2783 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2785 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2787 self.setWidget(self.vbox.Widget())
2789 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2791 def Find(self, value, direction, pattern, context):
2792 self.view.setFocus()
2793 self.find_bar.Busy()
2794 self.finder.Find(value, direction, pattern, context, self.FindDone)
2796 def FindDone(self, row):
2797 self.find_bar.Idle()
2799 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2801 self.find_bar.NotFound()
2805 def GetTableList(glb):
2807 query = QSqlQuery(glb.db)
2808 if glb.dbref.is_sqlite3:
2809 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2811 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2813 tables.append(query.value(0))
2814 if glb.dbref.is_sqlite3:
2815 tables.append("sqlite_master")
2817 tables.append("information_schema.tables")
2818 tables.append("information_schema.views")
2819 tables.append("information_schema.columns")
2822 # Top Calls data model
2824 class TopCallsModel(SQLTableModel):
2826 def __init__(self, glb, report_vars, parent=None):
2828 if not glb.dbref.is_sqlite3:
2831 if len(report_vars.limit):
2832 limit = " LIMIT " + report_vars.limit
2833 sql = ("SELECT comm, pid, tid, name,"
2835 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2838 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2840 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2841 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2842 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2846 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2847 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2848 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2849 " INNER JOIN comms ON calls.comm_id = comms.id"
2850 " INNER JOIN threads ON calls.thread_id = threads.id" +
2851 report_vars.where_clause +
2852 " ORDER BY elapsed_time DESC" +
2855 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2856 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2857 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2859 def columnAlignment(self, column):
2860 return self.alignment[column]
2862 # Top Calls report creation dialog
2864 class TopCallsDialog(ReportDialogBase):
2866 def __init__(self, glb, parent=None):
2867 title = "Top Calls by Elapsed Time"
2868 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2869 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2870 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2871 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2872 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2873 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2874 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2875 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2876 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2880 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2882 def __init__(self, glb, report_vars, parent=None):
2883 super(TopCallsWindow, self).__init__(parent)
2885 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2886 self.model = self.data_model
2888 self.view = QTableView()
2889 self.view.setModel(self.model)
2890 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2891 self.view.verticalHeader().setVisible(False)
2892 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2893 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2895 self.context_menu = ContextMenu(self.view)
2897 self.ResizeColumnsToContents()
2899 self.find_bar = FindBar(self, self, True)
2901 self.finder = ChildDataItemFinder(self.model)
2903 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2905 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2907 self.setWidget(self.vbox.Widget())
2909 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2911 def Find(self, value, direction, pattern, context):
2912 self.view.setFocus()
2913 self.find_bar.Busy()
2914 self.finder.Find(value, direction, pattern, context, self.FindDone)
2916 def FindDone(self, row):
2917 self.find_bar.Idle()
2919 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2921 self.find_bar.NotFound()
2925 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2926 action = QAction(label, parent)
2927 if shortcut != None:
2928 action.setShortcuts(shortcut)
2929 action.setStatusTip(tip)
2930 action.triggered.connect(callback)
2933 # Typical application actions
2935 def CreateExitAction(app, parent=None):
2936 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2938 # Typical MDI actions
2940 def CreateCloseActiveWindowAction(mdi_area):
2941 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2943 def CreateCloseAllWindowsAction(mdi_area):
2944 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2946 def CreateTileWindowsAction(mdi_area):
2947 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2949 def CreateCascadeWindowsAction(mdi_area):
2950 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2952 def CreateNextWindowAction(mdi_area):
2953 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2955 def CreatePreviousWindowAction(mdi_area):
2956 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2958 # Typical MDI window menu
2962 def __init__(self, mdi_area, menu):
2963 self.mdi_area = mdi_area
2964 self.window_menu = menu.addMenu("&Windows")
2965 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2966 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2967 self.tile_windows = CreateTileWindowsAction(mdi_area)
2968 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2969 self.next_window = CreateNextWindowAction(mdi_area)
2970 self.previous_window = CreatePreviousWindowAction(mdi_area)
2971 self.window_menu.aboutToShow.connect(self.Update)
2974 self.window_menu.clear()
2975 sub_window_count = len(self.mdi_area.subWindowList())
2976 have_sub_windows = sub_window_count != 0
2977 self.close_active_window.setEnabled(have_sub_windows)
2978 self.close_all_windows.setEnabled(have_sub_windows)
2979 self.tile_windows.setEnabled(have_sub_windows)
2980 self.cascade_windows.setEnabled(have_sub_windows)
2981 self.next_window.setEnabled(have_sub_windows)
2982 self.previous_window.setEnabled(have_sub_windows)
2983 self.window_menu.addAction(self.close_active_window)
2984 self.window_menu.addAction(self.close_all_windows)
2985 self.window_menu.addSeparator()
2986 self.window_menu.addAction(self.tile_windows)
2987 self.window_menu.addAction(self.cascade_windows)
2988 self.window_menu.addSeparator()
2989 self.window_menu.addAction(self.next_window)
2990 self.window_menu.addAction(self.previous_window)
2991 if sub_window_count == 0:
2993 self.window_menu.addSeparator()
2995 for sub_window in self.mdi_area.subWindowList():
2996 label = str(nr) + " " + sub_window.name
2999 action = self.window_menu.addAction(label)
3000 action.setCheckable(True)
3001 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
3002 action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
3003 self.window_menu.addAction(action)
3006 def setActiveSubWindow(self, nr):
3007 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
3022 <p class=c1><a href=#reports>1. Reports</a></p>
3023 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
3024 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
3025 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
3026 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
3027 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
3028 <p class=c1><a href=#tables>2. Tables</a></p>
3029 <h1 id=reports>1. Reports</h1>
3030 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
3031 The result is a GUI window with a tree representing a context-sensitive
3032 call-graph. Expanding a couple of levels of the tree and adjusting column
3033 widths to suit will display something like:
3035 Call Graph: pt_example
3036 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
3039 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
3040 |- unknown unknown 1 13198 0.1 1 0.0
3041 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
3042 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
3043 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
3044 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
3045 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
3046 >- __libc_csu_init ls 1 10354 0.1 10 0.0
3047 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
3048 v- main ls 1 8182043 99.6 180254 99.9
3050 <h3>Points to note:</h3>
3052 <li>The top level is a command name (comm)</li>
3053 <li>The next level is a thread (pid:tid)</li>
3054 <li>Subsequent levels are functions</li>
3055 <li>'Count' is the number of calls</li>
3056 <li>'Time' is the elapsed time until the function returns</li>
3057 <li>Percentages are relative to the level above</li>
3058 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
3061 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
3062 The pattern matching symbols are ? for any character and * for zero or more characters.
3063 <h2 id=calltree>1.2 Call Tree</h2>
3064 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
3065 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
3066 <h2 id=allbranches>1.3 All branches</h2>
3067 The All branches report displays all branches in chronological order.
3068 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
3069 <h3>Disassembly</h3>
3070 Open a branch to display disassembly. This only works if:
3072 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
3073 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
3074 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
3075 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
3076 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
3078 <h4 id=xed>Intel XED Setup</h4>
3079 To use Intel XED, libxed.so must be present. To build and install libxed.so:
3081 git clone https://github.com/intelxed/mbuild.git mbuild
3082 git clone https://github.com/intelxed/xed
3085 sudo ./mfile.py --prefix=/usr/local install
3088 <h3>Instructions per Cycle (IPC)</h3>
3089 If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
3090 <p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
3091 Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
3092 In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
3093 since the previous displayed 'IPC'.
3095 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3096 Refer to Python documentation for the regular expression syntax.
3097 All columns are searched, but only currently fetched rows are searched.
3098 <h2 id=selectedbranches>1.4 Selected branches</h2>
3099 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
3100 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3101 <h3>1.4.1 Time ranges</h3>
3102 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
3103 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
3105 81073085947329-81073085958238 From 81073085947329 to 81073085958238
3106 100us-200us From 100us to 200us
3107 10ms- From 10ms to the end
3108 -100ns The first 100ns
3109 -10ms- The last 10ms
3111 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
3112 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
3113 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.
3114 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3115 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
3116 <h1 id=tables>2. Tables</h1>
3117 The Tables menu shows all tables and views in the database. Most tables have an associated view
3118 which displays the information in a more friendly way. Not all data for large tables is fetched
3119 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
3120 but that can be slow for large tables.
3121 <p>There are also tables of database meta-information.
3122 For SQLite3 databases, the sqlite_master table is included.
3123 For PostgreSQL databases, information_schema.tables/views/columns are included.
3125 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3126 Refer to Python documentation for the regular expression syntax.
3127 All columns are searched, but only currently fetched rows are searched.
3128 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
3129 will go to the next/previous result in id order, instead of display order.
3134 class HelpWindow(QMdiSubWindow):
3136 def __init__(self, glb, parent=None):
3137 super(HelpWindow, self).__init__(parent)
3139 self.text = QTextBrowser()
3140 self.text.setHtml(glb_help_text)
3141 self.text.setReadOnly(True)
3142 self.text.setOpenExternalLinks(True)
3144 self.setWidget(self.text)
3146 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
3148 # Main window that only displays the help text
3150 class HelpOnlyWindow(QMainWindow):
3152 def __init__(self, parent=None):
3153 super(HelpOnlyWindow, self).__init__(parent)
3155 self.setMinimumSize(200, 100)
3156 self.resize(800, 600)
3157 self.setWindowTitle("Exported SQL Viewer Help")
3158 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
3160 self.text = QTextBrowser()
3161 self.text.setHtml(glb_help_text)
3162 self.text.setReadOnly(True)
3163 self.text.setOpenExternalLinks(True)
3165 self.setCentralWidget(self.text)
3167 # PostqreSQL server version
3169 def PostqreSQLServerVersion(db):
3170 query = QSqlQuery(db)
3171 QueryExec(query, "SELECT VERSION()")
3173 v_str = query.value(0)
3174 v_list = v_str.strip().split(" ")
3175 if v_list[0] == "PostgreSQL" and v_list[2] == "on":
3182 def SQLiteVersion(db):
3183 query = QSqlQuery(db)
3184 QueryExec(query, "SELECT sqlite_version()")
3186 return query.value(0)
3191 class AboutDialog(QDialog):
3193 def __init__(self, glb, parent=None):
3194 super(AboutDialog, self).__init__(parent)
3196 self.setWindowTitle("About Exported SQL Viewer")
3197 self.setMinimumWidth(300)
3199 pyside_version = "1" if pyside_version_1 else "2"
3202 text += "Python version: " + sys.version.split(" ")[0] + "\n"
3203 text += "PySide version: " + pyside_version + "\n"
3204 text += "Qt version: " + qVersion() + "\n"
3205 if glb.dbref.is_sqlite3:
3206 text += "SQLite version: " + SQLiteVersion(glb.db) + "\n"
3208 text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
3211 self.text = QTextBrowser()
3212 self.text.setHtml(text)
3213 self.text.setReadOnly(True)
3214 self.text.setOpenExternalLinks(True)
3216 self.vbox = QVBoxLayout()
3217 self.vbox.addWidget(self.text)
3219 self.setLayout(self.vbox)
3223 def ResizeFont(widget, diff):
3224 font = widget.font()
3225 sz = font.pointSize()
3226 font.setPointSize(sz + diff)
3227 widget.setFont(font)
3229 def ShrinkFont(widget):
3230 ResizeFont(widget, -1)
3232 def EnlargeFont(widget):
3233 ResizeFont(widget, 1)
3235 # Unique name for sub-windows
3237 def NumberedWindowName(name, nr):
3239 name += " <" + str(nr) + ">"
3242 def UniqueSubWindowName(mdi_area, name):
3245 unique_name = NumberedWindowName(name, nr)
3247 for sub_window in mdi_area.subWindowList():
3248 if sub_window.name == unique_name:
3257 def AddSubWindow(mdi_area, sub_window, name):
3258 unique_name = UniqueSubWindowName(mdi_area, name)
3259 sub_window.setMinimumSize(200, 100)
3260 sub_window.resize(800, 600)
3261 sub_window.setWindowTitle(unique_name)
3262 sub_window.setAttribute(Qt.WA_DeleteOnClose)
3263 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
3264 sub_window.name = unique_name
3265 mdi_area.addSubWindow(sub_window)
3270 class MainWindow(QMainWindow):
3272 def __init__(self, glb, parent=None):
3273 super(MainWindow, self).__init__(parent)
3277 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3278 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3279 self.setMinimumSize(200, 100)
3281 self.mdi_area = QMdiArea()
3282 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3283 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3285 self.setCentralWidget(self.mdi_area)
3287 menu = self.menuBar()
3289 file_menu = menu.addMenu("&File")
3290 file_menu.addAction(CreateExitAction(glb.app, self))
3292 edit_menu = menu.addMenu("&Edit")
3293 edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
3294 edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3295 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
3296 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
3297 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
3298 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3300 reports_menu = menu.addMenu("&Reports")
3301 if IsSelectable(glb.db, "calls"):
3302 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
3304 if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3305 reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3307 self.EventMenu(GetEventList(glb.db), reports_menu)
3309 if IsSelectable(glb.db, "calls"):
3310 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3312 self.TableMenu(GetTableList(glb), menu)
3314 self.window_menu = WindowMenu(self.mdi_area, menu)
3316 help_menu = menu.addMenu("&Help")
3317 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3318 help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
3321 win = self.mdi_area.activeSubWindow()
3328 def CopyToClipboard(self):
3329 self.Try(CopyCellsToClipboardHdr)
3331 def CopyToClipboardCSV(self):
3332 self.Try(CopyCellsToClipboardCSV)
3335 win = self.mdi_area.activeSubWindow()
3338 win.find_bar.Activate()
3342 def FetchMoreRecords(self):
3343 win = self.mdi_area.activeSubWindow()
3346 win.fetch_bar.Activate()
3350 def ShrinkFont(self):
3351 self.Try(ShrinkFont)
3353 def EnlargeFont(self):
3354 self.Try(EnlargeFont)
3356 def EventMenu(self, events, reports_menu):
3358 for event in events:
3359 event = event.split(":")[0]
3360 if event == "branches":
3361 branches_events += 1
3363 for event in events:
3365 event = event.split(":")[0]
3366 if event == "branches":
3367 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3368 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
3369 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3370 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
3372 def TableMenu(self, tables, menu):
3373 table_menu = menu.addMenu("&Tables")
3374 for table in tables:
3375 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
3377 def NewCallGraph(self):
3378 CallGraphWindow(self.glb, self)
3380 def NewCallTree(self):
3381 CallTreeWindow(self.glb, self)
3383 def NewTopCalls(self):
3384 dialog = TopCallsDialog(self.glb, self)
3385 ret = dialog.exec_()
3387 TopCallsWindow(self.glb, dialog.report_vars, self)
3389 def NewBranchView(self, event_id):
3390 BranchWindow(self.glb, event_id, ReportVars(), self)
3392 def NewSelectedBranchView(self, event_id):
3393 dialog = SelectedBranchDialog(self.glb, self)
3394 ret = dialog.exec_()
3396 BranchWindow(self.glb, event_id, dialog.report_vars, self)
3398 def NewTableView(self, table_name):
3399 TableWindow(self.glb, table_name, self)
3402 HelpWindow(self.glb, self)
3405 dialog = AboutDialog(self.glb, self)
3410 class xed_state_t(Structure):
3417 class XEDInstruction():
3419 def __init__(self, libxed):
3420 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3421 xedd_t = c_byte * 512
3422 self.xedd = xedd_t()
3423 self.xedp = addressof(self.xedd)
3424 libxed.xed_decoded_inst_zero(self.xedp)
3425 self.state = xed_state_t()
3426 self.statep = addressof(self.state)
3427 # Buffer for disassembled instruction text
3428 self.buffer = create_string_buffer(256)
3429 self.bufferp = addressof(self.buffer)
3435 self.libxed = CDLL("libxed.so")
3439 self.libxed = CDLL("/usr/local/lib/libxed.so")
3441 self.xed_tables_init = self.libxed.xed_tables_init
3442 self.xed_tables_init.restype = None
3443 self.xed_tables_init.argtypes = []
3445 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3446 self.xed_decoded_inst_zero.restype = None
3447 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3449 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3450 self.xed_operand_values_set_mode.restype = None
3451 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3453 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3454 self.xed_decoded_inst_zero_keep_mode.restype = None
3455 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3457 self.xed_decode = self.libxed.xed_decode
3458 self.xed_decode.restype = c_int
3459 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3461 self.xed_format_context = self.libxed.xed_format_context
3462 self.xed_format_context.restype = c_uint
3463 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3465 self.xed_tables_init()
3467 def Instruction(self):
3468 return XEDInstruction(self)
3470 def SetMode(self, inst, mode):
3472 inst.state.mode = 4 # 32-bit
3473 inst.state.width = 4 # 4 bytes
3475 inst.state.mode = 1 # 64-bit
3476 inst.state.width = 8 # 8 bytes
3477 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3479 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3480 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3481 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3484 # Use AT&T mode (2), alternative is Intel (3)
3485 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3488 if sys.version_info[0] == 2:
3489 result = inst.buffer.value
3491 result = inst.buffer.value.decode()
3492 # Return instruction length and the disassembled instruction text
3493 # For now, assume the length is in byte 166
3494 return inst.xedd[166], result
3496 def TryOpen(file_name):
3498 return open(file_name, "rb")
3503 result = sizeof(c_void_p)
3510 if sys.version_info[0] == 2:
3511 eclass = ord(header[4])
3512 encoding = ord(header[5])
3513 version = ord(header[6])
3516 encoding = header[5]
3518 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3519 result = True if eclass == 2 else False
3526 def __init__(self, dbref, db, dbname):
3529 self.dbname = dbname
3530 self.home_dir = os.path.expanduser("~")
3531 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3532 if self.buildid_dir:
3533 self.buildid_dir += "/.build-id/"
3535 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3537 self.mainwindow = None
3538 self.instances_to_shutdown_on_exit = weakref.WeakSet()
3540 self.disassembler = LibXED()
3541 self.have_disassembler = True
3543 self.have_disassembler = False
3544 self.host_machine_id = 0
3545 self.host_start_time = 0
3546 self.host_finish_time = 0
3548 def FileFromBuildId(self, build_id):
3549 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3550 return TryOpen(file_name)
3552 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3553 # Assume current machine i.e. no support for virtualization
3554 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3555 file_name = os.getenv("PERF_KCORE")
3556 f = TryOpen(file_name) if file_name else None
3559 # For now, no special handling if long_name is /proc/kcore
3560 f = TryOpen(long_name)
3563 f = self.FileFromBuildId(build_id)
3568 def AddInstanceToShutdownOnExit(self, instance):
3569 self.instances_to_shutdown_on_exit.add(instance)
3571 # Shutdown any background processes or threads
3572 def ShutdownInstances(self):
3573 for x in self.instances_to_shutdown_on_exit:
3579 def GetHostMachineId(self):
3580 query = QSqlQuery(self.db)
3581 QueryExec(query, "SELECT id FROM machines WHERE pid = -1")
3583 self.host_machine_id = query.value(0)
3585 self.host_machine_id = 0
3586 return self.host_machine_id
3588 def HostMachineId(self):
3589 if self.host_machine_id:
3590 return self.host_machine_id
3591 return self.GetHostMachineId()
3593 def SelectValue(self, sql):
3594 query = QSqlQuery(self.db)
3596 QueryExec(query, sql)
3600 return Decimal(query.value(0))
3603 def SwitchesMinTime(self, machine_id):
3604 return self.SelectValue("SELECT time"
3605 " FROM context_switches"
3606 " WHERE time != 0 AND machine_id = " + str(machine_id) +
3607 " ORDER BY id LIMIT 1")
3609 def SwitchesMaxTime(self, machine_id):
3610 return self.SelectValue("SELECT time"
3611 " FROM context_switches"
3612 " WHERE time != 0 AND machine_id = " + str(machine_id) +
3613 " ORDER BY id DESC LIMIT 1")
3615 def SamplesMinTime(self, machine_id):
3616 return self.SelectValue("SELECT time"
3618 " WHERE time != 0 AND machine_id = " + str(machine_id) +
3619 " ORDER BY id LIMIT 1")
3621 def SamplesMaxTime(self, machine_id):
3622 return self.SelectValue("SELECT time"
3624 " WHERE time != 0 AND machine_id = " + str(machine_id) +
3625 " ORDER BY id DESC LIMIT 1")
3627 def CallsMinTime(self, machine_id):
3628 return self.SelectValue("SELECT calls.call_time"
3630 " INNER JOIN threads ON threads.thread_id = calls.thread_id"
3631 " WHERE calls.call_time != 0 AND threads.machine_id = " + str(machine_id) +
3632 " ORDER BY calls.id LIMIT 1")
3634 def CallsMaxTime(self, machine_id):
3635 return self.SelectValue("SELECT calls.return_time"
3637 " INNER JOIN threads ON threads.thread_id = calls.thread_id"
3638 " WHERE calls.return_time != 0 AND threads.machine_id = " + str(machine_id) +
3639 " ORDER BY calls.return_time DESC LIMIT 1")
3641 def GetStartTime(self, machine_id):
3642 t0 = self.SwitchesMinTime(machine_id)
3643 t1 = self.SamplesMinTime(machine_id)
3644 t2 = self.CallsMinTime(machine_id)
3645 if t0 is None or (not(t1 is None) and t1 < t0):
3647 if t0 is None or (not(t2 is None) and t2 < t0):
3651 def GetFinishTime(self, machine_id):
3652 t0 = self.SwitchesMaxTime(machine_id)
3653 t1 = self.SamplesMaxTime(machine_id)
3654 t2 = self.CallsMaxTime(machine_id)
3655 if t0 is None or (not(t1 is None) and t1 > t0):
3657 if t0 is None or (not(t2 is None) and t2 > t0):
3661 def HostStartTime(self):
3662 if self.host_start_time:
3663 return self.host_start_time
3664 self.host_start_time = self.GetStartTime(self.HostMachineId())
3665 return self.host_start_time
3667 def HostFinishTime(self):
3668 if self.host_finish_time:
3669 return self.host_finish_time
3670 self.host_finish_time = self.GetFinishTime(self.HostMachineId())
3671 return self.host_finish_time
3673 def StartTime(self, machine_id):
3674 if machine_id == self.HostMachineId():
3675 return self.HostStartTime()
3676 return self.GetStartTime(machine_id)
3678 def FinishTime(self, machine_id):
3679 if machine_id == self.HostMachineId():
3680 return self.HostFinishTime()
3681 return self.GetFinishTime(machine_id)
3683 # Database reference
3687 def __init__(self, is_sqlite3, dbname):
3688 self.is_sqlite3 = is_sqlite3
3689 self.dbname = dbname
3691 def Open(self, connection_name):
3692 dbname = self.dbname
3694 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3696 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3697 opts = dbname.split()
3700 opt = opt.split("=")
3701 if opt[0] == "hostname":
3702 db.setHostName(opt[1])
3703 elif opt[0] == "port":
3704 db.setPort(int(opt[1]))
3705 elif opt[0] == "username":
3706 db.setUserName(opt[1])
3707 elif opt[0] == "password":
3708 db.setPassword(opt[1])
3709 elif opt[0] == "dbname":
3714 db.setDatabaseName(dbname)
3716 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3722 usage_str = "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
3723 " or: exported-sql-viewer.py --help-only"
3724 ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
3725 ap.add_argument("--pyside-version-1", action='store_true')
3726 ap.add_argument("dbname", nargs="?")
3727 ap.add_argument("--help-only", action='store_true')
3728 args = ap.parse_args()
3731 app = QApplication(sys.argv)
3732 mainwindow = HelpOnlyWindow()
3737 dbname = args.dbname
3740 print("Too few arguments")
3745 f = open(dbname, "rb")
3746 if f.read(15) == b'SQLite format 3':
3752 dbref = DBRef(is_sqlite3, dbname)
3753 db, dbname = dbref.Open("main")
3754 glb = Glb(dbref, db, dbname)
3755 app = QApplication(sys.argv)
3757 mainwindow = MainWindow(glb)
3758 glb.mainwindow = mainwindow
3761 glb.ShutdownInstances()
3765 if __name__ == "__main__":