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.setFocus()
405 def Deactivate(self):
409 self.textbox.setEnabled(False)
411 self.next_button.hide()
412 self.prev_button.hide()
416 self.textbox.setEnabled(True)
419 self.next_button.show()
420 self.prev_button.show()
422 def Find(self, direction):
423 value = self.textbox.currentText()
424 pattern = self.pattern.isChecked()
425 self.last_value = value
426 self.last_pattern = pattern
427 self.finder.Find(value, direction, pattern, self.context)
429 def ValueChanged(self):
430 value = self.textbox.currentText()
431 pattern = self.pattern.isChecked()
432 index = self.textbox.currentIndex()
433 data = self.textbox.itemData(index)
434 # Store the pattern in the combo box to keep it with the text value
436 self.textbox.setItemData(index, pattern)
438 self.pattern.setChecked(data)
441 def NextPrev(self, direction):
442 value = self.textbox.currentText()
443 pattern = self.pattern.isChecked()
444 if value != self.last_value:
445 index = self.textbox.findText(value)
446 # Allow for a button press before the value has been added to the combo box
448 index = self.textbox.count()
449 self.textbox.addItem(value, pattern)
450 self.textbox.setCurrentIndex(index)
453 self.textbox.setItemData(index, pattern)
454 elif pattern != self.last_pattern:
455 # Keep the pattern recorded in the combo box up to date
456 index = self.textbox.currentIndex()
457 self.textbox.setItemData(index, pattern)
461 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
463 # Context-sensitive call graph data model item base
465 class CallGraphLevelItemBase(object):
467 def __init__(self, glb, params, row, parent_item):
471 self.parent_item = parent_item
472 self.query_done = False;
474 self.child_items = []
476 self.level = parent_item.level + 1
480 def getChildItem(self, row):
481 return self.child_items[row]
483 def getParentItem(self):
484 return self.parent_item
489 def childCount(self):
490 if not self.query_done:
492 if not self.child_count:
494 return self.child_count
496 def hasChildren(self):
497 if not self.query_done:
499 return self.child_count > 0
501 def getData(self, column):
502 return self.data[column]
504 # Context-sensitive call graph data model level 2+ item base
506 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
508 def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
509 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
510 self.comm_id = comm_id
511 self.thread_id = thread_id
512 self.call_path_id = call_path_id
513 self.insn_cnt = insn_cnt
514 self.cyc_cnt = cyc_cnt
515 self.branch_count = branch_count
519 self.query_done = True;
520 query = QSqlQuery(self.glb.db)
521 if self.params.have_ipc:
522 ipc_str = ", SUM(insn_count), SUM(cyc_count)"
525 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
527 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
528 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
529 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
530 " WHERE parent_call_path_id = " + str(self.call_path_id) +
531 " AND comm_id = " + str(self.comm_id) +
532 " AND thread_id = " + str(self.thread_id) +
533 " GROUP BY call_path_id, name, short_name"
534 " ORDER BY call_path_id")
536 if self.params.have_ipc:
537 insn_cnt = int(query.value(5))
538 cyc_cnt = int(query.value(6))
539 branch_count = int(query.value(7))
543 branch_count = int(query.value(5))
544 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)
545 self.child_items.append(child_item)
546 self.child_count += 1
548 # Context-sensitive call graph data model level three item
550 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
552 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):
553 super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
555 if self.params.have_ipc:
556 insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
557 cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
558 br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
559 ipc = CalcIPC(cyc_cnt, insn_cnt)
560 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 ]
562 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
563 self.dbid = call_path_id
565 # Context-sensitive call graph data model level two item
567 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
569 def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
570 super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
571 if self.params.have_ipc:
572 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
574 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
575 self.dbid = thread_id
578 super(CallGraphLevelTwoItem, self).Select()
579 for child_item in self.child_items:
580 self.time += child_item.time
581 self.insn_cnt += child_item.insn_cnt
582 self.cyc_cnt += child_item.cyc_cnt
583 self.branch_count += child_item.branch_count
584 for child_item in self.child_items:
585 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
586 if self.params.have_ipc:
587 child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
588 child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
589 child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
591 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
593 # Context-sensitive call graph data model level one item
595 class CallGraphLevelOneItem(CallGraphLevelItemBase):
597 def __init__(self, glb, params, row, comm_id, comm, parent_item):
598 super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
599 if self.params.have_ipc:
600 self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
602 self.data = [comm, "", "", "", "", "", ""]
606 self.query_done = True;
607 query = QSqlQuery(self.glb.db)
608 QueryExec(query, "SELECT thread_id, pid, tid"
610 " INNER JOIN threads ON thread_id = threads.id"
611 " WHERE comm_id = " + str(self.dbid))
613 child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
614 self.child_items.append(child_item)
615 self.child_count += 1
617 # Context-sensitive call graph data model root item
619 class CallGraphRootItem(CallGraphLevelItemBase):
621 def __init__(self, glb, params):
622 super(CallGraphRootItem, self).__init__(glb, params, 0, None)
624 self.query_done = True;
625 query = QSqlQuery(glb.db)
626 QueryExec(query, "SELECT id, comm FROM comms")
628 if not query.value(0):
630 child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
631 self.child_items.append(child_item)
632 self.child_count += 1
634 # Call graph model parameters
636 class CallGraphModelParams():
638 def __init__(self, glb, parent=None):
639 self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
641 # Context-sensitive call graph data model base
643 class CallGraphModelBase(TreeModel):
645 def __init__(self, glb, parent=None):
646 super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
648 def FindSelect(self, value, pattern, query):
650 # postgresql and sqlite pattern patching differences:
651 # postgresql LIKE is case sensitive but sqlite LIKE is not
652 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
653 # postgresql supports ILIKE which is case insensitive
654 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
655 if not self.glb.dbref.is_sqlite3:
657 s = value.replace("%", "\%")
658 s = s.replace("_", "\_")
659 # Translate * and ? into SQL LIKE pattern characters % and _
660 trans = string.maketrans("*?", "%_")
661 match = " LIKE '" + str(s).translate(trans) + "'"
663 match = " GLOB '" + str(value) + "'"
665 match = " = '" + str(value) + "'"
666 self.DoFindSelect(query, match)
668 def Found(self, query, found):
670 return self.FindPath(query)
673 def FindValue(self, value, pattern, query, last_value, last_pattern):
674 if last_value == value and pattern == last_pattern:
675 found = query.first()
677 self.FindSelect(value, pattern, query)
679 return self.Found(query, found)
681 def FindNext(self, query):
684 found = query.first()
685 return self.Found(query, found)
687 def FindPrev(self, query):
688 found = query.previous()
691 return self.Found(query, found)
693 def FindThread(self, c):
694 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
695 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
696 elif c.direction > 0:
697 ids = self.FindNext(c.query)
699 ids = self.FindPrev(c.query)
702 def Find(self, value, direction, pattern, context, callback):
704 def __init__(self, *x):
705 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
706 def Update(self, *x):
707 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
709 context[0].Update(value, direction, pattern)
711 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
712 # Use a thread so the UI is not blocked during the SELECT
713 thread = Thread(self.FindThread, context[0])
714 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
717 def FindDone(self, thread, callback, ids):
720 # Context-sensitive call graph data model
722 class CallGraphModel(CallGraphModelBase):
724 def __init__(self, glb, parent=None):
725 super(CallGraphModel, self).__init__(glb, parent)
728 return CallGraphRootItem(self.glb, self.params)
730 def columnCount(self, parent=None):
731 if self.params.have_ipc:
736 def columnHeader(self, column):
737 if self.params.have_ipc:
738 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
740 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
741 return headers[column]
743 def columnAlignment(self, column):
744 if self.params.have_ipc:
745 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 ]
747 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
748 return alignment[column]
750 def DoFindSelect(self, query, match):
751 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
753 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
754 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
755 " WHERE symbols.name" + match +
756 " GROUP BY comm_id, thread_id, call_path_id"
757 " ORDER BY comm_id, thread_id, call_path_id")
759 def FindPath(self, query):
760 # Turn the query result into a list of ids that the tree view can walk
761 # to open the tree at the right place.
763 parent_id = query.value(0)
765 ids.insert(0, parent_id)
766 q2 = QSqlQuery(self.glb.db)
767 QueryExec(q2, "SELECT parent_id"
769 " WHERE id = " + str(parent_id))
772 parent_id = q2.value(0)
773 # The call path root is not used
776 ids.insert(0, query.value(2))
777 ids.insert(0, query.value(1))
780 # Call tree data model level 2+ item base
782 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
784 def __init__(self, glb, params, row, comm_id, thread_id, calls_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
785 super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
786 self.comm_id = comm_id
787 self.thread_id = thread_id
788 self.calls_id = calls_id
789 self.insn_cnt = insn_cnt
790 self.cyc_cnt = cyc_cnt
791 self.branch_count = branch_count
795 self.query_done = True;
796 if self.calls_id == 0:
797 comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
800 if self.params.have_ipc:
801 ipc_str = ", insn_count, cyc_count"
804 query = QSqlQuery(self.glb.db)
805 QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
807 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
808 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
809 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
810 " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
811 " ORDER BY call_time, calls.id")
813 if self.params.have_ipc:
814 insn_cnt = int(query.value(5))
815 cyc_cnt = int(query.value(6))
816 branch_count = int(query.value(7))
820 branch_count = int(query.value(5))
821 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)
822 self.child_items.append(child_item)
823 self.child_count += 1
825 # Call tree data model level three item
827 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
829 def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item):
830 super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
832 if self.params.have_ipc:
833 insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
834 cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
835 br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
836 ipc = CalcIPC(cyc_cnt, insn_cnt)
837 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 ]
839 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
842 # Call tree data model level two item
844 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
846 def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
847 super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, parent_item)
848 if self.params.have_ipc:
849 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
851 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
852 self.dbid = thread_id
855 super(CallTreeLevelTwoItem, self).Select()
856 for child_item in self.child_items:
857 self.time += child_item.time
858 self.insn_cnt += child_item.insn_cnt
859 self.cyc_cnt += child_item.cyc_cnt
860 self.branch_count += child_item.branch_count
861 for child_item in self.child_items:
862 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
863 if self.params.have_ipc:
864 child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
865 child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
866 child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
868 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
870 # Call tree data model level one item
872 class CallTreeLevelOneItem(CallGraphLevelItemBase):
874 def __init__(self, glb, params, row, comm_id, comm, parent_item):
875 super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
876 if self.params.have_ipc:
877 self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
879 self.data = [comm, "", "", "", "", "", ""]
883 self.query_done = True;
884 query = QSqlQuery(self.glb.db)
885 QueryExec(query, "SELECT thread_id, pid, tid"
887 " INNER JOIN threads ON thread_id = threads.id"
888 " WHERE comm_id = " + str(self.dbid))
890 child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
891 self.child_items.append(child_item)
892 self.child_count += 1
894 # Call tree data model root item
896 class CallTreeRootItem(CallGraphLevelItemBase):
898 def __init__(self, glb, params):
899 super(CallTreeRootItem, self).__init__(glb, params, 0, None)
901 self.query_done = True;
902 query = QSqlQuery(glb.db)
903 QueryExec(query, "SELECT id, comm FROM comms")
905 if not query.value(0):
907 child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
908 self.child_items.append(child_item)
909 self.child_count += 1
911 # Call Tree data model
913 class CallTreeModel(CallGraphModelBase):
915 def __init__(self, glb, parent=None):
916 super(CallTreeModel, self).__init__(glb, parent)
919 return CallTreeRootItem(self.glb, self.params)
921 def columnCount(self, parent=None):
922 if self.params.have_ipc:
927 def columnHeader(self, column):
928 if self.params.have_ipc:
929 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
931 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
932 return headers[column]
934 def columnAlignment(self, column):
935 if self.params.have_ipc:
936 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 ]
938 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
939 return alignment[column]
941 def DoFindSelect(self, query, match):
942 QueryExec(query, "SELECT calls.id, comm_id, thread_id"
944 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
945 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
946 " WHERE symbols.name" + match +
947 " ORDER BY comm_id, thread_id, call_time, calls.id")
949 def FindPath(self, query):
950 # Turn the query result into a list of ids that the tree view can walk
951 # to open the tree at the right place.
953 parent_id = query.value(0)
955 ids.insert(0, parent_id)
956 q2 = QSqlQuery(self.glb.db)
957 QueryExec(q2, "SELECT parent_id"
959 " WHERE id = " + str(parent_id))
962 parent_id = q2.value(0)
963 ids.insert(0, query.value(2))
964 ids.insert(0, query.value(1))
967 # Vertical widget layout
971 def __init__(self, w1, w2, w3=None):
972 self.vbox = QWidget()
973 self.vbox.setLayout(QVBoxLayout());
975 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
977 self.vbox.layout().addWidget(w1)
978 self.vbox.layout().addWidget(w2)
980 self.vbox.layout().addWidget(w3)
987 class TreeWindowBase(QMdiSubWindow):
989 def __init__(self, parent=None):
990 super(TreeWindowBase, self).__init__(parent)
995 self.view = QTreeView()
996 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
997 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
999 self.context_menu = TreeContextMenu(self.view)
1001 def DisplayFound(self, ids):
1004 parent = QModelIndex()
1007 n = self.model.rowCount(parent)
1008 for row in xrange(n):
1009 child = self.model.index(row, 0, parent)
1010 if child.internalPointer().dbid == dbid:
1012 self.view.setCurrentIndex(child)
1019 def Find(self, value, direction, pattern, context):
1020 self.view.setFocus()
1021 self.find_bar.Busy()
1022 self.model.Find(value, direction, pattern, context, self.FindDone)
1024 def FindDone(self, ids):
1026 if not self.DisplayFound(ids):
1028 self.find_bar.Idle()
1030 self.find_bar.NotFound()
1033 # Context-sensitive call graph window
1035 class CallGraphWindow(TreeWindowBase):
1037 def __init__(self, glb, parent=None):
1038 super(CallGraphWindow, self).__init__(parent)
1040 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
1042 self.view.setModel(self.model)
1044 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1045 self.view.setColumnWidth(c, w)
1047 self.find_bar = FindBar(self, self)
1049 self.vbox = VBox(self.view, self.find_bar.Widget())
1051 self.setWidget(self.vbox.Widget())
1053 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
1057 class CallTreeWindow(TreeWindowBase):
1059 def __init__(self, glb, parent=None):
1060 super(CallTreeWindow, self).__init__(parent)
1062 self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
1064 self.view.setModel(self.model)
1066 for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1067 self.view.setColumnWidth(c, w)
1069 self.find_bar = FindBar(self, self)
1071 self.vbox = VBox(self.view, self.find_bar.Widget())
1073 self.setWidget(self.vbox.Widget())
1075 AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
1077 # Child data item finder
1079 class ChildDataItemFinder():
1081 def __init__(self, root):
1083 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
1087 def FindSelect(self):
1090 pattern = re.compile(self.value)
1091 for child in self.root.child_items:
1092 for column_data in child.data:
1093 if re.search(pattern, str(column_data)) is not None:
1094 self.rows.append(child.row)
1097 for child in self.root.child_items:
1098 for column_data in child.data:
1099 if self.value in str(column_data):
1100 self.rows.append(child.row)
1103 def FindValue(self):
1105 if self.last_value != self.value or self.pattern != self.last_pattern:
1107 if not len(self.rows):
1109 return self.rows[self.pos]
1111 def FindThread(self):
1112 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1113 row = self.FindValue()
1114 elif len(self.rows):
1115 if self.direction > 0:
1117 if self.pos >= len(self.rows):
1122 self.pos = len(self.rows) - 1
1123 row = self.rows[self.pos]
1128 def Find(self, value, direction, pattern, context, callback):
1129 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1130 # Use a thread so the UI is not blocked
1131 thread = Thread(self.FindThread)
1132 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1135 def FindDone(self, thread, callback, row):
1138 # Number of database records to fetch in one go
1140 glb_chunk_sz = 10000
1142 # Background process for SQL data fetcher
1144 class SQLFetcherProcess():
1146 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1147 # Need a unique connection name
1148 conn_name = "SQLFetcher" + str(os.getpid())
1149 self.db, dbname = dbref.Open(conn_name)
1151 self.buffer = buffer
1154 self.fetch_count = fetch_count
1155 self.fetching_done = fetching_done
1156 self.process_target = process_target
1157 self.wait_event = wait_event
1158 self.fetched_event = fetched_event
1160 self.query = QSqlQuery(self.db)
1161 self.query_limit = 0 if "$$last_id$$" in sql else 2
1165 self.local_head = self.head.value
1166 self.local_tail = self.tail.value
1169 if self.query_limit:
1170 if self.query_limit == 1:
1172 self.query_limit -= 1
1173 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1174 QueryExec(self.query, stmt)
1177 if not self.query.next():
1179 if not self.query.next():
1181 self.last_id = self.query.value(0)
1182 return self.prep(self.query)
1184 def WaitForTarget(self):
1186 self.wait_event.clear()
1187 target = self.process_target.value
1188 if target > self.fetched or target < 0:
1190 self.wait_event.wait()
1193 def HasSpace(self, sz):
1194 if self.local_tail <= self.local_head:
1195 space = len(self.buffer) - self.local_head
1198 if space >= glb_nsz:
1199 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1200 nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
1201 self.buffer[self.local_head : self.local_head + len(nd)] = nd
1203 if self.local_tail - self.local_head > sz:
1207 def WaitForSpace(self, sz):
1208 if self.HasSpace(sz):
1211 self.wait_event.clear()
1212 self.local_tail = self.tail.value
1213 if self.HasSpace(sz):
1215 self.wait_event.wait()
1217 def AddToBuffer(self, obj):
1218 d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
1220 nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
1222 self.WaitForSpace(sz)
1223 pos = self.local_head
1224 self.buffer[pos : pos + len(nd)] = nd
1225 self.buffer[pos + glb_nsz : pos + sz] = d
1226 self.local_head += sz
1228 def FetchBatch(self, batch_size):
1230 while batch_size > fetched:
1235 self.AddToBuffer(obj)
1238 self.fetched += fetched
1239 with self.fetch_count.get_lock():
1240 self.fetch_count.value += fetched
1241 self.head.value = self.local_head
1242 self.fetched_event.set()
1246 target = self.WaitForTarget()
1249 batch_size = min(glb_chunk_sz, target - self.fetched)
1250 self.FetchBatch(batch_size)
1251 self.fetching_done.value = True
1252 self.fetched_event.set()
1254 def SQLFetcherFn(*x):
1255 process = SQLFetcherProcess(*x)
1260 class SQLFetcher(QObject):
1262 done = Signal(object)
1264 def __init__(self, glb, sql, prep, process_data, parent=None):
1265 super(SQLFetcher, self).__init__(parent)
1266 self.process_data = process_data
1269 self.last_target = 0
1271 self.buffer_size = 16 * 1024 * 1024
1272 self.buffer = Array(c_char, self.buffer_size, lock=False)
1273 self.head = Value(c_longlong)
1274 self.tail = Value(c_longlong)
1276 self.fetch_count = Value(c_longlong)
1277 self.fetching_done = Value(c_bool)
1279 self.process_target = Value(c_longlong)
1280 self.wait_event = Event()
1281 self.fetched_event = Event()
1282 glb.AddInstanceToShutdownOnExit(self)
1283 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))
1284 self.process.start()
1285 self.thread = Thread(self.Thread)
1286 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1290 # Tell the thread and process to exit
1291 self.process_target.value = -1
1292 self.wait_event.set()
1294 self.fetching_done.value = True
1295 self.fetched_event.set()
1301 self.fetched_event.clear()
1302 fetch_count = self.fetch_count.value
1303 if fetch_count != self.last_count:
1305 if self.fetching_done.value:
1308 self.fetched_event.wait()
1309 count = fetch_count - self.last_count
1310 self.last_count = fetch_count
1311 self.fetched += count
1314 def Fetch(self, nr):
1316 # -1 inidcates there are no more
1318 result = self.fetched
1319 extra = result + nr - self.target
1321 self.target += extra
1322 # process_target < 0 indicates shutting down
1323 if self.process_target.value >= 0:
1324 self.process_target.value = self.target
1325 self.wait_event.set()
1328 def RemoveFromBuffer(self):
1329 pos = self.local_tail
1330 if len(self.buffer) - pos < glb_nsz:
1332 n = pickle.loads(self.buffer[pos : pos + glb_nsz])
1335 n = pickle.loads(self.buffer[0 : glb_nsz])
1337 obj = pickle.loads(self.buffer[pos : pos + n])
1338 self.local_tail = pos + n
1341 def ProcessData(self, count):
1342 for i in xrange(count):
1343 obj = self.RemoveFromBuffer()
1344 self.process_data(obj)
1345 self.tail.value = self.local_tail
1346 self.wait_event.set()
1347 self.done.emit(count)
1349 # Fetch more records bar
1351 class FetchMoreRecordsBar():
1353 def __init__(self, model, parent):
1356 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1357 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1359 self.fetch_count = QSpinBox()
1360 self.fetch_count.setRange(1, 1000000)
1361 self.fetch_count.setValue(10)
1362 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1364 self.fetch = QPushButton("Go!")
1365 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1366 self.fetch.released.connect(self.FetchMoreRecords)
1368 self.progress = QProgressBar()
1369 self.progress.setRange(0, 100)
1370 self.progress.hide()
1372 self.done_label = QLabel("All records fetched")
1373 self.done_label.hide()
1375 self.spacer = QLabel("")
1377 self.close_button = QToolButton()
1378 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1379 self.close_button.released.connect(self.Deactivate)
1381 self.hbox = QHBoxLayout()
1382 self.hbox.setContentsMargins(0, 0, 0, 0)
1384 self.hbox.addWidget(self.label)
1385 self.hbox.addWidget(self.fetch_count)
1386 self.hbox.addWidget(self.fetch)
1387 self.hbox.addWidget(self.spacer)
1388 self.hbox.addWidget(self.progress)
1389 self.hbox.addWidget(self.done_label)
1390 self.hbox.addWidget(self.close_button)
1392 self.bar = QWidget()
1393 self.bar.setLayout(self.hbox);
1396 self.in_progress = False
1397 self.model.progress.connect(self.Progress)
1401 if not model.HasMoreRecords():
1409 self.fetch.setFocus()
1411 def Deactivate(self):
1414 def Enable(self, enable):
1415 self.fetch.setEnabled(enable)
1416 self.fetch_count.setEnabled(enable)
1422 self.progress.show()
1425 self.in_progress = False
1427 self.progress.hide()
1432 return self.fetch_count.value() * glb_chunk_sz
1438 self.fetch_count.hide()
1441 self.done_label.show()
1443 def Progress(self, count):
1444 if self.in_progress:
1446 percent = ((count - self.start) * 100) / self.Target()
1450 self.progress.setValue(percent)
1452 # Count value of zero means no more records
1455 def FetchMoreRecords(self):
1458 self.progress.setValue(0)
1460 self.in_progress = True
1461 self.start = self.model.FetchMoreRecords(self.Target())
1463 # Brance data model level two item
1465 class BranchLevelTwoItem():
1467 def __init__(self, row, col, text, parent_item):
1469 self.parent_item = parent_item
1470 self.data = [""] * (col + 1)
1471 self.data[col] = text
1474 def getParentItem(self):
1475 return self.parent_item
1480 def childCount(self):
1483 def hasChildren(self):
1486 def getData(self, column):
1487 return self.data[column]
1489 # Brance data model level one item
1491 class BranchLevelOneItem():
1493 def __init__(self, glb, row, data, parent_item):
1496 self.parent_item = parent_item
1497 self.child_count = 0
1498 self.child_items = []
1499 self.data = data[1:]
1502 self.query_done = False
1503 self.br_col = len(self.data) - 1
1505 def getChildItem(self, row):
1506 return self.child_items[row]
1508 def getParentItem(self):
1509 return self.parent_item
1515 self.query_done = True
1517 if not self.glb.have_disassembler:
1520 query = QSqlQuery(self.glb.db)
1522 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1524 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1525 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1526 " WHERE samples.id = " + str(self.dbid))
1527 if not query.next():
1529 cpu = query.value(0)
1530 dso = query.value(1)
1531 sym = query.value(2)
1532 if dso == 0 or sym == 0:
1534 off = query.value(3)
1535 short_name = query.value(4)
1536 long_name = query.value(5)
1537 build_id = query.value(6)
1538 sym_start = query.value(7)
1541 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1543 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1544 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1545 " ORDER BY samples.id"
1547 if not query.next():
1549 if query.value(0) != dso:
1550 # Cannot disassemble from one dso to another
1552 bsym = query.value(1)
1553 boff = query.value(2)
1554 bsym_start = query.value(3)
1557 tot = bsym_start + boff + 1 - sym_start - off
1558 if tot <= 0 or tot > 16384:
1561 inst = self.glb.disassembler.Instruction()
1562 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1565 mode = 0 if Is64Bit(f) else 1
1566 self.glb.disassembler.SetMode(inst, mode)
1569 buf = create_string_buffer(tot + 16)
1570 f.seek(sym_start + off)
1571 buf.value = f.read(buf_sz)
1572 buf_ptr = addressof(buf)
1575 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1577 byte_str = tohex(ip).rjust(16)
1578 for k in xrange(cnt):
1579 byte_str += " %02x" % ord(buf[i])
1584 self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
1585 self.child_count += 1
1593 def childCount(self):
1594 if not self.query_done:
1596 if not self.child_count:
1598 return self.child_count
1600 def hasChildren(self):
1601 if not self.query_done:
1603 return self.child_count > 0
1605 def getData(self, column):
1606 return self.data[column]
1608 # Brance data model root item
1610 class BranchRootItem():
1613 self.child_count = 0
1614 self.child_items = []
1617 def getChildItem(self, row):
1618 return self.child_items[row]
1620 def getParentItem(self):
1626 def childCount(self):
1627 return self.child_count
1629 def hasChildren(self):
1630 return self.child_count > 0
1632 def getData(self, column):
1635 # Calculate instructions per cycle
1637 def CalcIPC(cyc_cnt, insn_cnt):
1638 if cyc_cnt and insn_cnt:
1639 ipc = Decimal(float(insn_cnt) / cyc_cnt)
1640 ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
1645 # Branch data preparation
1647 def BranchDataPrepBr(query, data):
1648 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1649 " (" + dsoname(query.value(11)) + ")" + " -> " +
1650 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1651 " (" + dsoname(query.value(15)) + ")")
1653 def BranchDataPrepIPC(query, data):
1654 insn_cnt = query.value(16)
1655 cyc_cnt = query.value(17)
1656 ipc = CalcIPC(cyc_cnt, insn_cnt)
1657 data.append(insn_cnt)
1658 data.append(cyc_cnt)
1661 def BranchDataPrep(query):
1663 for i in xrange(0, 8):
1664 data.append(query.value(i))
1665 BranchDataPrepBr(query, data)
1668 def BranchDataPrepWA(query):
1670 data.append(query.value(0))
1671 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1672 data.append("{:>19}".format(query.value(1)))
1673 for i in xrange(2, 8):
1674 data.append(query.value(i))
1675 BranchDataPrepBr(query, data)
1678 def BranchDataWithIPCPrep(query):
1680 for i in xrange(0, 8):
1681 data.append(query.value(i))
1682 BranchDataPrepIPC(query, data)
1683 BranchDataPrepBr(query, data)
1686 def BranchDataWithIPCPrepWA(query):
1688 data.append(query.value(0))
1689 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1690 data.append("{:>19}".format(query.value(1)))
1691 for i in xrange(2, 8):
1692 data.append(query.value(i))
1693 BranchDataPrepIPC(query, data)
1694 BranchDataPrepBr(query, data)
1699 class BranchModel(TreeModel):
1701 progress = Signal(object)
1703 def __init__(self, glb, event_id, where_clause, parent=None):
1704 super(BranchModel, self).__init__(glb, None, parent)
1705 self.event_id = event_id
1708 self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
1710 select_ipc = ", insn_count, cyc_count"
1711 prep_fn = BranchDataWithIPCPrep
1712 prep_wa_fn = BranchDataWithIPCPrepWA
1715 prep_fn = BranchDataPrep
1716 prep_wa_fn = BranchDataPrepWA
1717 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1718 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1719 " ip, symbols.name, sym_offset, dsos.short_name,"
1720 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1723 " INNER JOIN comms ON comm_id = comms.id"
1724 " INNER JOIN threads ON thread_id = threads.id"
1725 " INNER JOIN branch_types ON branch_type = branch_types.id"
1726 " INNER JOIN symbols ON symbol_id = symbols.id"
1727 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1728 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1729 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1730 " WHERE samples.id > $$last_id$$" + where_clause +
1731 " AND evsel_id = " + str(self.event_id) +
1732 " ORDER BY samples.id"
1733 " LIMIT " + str(glb_chunk_sz))
1734 if pyside_version_1 and sys.version_info[0] == 3:
1738 self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
1739 self.fetcher.done.connect(self.Update)
1740 self.fetcher.Fetch(glb_chunk_sz)
1743 return BranchRootItem()
1745 def columnCount(self, parent=None):
1751 def columnHeader(self, column):
1753 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
1755 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1757 def columnFont(self, column):
1762 if column != br_col:
1764 return QFont("Monospace")
1766 def DisplayData(self, item, index):
1768 self.FetchIfNeeded(item.row)
1769 return item.getData(index.column())
1771 def AddSample(self, data):
1772 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1773 self.root.child_items.append(child)
1776 def Update(self, fetched):
1779 self.progress.emit(0)
1780 child_count = self.root.child_count
1781 count = self.populated - child_count
1783 parent = QModelIndex()
1784 self.beginInsertRows(parent, child_count, child_count + count - 1)
1785 self.insertRows(child_count, count, parent)
1786 self.root.child_count += count
1787 self.endInsertRows()
1788 self.progress.emit(self.root.child_count)
1790 def FetchMoreRecords(self, count):
1791 current = self.root.child_count
1793 self.fetcher.Fetch(count)
1795 self.progress.emit(0)
1798 def HasMoreRecords(self):
1805 def __init__(self, name = "", where_clause = "", limit = ""):
1807 self.where_clause = where_clause
1811 return str(self.where_clause + ";" + self.limit)
1815 class BranchWindow(QMdiSubWindow):
1817 def __init__(self, glb, event_id, report_vars, parent=None):
1818 super(BranchWindow, self).__init__(parent)
1820 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
1822 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1824 self.view = QTreeView()
1825 self.view.setUniformRowHeights(True)
1826 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1827 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1828 self.view.setModel(self.model)
1830 self.ResizeColumnsToContents()
1832 self.context_menu = TreeContextMenu(self.view)
1834 self.find_bar = FindBar(self, self, True)
1836 self.finder = ChildDataItemFinder(self.model.root)
1838 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1840 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1842 self.setWidget(self.vbox.Widget())
1844 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1846 def ResizeColumnToContents(self, column, n):
1847 # Using the view's resizeColumnToContents() here is extrememly slow
1848 # so implement a crude alternative
1849 mm = "MM" if column else "MMMM"
1850 font = self.view.font()
1851 metrics = QFontMetrics(font)
1853 for row in xrange(n):
1854 val = self.model.root.child_items[row].data[column]
1855 len = metrics.width(str(val) + mm)
1856 max = len if len > max else max
1857 val = self.model.columnHeader(column)
1858 len = metrics.width(str(val) + mm)
1859 max = len if len > max else max
1860 self.view.setColumnWidth(column, max)
1862 def ResizeColumnsToContents(self):
1863 n = min(self.model.root.child_count, 100)
1865 # No data yet, so connect a signal to notify when there is
1866 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1868 columns = self.model.columnCount()
1869 for i in xrange(columns):
1870 self.ResizeColumnToContents(i, n)
1872 def UpdateColumnWidths(self, *x):
1873 # This only needs to be done once, so disconnect the signal now
1874 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1875 self.ResizeColumnsToContents()
1877 def Find(self, value, direction, pattern, context):
1878 self.view.setFocus()
1879 self.find_bar.Busy()
1880 self.finder.Find(value, direction, pattern, context, self.FindDone)
1882 def FindDone(self, row):
1883 self.find_bar.Idle()
1885 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1887 self.find_bar.NotFound()
1889 # Line edit data item
1891 class LineEditDataItem(object):
1893 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1896 self.placeholder_text = placeholder_text
1897 self.parent = parent
1900 self.value = default
1902 self.widget = QLineEdit(default)
1903 self.widget.editingFinished.connect(self.Validate)
1904 self.widget.textChanged.connect(self.Invalidate)
1907 self.validated = True
1909 if placeholder_text:
1910 self.widget.setPlaceholderText(placeholder_text)
1912 def TurnTextRed(self):
1914 palette = QPalette()
1915 palette.setColor(QPalette.Text,Qt.red)
1916 self.widget.setPalette(palette)
1919 def TurnTextNormal(self):
1921 palette = QPalette()
1922 self.widget.setPalette(palette)
1925 def InvalidValue(self, value):
1928 self.error = self.label + " invalid value '" + value + "'"
1929 self.parent.ShowMessage(self.error)
1931 def Invalidate(self):
1932 self.validated = False
1934 def DoValidate(self, input_string):
1935 self.value = input_string.strip()
1938 self.validated = True
1940 self.TurnTextNormal()
1941 self.parent.ClearMessage()
1942 input_string = self.widget.text()
1943 if not len(input_string.strip()):
1946 self.DoValidate(input_string)
1949 if not self.validated:
1952 self.parent.ShowMessage(self.error)
1956 def IsNumber(self, value):
1961 return str(x) == value
1963 # Non-negative integer ranges dialog data item
1965 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1967 def __init__(self, glb, label, placeholder_text, column_name, parent):
1968 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1970 self.column_name = column_name
1972 def DoValidate(self, input_string):
1975 for value in [x.strip() for x in input_string.split(",")]:
1977 vrange = value.split("-")
1978 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1979 return self.InvalidValue(value)
1980 ranges.append(vrange)
1982 if not self.IsNumber(value):
1983 return self.InvalidValue(value)
1984 singles.append(value)
1985 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1987 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1988 self.value = " OR ".join(ranges)
1990 # Positive integer dialog data item
1992 class PositiveIntegerDataItem(LineEditDataItem):
1994 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1995 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1997 def DoValidate(self, input_string):
1998 if not self.IsNumber(input_string.strip()):
1999 return self.InvalidValue(input_string)
2000 value = int(input_string.strip())
2002 return self.InvalidValue(input_string)
2003 self.value = str(value)
2005 # Dialog data item converted and validated using a SQL table
2007 class SQLTableDataItem(LineEditDataItem):
2009 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
2010 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
2012 self.table_name = table_name
2013 self.match_column = match_column
2014 self.column_name1 = column_name1
2015 self.column_name2 = column_name2
2017 def ValueToIds(self, value):
2019 query = QSqlQuery(self.glb.db)
2020 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
2021 ret = query.exec_(stmt)
2024 ids.append(str(query.value(0)))
2027 def DoValidate(self, input_string):
2029 for value in [x.strip() for x in input_string.split(",")]:
2030 ids = self.ValueToIds(value)
2034 return self.InvalidValue(value)
2035 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
2036 if self.column_name2:
2037 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
2039 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
2041 class SampleTimeRangesDataItem(LineEditDataItem):
2043 def __init__(self, glb, label, placeholder_text, column_name, parent):
2044 self.column_name = column_name
2048 self.last_time = 2 ** 64
2050 query = QSqlQuery(glb.db)
2051 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
2053 self.last_id = int(query.value(0))
2054 self.last_time = int(query.value(1))
2055 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
2057 self.first_time = int(query.value(0))
2058 if placeholder_text:
2059 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
2061 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
2063 def IdBetween(self, query, lower_id, higher_id, order):
2064 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
2066 return True, int(query.value(0))
2070 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
2071 query = QSqlQuery(self.glb.db)
2073 next_id = int((lower_id + higher_id) / 2)
2074 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2075 if not query.next():
2076 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
2078 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
2080 return str(higher_id)
2082 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2083 next_time = int(query.value(0))
2085 if target_time > next_time:
2089 if higher_id <= lower_id + 1:
2090 return str(higher_id)
2092 if target_time >= next_time:
2096 if higher_id <= lower_id + 1:
2097 return str(lower_id)
2099 def ConvertRelativeTime(self, val):
2104 elif suffix == "us":
2106 elif suffix == "ns":
2110 val = val[:-2].strip()
2111 if not self.IsNumber(val):
2113 val = int(val) * mult
2115 val += self.first_time
2117 val += self.last_time
2120 def ConvertTimeRange(self, vrange):
2122 vrange[0] = str(self.first_time)
2124 vrange[1] = str(self.last_time)
2125 vrange[0] = self.ConvertRelativeTime(vrange[0])
2126 vrange[1] = self.ConvertRelativeTime(vrange[1])
2127 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
2129 beg_range = max(int(vrange[0]), self.first_time)
2130 end_range = min(int(vrange[1]), self.last_time)
2131 if beg_range > self.last_time or end_range < self.first_time:
2133 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
2134 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
2137 def AddTimeRange(self, value, ranges):
2138 n = value.count("-")
2142 if value.split("-")[1].strip() == "":
2148 pos = findnth(value, "-", n)
2149 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
2150 if self.ConvertTimeRange(vrange):
2151 ranges.append(vrange)
2155 def DoValidate(self, input_string):
2157 for value in [x.strip() for x in input_string.split(",")]:
2158 if not self.AddTimeRange(value, ranges):
2159 return self.InvalidValue(value)
2160 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
2161 self.value = " OR ".join(ranges)
2163 # Report Dialog Base
2165 class ReportDialogBase(QDialog):
2167 def __init__(self, glb, title, items, partial, parent=None):
2168 super(ReportDialogBase, self).__init__(parent)
2172 self.report_vars = ReportVars()
2174 self.setWindowTitle(title)
2175 self.setMinimumWidth(600)
2177 self.data_items = [x(glb, self) for x in items]
2179 self.partial = partial
2181 self.grid = QGridLayout()
2183 for row in xrange(len(self.data_items)):
2184 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2185 self.grid.addWidget(self.data_items[row].widget, row, 1)
2187 self.status = QLabel()
2189 self.ok_button = QPushButton("Ok", self)
2190 self.ok_button.setDefault(True)
2191 self.ok_button.released.connect(self.Ok)
2192 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2194 self.cancel_button = QPushButton("Cancel", self)
2195 self.cancel_button.released.connect(self.reject)
2196 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2198 self.hbox = QHBoxLayout()
2199 #self.hbox.addStretch()
2200 self.hbox.addWidget(self.status)
2201 self.hbox.addWidget(self.ok_button)
2202 self.hbox.addWidget(self.cancel_button)
2204 self.vbox = QVBoxLayout()
2205 self.vbox.addLayout(self.grid)
2206 self.vbox.addLayout(self.hbox)
2208 self.setLayout(self.vbox);
2211 vars = self.report_vars
2212 for d in self.data_items:
2213 if d.id == "REPORTNAME":
2216 self.ShowMessage("Report name is required")
2218 for d in self.data_items:
2221 for d in self.data_items[1:]:
2223 vars.limit = d.value
2225 if len(vars.where_clause):
2226 vars.where_clause += " AND "
2227 vars.where_clause += d.value
2228 if len(vars.where_clause):
2230 vars.where_clause = " AND ( " + vars.where_clause + " ) "
2232 vars.where_clause = " WHERE " + vars.where_clause + " "
2235 def ShowMessage(self, msg):
2236 self.status.setText("<font color=#FF0000>" + msg)
2238 def ClearMessage(self):
2239 self.status.setText("")
2241 # Selected branch report creation dialog
2243 class SelectedBranchDialog(ReportDialogBase):
2245 def __init__(self, glb, parent=None):
2246 title = "Selected Branches"
2247 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2248 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2249 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2250 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2251 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2252 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2253 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2254 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2255 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2256 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2260 def GetEventList(db):
2262 query = QSqlQuery(db)
2263 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2265 events.append(query.value(0))
2268 # Is a table selectable
2270 def IsSelectable(db, table, sql = "", columns = "*"):
2271 query = QSqlQuery(db)
2273 QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
2278 # SQL table data model item
2280 class SQLTableItem():
2282 def __init__(self, row, data):
2286 def getData(self, column):
2287 return self.data[column]
2289 # SQL table data model
2291 class SQLTableModel(TableModel):
2293 progress = Signal(object)
2295 def __init__(self, glb, sql, column_headers, parent=None):
2296 super(SQLTableModel, self).__init__(parent)
2300 self.column_headers = column_headers
2301 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
2302 self.fetcher.done.connect(self.Update)
2303 self.fetcher.Fetch(glb_chunk_sz)
2305 def DisplayData(self, item, index):
2306 self.FetchIfNeeded(item.row)
2307 return item.getData(index.column())
2309 def AddSample(self, data):
2310 child = SQLTableItem(self.populated, data)
2311 self.child_items.append(child)
2314 def Update(self, fetched):
2317 self.progress.emit(0)
2318 child_count = self.child_count
2319 count = self.populated - child_count
2321 parent = QModelIndex()
2322 self.beginInsertRows(parent, child_count, child_count + count - 1)
2323 self.insertRows(child_count, count, parent)
2324 self.child_count += count
2325 self.endInsertRows()
2326 self.progress.emit(self.child_count)
2328 def FetchMoreRecords(self, count):
2329 current = self.child_count
2331 self.fetcher.Fetch(count)
2333 self.progress.emit(0)
2336 def HasMoreRecords(self):
2339 def columnCount(self, parent=None):
2340 return len(self.column_headers)
2342 def columnHeader(self, column):
2343 return self.column_headers[column]
2345 def SQLTableDataPrep(self, query, count):
2347 for i in xrange(count):
2348 data.append(query.value(i))
2351 # SQL automatic table data model
2353 class SQLAutoTableModel(SQLTableModel):
2355 def __init__(self, glb, table_name, parent=None):
2356 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2357 if table_name == "comm_threads_view":
2358 # For now, comm_threads_view has no id column
2359 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2361 query = QSqlQuery(glb.db)
2362 if glb.dbref.is_sqlite3:
2363 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2365 column_headers.append(query.value(1))
2366 if table_name == "sqlite_master":
2367 sql = "SELECT * FROM " + table_name
2369 if table_name[:19] == "information_schema.":
2370 sql = "SELECT * FROM " + table_name
2371 select_table_name = table_name[19:]
2372 schema = "information_schema"
2374 select_table_name = table_name
2376 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2378 column_headers.append(query.value(0))
2379 if pyside_version_1 and sys.version_info[0] == 3:
2380 if table_name == "samples_view":
2381 self.SQLTableDataPrep = self.samples_view_DataPrep
2382 if table_name == "samples":
2383 self.SQLTableDataPrep = self.samples_DataPrep
2384 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2386 def samples_view_DataPrep(self, query, count):
2388 data.append(query.value(0))
2389 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2390 data.append("{:>19}".format(query.value(1)))
2391 for i in xrange(2, count):
2392 data.append(query.value(i))
2395 def samples_DataPrep(self, query, count):
2398 data.append(query.value(i))
2399 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2400 data.append("{:>19}".format(query.value(9)))
2401 for i in xrange(10, count):
2402 data.append(query.value(i))
2405 # Base class for custom ResizeColumnsToContents
2407 class ResizeColumnsToContentsBase(QObject):
2409 def __init__(self, parent=None):
2410 super(ResizeColumnsToContentsBase, self).__init__(parent)
2412 def ResizeColumnToContents(self, column, n):
2413 # Using the view's resizeColumnToContents() here is extrememly slow
2414 # so implement a crude alternative
2415 font = self.view.font()
2416 metrics = QFontMetrics(font)
2418 for row in xrange(n):
2419 val = self.data_model.child_items[row].data[column]
2420 len = metrics.width(str(val) + "MM")
2421 max = len if len > max else max
2422 val = self.data_model.columnHeader(column)
2423 len = metrics.width(str(val) + "MM")
2424 max = len if len > max else max
2425 self.view.setColumnWidth(column, max)
2427 def ResizeColumnsToContents(self):
2428 n = min(self.data_model.child_count, 100)
2430 # No data yet, so connect a signal to notify when there is
2431 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2433 columns = self.data_model.columnCount()
2434 for i in xrange(columns):
2435 self.ResizeColumnToContents(i, n)
2437 def UpdateColumnWidths(self, *x):
2438 # This only needs to be done once, so disconnect the signal now
2439 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2440 self.ResizeColumnsToContents()
2442 # Convert value to CSV
2446 val = val.replace('"', '""')
2447 if "," in val or '"' in val:
2448 val = '"' + val + '"'
2451 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2455 def RowColumnKey(a):
2456 return a.row() * glb_max_cols + a.column()
2458 # Copy selected table cells to clipboard
2460 def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2461 indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2462 idx_cnt = len(indexes)
2467 min_row = indexes[0].row()
2468 max_row = indexes[0].row()
2469 min_col = indexes[0].column()
2470 max_col = indexes[0].column()
2472 min_row = min(min_row, i.row())
2473 max_row = max(max_row, i.row())
2474 min_col = min(min_col, i.column())
2475 max_col = max(max_col, i.column())
2476 if max_col > glb_max_cols:
2477 raise RuntimeError("glb_max_cols is too low")
2478 max_width = [0] * (1 + max_col - min_col)
2480 c = i.column() - min_col
2481 max_width[c] = max(max_width[c], len(str(i.data())))
2486 model = indexes[0].model()
2487 for col in range(min_col, max_col + 1):
2488 val = model.headerData(col, Qt.Horizontal)
2490 text += sep + ToCSValue(val)
2494 max_width[c] = max(max_width[c], len(val))
2495 width = max_width[c]
2496 align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2497 if align & Qt.AlignRight:
2498 val = val.rjust(width)
2499 text += pad + sep + val
2500 pad = " " * (width - len(val))
2507 if i.row() > last_row:
2513 text += sep + ToCSValue(str(i.data()))
2516 width = max_width[i.column() - min_col]
2517 if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2518 val = str(i.data()).rjust(width)
2521 text += pad + sep + val
2522 pad = " " * (width - len(val))
2524 QApplication.clipboard().setText(text)
2526 def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2527 indexes = view.selectedIndexes()
2528 if not len(indexes):
2531 selection = view.selectionModel()
2535 above = view.indexAbove(i)
2536 if not selection.isSelected(above):
2541 raise RuntimeError("CopyTreeCellsToClipboard internal error")
2543 model = first.model()
2545 col_cnt = model.columnCount(first)
2546 max_width = [0] * col_cnt
2549 indent_str = " " * indent_sz
2551 expanded_mark_sz = 2
2552 if sys.version_info[0] == 3:
2553 expanded_mark = "\u25BC "
2554 not_expanded_mark = "\u25B6 "
2556 expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2557 not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2565 for c in range(col_cnt):
2566 i = pos.sibling(row, c)
2568 n = len(str(i.data()))
2570 n = len(str(i.data()).strip())
2571 n += (i.internalPointer().level - 1) * indent_sz
2572 n += expanded_mark_sz
2573 max_width[c] = max(max_width[c], n)
2574 pos = view.indexBelow(pos)
2575 if not selection.isSelected(pos):
2582 for c in range(col_cnt):
2583 val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2585 text += sep + ToCSValue(val)
2588 max_width[c] = max(max_width[c], len(val))
2589 width = max_width[c]
2590 align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2591 if align & Qt.AlignRight:
2592 val = val.rjust(width)
2593 text += pad + sep + val
2594 pad = " " * (width - len(val))
2603 for c in range(col_cnt):
2604 i = pos.sibling(row, c)
2607 if model.hasChildren(i):
2608 if view.isExpanded(i):
2609 mark = expanded_mark
2611 mark = not_expanded_mark
2614 val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2616 text += sep + ToCSValue(val)
2619 width = max_width[c]
2620 if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2621 val = val.rjust(width)
2622 text += pad + sep + val
2623 pad = " " * (width - len(val))
2625 pos = view.indexBelow(pos)
2626 if not selection.isSelected(pos):
2628 text = text.rstrip() + "\n"
2632 QApplication.clipboard().setText(text)
2634 def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2635 view.CopyCellsToClipboard(view, as_csv, with_hdr)
2637 def CopyCellsToClipboardHdr(view):
2638 CopyCellsToClipboard(view, False, True)
2640 def CopyCellsToClipboardCSV(view):
2641 CopyCellsToClipboard(view, True, True)
2645 class ContextMenu(object):
2647 def __init__(self, view):
2649 self.view.setContextMenuPolicy(Qt.CustomContextMenu)
2650 self.view.customContextMenuRequested.connect(self.ShowContextMenu)
2652 def ShowContextMenu(self, pos):
2653 menu = QMenu(self.view)
2654 self.AddActions(menu)
2655 menu.exec_(self.view.mapToGlobal(pos))
2657 def AddCopy(self, menu):
2658 menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
2659 menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
2661 def AddActions(self, menu):
2664 class TreeContextMenu(ContextMenu):
2666 def __init__(self, view):
2667 super(TreeContextMenu, self).__init__(view)
2669 def AddActions(self, menu):
2670 i = self.view.currentIndex()
2671 text = str(i.data()).strip()
2673 menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
2678 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2680 def __init__(self, glb, table_name, parent=None):
2681 super(TableWindow, self).__init__(parent)
2683 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2685 self.model = QSortFilterProxyModel()
2686 self.model.setSourceModel(self.data_model)
2688 self.view = QTableView()
2689 self.view.setModel(self.model)
2690 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2691 self.view.verticalHeader().setVisible(False)
2692 self.view.sortByColumn(-1, Qt.AscendingOrder)
2693 self.view.setSortingEnabled(True)
2694 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2695 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2697 self.ResizeColumnsToContents()
2699 self.context_menu = ContextMenu(self.view)
2701 self.find_bar = FindBar(self, self, True)
2703 self.finder = ChildDataItemFinder(self.data_model)
2705 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2707 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2709 self.setWidget(self.vbox.Widget())
2711 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2713 def Find(self, value, direction, pattern, context):
2714 self.view.setFocus()
2715 self.find_bar.Busy()
2716 self.finder.Find(value, direction, pattern, context, self.FindDone)
2718 def FindDone(self, row):
2719 self.find_bar.Idle()
2721 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2723 self.find_bar.NotFound()
2727 def GetTableList(glb):
2729 query = QSqlQuery(glb.db)
2730 if glb.dbref.is_sqlite3:
2731 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2733 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2735 tables.append(query.value(0))
2736 if glb.dbref.is_sqlite3:
2737 tables.append("sqlite_master")
2739 tables.append("information_schema.tables")
2740 tables.append("information_schema.views")
2741 tables.append("information_schema.columns")
2744 # Top Calls data model
2746 class TopCallsModel(SQLTableModel):
2748 def __init__(self, glb, report_vars, parent=None):
2750 if not glb.dbref.is_sqlite3:
2753 if len(report_vars.limit):
2754 limit = " LIMIT " + report_vars.limit
2755 sql = ("SELECT comm, pid, tid, name,"
2757 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2760 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2762 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2763 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2764 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2768 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2769 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2770 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2771 " INNER JOIN comms ON calls.comm_id = comms.id"
2772 " INNER JOIN threads ON calls.thread_id = threads.id" +
2773 report_vars.where_clause +
2774 " ORDER BY elapsed_time DESC" +
2777 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2778 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2779 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2781 def columnAlignment(self, column):
2782 return self.alignment[column]
2784 # Top Calls report creation dialog
2786 class TopCallsDialog(ReportDialogBase):
2788 def __init__(self, glb, parent=None):
2789 title = "Top Calls by Elapsed Time"
2790 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2791 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2792 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2793 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2794 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2795 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2796 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2797 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2798 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2802 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2804 def __init__(self, glb, report_vars, parent=None):
2805 super(TopCallsWindow, self).__init__(parent)
2807 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2808 self.model = self.data_model
2810 self.view = QTableView()
2811 self.view.setModel(self.model)
2812 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2813 self.view.verticalHeader().setVisible(False)
2814 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2815 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2817 self.context_menu = ContextMenu(self.view)
2819 self.ResizeColumnsToContents()
2821 self.find_bar = FindBar(self, self, True)
2823 self.finder = ChildDataItemFinder(self.model)
2825 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2827 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2829 self.setWidget(self.vbox.Widget())
2831 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2833 def Find(self, value, direction, pattern, context):
2834 self.view.setFocus()
2835 self.find_bar.Busy()
2836 self.finder.Find(value, direction, pattern, context, self.FindDone)
2838 def FindDone(self, row):
2839 self.find_bar.Idle()
2841 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2843 self.find_bar.NotFound()
2847 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2848 action = QAction(label, parent)
2849 if shortcut != None:
2850 action.setShortcuts(shortcut)
2851 action.setStatusTip(tip)
2852 action.triggered.connect(callback)
2855 # Typical application actions
2857 def CreateExitAction(app, parent=None):
2858 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2860 # Typical MDI actions
2862 def CreateCloseActiveWindowAction(mdi_area):
2863 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2865 def CreateCloseAllWindowsAction(mdi_area):
2866 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2868 def CreateTileWindowsAction(mdi_area):
2869 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2871 def CreateCascadeWindowsAction(mdi_area):
2872 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2874 def CreateNextWindowAction(mdi_area):
2875 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2877 def CreatePreviousWindowAction(mdi_area):
2878 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2880 # Typical MDI window menu
2884 def __init__(self, mdi_area, menu):
2885 self.mdi_area = mdi_area
2886 self.window_menu = menu.addMenu("&Windows")
2887 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2888 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2889 self.tile_windows = CreateTileWindowsAction(mdi_area)
2890 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2891 self.next_window = CreateNextWindowAction(mdi_area)
2892 self.previous_window = CreatePreviousWindowAction(mdi_area)
2893 self.window_menu.aboutToShow.connect(self.Update)
2896 self.window_menu.clear()
2897 sub_window_count = len(self.mdi_area.subWindowList())
2898 have_sub_windows = sub_window_count != 0
2899 self.close_active_window.setEnabled(have_sub_windows)
2900 self.close_all_windows.setEnabled(have_sub_windows)
2901 self.tile_windows.setEnabled(have_sub_windows)
2902 self.cascade_windows.setEnabled(have_sub_windows)
2903 self.next_window.setEnabled(have_sub_windows)
2904 self.previous_window.setEnabled(have_sub_windows)
2905 self.window_menu.addAction(self.close_active_window)
2906 self.window_menu.addAction(self.close_all_windows)
2907 self.window_menu.addSeparator()
2908 self.window_menu.addAction(self.tile_windows)
2909 self.window_menu.addAction(self.cascade_windows)
2910 self.window_menu.addSeparator()
2911 self.window_menu.addAction(self.next_window)
2912 self.window_menu.addAction(self.previous_window)
2913 if sub_window_count == 0:
2915 self.window_menu.addSeparator()
2917 for sub_window in self.mdi_area.subWindowList():
2918 label = str(nr) + " " + sub_window.name
2921 action = self.window_menu.addAction(label)
2922 action.setCheckable(True)
2923 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2924 action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
2925 self.window_menu.addAction(action)
2928 def setActiveSubWindow(self, nr):
2929 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2944 <p class=c1><a href=#reports>1. Reports</a></p>
2945 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2946 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2947 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
2948 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2949 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2950 <p class=c1><a href=#tables>2. Tables</a></p>
2951 <h1 id=reports>1. Reports</h1>
2952 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2953 The result is a GUI window with a tree representing a context-sensitive
2954 call-graph. Expanding a couple of levels of the tree and adjusting column
2955 widths to suit will display something like:
2957 Call Graph: pt_example
2958 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2961 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2962 |- unknown unknown 1 13198 0.1 1 0.0
2963 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2964 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2965 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2966 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2967 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2968 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2969 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2970 v- main ls 1 8182043 99.6 180254 99.9
2972 <h3>Points to note:</h3>
2974 <li>The top level is a command name (comm)</li>
2975 <li>The next level is a thread (pid:tid)</li>
2976 <li>Subsequent levels are functions</li>
2977 <li>'Count' is the number of calls</li>
2978 <li>'Time' is the elapsed time until the function returns</li>
2979 <li>Percentages are relative to the level above</li>
2980 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2983 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2984 The pattern matching symbols are ? for any character and * for zero or more characters.
2985 <h2 id=calltree>1.2 Call Tree</h2>
2986 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2987 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2988 <h2 id=allbranches>1.3 All branches</h2>
2989 The All branches report displays all branches in chronological order.
2990 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2991 <h3>Disassembly</h3>
2992 Open a branch to display disassembly. This only works if:
2994 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2995 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2996 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2997 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2998 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
3000 <h4 id=xed>Intel XED Setup</h4>
3001 To use Intel XED, libxed.so must be present. To build and install libxed.so:
3003 git clone https://github.com/intelxed/mbuild.git mbuild
3004 git clone https://github.com/intelxed/xed
3007 sudo ./mfile.py --prefix=/usr/local install
3010 <h3>Instructions per Cycle (IPC)</h3>
3011 If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
3012 <p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
3013 Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
3014 In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
3015 since the previous displayed 'IPC'.
3017 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3018 Refer to Python documentation for the regular expression syntax.
3019 All columns are searched, but only currently fetched rows are searched.
3020 <h2 id=selectedbranches>1.4 Selected branches</h2>
3021 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
3022 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3023 <h3>1.4.1 Time ranges</h3>
3024 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
3025 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
3027 81073085947329-81073085958238 From 81073085947329 to 81073085958238
3028 100us-200us From 100us to 200us
3029 10ms- From 10ms to the end
3030 -100ns The first 100ns
3031 -10ms- The last 10ms
3033 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
3034 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
3035 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.
3036 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3037 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
3038 <h1 id=tables>2. Tables</h1>
3039 The Tables menu shows all tables and views in the database. Most tables have an associated view
3040 which displays the information in a more friendly way. Not all data for large tables is fetched
3041 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
3042 but that can be slow for large tables.
3043 <p>There are also tables of database meta-information.
3044 For SQLite3 databases, the sqlite_master table is included.
3045 For PostgreSQL databases, information_schema.tables/views/columns are included.
3047 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3048 Refer to Python documentation for the regular expression syntax.
3049 All columns are searched, but only currently fetched rows are searched.
3050 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
3051 will go to the next/previous result in id order, instead of display order.
3056 class HelpWindow(QMdiSubWindow):
3058 def __init__(self, glb, parent=None):
3059 super(HelpWindow, self).__init__(parent)
3061 self.text = QTextBrowser()
3062 self.text.setHtml(glb_help_text)
3063 self.text.setReadOnly(True)
3064 self.text.setOpenExternalLinks(True)
3066 self.setWidget(self.text)
3068 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
3070 # Main window that only displays the help text
3072 class HelpOnlyWindow(QMainWindow):
3074 def __init__(self, parent=None):
3075 super(HelpOnlyWindow, self).__init__(parent)
3077 self.setMinimumSize(200, 100)
3078 self.resize(800, 600)
3079 self.setWindowTitle("Exported SQL Viewer Help")
3080 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
3082 self.text = QTextBrowser()
3083 self.text.setHtml(glb_help_text)
3084 self.text.setReadOnly(True)
3085 self.text.setOpenExternalLinks(True)
3087 self.setCentralWidget(self.text)
3089 # PostqreSQL server version
3091 def PostqreSQLServerVersion(db):
3092 query = QSqlQuery(db)
3093 QueryExec(query, "SELECT VERSION()")
3095 v_str = query.value(0)
3096 v_list = v_str.strip().split(" ")
3097 if v_list[0] == "PostgreSQL" and v_list[2] == "on":
3104 def SQLiteVersion(db):
3105 query = QSqlQuery(db)
3106 QueryExec(query, "SELECT sqlite_version()")
3108 return query.value(0)
3113 class AboutDialog(QDialog):
3115 def __init__(self, glb, parent=None):
3116 super(AboutDialog, self).__init__(parent)
3118 self.setWindowTitle("About Exported SQL Viewer")
3119 self.setMinimumWidth(300)
3121 pyside_version = "1" if pyside_version_1 else "2"
3124 text += "Python version: " + sys.version.split(" ")[0] + "\n"
3125 text += "PySide version: " + pyside_version + "\n"
3126 text += "Qt version: " + qVersion() + "\n"
3127 if glb.dbref.is_sqlite3:
3128 text += "SQLite version: " + SQLiteVersion(glb.db) + "\n"
3130 text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
3133 self.text = QTextBrowser()
3134 self.text.setHtml(text)
3135 self.text.setReadOnly(True)
3136 self.text.setOpenExternalLinks(True)
3138 self.vbox = QVBoxLayout()
3139 self.vbox.addWidget(self.text)
3141 self.setLayout(self.vbox);
3145 def ResizeFont(widget, diff):
3146 font = widget.font()
3147 sz = font.pointSize()
3148 font.setPointSize(sz + diff)
3149 widget.setFont(font)
3151 def ShrinkFont(widget):
3152 ResizeFont(widget, -1)
3154 def EnlargeFont(widget):
3155 ResizeFont(widget, 1)
3157 # Unique name for sub-windows
3159 def NumberedWindowName(name, nr):
3161 name += " <" + str(nr) + ">"
3164 def UniqueSubWindowName(mdi_area, name):
3167 unique_name = NumberedWindowName(name, nr)
3169 for sub_window in mdi_area.subWindowList():
3170 if sub_window.name == unique_name:
3179 def AddSubWindow(mdi_area, sub_window, name):
3180 unique_name = UniqueSubWindowName(mdi_area, name)
3181 sub_window.setMinimumSize(200, 100)
3182 sub_window.resize(800, 600)
3183 sub_window.setWindowTitle(unique_name)
3184 sub_window.setAttribute(Qt.WA_DeleteOnClose)
3185 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
3186 sub_window.name = unique_name
3187 mdi_area.addSubWindow(sub_window)
3192 class MainWindow(QMainWindow):
3194 def __init__(self, glb, parent=None):
3195 super(MainWindow, self).__init__(parent)
3199 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3200 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3201 self.setMinimumSize(200, 100)
3203 self.mdi_area = QMdiArea()
3204 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3205 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3207 self.setCentralWidget(self.mdi_area)
3209 menu = self.menuBar()
3211 file_menu = menu.addMenu("&File")
3212 file_menu.addAction(CreateExitAction(glb.app, self))
3214 edit_menu = menu.addMenu("&Edit")
3215 edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
3216 edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3217 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
3218 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
3219 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
3220 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3222 reports_menu = menu.addMenu("&Reports")
3223 if IsSelectable(glb.db, "calls"):
3224 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
3226 if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3227 reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3229 self.EventMenu(GetEventList(glb.db), reports_menu)
3231 if IsSelectable(glb.db, "calls"):
3232 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3234 self.TableMenu(GetTableList(glb), menu)
3236 self.window_menu = WindowMenu(self.mdi_area, menu)
3238 help_menu = menu.addMenu("&Help")
3239 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3240 help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
3243 win = self.mdi_area.activeSubWindow()
3250 def CopyToClipboard(self):
3251 self.Try(CopyCellsToClipboardHdr)
3253 def CopyToClipboardCSV(self):
3254 self.Try(CopyCellsToClipboardCSV)
3257 win = self.mdi_area.activeSubWindow()
3260 win.find_bar.Activate()
3264 def FetchMoreRecords(self):
3265 win = self.mdi_area.activeSubWindow()
3268 win.fetch_bar.Activate()
3272 def ShrinkFont(self):
3273 self.Try(ShrinkFont)
3275 def EnlargeFont(self):
3276 self.Try(EnlargeFont)
3278 def EventMenu(self, events, reports_menu):
3280 for event in events:
3281 event = event.split(":")[0]
3282 if event == "branches":
3283 branches_events += 1
3285 for event in events:
3287 event = event.split(":")[0]
3288 if event == "branches":
3289 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3290 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
3291 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3292 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
3294 def TableMenu(self, tables, menu):
3295 table_menu = menu.addMenu("&Tables")
3296 for table in tables:
3297 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
3299 def NewCallGraph(self):
3300 CallGraphWindow(self.glb, self)
3302 def NewCallTree(self):
3303 CallTreeWindow(self.glb, self)
3305 def NewTopCalls(self):
3306 dialog = TopCallsDialog(self.glb, self)
3307 ret = dialog.exec_()
3309 TopCallsWindow(self.glb, dialog.report_vars, self)
3311 def NewBranchView(self, event_id):
3312 BranchWindow(self.glb, event_id, ReportVars(), self)
3314 def NewSelectedBranchView(self, event_id):
3315 dialog = SelectedBranchDialog(self.glb, self)
3316 ret = dialog.exec_()
3318 BranchWindow(self.glb, event_id, dialog.report_vars, self)
3320 def NewTableView(self, table_name):
3321 TableWindow(self.glb, table_name, self)
3324 HelpWindow(self.glb, self)
3327 dialog = AboutDialog(self.glb, self)
3332 class xed_state_t(Structure):
3339 class XEDInstruction():
3341 def __init__(self, libxed):
3342 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3343 xedd_t = c_byte * 512
3344 self.xedd = xedd_t()
3345 self.xedp = addressof(self.xedd)
3346 libxed.xed_decoded_inst_zero(self.xedp)
3347 self.state = xed_state_t()
3348 self.statep = addressof(self.state)
3349 # Buffer for disassembled instruction text
3350 self.buffer = create_string_buffer(256)
3351 self.bufferp = addressof(self.buffer)
3357 self.libxed = CDLL("libxed.so")
3361 self.libxed = CDLL("/usr/local/lib/libxed.so")
3363 self.xed_tables_init = self.libxed.xed_tables_init
3364 self.xed_tables_init.restype = None
3365 self.xed_tables_init.argtypes = []
3367 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3368 self.xed_decoded_inst_zero.restype = None
3369 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3371 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3372 self.xed_operand_values_set_mode.restype = None
3373 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3375 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3376 self.xed_decoded_inst_zero_keep_mode.restype = None
3377 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3379 self.xed_decode = self.libxed.xed_decode
3380 self.xed_decode.restype = c_int
3381 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3383 self.xed_format_context = self.libxed.xed_format_context
3384 self.xed_format_context.restype = c_uint
3385 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3387 self.xed_tables_init()
3389 def Instruction(self):
3390 return XEDInstruction(self)
3392 def SetMode(self, inst, mode):
3394 inst.state.mode = 4 # 32-bit
3395 inst.state.width = 4 # 4 bytes
3397 inst.state.mode = 1 # 64-bit
3398 inst.state.width = 8 # 8 bytes
3399 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3401 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3402 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3403 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3406 # Use AT&T mode (2), alternative is Intel (3)
3407 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3410 if sys.version_info[0] == 2:
3411 result = inst.buffer.value
3413 result = inst.buffer.value.decode()
3414 # Return instruction length and the disassembled instruction text
3415 # For now, assume the length is in byte 166
3416 return inst.xedd[166], result
3418 def TryOpen(file_name):
3420 return open(file_name, "rb")
3425 result = sizeof(c_void_p)
3432 if sys.version_info[0] == 2:
3433 eclass = ord(header[4])
3434 encoding = ord(header[5])
3435 version = ord(header[6])
3438 encoding = header[5]
3440 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3441 result = True if eclass == 2 else False
3448 def __init__(self, dbref, db, dbname):
3451 self.dbname = dbname
3452 self.home_dir = os.path.expanduser("~")
3453 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3454 if self.buildid_dir:
3455 self.buildid_dir += "/.build-id/"
3457 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3459 self.mainwindow = None
3460 self.instances_to_shutdown_on_exit = weakref.WeakSet()
3462 self.disassembler = LibXED()
3463 self.have_disassembler = True
3465 self.have_disassembler = False
3467 def FileFromBuildId(self, build_id):
3468 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3469 return TryOpen(file_name)
3471 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3472 # Assume current machine i.e. no support for virtualization
3473 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3474 file_name = os.getenv("PERF_KCORE")
3475 f = TryOpen(file_name) if file_name else None
3478 # For now, no special handling if long_name is /proc/kcore
3479 f = TryOpen(long_name)
3482 f = self.FileFromBuildId(build_id)
3487 def AddInstanceToShutdownOnExit(self, instance):
3488 self.instances_to_shutdown_on_exit.add(instance)
3490 # Shutdown any background processes or threads
3491 def ShutdownInstances(self):
3492 for x in self.instances_to_shutdown_on_exit:
3498 # Database reference
3502 def __init__(self, is_sqlite3, dbname):
3503 self.is_sqlite3 = is_sqlite3
3504 self.dbname = dbname
3506 def Open(self, connection_name):
3507 dbname = self.dbname
3509 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3511 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3512 opts = dbname.split()
3515 opt = opt.split("=")
3516 if opt[0] == "hostname":
3517 db.setHostName(opt[1])
3518 elif opt[0] == "port":
3519 db.setPort(int(opt[1]))
3520 elif opt[0] == "username":
3521 db.setUserName(opt[1])
3522 elif opt[0] == "password":
3523 db.setPassword(opt[1])
3524 elif opt[0] == "dbname":
3529 db.setDatabaseName(dbname)
3531 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3537 usage_str = "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
3538 " or: exported-sql-viewer.py --help-only"
3539 ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
3540 ap.add_argument("--pyside-version-1", action='store_true')
3541 ap.add_argument("dbname", nargs="?")
3542 ap.add_argument("--help-only", action='store_true')
3543 args = ap.parse_args()
3546 app = QApplication(sys.argv)
3547 mainwindow = HelpOnlyWindow()
3552 dbname = args.dbname
3555 print("Too few arguments")
3560 f = open(dbname, "rb")
3561 if f.read(15) == b'SQLite format 3':
3567 dbref = DBRef(is_sqlite3, dbname)
3568 db, dbname = dbref.Open("main")
3569 glb = Glb(dbref, db, dbname)
3570 app = QApplication(sys.argv)
3572 mainwindow = MainWindow(glb)
3573 glb.mainwindow = mainwindow
3576 glb.ShutdownInstances()
3580 if __name__ == "__main__":