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()
348 def __init__(self, parent, finder, is_reg_expr=False):
351 self.last_value = None
352 self.last_pattern = None
354 label = QLabel("Find:")
355 label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
357 self.textbox = QComboBox()
358 self.textbox.setEditable(True)
359 self.textbox.currentIndexChanged.connect(self.ValueChanged)
361 self.progress = QProgressBar()
362 self.progress.setRange(0, 0)
366 self.pattern = QCheckBox("Regular Expression")
368 self.pattern = QCheckBox("Pattern")
369 self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
371 self.next_button = QToolButton()
372 self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
373 self.next_button.released.connect(lambda: self.NextPrev(1))
375 self.prev_button = QToolButton()
376 self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
377 self.prev_button.released.connect(lambda: self.NextPrev(-1))
379 self.close_button = QToolButton()
380 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
381 self.close_button.released.connect(self.Deactivate)
383 self.hbox = QHBoxLayout()
384 self.hbox.setContentsMargins(0, 0, 0, 0)
386 self.hbox.addWidget(label)
387 self.hbox.addWidget(self.textbox)
388 self.hbox.addWidget(self.progress)
389 self.hbox.addWidget(self.pattern)
390 self.hbox.addWidget(self.next_button)
391 self.hbox.addWidget(self.prev_button)
392 self.hbox.addWidget(self.close_button)
395 self.bar.setLayout(self.hbox);
403 self.textbox.lineEdit().selectAll()
404 self.textbox.setFocus()
406 def Deactivate(self):
410 self.textbox.setEnabled(False)
412 self.next_button.hide()
413 self.prev_button.hide()
417 self.textbox.setEnabled(True)
420 self.next_button.show()
421 self.prev_button.show()
423 def Find(self, direction):
424 value = self.textbox.currentText()
425 pattern = self.pattern.isChecked()
426 self.last_value = value
427 self.last_pattern = pattern
428 self.finder.Find(value, direction, pattern, self.context)
430 def ValueChanged(self):
431 value = self.textbox.currentText()
432 pattern = self.pattern.isChecked()
433 index = self.textbox.currentIndex()
434 data = self.textbox.itemData(index)
435 # Store the pattern in the combo box to keep it with the text value
437 self.textbox.setItemData(index, pattern)
439 self.pattern.setChecked(data)
442 def NextPrev(self, direction):
443 value = self.textbox.currentText()
444 pattern = self.pattern.isChecked()
445 if value != self.last_value:
446 index = self.textbox.findText(value)
447 # Allow for a button press before the value has been added to the combo box
449 index = self.textbox.count()
450 self.textbox.addItem(value, pattern)
451 self.textbox.setCurrentIndex(index)
454 self.textbox.setItemData(index, pattern)
455 elif pattern != self.last_pattern:
456 # Keep the pattern recorded in the combo box up to date
457 index = self.textbox.currentIndex()
458 self.textbox.setItemData(index, pattern)
462 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
464 # Context-sensitive call graph data model item base
466 class CallGraphLevelItemBase(object):
468 def __init__(self, glb, params, row, parent_item):
472 self.parent_item = parent_item
473 self.query_done = False;
475 self.child_items = []
477 self.level = parent_item.level + 1
481 def getChildItem(self, row):
482 return self.child_items[row]
484 def getParentItem(self):
485 return self.parent_item
490 def childCount(self):
491 if not self.query_done:
493 if not self.child_count:
495 return self.child_count
497 def hasChildren(self):
498 if not self.query_done:
500 return self.child_count > 0
502 def getData(self, column):
503 return self.data[column]
505 # Context-sensitive call graph data model level 2+ item base
507 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
509 def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
510 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
511 self.comm_id = comm_id
512 self.thread_id = thread_id
513 self.call_path_id = call_path_id
514 self.insn_cnt = insn_cnt
515 self.cyc_cnt = cyc_cnt
516 self.branch_count = branch_count
520 self.query_done = True;
521 query = QSqlQuery(self.glb.db)
522 if self.params.have_ipc:
523 ipc_str = ", SUM(insn_count), SUM(cyc_count)"
526 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
528 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
529 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
530 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
531 " WHERE parent_call_path_id = " + str(self.call_path_id) +
532 " AND comm_id = " + str(self.comm_id) +
533 " AND thread_id = " + str(self.thread_id) +
534 " GROUP BY call_path_id, name, short_name"
535 " ORDER BY call_path_id")
537 if self.params.have_ipc:
538 insn_cnt = int(query.value(5))
539 cyc_cnt = int(query.value(6))
540 branch_count = int(query.value(7))
544 branch_count = int(query.value(5))
545 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)
546 self.child_items.append(child_item)
547 self.child_count += 1
549 # Context-sensitive call graph data model level three item
551 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
553 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):
554 super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
556 if self.params.have_ipc:
557 insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
558 cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
559 br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
560 ipc = CalcIPC(cyc_cnt, insn_cnt)
561 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 ]
563 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
564 self.dbid = call_path_id
566 # Context-sensitive call graph data model level two item
568 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
570 def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
571 super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
572 if self.params.have_ipc:
573 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
575 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
576 self.dbid = thread_id
579 super(CallGraphLevelTwoItem, self).Select()
580 for child_item in self.child_items:
581 self.time += child_item.time
582 self.insn_cnt += child_item.insn_cnt
583 self.cyc_cnt += child_item.cyc_cnt
584 self.branch_count += child_item.branch_count
585 for child_item in self.child_items:
586 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
587 if self.params.have_ipc:
588 child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
589 child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
590 child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
592 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
594 # Context-sensitive call graph data model level one item
596 class CallGraphLevelOneItem(CallGraphLevelItemBase):
598 def __init__(self, glb, params, row, comm_id, comm, parent_item):
599 super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
600 if self.params.have_ipc:
601 self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
603 self.data = [comm, "", "", "", "", "", ""]
607 self.query_done = True;
608 query = QSqlQuery(self.glb.db)
609 QueryExec(query, "SELECT thread_id, pid, tid"
611 " INNER JOIN threads ON thread_id = threads.id"
612 " WHERE comm_id = " + str(self.dbid))
614 child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
615 self.child_items.append(child_item)
616 self.child_count += 1
618 # Context-sensitive call graph data model root item
620 class CallGraphRootItem(CallGraphLevelItemBase):
622 def __init__(self, glb, params):
623 super(CallGraphRootItem, self).__init__(glb, params, 0, None)
625 self.query_done = True;
626 query = QSqlQuery(glb.db)
627 QueryExec(query, "SELECT id, comm FROM comms")
629 if not query.value(0):
631 child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
632 self.child_items.append(child_item)
633 self.child_count += 1
635 # Call graph model parameters
637 class CallGraphModelParams():
639 def __init__(self, glb, parent=None):
640 self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
642 # Context-sensitive call graph data model base
644 class CallGraphModelBase(TreeModel):
646 def __init__(self, glb, parent=None):
647 super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
649 def FindSelect(self, value, pattern, query):
651 # postgresql and sqlite pattern patching differences:
652 # postgresql LIKE is case sensitive but sqlite LIKE is not
653 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
654 # postgresql supports ILIKE which is case insensitive
655 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
656 if not self.glb.dbref.is_sqlite3:
658 s = value.replace("%", "\%")
659 s = s.replace("_", "\_")
660 # Translate * and ? into SQL LIKE pattern characters % and _
661 trans = string.maketrans("*?", "%_")
662 match = " LIKE '" + str(s).translate(trans) + "'"
664 match = " GLOB '" + str(value) + "'"
666 match = " = '" + str(value) + "'"
667 self.DoFindSelect(query, match)
669 def Found(self, query, found):
671 return self.FindPath(query)
674 def FindValue(self, value, pattern, query, last_value, last_pattern):
675 if last_value == value and pattern == last_pattern:
676 found = query.first()
678 self.FindSelect(value, pattern, query)
680 return self.Found(query, found)
682 def FindNext(self, query):
685 found = query.first()
686 return self.Found(query, found)
688 def FindPrev(self, query):
689 found = query.previous()
692 return self.Found(query, found)
694 def FindThread(self, c):
695 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
696 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
697 elif c.direction > 0:
698 ids = self.FindNext(c.query)
700 ids = self.FindPrev(c.query)
703 def Find(self, value, direction, pattern, context, callback):
705 def __init__(self, *x):
706 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
707 def Update(self, *x):
708 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
710 context[0].Update(value, direction, pattern)
712 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
713 # Use a thread so the UI is not blocked during the SELECT
714 thread = Thread(self.FindThread, context[0])
715 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
718 def FindDone(self, thread, callback, ids):
721 # Context-sensitive call graph data model
723 class CallGraphModel(CallGraphModelBase):
725 def __init__(self, glb, parent=None):
726 super(CallGraphModel, self).__init__(glb, parent)
729 return CallGraphRootItem(self.glb, self.params)
731 def columnCount(self, parent=None):
732 if self.params.have_ipc:
737 def columnHeader(self, column):
738 if self.params.have_ipc:
739 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
741 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
742 return headers[column]
744 def columnAlignment(self, column):
745 if self.params.have_ipc:
746 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 ]
748 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
749 return alignment[column]
751 def DoFindSelect(self, query, match):
752 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
754 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
755 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
756 " WHERE symbols.name" + match +
757 " GROUP BY comm_id, thread_id, call_path_id"
758 " ORDER BY comm_id, thread_id, call_path_id")
760 def FindPath(self, query):
761 # Turn the query result into a list of ids that the tree view can walk
762 # to open the tree at the right place.
764 parent_id = query.value(0)
766 ids.insert(0, parent_id)
767 q2 = QSqlQuery(self.glb.db)
768 QueryExec(q2, "SELECT parent_id"
770 " WHERE id = " + str(parent_id))
773 parent_id = q2.value(0)
774 # The call path root is not used
777 ids.insert(0, query.value(2))
778 ids.insert(0, query.value(1))
781 # Call tree data model level 2+ item base
783 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
785 def __init__(self, glb, params, row, comm_id, thread_id, calls_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
786 super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
787 self.comm_id = comm_id
788 self.thread_id = thread_id
789 self.calls_id = calls_id
790 self.insn_cnt = insn_cnt
791 self.cyc_cnt = cyc_cnt
792 self.branch_count = branch_count
796 self.query_done = True;
797 if self.calls_id == 0:
798 comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
801 if self.params.have_ipc:
802 ipc_str = ", insn_count, cyc_count"
805 query = QSqlQuery(self.glb.db)
806 QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
808 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
809 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
810 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
811 " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
812 " ORDER BY call_time, calls.id")
814 if self.params.have_ipc:
815 insn_cnt = int(query.value(5))
816 cyc_cnt = int(query.value(6))
817 branch_count = int(query.value(7))
821 branch_count = int(query.value(5))
822 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)
823 self.child_items.append(child_item)
824 self.child_count += 1
826 # Call tree data model level three item
828 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
830 def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item):
831 super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
833 if self.params.have_ipc:
834 insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
835 cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
836 br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
837 ipc = CalcIPC(cyc_cnt, insn_cnt)
838 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 ]
840 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
843 # Call tree data model level two item
845 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
847 def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
848 super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, parent_item)
849 if self.params.have_ipc:
850 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
852 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
853 self.dbid = thread_id
856 super(CallTreeLevelTwoItem, self).Select()
857 for child_item in self.child_items:
858 self.time += child_item.time
859 self.insn_cnt += child_item.insn_cnt
860 self.cyc_cnt += child_item.cyc_cnt
861 self.branch_count += child_item.branch_count
862 for child_item in self.child_items:
863 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
864 if self.params.have_ipc:
865 child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
866 child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
867 child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
869 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
871 # Call tree data model level one item
873 class CallTreeLevelOneItem(CallGraphLevelItemBase):
875 def __init__(self, glb, params, row, comm_id, comm, parent_item):
876 super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
877 if self.params.have_ipc:
878 self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
880 self.data = [comm, "", "", "", "", "", ""]
884 self.query_done = True;
885 query = QSqlQuery(self.glb.db)
886 QueryExec(query, "SELECT thread_id, pid, tid"
888 " INNER JOIN threads ON thread_id = threads.id"
889 " WHERE comm_id = " + str(self.dbid))
891 child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
892 self.child_items.append(child_item)
893 self.child_count += 1
895 # Call tree data model root item
897 class CallTreeRootItem(CallGraphLevelItemBase):
899 def __init__(self, glb, params):
900 super(CallTreeRootItem, self).__init__(glb, params, 0, None)
902 self.query_done = True;
903 query = QSqlQuery(glb.db)
904 QueryExec(query, "SELECT id, comm FROM comms")
906 if not query.value(0):
908 child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
909 self.child_items.append(child_item)
910 self.child_count += 1
912 # Call Tree data model
914 class CallTreeModel(CallGraphModelBase):
916 def __init__(self, glb, parent=None):
917 super(CallTreeModel, self).__init__(glb, parent)
920 return CallTreeRootItem(self.glb, self.params)
922 def columnCount(self, parent=None):
923 if self.params.have_ipc:
928 def columnHeader(self, column):
929 if self.params.have_ipc:
930 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
932 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
933 return headers[column]
935 def columnAlignment(self, column):
936 if self.params.have_ipc:
937 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 ]
939 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
940 return alignment[column]
942 def DoFindSelect(self, query, match):
943 QueryExec(query, "SELECT calls.id, comm_id, thread_id"
945 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
946 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
947 " WHERE symbols.name" + match +
948 " ORDER BY comm_id, thread_id, call_time, calls.id")
950 def FindPath(self, query):
951 # Turn the query result into a list of ids that the tree view can walk
952 # to open the tree at the right place.
954 parent_id = query.value(0)
956 ids.insert(0, parent_id)
957 q2 = QSqlQuery(self.glb.db)
958 QueryExec(q2, "SELECT parent_id"
960 " WHERE id = " + str(parent_id))
963 parent_id = q2.value(0)
964 ids.insert(0, query.value(2))
965 ids.insert(0, query.value(1))
968 # Vertical widget layout
972 def __init__(self, w1, w2, w3=None):
973 self.vbox = QWidget()
974 self.vbox.setLayout(QVBoxLayout());
976 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
978 self.vbox.layout().addWidget(w1)
979 self.vbox.layout().addWidget(w2)
981 self.vbox.layout().addWidget(w3)
988 class TreeWindowBase(QMdiSubWindow):
990 def __init__(self, parent=None):
991 super(TreeWindowBase, self).__init__(parent)
996 self.view = QTreeView()
997 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
998 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1000 self.context_menu = TreeContextMenu(self.view)
1002 def DisplayFound(self, ids):
1005 parent = QModelIndex()
1008 n = self.model.rowCount(parent)
1009 for row in xrange(n):
1010 child = self.model.index(row, 0, parent)
1011 if child.internalPointer().dbid == dbid:
1013 self.view.setCurrentIndex(child)
1020 def Find(self, value, direction, pattern, context):
1021 self.view.setFocus()
1022 self.find_bar.Busy()
1023 self.model.Find(value, direction, pattern, context, self.FindDone)
1025 def FindDone(self, ids):
1027 if not self.DisplayFound(ids):
1029 self.find_bar.Idle()
1031 self.find_bar.NotFound()
1034 # Context-sensitive call graph window
1036 class CallGraphWindow(TreeWindowBase):
1038 def __init__(self, glb, parent=None):
1039 super(CallGraphWindow, self).__init__(parent)
1041 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
1043 self.view.setModel(self.model)
1045 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1046 self.view.setColumnWidth(c, w)
1048 self.find_bar = FindBar(self, self)
1050 self.vbox = VBox(self.view, self.find_bar.Widget())
1052 self.setWidget(self.vbox.Widget())
1054 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
1058 class CallTreeWindow(TreeWindowBase):
1060 def __init__(self, glb, parent=None):
1061 super(CallTreeWindow, self).__init__(parent)
1063 self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
1065 self.view.setModel(self.model)
1067 for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1068 self.view.setColumnWidth(c, w)
1070 self.find_bar = FindBar(self, self)
1072 self.vbox = VBox(self.view, self.find_bar.Widget())
1074 self.setWidget(self.vbox.Widget())
1076 AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
1078 # Child data item finder
1080 class ChildDataItemFinder():
1082 def __init__(self, root):
1084 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
1088 def FindSelect(self):
1091 pattern = re.compile(self.value)
1092 for child in self.root.child_items:
1093 for column_data in child.data:
1094 if re.search(pattern, str(column_data)) is not None:
1095 self.rows.append(child.row)
1098 for child in self.root.child_items:
1099 for column_data in child.data:
1100 if self.value in str(column_data):
1101 self.rows.append(child.row)
1104 def FindValue(self):
1106 if self.last_value != self.value or self.pattern != self.last_pattern:
1108 if not len(self.rows):
1110 return self.rows[self.pos]
1112 def FindThread(self):
1113 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1114 row = self.FindValue()
1115 elif len(self.rows):
1116 if self.direction > 0:
1118 if self.pos >= len(self.rows):
1123 self.pos = len(self.rows) - 1
1124 row = self.rows[self.pos]
1129 def Find(self, value, direction, pattern, context, callback):
1130 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1131 # Use a thread so the UI is not blocked
1132 thread = Thread(self.FindThread)
1133 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1136 def FindDone(self, thread, callback, row):
1139 # Number of database records to fetch in one go
1141 glb_chunk_sz = 10000
1143 # Background process for SQL data fetcher
1145 class SQLFetcherProcess():
1147 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1148 # Need a unique connection name
1149 conn_name = "SQLFetcher" + str(os.getpid())
1150 self.db, dbname = dbref.Open(conn_name)
1152 self.buffer = buffer
1155 self.fetch_count = fetch_count
1156 self.fetching_done = fetching_done
1157 self.process_target = process_target
1158 self.wait_event = wait_event
1159 self.fetched_event = fetched_event
1161 self.query = QSqlQuery(self.db)
1162 self.query_limit = 0 if "$$last_id$$" in sql else 2
1166 self.local_head = self.head.value
1167 self.local_tail = self.tail.value
1170 if self.query_limit:
1171 if self.query_limit == 1:
1173 self.query_limit -= 1
1174 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1175 QueryExec(self.query, stmt)
1178 if not self.query.next():
1180 if not self.query.next():
1182 self.last_id = self.query.value(0)
1183 return self.prep(self.query)
1185 def WaitForTarget(self):
1187 self.wait_event.clear()
1188 target = self.process_target.value
1189 if target > self.fetched or target < 0:
1191 self.wait_event.wait()
1194 def HasSpace(self, sz):
1195 if self.local_tail <= self.local_head:
1196 space = len(self.buffer) - self.local_head
1199 if space >= glb_nsz:
1200 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1201 nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
1202 self.buffer[self.local_head : self.local_head + len(nd)] = nd
1204 if self.local_tail - self.local_head > sz:
1208 def WaitForSpace(self, sz):
1209 if self.HasSpace(sz):
1212 self.wait_event.clear()
1213 self.local_tail = self.tail.value
1214 if self.HasSpace(sz):
1216 self.wait_event.wait()
1218 def AddToBuffer(self, obj):
1219 d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
1221 nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
1223 self.WaitForSpace(sz)
1224 pos = self.local_head
1225 self.buffer[pos : pos + len(nd)] = nd
1226 self.buffer[pos + glb_nsz : pos + sz] = d
1227 self.local_head += sz
1229 def FetchBatch(self, batch_size):
1231 while batch_size > fetched:
1236 self.AddToBuffer(obj)
1239 self.fetched += fetched
1240 with self.fetch_count.get_lock():
1241 self.fetch_count.value += fetched
1242 self.head.value = self.local_head
1243 self.fetched_event.set()
1247 target = self.WaitForTarget()
1250 batch_size = min(glb_chunk_sz, target - self.fetched)
1251 self.FetchBatch(batch_size)
1252 self.fetching_done.value = True
1253 self.fetched_event.set()
1255 def SQLFetcherFn(*x):
1256 process = SQLFetcherProcess(*x)
1261 class SQLFetcher(QObject):
1263 done = Signal(object)
1265 def __init__(self, glb, sql, prep, process_data, parent=None):
1266 super(SQLFetcher, self).__init__(parent)
1267 self.process_data = process_data
1270 self.last_target = 0
1272 self.buffer_size = 16 * 1024 * 1024
1273 self.buffer = Array(c_char, self.buffer_size, lock=False)
1274 self.head = Value(c_longlong)
1275 self.tail = Value(c_longlong)
1277 self.fetch_count = Value(c_longlong)
1278 self.fetching_done = Value(c_bool)
1280 self.process_target = Value(c_longlong)
1281 self.wait_event = Event()
1282 self.fetched_event = Event()
1283 glb.AddInstanceToShutdownOnExit(self)
1284 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))
1285 self.process.start()
1286 self.thread = Thread(self.Thread)
1287 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1291 # Tell the thread and process to exit
1292 self.process_target.value = -1
1293 self.wait_event.set()
1295 self.fetching_done.value = True
1296 self.fetched_event.set()
1302 self.fetched_event.clear()
1303 fetch_count = self.fetch_count.value
1304 if fetch_count != self.last_count:
1306 if self.fetching_done.value:
1309 self.fetched_event.wait()
1310 count = fetch_count - self.last_count
1311 self.last_count = fetch_count
1312 self.fetched += count
1315 def Fetch(self, nr):
1317 # -1 inidcates there are no more
1319 result = self.fetched
1320 extra = result + nr - self.target
1322 self.target += extra
1323 # process_target < 0 indicates shutting down
1324 if self.process_target.value >= 0:
1325 self.process_target.value = self.target
1326 self.wait_event.set()
1329 def RemoveFromBuffer(self):
1330 pos = self.local_tail
1331 if len(self.buffer) - pos < glb_nsz:
1333 n = pickle.loads(self.buffer[pos : pos + glb_nsz])
1336 n = pickle.loads(self.buffer[0 : glb_nsz])
1338 obj = pickle.loads(self.buffer[pos : pos + n])
1339 self.local_tail = pos + n
1342 def ProcessData(self, count):
1343 for i in xrange(count):
1344 obj = self.RemoveFromBuffer()
1345 self.process_data(obj)
1346 self.tail.value = self.local_tail
1347 self.wait_event.set()
1348 self.done.emit(count)
1350 # Fetch more records bar
1352 class FetchMoreRecordsBar():
1354 def __init__(self, model, parent):
1357 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1358 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1360 self.fetch_count = QSpinBox()
1361 self.fetch_count.setRange(1, 1000000)
1362 self.fetch_count.setValue(10)
1363 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1365 self.fetch = QPushButton("Go!")
1366 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1367 self.fetch.released.connect(self.FetchMoreRecords)
1369 self.progress = QProgressBar()
1370 self.progress.setRange(0, 100)
1371 self.progress.hide()
1373 self.done_label = QLabel("All records fetched")
1374 self.done_label.hide()
1376 self.spacer = QLabel("")
1378 self.close_button = QToolButton()
1379 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1380 self.close_button.released.connect(self.Deactivate)
1382 self.hbox = QHBoxLayout()
1383 self.hbox.setContentsMargins(0, 0, 0, 0)
1385 self.hbox.addWidget(self.label)
1386 self.hbox.addWidget(self.fetch_count)
1387 self.hbox.addWidget(self.fetch)
1388 self.hbox.addWidget(self.spacer)
1389 self.hbox.addWidget(self.progress)
1390 self.hbox.addWidget(self.done_label)
1391 self.hbox.addWidget(self.close_button)
1393 self.bar = QWidget()
1394 self.bar.setLayout(self.hbox);
1397 self.in_progress = False
1398 self.model.progress.connect(self.Progress)
1402 if not model.HasMoreRecords():
1410 self.fetch.setFocus()
1412 def Deactivate(self):
1415 def Enable(self, enable):
1416 self.fetch.setEnabled(enable)
1417 self.fetch_count.setEnabled(enable)
1423 self.progress.show()
1426 self.in_progress = False
1428 self.progress.hide()
1433 return self.fetch_count.value() * glb_chunk_sz
1439 self.fetch_count.hide()
1442 self.done_label.show()
1444 def Progress(self, count):
1445 if self.in_progress:
1447 percent = ((count - self.start) * 100) / self.Target()
1451 self.progress.setValue(percent)
1453 # Count value of zero means no more records
1456 def FetchMoreRecords(self):
1459 self.progress.setValue(0)
1461 self.in_progress = True
1462 self.start = self.model.FetchMoreRecords(self.Target())
1464 # Brance data model level two item
1466 class BranchLevelTwoItem():
1468 def __init__(self, row, col, text, parent_item):
1470 self.parent_item = parent_item
1471 self.data = [""] * (col + 1)
1472 self.data[col] = text
1475 def getParentItem(self):
1476 return self.parent_item
1481 def childCount(self):
1484 def hasChildren(self):
1487 def getData(self, column):
1488 return self.data[column]
1490 # Brance data model level one item
1492 class BranchLevelOneItem():
1494 def __init__(self, glb, row, data, parent_item):
1497 self.parent_item = parent_item
1498 self.child_count = 0
1499 self.child_items = []
1500 self.data = data[1:]
1503 self.query_done = False
1504 self.br_col = len(self.data) - 1
1506 def getChildItem(self, row):
1507 return self.child_items[row]
1509 def getParentItem(self):
1510 return self.parent_item
1516 self.query_done = True
1518 if not self.glb.have_disassembler:
1521 query = QSqlQuery(self.glb.db)
1523 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1525 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1526 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1527 " WHERE samples.id = " + str(self.dbid))
1528 if not query.next():
1530 cpu = query.value(0)
1531 dso = query.value(1)
1532 sym = query.value(2)
1533 if dso == 0 or sym == 0:
1535 off = query.value(3)
1536 short_name = query.value(4)
1537 long_name = query.value(5)
1538 build_id = query.value(6)
1539 sym_start = query.value(7)
1542 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1544 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1545 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1546 " ORDER BY samples.id"
1548 if not query.next():
1550 if query.value(0) != dso:
1551 # Cannot disassemble from one dso to another
1553 bsym = query.value(1)
1554 boff = query.value(2)
1555 bsym_start = query.value(3)
1558 tot = bsym_start + boff + 1 - sym_start - off
1559 if tot <= 0 or tot > 16384:
1562 inst = self.glb.disassembler.Instruction()
1563 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1566 mode = 0 if Is64Bit(f) else 1
1567 self.glb.disassembler.SetMode(inst, mode)
1570 buf = create_string_buffer(tot + 16)
1571 f.seek(sym_start + off)
1572 buf.value = f.read(buf_sz)
1573 buf_ptr = addressof(buf)
1576 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1578 byte_str = tohex(ip).rjust(16)
1579 for k in xrange(cnt):
1580 byte_str += " %02x" % ord(buf[i])
1585 self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
1586 self.child_count += 1
1594 def childCount(self):
1595 if not self.query_done:
1597 if not self.child_count:
1599 return self.child_count
1601 def hasChildren(self):
1602 if not self.query_done:
1604 return self.child_count > 0
1606 def getData(self, column):
1607 return self.data[column]
1609 # Brance data model root item
1611 class BranchRootItem():
1614 self.child_count = 0
1615 self.child_items = []
1618 def getChildItem(self, row):
1619 return self.child_items[row]
1621 def getParentItem(self):
1627 def childCount(self):
1628 return self.child_count
1630 def hasChildren(self):
1631 return self.child_count > 0
1633 def getData(self, column):
1636 # Calculate instructions per cycle
1638 def CalcIPC(cyc_cnt, insn_cnt):
1639 if cyc_cnt and insn_cnt:
1640 ipc = Decimal(float(insn_cnt) / cyc_cnt)
1641 ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
1646 # Branch data preparation
1648 def BranchDataPrepBr(query, data):
1649 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1650 " (" + dsoname(query.value(11)) + ")" + " -> " +
1651 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1652 " (" + dsoname(query.value(15)) + ")")
1654 def BranchDataPrepIPC(query, data):
1655 insn_cnt = query.value(16)
1656 cyc_cnt = query.value(17)
1657 ipc = CalcIPC(cyc_cnt, insn_cnt)
1658 data.append(insn_cnt)
1659 data.append(cyc_cnt)
1662 def BranchDataPrep(query):
1664 for i in xrange(0, 8):
1665 data.append(query.value(i))
1666 BranchDataPrepBr(query, data)
1669 def BranchDataPrepWA(query):
1671 data.append(query.value(0))
1672 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1673 data.append("{:>19}".format(query.value(1)))
1674 for i in xrange(2, 8):
1675 data.append(query.value(i))
1676 BranchDataPrepBr(query, data)
1679 def BranchDataWithIPCPrep(query):
1681 for i in xrange(0, 8):
1682 data.append(query.value(i))
1683 BranchDataPrepIPC(query, data)
1684 BranchDataPrepBr(query, data)
1687 def BranchDataWithIPCPrepWA(query):
1689 data.append(query.value(0))
1690 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1691 data.append("{:>19}".format(query.value(1)))
1692 for i in xrange(2, 8):
1693 data.append(query.value(i))
1694 BranchDataPrepIPC(query, data)
1695 BranchDataPrepBr(query, data)
1700 class BranchModel(TreeModel):
1702 progress = Signal(object)
1704 def __init__(self, glb, event_id, where_clause, parent=None):
1705 super(BranchModel, self).__init__(glb, None, parent)
1706 self.event_id = event_id
1709 self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
1711 select_ipc = ", insn_count, cyc_count"
1712 prep_fn = BranchDataWithIPCPrep
1713 prep_wa_fn = BranchDataWithIPCPrepWA
1716 prep_fn = BranchDataPrep
1717 prep_wa_fn = BranchDataPrepWA
1718 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1719 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1720 " ip, symbols.name, sym_offset, dsos.short_name,"
1721 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1724 " INNER JOIN comms ON comm_id = comms.id"
1725 " INNER JOIN threads ON thread_id = threads.id"
1726 " INNER JOIN branch_types ON branch_type = branch_types.id"
1727 " INNER JOIN symbols ON symbol_id = symbols.id"
1728 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1729 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1730 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1731 " WHERE samples.id > $$last_id$$" + where_clause +
1732 " AND evsel_id = " + str(self.event_id) +
1733 " ORDER BY samples.id"
1734 " LIMIT " + str(glb_chunk_sz))
1735 if pyside_version_1 and sys.version_info[0] == 3:
1739 self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
1740 self.fetcher.done.connect(self.Update)
1741 self.fetcher.Fetch(glb_chunk_sz)
1744 return BranchRootItem()
1746 def columnCount(self, parent=None):
1752 def columnHeader(self, column):
1754 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
1756 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1758 def columnFont(self, column):
1763 if column != br_col:
1765 return QFont("Monospace")
1767 def DisplayData(self, item, index):
1769 self.FetchIfNeeded(item.row)
1770 return item.getData(index.column())
1772 def AddSample(self, data):
1773 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1774 self.root.child_items.append(child)
1777 def Update(self, fetched):
1780 self.progress.emit(0)
1781 child_count = self.root.child_count
1782 count = self.populated - child_count
1784 parent = QModelIndex()
1785 self.beginInsertRows(parent, child_count, child_count + count - 1)
1786 self.insertRows(child_count, count, parent)
1787 self.root.child_count += count
1788 self.endInsertRows()
1789 self.progress.emit(self.root.child_count)
1791 def FetchMoreRecords(self, count):
1792 current = self.root.child_count
1794 self.fetcher.Fetch(count)
1796 self.progress.emit(0)
1799 def HasMoreRecords(self):
1806 def __init__(self, name = "", where_clause = "", limit = ""):
1808 self.where_clause = where_clause
1812 return str(self.where_clause + ";" + self.limit)
1816 class BranchWindow(QMdiSubWindow):
1818 def __init__(self, glb, event_id, report_vars, parent=None):
1819 super(BranchWindow, self).__init__(parent)
1821 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
1823 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1825 self.view = QTreeView()
1826 self.view.setUniformRowHeights(True)
1827 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1828 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1829 self.view.setModel(self.model)
1831 self.ResizeColumnsToContents()
1833 self.context_menu = TreeContextMenu(self.view)
1835 self.find_bar = FindBar(self, self, True)
1837 self.finder = ChildDataItemFinder(self.model.root)
1839 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1841 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1843 self.setWidget(self.vbox.Widget())
1845 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1847 def ResizeColumnToContents(self, column, n):
1848 # Using the view's resizeColumnToContents() here is extrememly slow
1849 # so implement a crude alternative
1850 mm = "MM" if column else "MMMM"
1851 font = self.view.font()
1852 metrics = QFontMetrics(font)
1854 for row in xrange(n):
1855 val = self.model.root.child_items[row].data[column]
1856 len = metrics.width(str(val) + mm)
1857 max = len if len > max else max
1858 val = self.model.columnHeader(column)
1859 len = metrics.width(str(val) + mm)
1860 max = len if len > max else max
1861 self.view.setColumnWidth(column, max)
1863 def ResizeColumnsToContents(self):
1864 n = min(self.model.root.child_count, 100)
1866 # No data yet, so connect a signal to notify when there is
1867 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1869 columns = self.model.columnCount()
1870 for i in xrange(columns):
1871 self.ResizeColumnToContents(i, n)
1873 def UpdateColumnWidths(self, *x):
1874 # This only needs to be done once, so disconnect the signal now
1875 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1876 self.ResizeColumnsToContents()
1878 def Find(self, value, direction, pattern, context):
1879 self.view.setFocus()
1880 self.find_bar.Busy()
1881 self.finder.Find(value, direction, pattern, context, self.FindDone)
1883 def FindDone(self, row):
1884 self.find_bar.Idle()
1886 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1888 self.find_bar.NotFound()
1890 # Line edit data item
1892 class LineEditDataItem(object):
1894 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1897 self.placeholder_text = placeholder_text
1898 self.parent = parent
1901 self.value = default
1903 self.widget = QLineEdit(default)
1904 self.widget.editingFinished.connect(self.Validate)
1905 self.widget.textChanged.connect(self.Invalidate)
1908 self.validated = True
1910 if placeholder_text:
1911 self.widget.setPlaceholderText(placeholder_text)
1913 def TurnTextRed(self):
1915 palette = QPalette()
1916 palette.setColor(QPalette.Text,Qt.red)
1917 self.widget.setPalette(palette)
1920 def TurnTextNormal(self):
1922 palette = QPalette()
1923 self.widget.setPalette(palette)
1926 def InvalidValue(self, value):
1929 self.error = self.label + " invalid value '" + value + "'"
1930 self.parent.ShowMessage(self.error)
1932 def Invalidate(self):
1933 self.validated = False
1935 def DoValidate(self, input_string):
1936 self.value = input_string.strip()
1939 self.validated = True
1941 self.TurnTextNormal()
1942 self.parent.ClearMessage()
1943 input_string = self.widget.text()
1944 if not len(input_string.strip()):
1947 self.DoValidate(input_string)
1950 if not self.validated:
1953 self.parent.ShowMessage(self.error)
1957 def IsNumber(self, value):
1962 return str(x) == value
1964 # Non-negative integer ranges dialog data item
1966 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1968 def __init__(self, glb, label, placeholder_text, column_name, parent):
1969 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1971 self.column_name = column_name
1973 def DoValidate(self, input_string):
1976 for value in [x.strip() for x in input_string.split(",")]:
1978 vrange = value.split("-")
1979 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1980 return self.InvalidValue(value)
1981 ranges.append(vrange)
1983 if not self.IsNumber(value):
1984 return self.InvalidValue(value)
1985 singles.append(value)
1986 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1988 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1989 self.value = " OR ".join(ranges)
1991 # Positive integer dialog data item
1993 class PositiveIntegerDataItem(LineEditDataItem):
1995 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1996 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1998 def DoValidate(self, input_string):
1999 if not self.IsNumber(input_string.strip()):
2000 return self.InvalidValue(input_string)
2001 value = int(input_string.strip())
2003 return self.InvalidValue(input_string)
2004 self.value = str(value)
2006 # Dialog data item converted and validated using a SQL table
2008 class SQLTableDataItem(LineEditDataItem):
2010 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
2011 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
2013 self.table_name = table_name
2014 self.match_column = match_column
2015 self.column_name1 = column_name1
2016 self.column_name2 = column_name2
2018 def ValueToIds(self, value):
2020 query = QSqlQuery(self.glb.db)
2021 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
2022 ret = query.exec_(stmt)
2025 ids.append(str(query.value(0)))
2028 def DoValidate(self, input_string):
2030 for value in [x.strip() for x in input_string.split(",")]:
2031 ids = self.ValueToIds(value)
2035 return self.InvalidValue(value)
2036 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
2037 if self.column_name2:
2038 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
2040 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
2042 class SampleTimeRangesDataItem(LineEditDataItem):
2044 def __init__(self, glb, label, placeholder_text, column_name, parent):
2045 self.column_name = column_name
2049 self.last_time = 2 ** 64
2051 query = QSqlQuery(glb.db)
2052 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
2054 self.last_id = int(query.value(0))
2055 self.last_time = int(query.value(1))
2056 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
2058 self.first_time = int(query.value(0))
2059 if placeholder_text:
2060 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
2062 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
2064 def IdBetween(self, query, lower_id, higher_id, order):
2065 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
2067 return True, int(query.value(0))
2071 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
2072 query = QSqlQuery(self.glb.db)
2074 next_id = int((lower_id + higher_id) / 2)
2075 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2076 if not query.next():
2077 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
2079 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
2081 return str(higher_id)
2083 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2084 next_time = int(query.value(0))
2086 if target_time > next_time:
2090 if higher_id <= lower_id + 1:
2091 return str(higher_id)
2093 if target_time >= next_time:
2097 if higher_id <= lower_id + 1:
2098 return str(lower_id)
2100 def ConvertRelativeTime(self, val):
2105 elif suffix == "us":
2107 elif suffix == "ns":
2111 val = val[:-2].strip()
2112 if not self.IsNumber(val):
2114 val = int(val) * mult
2116 val += self.first_time
2118 val += self.last_time
2121 def ConvertTimeRange(self, vrange):
2123 vrange[0] = str(self.first_time)
2125 vrange[1] = str(self.last_time)
2126 vrange[0] = self.ConvertRelativeTime(vrange[0])
2127 vrange[1] = self.ConvertRelativeTime(vrange[1])
2128 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
2130 beg_range = max(int(vrange[0]), self.first_time)
2131 end_range = min(int(vrange[1]), self.last_time)
2132 if beg_range > self.last_time or end_range < self.first_time:
2134 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
2135 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
2138 def AddTimeRange(self, value, ranges):
2139 n = value.count("-")
2143 if value.split("-")[1].strip() == "":
2149 pos = findnth(value, "-", n)
2150 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
2151 if self.ConvertTimeRange(vrange):
2152 ranges.append(vrange)
2156 def DoValidate(self, input_string):
2158 for value in [x.strip() for x in input_string.split(",")]:
2159 if not self.AddTimeRange(value, ranges):
2160 return self.InvalidValue(value)
2161 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
2162 self.value = " OR ".join(ranges)
2164 # Report Dialog Base
2166 class ReportDialogBase(QDialog):
2168 def __init__(self, glb, title, items, partial, parent=None):
2169 super(ReportDialogBase, self).__init__(parent)
2173 self.report_vars = ReportVars()
2175 self.setWindowTitle(title)
2176 self.setMinimumWidth(600)
2178 self.data_items = [x(glb, self) for x in items]
2180 self.partial = partial
2182 self.grid = QGridLayout()
2184 for row in xrange(len(self.data_items)):
2185 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2186 self.grid.addWidget(self.data_items[row].widget, row, 1)
2188 self.status = QLabel()
2190 self.ok_button = QPushButton("Ok", self)
2191 self.ok_button.setDefault(True)
2192 self.ok_button.released.connect(self.Ok)
2193 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2195 self.cancel_button = QPushButton("Cancel", self)
2196 self.cancel_button.released.connect(self.reject)
2197 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2199 self.hbox = QHBoxLayout()
2200 #self.hbox.addStretch()
2201 self.hbox.addWidget(self.status)
2202 self.hbox.addWidget(self.ok_button)
2203 self.hbox.addWidget(self.cancel_button)
2205 self.vbox = QVBoxLayout()
2206 self.vbox.addLayout(self.grid)
2207 self.vbox.addLayout(self.hbox)
2209 self.setLayout(self.vbox);
2212 vars = self.report_vars
2213 for d in self.data_items:
2214 if d.id == "REPORTNAME":
2217 self.ShowMessage("Report name is required")
2219 for d in self.data_items:
2222 for d in self.data_items[1:]:
2224 vars.limit = d.value
2226 if len(vars.where_clause):
2227 vars.where_clause += " AND "
2228 vars.where_clause += d.value
2229 if len(vars.where_clause):
2231 vars.where_clause = " AND ( " + vars.where_clause + " ) "
2233 vars.where_clause = " WHERE " + vars.where_clause + " "
2236 def ShowMessage(self, msg):
2237 self.status.setText("<font color=#FF0000>" + msg)
2239 def ClearMessage(self):
2240 self.status.setText("")
2242 # Selected branch report creation dialog
2244 class SelectedBranchDialog(ReportDialogBase):
2246 def __init__(self, glb, parent=None):
2247 title = "Selected Branches"
2248 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2249 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2250 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2251 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2252 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2253 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2254 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2255 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2256 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2257 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2261 def GetEventList(db):
2263 query = QSqlQuery(db)
2264 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2266 events.append(query.value(0))
2269 # Is a table selectable
2271 def IsSelectable(db, table, sql = "", columns = "*"):
2272 query = QSqlQuery(db)
2274 QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
2279 # SQL table data model item
2281 class SQLTableItem():
2283 def __init__(self, row, data):
2287 def getData(self, column):
2288 return self.data[column]
2290 # SQL table data model
2292 class SQLTableModel(TableModel):
2294 progress = Signal(object)
2296 def __init__(self, glb, sql, column_headers, parent=None):
2297 super(SQLTableModel, self).__init__(parent)
2301 self.column_headers = column_headers
2302 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
2303 self.fetcher.done.connect(self.Update)
2304 self.fetcher.Fetch(glb_chunk_sz)
2306 def DisplayData(self, item, index):
2307 self.FetchIfNeeded(item.row)
2308 return item.getData(index.column())
2310 def AddSample(self, data):
2311 child = SQLTableItem(self.populated, data)
2312 self.child_items.append(child)
2315 def Update(self, fetched):
2318 self.progress.emit(0)
2319 child_count = self.child_count
2320 count = self.populated - child_count
2322 parent = QModelIndex()
2323 self.beginInsertRows(parent, child_count, child_count + count - 1)
2324 self.insertRows(child_count, count, parent)
2325 self.child_count += count
2326 self.endInsertRows()
2327 self.progress.emit(self.child_count)
2329 def FetchMoreRecords(self, count):
2330 current = self.child_count
2332 self.fetcher.Fetch(count)
2334 self.progress.emit(0)
2337 def HasMoreRecords(self):
2340 def columnCount(self, parent=None):
2341 return len(self.column_headers)
2343 def columnHeader(self, column):
2344 return self.column_headers[column]
2346 def SQLTableDataPrep(self, query, count):
2348 for i in xrange(count):
2349 data.append(query.value(i))
2352 # SQL automatic table data model
2354 class SQLAutoTableModel(SQLTableModel):
2356 def __init__(self, glb, table_name, parent=None):
2357 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2358 if table_name == "comm_threads_view":
2359 # For now, comm_threads_view has no id column
2360 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2362 query = QSqlQuery(glb.db)
2363 if glb.dbref.is_sqlite3:
2364 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2366 column_headers.append(query.value(1))
2367 if table_name == "sqlite_master":
2368 sql = "SELECT * FROM " + table_name
2370 if table_name[:19] == "information_schema.":
2371 sql = "SELECT * FROM " + table_name
2372 select_table_name = table_name[19:]
2373 schema = "information_schema"
2375 select_table_name = table_name
2377 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2379 column_headers.append(query.value(0))
2380 if pyside_version_1 and sys.version_info[0] == 3:
2381 if table_name == "samples_view":
2382 self.SQLTableDataPrep = self.samples_view_DataPrep
2383 if table_name == "samples":
2384 self.SQLTableDataPrep = self.samples_DataPrep
2385 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2387 def samples_view_DataPrep(self, query, count):
2389 data.append(query.value(0))
2390 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2391 data.append("{:>19}".format(query.value(1)))
2392 for i in xrange(2, count):
2393 data.append(query.value(i))
2396 def samples_DataPrep(self, query, count):
2399 data.append(query.value(i))
2400 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2401 data.append("{:>19}".format(query.value(9)))
2402 for i in xrange(10, count):
2403 data.append(query.value(i))
2406 # Base class for custom ResizeColumnsToContents
2408 class ResizeColumnsToContentsBase(QObject):
2410 def __init__(self, parent=None):
2411 super(ResizeColumnsToContentsBase, self).__init__(parent)
2413 def ResizeColumnToContents(self, column, n):
2414 # Using the view's resizeColumnToContents() here is extrememly slow
2415 # so implement a crude alternative
2416 font = self.view.font()
2417 metrics = QFontMetrics(font)
2419 for row in xrange(n):
2420 val = self.data_model.child_items[row].data[column]
2421 len = metrics.width(str(val) + "MM")
2422 max = len if len > max else max
2423 val = self.data_model.columnHeader(column)
2424 len = metrics.width(str(val) + "MM")
2425 max = len if len > max else max
2426 self.view.setColumnWidth(column, max)
2428 def ResizeColumnsToContents(self):
2429 n = min(self.data_model.child_count, 100)
2431 # No data yet, so connect a signal to notify when there is
2432 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2434 columns = self.data_model.columnCount()
2435 for i in xrange(columns):
2436 self.ResizeColumnToContents(i, n)
2438 def UpdateColumnWidths(self, *x):
2439 # This only needs to be done once, so disconnect the signal now
2440 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2441 self.ResizeColumnsToContents()
2443 # Convert value to CSV
2447 val = val.replace('"', '""')
2448 if "," in val or '"' in val:
2449 val = '"' + val + '"'
2452 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2456 def RowColumnKey(a):
2457 return a.row() * glb_max_cols + a.column()
2459 # Copy selected table cells to clipboard
2461 def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2462 indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2463 idx_cnt = len(indexes)
2468 min_row = indexes[0].row()
2469 max_row = indexes[0].row()
2470 min_col = indexes[0].column()
2471 max_col = indexes[0].column()
2473 min_row = min(min_row, i.row())
2474 max_row = max(max_row, i.row())
2475 min_col = min(min_col, i.column())
2476 max_col = max(max_col, i.column())
2477 if max_col > glb_max_cols:
2478 raise RuntimeError("glb_max_cols is too low")
2479 max_width = [0] * (1 + max_col - min_col)
2481 c = i.column() - min_col
2482 max_width[c] = max(max_width[c], len(str(i.data())))
2487 model = indexes[0].model()
2488 for col in range(min_col, max_col + 1):
2489 val = model.headerData(col, Qt.Horizontal)
2491 text += sep + ToCSValue(val)
2495 max_width[c] = max(max_width[c], len(val))
2496 width = max_width[c]
2497 align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2498 if align & Qt.AlignRight:
2499 val = val.rjust(width)
2500 text += pad + sep + val
2501 pad = " " * (width - len(val))
2508 if i.row() > last_row:
2514 text += sep + ToCSValue(str(i.data()))
2517 width = max_width[i.column() - min_col]
2518 if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2519 val = str(i.data()).rjust(width)
2522 text += pad + sep + val
2523 pad = " " * (width - len(val))
2525 QApplication.clipboard().setText(text)
2527 def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2528 indexes = view.selectedIndexes()
2529 if not len(indexes):
2532 selection = view.selectionModel()
2536 above = view.indexAbove(i)
2537 if not selection.isSelected(above):
2542 raise RuntimeError("CopyTreeCellsToClipboard internal error")
2544 model = first.model()
2546 col_cnt = model.columnCount(first)
2547 max_width = [0] * col_cnt
2550 indent_str = " " * indent_sz
2552 expanded_mark_sz = 2
2553 if sys.version_info[0] == 3:
2554 expanded_mark = "\u25BC "
2555 not_expanded_mark = "\u25B6 "
2557 expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2558 not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2566 for c in range(col_cnt):
2567 i = pos.sibling(row, c)
2569 n = len(str(i.data()))
2571 n = len(str(i.data()).strip())
2572 n += (i.internalPointer().level - 1) * indent_sz
2573 n += expanded_mark_sz
2574 max_width[c] = max(max_width[c], n)
2575 pos = view.indexBelow(pos)
2576 if not selection.isSelected(pos):
2583 for c in range(col_cnt):
2584 val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2586 text += sep + ToCSValue(val)
2589 max_width[c] = max(max_width[c], len(val))
2590 width = max_width[c]
2591 align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2592 if align & Qt.AlignRight:
2593 val = val.rjust(width)
2594 text += pad + sep + val
2595 pad = " " * (width - len(val))
2604 for c in range(col_cnt):
2605 i = pos.sibling(row, c)
2608 if model.hasChildren(i):
2609 if view.isExpanded(i):
2610 mark = expanded_mark
2612 mark = not_expanded_mark
2615 val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2617 text += sep + ToCSValue(val)
2620 width = max_width[c]
2621 if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2622 val = val.rjust(width)
2623 text += pad + sep + val
2624 pad = " " * (width - len(val))
2626 pos = view.indexBelow(pos)
2627 if not selection.isSelected(pos):
2629 text = text.rstrip() + "\n"
2633 QApplication.clipboard().setText(text)
2635 def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2636 view.CopyCellsToClipboard(view, as_csv, with_hdr)
2638 def CopyCellsToClipboardHdr(view):
2639 CopyCellsToClipboard(view, False, True)
2641 def CopyCellsToClipboardCSV(view):
2642 CopyCellsToClipboard(view, True, True)
2646 class ContextMenu(object):
2648 def __init__(self, view):
2650 self.view.setContextMenuPolicy(Qt.CustomContextMenu)
2651 self.view.customContextMenuRequested.connect(self.ShowContextMenu)
2653 def ShowContextMenu(self, pos):
2654 menu = QMenu(self.view)
2655 self.AddActions(menu)
2656 menu.exec_(self.view.mapToGlobal(pos))
2658 def AddCopy(self, menu):
2659 menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
2660 menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
2662 def AddActions(self, menu):
2665 class TreeContextMenu(ContextMenu):
2667 def __init__(self, view):
2668 super(TreeContextMenu, self).__init__(view)
2670 def AddActions(self, menu):
2671 i = self.view.currentIndex()
2672 text = str(i.data()).strip()
2674 menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
2679 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2681 def __init__(self, glb, table_name, parent=None):
2682 super(TableWindow, self).__init__(parent)
2684 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2686 self.model = QSortFilterProxyModel()
2687 self.model.setSourceModel(self.data_model)
2689 self.view = QTableView()
2690 self.view.setModel(self.model)
2691 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2692 self.view.verticalHeader().setVisible(False)
2693 self.view.sortByColumn(-1, Qt.AscendingOrder)
2694 self.view.setSortingEnabled(True)
2695 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2696 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2698 self.ResizeColumnsToContents()
2700 self.context_menu = ContextMenu(self.view)
2702 self.find_bar = FindBar(self, self, True)
2704 self.finder = ChildDataItemFinder(self.data_model)
2706 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2708 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2710 self.setWidget(self.vbox.Widget())
2712 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2714 def Find(self, value, direction, pattern, context):
2715 self.view.setFocus()
2716 self.find_bar.Busy()
2717 self.finder.Find(value, direction, pattern, context, self.FindDone)
2719 def FindDone(self, row):
2720 self.find_bar.Idle()
2722 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2724 self.find_bar.NotFound()
2728 def GetTableList(glb):
2730 query = QSqlQuery(glb.db)
2731 if glb.dbref.is_sqlite3:
2732 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2734 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2736 tables.append(query.value(0))
2737 if glb.dbref.is_sqlite3:
2738 tables.append("sqlite_master")
2740 tables.append("information_schema.tables")
2741 tables.append("information_schema.views")
2742 tables.append("information_schema.columns")
2745 # Top Calls data model
2747 class TopCallsModel(SQLTableModel):
2749 def __init__(self, glb, report_vars, parent=None):
2751 if not glb.dbref.is_sqlite3:
2754 if len(report_vars.limit):
2755 limit = " LIMIT " + report_vars.limit
2756 sql = ("SELECT comm, pid, tid, name,"
2758 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2761 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2763 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2764 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2765 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2769 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2770 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2771 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2772 " INNER JOIN comms ON calls.comm_id = comms.id"
2773 " INNER JOIN threads ON calls.thread_id = threads.id" +
2774 report_vars.where_clause +
2775 " ORDER BY elapsed_time DESC" +
2778 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2779 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2780 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2782 def columnAlignment(self, column):
2783 return self.alignment[column]
2785 # Top Calls report creation dialog
2787 class TopCallsDialog(ReportDialogBase):
2789 def __init__(self, glb, parent=None):
2790 title = "Top Calls by Elapsed Time"
2791 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2792 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2793 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2794 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2795 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2796 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2797 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2798 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2799 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2803 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2805 def __init__(self, glb, report_vars, parent=None):
2806 super(TopCallsWindow, self).__init__(parent)
2808 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2809 self.model = self.data_model
2811 self.view = QTableView()
2812 self.view.setModel(self.model)
2813 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2814 self.view.verticalHeader().setVisible(False)
2815 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2816 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2818 self.context_menu = ContextMenu(self.view)
2820 self.ResizeColumnsToContents()
2822 self.find_bar = FindBar(self, self, True)
2824 self.finder = ChildDataItemFinder(self.model)
2826 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2828 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2830 self.setWidget(self.vbox.Widget())
2832 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2834 def Find(self, value, direction, pattern, context):
2835 self.view.setFocus()
2836 self.find_bar.Busy()
2837 self.finder.Find(value, direction, pattern, context, self.FindDone)
2839 def FindDone(self, row):
2840 self.find_bar.Idle()
2842 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2844 self.find_bar.NotFound()
2848 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2849 action = QAction(label, parent)
2850 if shortcut != None:
2851 action.setShortcuts(shortcut)
2852 action.setStatusTip(tip)
2853 action.triggered.connect(callback)
2856 # Typical application actions
2858 def CreateExitAction(app, parent=None):
2859 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2861 # Typical MDI actions
2863 def CreateCloseActiveWindowAction(mdi_area):
2864 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2866 def CreateCloseAllWindowsAction(mdi_area):
2867 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2869 def CreateTileWindowsAction(mdi_area):
2870 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2872 def CreateCascadeWindowsAction(mdi_area):
2873 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2875 def CreateNextWindowAction(mdi_area):
2876 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2878 def CreatePreviousWindowAction(mdi_area):
2879 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2881 # Typical MDI window menu
2885 def __init__(self, mdi_area, menu):
2886 self.mdi_area = mdi_area
2887 self.window_menu = menu.addMenu("&Windows")
2888 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2889 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2890 self.tile_windows = CreateTileWindowsAction(mdi_area)
2891 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2892 self.next_window = CreateNextWindowAction(mdi_area)
2893 self.previous_window = CreatePreviousWindowAction(mdi_area)
2894 self.window_menu.aboutToShow.connect(self.Update)
2897 self.window_menu.clear()
2898 sub_window_count = len(self.mdi_area.subWindowList())
2899 have_sub_windows = sub_window_count != 0
2900 self.close_active_window.setEnabled(have_sub_windows)
2901 self.close_all_windows.setEnabled(have_sub_windows)
2902 self.tile_windows.setEnabled(have_sub_windows)
2903 self.cascade_windows.setEnabled(have_sub_windows)
2904 self.next_window.setEnabled(have_sub_windows)
2905 self.previous_window.setEnabled(have_sub_windows)
2906 self.window_menu.addAction(self.close_active_window)
2907 self.window_menu.addAction(self.close_all_windows)
2908 self.window_menu.addSeparator()
2909 self.window_menu.addAction(self.tile_windows)
2910 self.window_menu.addAction(self.cascade_windows)
2911 self.window_menu.addSeparator()
2912 self.window_menu.addAction(self.next_window)
2913 self.window_menu.addAction(self.previous_window)
2914 if sub_window_count == 0:
2916 self.window_menu.addSeparator()
2918 for sub_window in self.mdi_area.subWindowList():
2919 label = str(nr) + " " + sub_window.name
2922 action = self.window_menu.addAction(label)
2923 action.setCheckable(True)
2924 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2925 action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
2926 self.window_menu.addAction(action)
2929 def setActiveSubWindow(self, nr):
2930 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2945 <p class=c1><a href=#reports>1. Reports</a></p>
2946 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2947 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2948 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
2949 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2950 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2951 <p class=c1><a href=#tables>2. Tables</a></p>
2952 <h1 id=reports>1. Reports</h1>
2953 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2954 The result is a GUI window with a tree representing a context-sensitive
2955 call-graph. Expanding a couple of levels of the tree and adjusting column
2956 widths to suit will display something like:
2958 Call Graph: pt_example
2959 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2962 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2963 |- unknown unknown 1 13198 0.1 1 0.0
2964 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2965 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2966 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2967 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2968 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2969 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2970 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2971 v- main ls 1 8182043 99.6 180254 99.9
2973 <h3>Points to note:</h3>
2975 <li>The top level is a command name (comm)</li>
2976 <li>The next level is a thread (pid:tid)</li>
2977 <li>Subsequent levels are functions</li>
2978 <li>'Count' is the number of calls</li>
2979 <li>'Time' is the elapsed time until the function returns</li>
2980 <li>Percentages are relative to the level above</li>
2981 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2984 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2985 The pattern matching symbols are ? for any character and * for zero or more characters.
2986 <h2 id=calltree>1.2 Call Tree</h2>
2987 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2988 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2989 <h2 id=allbranches>1.3 All branches</h2>
2990 The All branches report displays all branches in chronological order.
2991 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2992 <h3>Disassembly</h3>
2993 Open a branch to display disassembly. This only works if:
2995 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2996 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2997 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2998 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2999 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
3001 <h4 id=xed>Intel XED Setup</h4>
3002 To use Intel XED, libxed.so must be present. To build and install libxed.so:
3004 git clone https://github.com/intelxed/mbuild.git mbuild
3005 git clone https://github.com/intelxed/xed
3008 sudo ./mfile.py --prefix=/usr/local install
3011 <h3>Instructions per Cycle (IPC)</h3>
3012 If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
3013 <p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
3014 Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
3015 In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
3016 since the previous displayed 'IPC'.
3018 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3019 Refer to Python documentation for the regular expression syntax.
3020 All columns are searched, but only currently fetched rows are searched.
3021 <h2 id=selectedbranches>1.4 Selected branches</h2>
3022 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
3023 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3024 <h3>1.4.1 Time ranges</h3>
3025 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
3026 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
3028 81073085947329-81073085958238 From 81073085947329 to 81073085958238
3029 100us-200us From 100us to 200us
3030 10ms- From 10ms to the end
3031 -100ns The first 100ns
3032 -10ms- The last 10ms
3034 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
3035 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
3036 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.
3037 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3038 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
3039 <h1 id=tables>2. Tables</h1>
3040 The Tables menu shows all tables and views in the database. Most tables have an associated view
3041 which displays the information in a more friendly way. Not all data for large tables is fetched
3042 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
3043 but that can be slow for large tables.
3044 <p>There are also tables of database meta-information.
3045 For SQLite3 databases, the sqlite_master table is included.
3046 For PostgreSQL databases, information_schema.tables/views/columns are included.
3048 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3049 Refer to Python documentation for the regular expression syntax.
3050 All columns are searched, but only currently fetched rows are searched.
3051 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
3052 will go to the next/previous result in id order, instead of display order.
3057 class HelpWindow(QMdiSubWindow):
3059 def __init__(self, glb, parent=None):
3060 super(HelpWindow, self).__init__(parent)
3062 self.text = QTextBrowser()
3063 self.text.setHtml(glb_help_text)
3064 self.text.setReadOnly(True)
3065 self.text.setOpenExternalLinks(True)
3067 self.setWidget(self.text)
3069 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
3071 # Main window that only displays the help text
3073 class HelpOnlyWindow(QMainWindow):
3075 def __init__(self, parent=None):
3076 super(HelpOnlyWindow, self).__init__(parent)
3078 self.setMinimumSize(200, 100)
3079 self.resize(800, 600)
3080 self.setWindowTitle("Exported SQL Viewer Help")
3081 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
3083 self.text = QTextBrowser()
3084 self.text.setHtml(glb_help_text)
3085 self.text.setReadOnly(True)
3086 self.text.setOpenExternalLinks(True)
3088 self.setCentralWidget(self.text)
3090 # PostqreSQL server version
3092 def PostqreSQLServerVersion(db):
3093 query = QSqlQuery(db)
3094 QueryExec(query, "SELECT VERSION()")
3096 v_str = query.value(0)
3097 v_list = v_str.strip().split(" ")
3098 if v_list[0] == "PostgreSQL" and v_list[2] == "on":
3105 def SQLiteVersion(db):
3106 query = QSqlQuery(db)
3107 QueryExec(query, "SELECT sqlite_version()")
3109 return query.value(0)
3114 class AboutDialog(QDialog):
3116 def __init__(self, glb, parent=None):
3117 super(AboutDialog, self).__init__(parent)
3119 self.setWindowTitle("About Exported SQL Viewer")
3120 self.setMinimumWidth(300)
3122 pyside_version = "1" if pyside_version_1 else "2"
3125 text += "Python version: " + sys.version.split(" ")[0] + "\n"
3126 text += "PySide version: " + pyside_version + "\n"
3127 text += "Qt version: " + qVersion() + "\n"
3128 if glb.dbref.is_sqlite3:
3129 text += "SQLite version: " + SQLiteVersion(glb.db) + "\n"
3131 text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
3134 self.text = QTextBrowser()
3135 self.text.setHtml(text)
3136 self.text.setReadOnly(True)
3137 self.text.setOpenExternalLinks(True)
3139 self.vbox = QVBoxLayout()
3140 self.vbox.addWidget(self.text)
3142 self.setLayout(self.vbox);
3146 def ResizeFont(widget, diff):
3147 font = widget.font()
3148 sz = font.pointSize()
3149 font.setPointSize(sz + diff)
3150 widget.setFont(font)
3152 def ShrinkFont(widget):
3153 ResizeFont(widget, -1)
3155 def EnlargeFont(widget):
3156 ResizeFont(widget, 1)
3158 # Unique name for sub-windows
3160 def NumberedWindowName(name, nr):
3162 name += " <" + str(nr) + ">"
3165 def UniqueSubWindowName(mdi_area, name):
3168 unique_name = NumberedWindowName(name, nr)
3170 for sub_window in mdi_area.subWindowList():
3171 if sub_window.name == unique_name:
3180 def AddSubWindow(mdi_area, sub_window, name):
3181 unique_name = UniqueSubWindowName(mdi_area, name)
3182 sub_window.setMinimumSize(200, 100)
3183 sub_window.resize(800, 600)
3184 sub_window.setWindowTitle(unique_name)
3185 sub_window.setAttribute(Qt.WA_DeleteOnClose)
3186 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
3187 sub_window.name = unique_name
3188 mdi_area.addSubWindow(sub_window)
3193 class MainWindow(QMainWindow):
3195 def __init__(self, glb, parent=None):
3196 super(MainWindow, self).__init__(parent)
3200 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3201 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3202 self.setMinimumSize(200, 100)
3204 self.mdi_area = QMdiArea()
3205 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3206 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3208 self.setCentralWidget(self.mdi_area)
3210 menu = self.menuBar()
3212 file_menu = menu.addMenu("&File")
3213 file_menu.addAction(CreateExitAction(glb.app, self))
3215 edit_menu = menu.addMenu("&Edit")
3216 edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
3217 edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3218 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
3219 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
3220 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
3221 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3223 reports_menu = menu.addMenu("&Reports")
3224 if IsSelectable(glb.db, "calls"):
3225 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
3227 if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3228 reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3230 self.EventMenu(GetEventList(glb.db), reports_menu)
3232 if IsSelectable(glb.db, "calls"):
3233 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3235 self.TableMenu(GetTableList(glb), menu)
3237 self.window_menu = WindowMenu(self.mdi_area, menu)
3239 help_menu = menu.addMenu("&Help")
3240 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3241 help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
3244 win = self.mdi_area.activeSubWindow()
3251 def CopyToClipboard(self):
3252 self.Try(CopyCellsToClipboardHdr)
3254 def CopyToClipboardCSV(self):
3255 self.Try(CopyCellsToClipboardCSV)
3258 win = self.mdi_area.activeSubWindow()
3261 win.find_bar.Activate()
3265 def FetchMoreRecords(self):
3266 win = self.mdi_area.activeSubWindow()
3269 win.fetch_bar.Activate()
3273 def ShrinkFont(self):
3274 self.Try(ShrinkFont)
3276 def EnlargeFont(self):
3277 self.Try(EnlargeFont)
3279 def EventMenu(self, events, reports_menu):
3281 for event in events:
3282 event = event.split(":")[0]
3283 if event == "branches":
3284 branches_events += 1
3286 for event in events:
3288 event = event.split(":")[0]
3289 if event == "branches":
3290 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3291 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
3292 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3293 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
3295 def TableMenu(self, tables, menu):
3296 table_menu = menu.addMenu("&Tables")
3297 for table in tables:
3298 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
3300 def NewCallGraph(self):
3301 CallGraphWindow(self.glb, self)
3303 def NewCallTree(self):
3304 CallTreeWindow(self.glb, self)
3306 def NewTopCalls(self):
3307 dialog = TopCallsDialog(self.glb, self)
3308 ret = dialog.exec_()
3310 TopCallsWindow(self.glb, dialog.report_vars, self)
3312 def NewBranchView(self, event_id):
3313 BranchWindow(self.glb, event_id, ReportVars(), self)
3315 def NewSelectedBranchView(self, event_id):
3316 dialog = SelectedBranchDialog(self.glb, self)
3317 ret = dialog.exec_()
3319 BranchWindow(self.glb, event_id, dialog.report_vars, self)
3321 def NewTableView(self, table_name):
3322 TableWindow(self.glb, table_name, self)
3325 HelpWindow(self.glb, self)
3328 dialog = AboutDialog(self.glb, self)
3333 class xed_state_t(Structure):
3340 class XEDInstruction():
3342 def __init__(self, libxed):
3343 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3344 xedd_t = c_byte * 512
3345 self.xedd = xedd_t()
3346 self.xedp = addressof(self.xedd)
3347 libxed.xed_decoded_inst_zero(self.xedp)
3348 self.state = xed_state_t()
3349 self.statep = addressof(self.state)
3350 # Buffer for disassembled instruction text
3351 self.buffer = create_string_buffer(256)
3352 self.bufferp = addressof(self.buffer)
3358 self.libxed = CDLL("libxed.so")
3362 self.libxed = CDLL("/usr/local/lib/libxed.so")
3364 self.xed_tables_init = self.libxed.xed_tables_init
3365 self.xed_tables_init.restype = None
3366 self.xed_tables_init.argtypes = []
3368 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3369 self.xed_decoded_inst_zero.restype = None
3370 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3372 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3373 self.xed_operand_values_set_mode.restype = None
3374 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3376 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3377 self.xed_decoded_inst_zero_keep_mode.restype = None
3378 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3380 self.xed_decode = self.libxed.xed_decode
3381 self.xed_decode.restype = c_int
3382 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3384 self.xed_format_context = self.libxed.xed_format_context
3385 self.xed_format_context.restype = c_uint
3386 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3388 self.xed_tables_init()
3390 def Instruction(self):
3391 return XEDInstruction(self)
3393 def SetMode(self, inst, mode):
3395 inst.state.mode = 4 # 32-bit
3396 inst.state.width = 4 # 4 bytes
3398 inst.state.mode = 1 # 64-bit
3399 inst.state.width = 8 # 8 bytes
3400 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3402 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3403 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3404 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3407 # Use AT&T mode (2), alternative is Intel (3)
3408 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3411 if sys.version_info[0] == 2:
3412 result = inst.buffer.value
3414 result = inst.buffer.value.decode()
3415 # Return instruction length and the disassembled instruction text
3416 # For now, assume the length is in byte 166
3417 return inst.xedd[166], result
3419 def TryOpen(file_name):
3421 return open(file_name, "rb")
3426 result = sizeof(c_void_p)
3433 if sys.version_info[0] == 2:
3434 eclass = ord(header[4])
3435 encoding = ord(header[5])
3436 version = ord(header[6])
3439 encoding = header[5]
3441 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3442 result = True if eclass == 2 else False
3449 def __init__(self, dbref, db, dbname):
3452 self.dbname = dbname
3453 self.home_dir = os.path.expanduser("~")
3454 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3455 if self.buildid_dir:
3456 self.buildid_dir += "/.build-id/"
3458 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3460 self.mainwindow = None
3461 self.instances_to_shutdown_on_exit = weakref.WeakSet()
3463 self.disassembler = LibXED()
3464 self.have_disassembler = True
3466 self.have_disassembler = False
3468 def FileFromBuildId(self, build_id):
3469 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3470 return TryOpen(file_name)
3472 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3473 # Assume current machine i.e. no support for virtualization
3474 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3475 file_name = os.getenv("PERF_KCORE")
3476 f = TryOpen(file_name) if file_name else None
3479 # For now, no special handling if long_name is /proc/kcore
3480 f = TryOpen(long_name)
3483 f = self.FileFromBuildId(build_id)
3488 def AddInstanceToShutdownOnExit(self, instance):
3489 self.instances_to_shutdown_on_exit.add(instance)
3491 # Shutdown any background processes or threads
3492 def ShutdownInstances(self):
3493 for x in self.instances_to_shutdown_on_exit:
3499 # Database reference
3503 def __init__(self, is_sqlite3, dbname):
3504 self.is_sqlite3 = is_sqlite3
3505 self.dbname = dbname
3507 def Open(self, connection_name):
3508 dbname = self.dbname
3510 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3512 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3513 opts = dbname.split()
3516 opt = opt.split("=")
3517 if opt[0] == "hostname":
3518 db.setHostName(opt[1])
3519 elif opt[0] == "port":
3520 db.setPort(int(opt[1]))
3521 elif opt[0] == "username":
3522 db.setUserName(opt[1])
3523 elif opt[0] == "password":
3524 db.setPassword(opt[1])
3525 elif opt[0] == "dbname":
3530 db.setDatabaseName(dbname)
3532 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3538 usage_str = "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
3539 " or: exported-sql-viewer.py --help-only"
3540 ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
3541 ap.add_argument("--pyside-version-1", action='store_true')
3542 ap.add_argument("dbname", nargs="?")
3543 ap.add_argument("--help-only", action='store_true')
3544 args = ap.parse_args()
3547 app = QApplication(sys.argv)
3548 mainwindow = HelpOnlyWindow()
3553 dbname = args.dbname
3556 print("Too few arguments")
3561 f = open(dbname, "rb")
3562 if f.read(15) == b'SQLite format 3':
3568 dbref = DBRef(is_sqlite3, dbname)
3569 db, dbname = dbref.Open("main")
3570 glb = Glb(dbref, db, dbname)
3571 app = QApplication(sys.argv)
3573 mainwindow = MainWindow(glb)
3574 glb.mainwindow = mainwindow
3577 glb.ShutdownInstances()
3581 if __name__ == "__main__":