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, 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.branch_count = branch_count
793 self.query_done = True;
794 if self.calls_id == 0:
795 comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
798 query = QSqlQuery(self.glb.db)
799 QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
801 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
802 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
803 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
804 " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
805 " ORDER BY call_time, calls.id")
807 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)), int(query.value(5)), self)
808 self.child_items.append(child_item)
809 self.child_count += 1
811 # Call tree data model level three item
813 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
815 def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
816 super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
818 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
821 # Call tree data model level two item
823 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
825 def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
826 super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, parent_item)
827 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
828 self.dbid = thread_id
831 super(CallTreeLevelTwoItem, self).Select()
832 for child_item in self.child_items:
833 self.time += child_item.time
834 self.branch_count += child_item.branch_count
835 for child_item in self.child_items:
836 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
837 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
839 # Call tree data model level one item
841 class CallTreeLevelOneItem(CallGraphLevelItemBase):
843 def __init__(self, glb, params, row, comm_id, comm, parent_item):
844 super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
845 self.data = [comm, "", "", "", "", "", ""]
849 self.query_done = True;
850 query = QSqlQuery(self.glb.db)
851 QueryExec(query, "SELECT thread_id, pid, tid"
853 " INNER JOIN threads ON thread_id = threads.id"
854 " WHERE comm_id = " + str(self.dbid))
856 child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
857 self.child_items.append(child_item)
858 self.child_count += 1
860 # Call tree data model root item
862 class CallTreeRootItem(CallGraphLevelItemBase):
864 def __init__(self, glb, params):
865 super(CallTreeRootItem, self).__init__(glb, params, 0, None)
867 self.query_done = True;
868 query = QSqlQuery(glb.db)
869 QueryExec(query, "SELECT id, comm FROM comms")
871 if not query.value(0):
873 child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
874 self.child_items.append(child_item)
875 self.child_count += 1
877 # Call Tree data model
879 class CallTreeModel(CallGraphModelBase):
881 def __init__(self, glb, parent=None):
882 super(CallTreeModel, self).__init__(glb, parent)
885 return CallTreeRootItem(self.glb, self.params)
887 def columnCount(self, parent=None):
890 def columnHeader(self, column):
891 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
892 return headers[column]
894 def columnAlignment(self, column):
895 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
896 return alignment[column]
898 def DoFindSelect(self, query, match):
899 QueryExec(query, "SELECT calls.id, comm_id, thread_id"
901 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
902 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
903 " WHERE symbols.name" + match +
904 " ORDER BY comm_id, thread_id, call_time, calls.id")
906 def FindPath(self, query):
907 # Turn the query result into a list of ids that the tree view can walk
908 # to open the tree at the right place.
910 parent_id = query.value(0)
912 ids.insert(0, parent_id)
913 q2 = QSqlQuery(self.glb.db)
914 QueryExec(q2, "SELECT parent_id"
916 " WHERE id = " + str(parent_id))
919 parent_id = q2.value(0)
920 ids.insert(0, query.value(2))
921 ids.insert(0, query.value(1))
924 # Vertical widget layout
928 def __init__(self, w1, w2, w3=None):
929 self.vbox = QWidget()
930 self.vbox.setLayout(QVBoxLayout());
932 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
934 self.vbox.layout().addWidget(w1)
935 self.vbox.layout().addWidget(w2)
937 self.vbox.layout().addWidget(w3)
944 class TreeWindowBase(QMdiSubWindow):
946 def __init__(self, parent=None):
947 super(TreeWindowBase, self).__init__(parent)
952 self.view = QTreeView()
953 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
954 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
956 self.context_menu = TreeContextMenu(self.view)
958 def DisplayFound(self, ids):
961 parent = QModelIndex()
964 n = self.model.rowCount(parent)
965 for row in xrange(n):
966 child = self.model.index(row, 0, parent)
967 if child.internalPointer().dbid == dbid:
969 self.view.setCurrentIndex(child)
976 def Find(self, value, direction, pattern, context):
979 self.model.Find(value, direction, pattern, context, self.FindDone)
981 def FindDone(self, ids):
983 if not self.DisplayFound(ids):
987 self.find_bar.NotFound()
990 # Context-sensitive call graph window
992 class CallGraphWindow(TreeWindowBase):
994 def __init__(self, glb, parent=None):
995 super(CallGraphWindow, self).__init__(parent)
997 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
999 self.view.setModel(self.model)
1001 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1002 self.view.setColumnWidth(c, w)
1004 self.find_bar = FindBar(self, self)
1006 self.vbox = VBox(self.view, self.find_bar.Widget())
1008 self.setWidget(self.vbox.Widget())
1010 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
1014 class CallTreeWindow(TreeWindowBase):
1016 def __init__(self, glb, parent=None):
1017 super(CallTreeWindow, self).__init__(parent)
1019 self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
1021 self.view.setModel(self.model)
1023 for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1024 self.view.setColumnWidth(c, w)
1026 self.find_bar = FindBar(self, self)
1028 self.vbox = VBox(self.view, self.find_bar.Widget())
1030 self.setWidget(self.vbox.Widget())
1032 AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
1034 # Child data item finder
1036 class ChildDataItemFinder():
1038 def __init__(self, root):
1040 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
1044 def FindSelect(self):
1047 pattern = re.compile(self.value)
1048 for child in self.root.child_items:
1049 for column_data in child.data:
1050 if re.search(pattern, str(column_data)) is not None:
1051 self.rows.append(child.row)
1054 for child in self.root.child_items:
1055 for column_data in child.data:
1056 if self.value in str(column_data):
1057 self.rows.append(child.row)
1060 def FindValue(self):
1062 if self.last_value != self.value or self.pattern != self.last_pattern:
1064 if not len(self.rows):
1066 return self.rows[self.pos]
1068 def FindThread(self):
1069 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1070 row = self.FindValue()
1071 elif len(self.rows):
1072 if self.direction > 0:
1074 if self.pos >= len(self.rows):
1079 self.pos = len(self.rows) - 1
1080 row = self.rows[self.pos]
1085 def Find(self, value, direction, pattern, context, callback):
1086 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1087 # Use a thread so the UI is not blocked
1088 thread = Thread(self.FindThread)
1089 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1092 def FindDone(self, thread, callback, row):
1095 # Number of database records to fetch in one go
1097 glb_chunk_sz = 10000
1099 # Background process for SQL data fetcher
1101 class SQLFetcherProcess():
1103 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1104 # Need a unique connection name
1105 conn_name = "SQLFetcher" + str(os.getpid())
1106 self.db, dbname = dbref.Open(conn_name)
1108 self.buffer = buffer
1111 self.fetch_count = fetch_count
1112 self.fetching_done = fetching_done
1113 self.process_target = process_target
1114 self.wait_event = wait_event
1115 self.fetched_event = fetched_event
1117 self.query = QSqlQuery(self.db)
1118 self.query_limit = 0 if "$$last_id$$" in sql else 2
1122 self.local_head = self.head.value
1123 self.local_tail = self.tail.value
1126 if self.query_limit:
1127 if self.query_limit == 1:
1129 self.query_limit -= 1
1130 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1131 QueryExec(self.query, stmt)
1134 if not self.query.next():
1136 if not self.query.next():
1138 self.last_id = self.query.value(0)
1139 return self.prep(self.query)
1141 def WaitForTarget(self):
1143 self.wait_event.clear()
1144 target = self.process_target.value
1145 if target > self.fetched or target < 0:
1147 self.wait_event.wait()
1150 def HasSpace(self, sz):
1151 if self.local_tail <= self.local_head:
1152 space = len(self.buffer) - self.local_head
1155 if space >= glb_nsz:
1156 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1157 nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
1158 self.buffer[self.local_head : self.local_head + len(nd)] = nd
1160 if self.local_tail - self.local_head > sz:
1164 def WaitForSpace(self, sz):
1165 if self.HasSpace(sz):
1168 self.wait_event.clear()
1169 self.local_tail = self.tail.value
1170 if self.HasSpace(sz):
1172 self.wait_event.wait()
1174 def AddToBuffer(self, obj):
1175 d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
1177 nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
1179 self.WaitForSpace(sz)
1180 pos = self.local_head
1181 self.buffer[pos : pos + len(nd)] = nd
1182 self.buffer[pos + glb_nsz : pos + sz] = d
1183 self.local_head += sz
1185 def FetchBatch(self, batch_size):
1187 while batch_size > fetched:
1192 self.AddToBuffer(obj)
1195 self.fetched += fetched
1196 with self.fetch_count.get_lock():
1197 self.fetch_count.value += fetched
1198 self.head.value = self.local_head
1199 self.fetched_event.set()
1203 target = self.WaitForTarget()
1206 batch_size = min(glb_chunk_sz, target - self.fetched)
1207 self.FetchBatch(batch_size)
1208 self.fetching_done.value = True
1209 self.fetched_event.set()
1211 def SQLFetcherFn(*x):
1212 process = SQLFetcherProcess(*x)
1217 class SQLFetcher(QObject):
1219 done = Signal(object)
1221 def __init__(self, glb, sql, prep, process_data, parent=None):
1222 super(SQLFetcher, self).__init__(parent)
1223 self.process_data = process_data
1226 self.last_target = 0
1228 self.buffer_size = 16 * 1024 * 1024
1229 self.buffer = Array(c_char, self.buffer_size, lock=False)
1230 self.head = Value(c_longlong)
1231 self.tail = Value(c_longlong)
1233 self.fetch_count = Value(c_longlong)
1234 self.fetching_done = Value(c_bool)
1236 self.process_target = Value(c_longlong)
1237 self.wait_event = Event()
1238 self.fetched_event = Event()
1239 glb.AddInstanceToShutdownOnExit(self)
1240 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))
1241 self.process.start()
1242 self.thread = Thread(self.Thread)
1243 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1247 # Tell the thread and process to exit
1248 self.process_target.value = -1
1249 self.wait_event.set()
1251 self.fetching_done.value = True
1252 self.fetched_event.set()
1258 self.fetched_event.clear()
1259 fetch_count = self.fetch_count.value
1260 if fetch_count != self.last_count:
1262 if self.fetching_done.value:
1265 self.fetched_event.wait()
1266 count = fetch_count - self.last_count
1267 self.last_count = fetch_count
1268 self.fetched += count
1271 def Fetch(self, nr):
1273 # -1 inidcates there are no more
1275 result = self.fetched
1276 extra = result + nr - self.target
1278 self.target += extra
1279 # process_target < 0 indicates shutting down
1280 if self.process_target.value >= 0:
1281 self.process_target.value = self.target
1282 self.wait_event.set()
1285 def RemoveFromBuffer(self):
1286 pos = self.local_tail
1287 if len(self.buffer) - pos < glb_nsz:
1289 n = pickle.loads(self.buffer[pos : pos + glb_nsz])
1292 n = pickle.loads(self.buffer[0 : glb_nsz])
1294 obj = pickle.loads(self.buffer[pos : pos + n])
1295 self.local_tail = pos + n
1298 def ProcessData(self, count):
1299 for i in xrange(count):
1300 obj = self.RemoveFromBuffer()
1301 self.process_data(obj)
1302 self.tail.value = self.local_tail
1303 self.wait_event.set()
1304 self.done.emit(count)
1306 # Fetch more records bar
1308 class FetchMoreRecordsBar():
1310 def __init__(self, model, parent):
1313 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1314 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1316 self.fetch_count = QSpinBox()
1317 self.fetch_count.setRange(1, 1000000)
1318 self.fetch_count.setValue(10)
1319 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1321 self.fetch = QPushButton("Go!")
1322 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1323 self.fetch.released.connect(self.FetchMoreRecords)
1325 self.progress = QProgressBar()
1326 self.progress.setRange(0, 100)
1327 self.progress.hide()
1329 self.done_label = QLabel("All records fetched")
1330 self.done_label.hide()
1332 self.spacer = QLabel("")
1334 self.close_button = QToolButton()
1335 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1336 self.close_button.released.connect(self.Deactivate)
1338 self.hbox = QHBoxLayout()
1339 self.hbox.setContentsMargins(0, 0, 0, 0)
1341 self.hbox.addWidget(self.label)
1342 self.hbox.addWidget(self.fetch_count)
1343 self.hbox.addWidget(self.fetch)
1344 self.hbox.addWidget(self.spacer)
1345 self.hbox.addWidget(self.progress)
1346 self.hbox.addWidget(self.done_label)
1347 self.hbox.addWidget(self.close_button)
1349 self.bar = QWidget()
1350 self.bar.setLayout(self.hbox);
1353 self.in_progress = False
1354 self.model.progress.connect(self.Progress)
1358 if not model.HasMoreRecords():
1366 self.fetch.setFocus()
1368 def Deactivate(self):
1371 def Enable(self, enable):
1372 self.fetch.setEnabled(enable)
1373 self.fetch_count.setEnabled(enable)
1379 self.progress.show()
1382 self.in_progress = False
1384 self.progress.hide()
1389 return self.fetch_count.value() * glb_chunk_sz
1395 self.fetch_count.hide()
1398 self.done_label.show()
1400 def Progress(self, count):
1401 if self.in_progress:
1403 percent = ((count - self.start) * 100) / self.Target()
1407 self.progress.setValue(percent)
1409 # Count value of zero means no more records
1412 def FetchMoreRecords(self):
1415 self.progress.setValue(0)
1417 self.in_progress = True
1418 self.start = self.model.FetchMoreRecords(self.Target())
1420 # Brance data model level two item
1422 class BranchLevelTwoItem():
1424 def __init__(self, row, col, text, parent_item):
1426 self.parent_item = parent_item
1427 self.data = [""] * (col + 1)
1428 self.data[col] = text
1431 def getParentItem(self):
1432 return self.parent_item
1437 def childCount(self):
1440 def hasChildren(self):
1443 def getData(self, column):
1444 return self.data[column]
1446 # Brance data model level one item
1448 class BranchLevelOneItem():
1450 def __init__(self, glb, row, data, parent_item):
1453 self.parent_item = parent_item
1454 self.child_count = 0
1455 self.child_items = []
1456 self.data = data[1:]
1459 self.query_done = False
1460 self.br_col = len(self.data) - 1
1462 def getChildItem(self, row):
1463 return self.child_items[row]
1465 def getParentItem(self):
1466 return self.parent_item
1472 self.query_done = True
1474 if not self.glb.have_disassembler:
1477 query = QSqlQuery(self.glb.db)
1479 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1481 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1482 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1483 " WHERE samples.id = " + str(self.dbid))
1484 if not query.next():
1486 cpu = query.value(0)
1487 dso = query.value(1)
1488 sym = query.value(2)
1489 if dso == 0 or sym == 0:
1491 off = query.value(3)
1492 short_name = query.value(4)
1493 long_name = query.value(5)
1494 build_id = query.value(6)
1495 sym_start = query.value(7)
1498 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1500 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1501 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1502 " ORDER BY samples.id"
1504 if not query.next():
1506 if query.value(0) != dso:
1507 # Cannot disassemble from one dso to another
1509 bsym = query.value(1)
1510 boff = query.value(2)
1511 bsym_start = query.value(3)
1514 tot = bsym_start + boff + 1 - sym_start - off
1515 if tot <= 0 or tot > 16384:
1518 inst = self.glb.disassembler.Instruction()
1519 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1522 mode = 0 if Is64Bit(f) else 1
1523 self.glb.disassembler.SetMode(inst, mode)
1526 buf = create_string_buffer(tot + 16)
1527 f.seek(sym_start + off)
1528 buf.value = f.read(buf_sz)
1529 buf_ptr = addressof(buf)
1532 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1534 byte_str = tohex(ip).rjust(16)
1535 for k in xrange(cnt):
1536 byte_str += " %02x" % ord(buf[i])
1541 self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
1542 self.child_count += 1
1550 def childCount(self):
1551 if not self.query_done:
1553 if not self.child_count:
1555 return self.child_count
1557 def hasChildren(self):
1558 if not self.query_done:
1560 return self.child_count > 0
1562 def getData(self, column):
1563 return self.data[column]
1565 # Brance data model root item
1567 class BranchRootItem():
1570 self.child_count = 0
1571 self.child_items = []
1574 def getChildItem(self, row):
1575 return self.child_items[row]
1577 def getParentItem(self):
1583 def childCount(self):
1584 return self.child_count
1586 def hasChildren(self):
1587 return self.child_count > 0
1589 def getData(self, column):
1592 # Calculate instructions per cycle
1594 def CalcIPC(cyc_cnt, insn_cnt):
1595 if cyc_cnt and insn_cnt:
1596 ipc = Decimal(float(insn_cnt) / cyc_cnt)
1597 ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
1602 # Branch data preparation
1604 def BranchDataPrepBr(query, data):
1605 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1606 " (" + dsoname(query.value(11)) + ")" + " -> " +
1607 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1608 " (" + dsoname(query.value(15)) + ")")
1610 def BranchDataPrepIPC(query, data):
1611 insn_cnt = query.value(16)
1612 cyc_cnt = query.value(17)
1613 ipc = CalcIPC(cyc_cnt, insn_cnt)
1614 data.append(insn_cnt)
1615 data.append(cyc_cnt)
1618 def BranchDataPrep(query):
1620 for i in xrange(0, 8):
1621 data.append(query.value(i))
1622 BranchDataPrepBr(query, data)
1625 def BranchDataPrepWA(query):
1627 data.append(query.value(0))
1628 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1629 data.append("{:>19}".format(query.value(1)))
1630 for i in xrange(2, 8):
1631 data.append(query.value(i))
1632 BranchDataPrepBr(query, data)
1635 def BranchDataWithIPCPrep(query):
1637 for i in xrange(0, 8):
1638 data.append(query.value(i))
1639 BranchDataPrepIPC(query, data)
1640 BranchDataPrepBr(query, data)
1643 def BranchDataWithIPCPrepWA(query):
1645 data.append(query.value(0))
1646 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1647 data.append("{:>19}".format(query.value(1)))
1648 for i in xrange(2, 8):
1649 data.append(query.value(i))
1650 BranchDataPrepIPC(query, data)
1651 BranchDataPrepBr(query, data)
1656 class BranchModel(TreeModel):
1658 progress = Signal(object)
1660 def __init__(self, glb, event_id, where_clause, parent=None):
1661 super(BranchModel, self).__init__(glb, None, parent)
1662 self.event_id = event_id
1665 self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
1667 select_ipc = ", insn_count, cyc_count"
1668 prep_fn = BranchDataWithIPCPrep
1669 prep_wa_fn = BranchDataWithIPCPrepWA
1672 prep_fn = BranchDataPrep
1673 prep_wa_fn = BranchDataPrepWA
1674 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1675 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1676 " ip, symbols.name, sym_offset, dsos.short_name,"
1677 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1680 " INNER JOIN comms ON comm_id = comms.id"
1681 " INNER JOIN threads ON thread_id = threads.id"
1682 " INNER JOIN branch_types ON branch_type = branch_types.id"
1683 " INNER JOIN symbols ON symbol_id = symbols.id"
1684 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1685 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1686 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1687 " WHERE samples.id > $$last_id$$" + where_clause +
1688 " AND evsel_id = " + str(self.event_id) +
1689 " ORDER BY samples.id"
1690 " LIMIT " + str(glb_chunk_sz))
1691 if pyside_version_1 and sys.version_info[0] == 3:
1695 self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
1696 self.fetcher.done.connect(self.Update)
1697 self.fetcher.Fetch(glb_chunk_sz)
1700 return BranchRootItem()
1702 def columnCount(self, parent=None):
1708 def columnHeader(self, column):
1710 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
1712 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1714 def columnFont(self, column):
1719 if column != br_col:
1721 return QFont("Monospace")
1723 def DisplayData(self, item, index):
1725 self.FetchIfNeeded(item.row)
1726 return item.getData(index.column())
1728 def AddSample(self, data):
1729 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1730 self.root.child_items.append(child)
1733 def Update(self, fetched):
1736 self.progress.emit(0)
1737 child_count = self.root.child_count
1738 count = self.populated - child_count
1740 parent = QModelIndex()
1741 self.beginInsertRows(parent, child_count, child_count + count - 1)
1742 self.insertRows(child_count, count, parent)
1743 self.root.child_count += count
1744 self.endInsertRows()
1745 self.progress.emit(self.root.child_count)
1747 def FetchMoreRecords(self, count):
1748 current = self.root.child_count
1750 self.fetcher.Fetch(count)
1752 self.progress.emit(0)
1755 def HasMoreRecords(self):
1762 def __init__(self, name = "", where_clause = "", limit = ""):
1764 self.where_clause = where_clause
1768 return str(self.where_clause + ";" + self.limit)
1772 class BranchWindow(QMdiSubWindow):
1774 def __init__(self, glb, event_id, report_vars, parent=None):
1775 super(BranchWindow, self).__init__(parent)
1777 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
1779 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1781 self.view = QTreeView()
1782 self.view.setUniformRowHeights(True)
1783 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1784 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1785 self.view.setModel(self.model)
1787 self.ResizeColumnsToContents()
1789 self.context_menu = TreeContextMenu(self.view)
1791 self.find_bar = FindBar(self, self, True)
1793 self.finder = ChildDataItemFinder(self.model.root)
1795 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1797 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1799 self.setWidget(self.vbox.Widget())
1801 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1803 def ResizeColumnToContents(self, column, n):
1804 # Using the view's resizeColumnToContents() here is extrememly slow
1805 # so implement a crude alternative
1806 mm = "MM" if column else "MMMM"
1807 font = self.view.font()
1808 metrics = QFontMetrics(font)
1810 for row in xrange(n):
1811 val = self.model.root.child_items[row].data[column]
1812 len = metrics.width(str(val) + mm)
1813 max = len if len > max else max
1814 val = self.model.columnHeader(column)
1815 len = metrics.width(str(val) + mm)
1816 max = len if len > max else max
1817 self.view.setColumnWidth(column, max)
1819 def ResizeColumnsToContents(self):
1820 n = min(self.model.root.child_count, 100)
1822 # No data yet, so connect a signal to notify when there is
1823 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1825 columns = self.model.columnCount()
1826 for i in xrange(columns):
1827 self.ResizeColumnToContents(i, n)
1829 def UpdateColumnWidths(self, *x):
1830 # This only needs to be done once, so disconnect the signal now
1831 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1832 self.ResizeColumnsToContents()
1834 def Find(self, value, direction, pattern, context):
1835 self.view.setFocus()
1836 self.find_bar.Busy()
1837 self.finder.Find(value, direction, pattern, context, self.FindDone)
1839 def FindDone(self, row):
1840 self.find_bar.Idle()
1842 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1844 self.find_bar.NotFound()
1846 # Line edit data item
1848 class LineEditDataItem(object):
1850 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1853 self.placeholder_text = placeholder_text
1854 self.parent = parent
1857 self.value = default
1859 self.widget = QLineEdit(default)
1860 self.widget.editingFinished.connect(self.Validate)
1861 self.widget.textChanged.connect(self.Invalidate)
1864 self.validated = True
1866 if placeholder_text:
1867 self.widget.setPlaceholderText(placeholder_text)
1869 def TurnTextRed(self):
1871 palette = QPalette()
1872 palette.setColor(QPalette.Text,Qt.red)
1873 self.widget.setPalette(palette)
1876 def TurnTextNormal(self):
1878 palette = QPalette()
1879 self.widget.setPalette(palette)
1882 def InvalidValue(self, value):
1885 self.error = self.label + " invalid value '" + value + "'"
1886 self.parent.ShowMessage(self.error)
1888 def Invalidate(self):
1889 self.validated = False
1891 def DoValidate(self, input_string):
1892 self.value = input_string.strip()
1895 self.validated = True
1897 self.TurnTextNormal()
1898 self.parent.ClearMessage()
1899 input_string = self.widget.text()
1900 if not len(input_string.strip()):
1903 self.DoValidate(input_string)
1906 if not self.validated:
1909 self.parent.ShowMessage(self.error)
1913 def IsNumber(self, value):
1918 return str(x) == value
1920 # Non-negative integer ranges dialog data item
1922 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1924 def __init__(self, glb, label, placeholder_text, column_name, parent):
1925 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1927 self.column_name = column_name
1929 def DoValidate(self, input_string):
1932 for value in [x.strip() for x in input_string.split(",")]:
1934 vrange = value.split("-")
1935 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1936 return self.InvalidValue(value)
1937 ranges.append(vrange)
1939 if not self.IsNumber(value):
1940 return self.InvalidValue(value)
1941 singles.append(value)
1942 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1944 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1945 self.value = " OR ".join(ranges)
1947 # Positive integer dialog data item
1949 class PositiveIntegerDataItem(LineEditDataItem):
1951 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1952 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1954 def DoValidate(self, input_string):
1955 if not self.IsNumber(input_string.strip()):
1956 return self.InvalidValue(input_string)
1957 value = int(input_string.strip())
1959 return self.InvalidValue(input_string)
1960 self.value = str(value)
1962 # Dialog data item converted and validated using a SQL table
1964 class SQLTableDataItem(LineEditDataItem):
1966 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1967 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
1969 self.table_name = table_name
1970 self.match_column = match_column
1971 self.column_name1 = column_name1
1972 self.column_name2 = column_name2
1974 def ValueToIds(self, value):
1976 query = QSqlQuery(self.glb.db)
1977 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1978 ret = query.exec_(stmt)
1981 ids.append(str(query.value(0)))
1984 def DoValidate(self, input_string):
1986 for value in [x.strip() for x in input_string.split(",")]:
1987 ids = self.ValueToIds(value)
1991 return self.InvalidValue(value)
1992 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1993 if self.column_name2:
1994 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1996 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
1998 class SampleTimeRangesDataItem(LineEditDataItem):
2000 def __init__(self, glb, label, placeholder_text, column_name, parent):
2001 self.column_name = column_name
2005 self.last_time = 2 ** 64
2007 query = QSqlQuery(glb.db)
2008 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
2010 self.last_id = int(query.value(0))
2011 self.last_time = int(query.value(1))
2012 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
2014 self.first_time = int(query.value(0))
2015 if placeholder_text:
2016 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
2018 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
2020 def IdBetween(self, query, lower_id, higher_id, order):
2021 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
2023 return True, int(query.value(0))
2027 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
2028 query = QSqlQuery(self.glb.db)
2030 next_id = int((lower_id + higher_id) / 2)
2031 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2032 if not query.next():
2033 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
2035 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
2037 return str(higher_id)
2039 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2040 next_time = int(query.value(0))
2042 if target_time > next_time:
2046 if higher_id <= lower_id + 1:
2047 return str(higher_id)
2049 if target_time >= next_time:
2053 if higher_id <= lower_id + 1:
2054 return str(lower_id)
2056 def ConvertRelativeTime(self, val):
2061 elif suffix == "us":
2063 elif suffix == "ns":
2067 val = val[:-2].strip()
2068 if not self.IsNumber(val):
2070 val = int(val) * mult
2072 val += self.first_time
2074 val += self.last_time
2077 def ConvertTimeRange(self, vrange):
2079 vrange[0] = str(self.first_time)
2081 vrange[1] = str(self.last_time)
2082 vrange[0] = self.ConvertRelativeTime(vrange[0])
2083 vrange[1] = self.ConvertRelativeTime(vrange[1])
2084 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
2086 beg_range = max(int(vrange[0]), self.first_time)
2087 end_range = min(int(vrange[1]), self.last_time)
2088 if beg_range > self.last_time or end_range < self.first_time:
2090 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
2091 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
2094 def AddTimeRange(self, value, ranges):
2095 n = value.count("-")
2099 if value.split("-")[1].strip() == "":
2105 pos = findnth(value, "-", n)
2106 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
2107 if self.ConvertTimeRange(vrange):
2108 ranges.append(vrange)
2112 def DoValidate(self, input_string):
2114 for value in [x.strip() for x in input_string.split(",")]:
2115 if not self.AddTimeRange(value, ranges):
2116 return self.InvalidValue(value)
2117 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
2118 self.value = " OR ".join(ranges)
2120 # Report Dialog Base
2122 class ReportDialogBase(QDialog):
2124 def __init__(self, glb, title, items, partial, parent=None):
2125 super(ReportDialogBase, self).__init__(parent)
2129 self.report_vars = ReportVars()
2131 self.setWindowTitle(title)
2132 self.setMinimumWidth(600)
2134 self.data_items = [x(glb, self) for x in items]
2136 self.partial = partial
2138 self.grid = QGridLayout()
2140 for row in xrange(len(self.data_items)):
2141 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2142 self.grid.addWidget(self.data_items[row].widget, row, 1)
2144 self.status = QLabel()
2146 self.ok_button = QPushButton("Ok", self)
2147 self.ok_button.setDefault(True)
2148 self.ok_button.released.connect(self.Ok)
2149 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2151 self.cancel_button = QPushButton("Cancel", self)
2152 self.cancel_button.released.connect(self.reject)
2153 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2155 self.hbox = QHBoxLayout()
2156 #self.hbox.addStretch()
2157 self.hbox.addWidget(self.status)
2158 self.hbox.addWidget(self.ok_button)
2159 self.hbox.addWidget(self.cancel_button)
2161 self.vbox = QVBoxLayout()
2162 self.vbox.addLayout(self.grid)
2163 self.vbox.addLayout(self.hbox)
2165 self.setLayout(self.vbox);
2168 vars = self.report_vars
2169 for d in self.data_items:
2170 if d.id == "REPORTNAME":
2173 self.ShowMessage("Report name is required")
2175 for d in self.data_items:
2178 for d in self.data_items[1:]:
2180 vars.limit = d.value
2182 if len(vars.where_clause):
2183 vars.where_clause += " AND "
2184 vars.where_clause += d.value
2185 if len(vars.where_clause):
2187 vars.where_clause = " AND ( " + vars.where_clause + " ) "
2189 vars.where_clause = " WHERE " + vars.where_clause + " "
2192 def ShowMessage(self, msg):
2193 self.status.setText("<font color=#FF0000>" + msg)
2195 def ClearMessage(self):
2196 self.status.setText("")
2198 # Selected branch report creation dialog
2200 class SelectedBranchDialog(ReportDialogBase):
2202 def __init__(self, glb, parent=None):
2203 title = "Selected Branches"
2204 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2205 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2206 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2207 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2208 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2209 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2210 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2211 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2212 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2213 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2217 def GetEventList(db):
2219 query = QSqlQuery(db)
2220 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2222 events.append(query.value(0))
2225 # Is a table selectable
2227 def IsSelectable(db, table, sql = "", columns = "*"):
2228 query = QSqlQuery(db)
2230 QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
2235 # SQL table data model item
2237 class SQLTableItem():
2239 def __init__(self, row, data):
2243 def getData(self, column):
2244 return self.data[column]
2246 # SQL table data model
2248 class SQLTableModel(TableModel):
2250 progress = Signal(object)
2252 def __init__(self, glb, sql, column_headers, parent=None):
2253 super(SQLTableModel, self).__init__(parent)
2257 self.column_headers = column_headers
2258 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
2259 self.fetcher.done.connect(self.Update)
2260 self.fetcher.Fetch(glb_chunk_sz)
2262 def DisplayData(self, item, index):
2263 self.FetchIfNeeded(item.row)
2264 return item.getData(index.column())
2266 def AddSample(self, data):
2267 child = SQLTableItem(self.populated, data)
2268 self.child_items.append(child)
2271 def Update(self, fetched):
2274 self.progress.emit(0)
2275 child_count = self.child_count
2276 count = self.populated - child_count
2278 parent = QModelIndex()
2279 self.beginInsertRows(parent, child_count, child_count + count - 1)
2280 self.insertRows(child_count, count, parent)
2281 self.child_count += count
2282 self.endInsertRows()
2283 self.progress.emit(self.child_count)
2285 def FetchMoreRecords(self, count):
2286 current = self.child_count
2288 self.fetcher.Fetch(count)
2290 self.progress.emit(0)
2293 def HasMoreRecords(self):
2296 def columnCount(self, parent=None):
2297 return len(self.column_headers)
2299 def columnHeader(self, column):
2300 return self.column_headers[column]
2302 def SQLTableDataPrep(self, query, count):
2304 for i in xrange(count):
2305 data.append(query.value(i))
2308 # SQL automatic table data model
2310 class SQLAutoTableModel(SQLTableModel):
2312 def __init__(self, glb, table_name, parent=None):
2313 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2314 if table_name == "comm_threads_view":
2315 # For now, comm_threads_view has no id column
2316 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2318 query = QSqlQuery(glb.db)
2319 if glb.dbref.is_sqlite3:
2320 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2322 column_headers.append(query.value(1))
2323 if table_name == "sqlite_master":
2324 sql = "SELECT * FROM " + table_name
2326 if table_name[:19] == "information_schema.":
2327 sql = "SELECT * FROM " + table_name
2328 select_table_name = table_name[19:]
2329 schema = "information_schema"
2331 select_table_name = table_name
2333 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2335 column_headers.append(query.value(0))
2336 if pyside_version_1 and sys.version_info[0] == 3:
2337 if table_name == "samples_view":
2338 self.SQLTableDataPrep = self.samples_view_DataPrep
2339 if table_name == "samples":
2340 self.SQLTableDataPrep = self.samples_DataPrep
2341 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2343 def samples_view_DataPrep(self, query, count):
2345 data.append(query.value(0))
2346 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2347 data.append("{:>19}".format(query.value(1)))
2348 for i in xrange(2, count):
2349 data.append(query.value(i))
2352 def samples_DataPrep(self, query, count):
2355 data.append(query.value(i))
2356 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2357 data.append("{:>19}".format(query.value(9)))
2358 for i in xrange(10, count):
2359 data.append(query.value(i))
2362 # Base class for custom ResizeColumnsToContents
2364 class ResizeColumnsToContentsBase(QObject):
2366 def __init__(self, parent=None):
2367 super(ResizeColumnsToContentsBase, self).__init__(parent)
2369 def ResizeColumnToContents(self, column, n):
2370 # Using the view's resizeColumnToContents() here is extrememly slow
2371 # so implement a crude alternative
2372 font = self.view.font()
2373 metrics = QFontMetrics(font)
2375 for row in xrange(n):
2376 val = self.data_model.child_items[row].data[column]
2377 len = metrics.width(str(val) + "MM")
2378 max = len if len > max else max
2379 val = self.data_model.columnHeader(column)
2380 len = metrics.width(str(val) + "MM")
2381 max = len if len > max else max
2382 self.view.setColumnWidth(column, max)
2384 def ResizeColumnsToContents(self):
2385 n = min(self.data_model.child_count, 100)
2387 # No data yet, so connect a signal to notify when there is
2388 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2390 columns = self.data_model.columnCount()
2391 for i in xrange(columns):
2392 self.ResizeColumnToContents(i, n)
2394 def UpdateColumnWidths(self, *x):
2395 # This only needs to be done once, so disconnect the signal now
2396 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2397 self.ResizeColumnsToContents()
2399 # Convert value to CSV
2403 val = val.replace('"', '""')
2404 if "," in val or '"' in val:
2405 val = '"' + val + '"'
2408 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2412 def RowColumnKey(a):
2413 return a.row() * glb_max_cols + a.column()
2415 # Copy selected table cells to clipboard
2417 def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2418 indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2419 idx_cnt = len(indexes)
2424 min_row = indexes[0].row()
2425 max_row = indexes[0].row()
2426 min_col = indexes[0].column()
2427 max_col = indexes[0].column()
2429 min_row = min(min_row, i.row())
2430 max_row = max(max_row, i.row())
2431 min_col = min(min_col, i.column())
2432 max_col = max(max_col, i.column())
2433 if max_col > glb_max_cols:
2434 raise RuntimeError("glb_max_cols is too low")
2435 max_width = [0] * (1 + max_col - min_col)
2437 c = i.column() - min_col
2438 max_width[c] = max(max_width[c], len(str(i.data())))
2443 model = indexes[0].model()
2444 for col in range(min_col, max_col + 1):
2445 val = model.headerData(col, Qt.Horizontal)
2447 text += sep + ToCSValue(val)
2451 max_width[c] = max(max_width[c], len(val))
2452 width = max_width[c]
2453 align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2454 if align & Qt.AlignRight:
2455 val = val.rjust(width)
2456 text += pad + sep + val
2457 pad = " " * (width - len(val))
2464 if i.row() > last_row:
2470 text += sep + ToCSValue(str(i.data()))
2473 width = max_width[i.column() - min_col]
2474 if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2475 val = str(i.data()).rjust(width)
2478 text += pad + sep + val
2479 pad = " " * (width - len(val))
2481 QApplication.clipboard().setText(text)
2483 def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2484 indexes = view.selectedIndexes()
2485 if not len(indexes):
2488 selection = view.selectionModel()
2492 above = view.indexAbove(i)
2493 if not selection.isSelected(above):
2498 raise RuntimeError("CopyTreeCellsToClipboard internal error")
2500 model = first.model()
2502 col_cnt = model.columnCount(first)
2503 max_width = [0] * col_cnt
2506 indent_str = " " * indent_sz
2508 expanded_mark_sz = 2
2509 if sys.version_info[0] == 3:
2510 expanded_mark = "\u25BC "
2511 not_expanded_mark = "\u25B6 "
2513 expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2514 not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2522 for c in range(col_cnt):
2523 i = pos.sibling(row, c)
2525 n = len(str(i.data()))
2527 n = len(str(i.data()).strip())
2528 n += (i.internalPointer().level - 1) * indent_sz
2529 n += expanded_mark_sz
2530 max_width[c] = max(max_width[c], n)
2531 pos = view.indexBelow(pos)
2532 if not selection.isSelected(pos):
2539 for c in range(col_cnt):
2540 val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2542 text += sep + ToCSValue(val)
2545 max_width[c] = max(max_width[c], len(val))
2546 width = max_width[c]
2547 align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2548 if align & Qt.AlignRight:
2549 val = val.rjust(width)
2550 text += pad + sep + val
2551 pad = " " * (width - len(val))
2560 for c in range(col_cnt):
2561 i = pos.sibling(row, c)
2564 if model.hasChildren(i):
2565 if view.isExpanded(i):
2566 mark = expanded_mark
2568 mark = not_expanded_mark
2571 val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2573 text += sep + ToCSValue(val)
2576 width = max_width[c]
2577 if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2578 val = val.rjust(width)
2579 text += pad + sep + val
2580 pad = " " * (width - len(val))
2582 pos = view.indexBelow(pos)
2583 if not selection.isSelected(pos):
2585 text = text.rstrip() + "\n"
2589 QApplication.clipboard().setText(text)
2591 def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2592 view.CopyCellsToClipboard(view, as_csv, with_hdr)
2594 def CopyCellsToClipboardHdr(view):
2595 CopyCellsToClipboard(view, False, True)
2597 def CopyCellsToClipboardCSV(view):
2598 CopyCellsToClipboard(view, True, True)
2602 class ContextMenu(object):
2604 def __init__(self, view):
2606 self.view.setContextMenuPolicy(Qt.CustomContextMenu)
2607 self.view.customContextMenuRequested.connect(self.ShowContextMenu)
2609 def ShowContextMenu(self, pos):
2610 menu = QMenu(self.view)
2611 self.AddActions(menu)
2612 menu.exec_(self.view.mapToGlobal(pos))
2614 def AddCopy(self, menu):
2615 menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
2616 menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
2618 def AddActions(self, menu):
2621 class TreeContextMenu(ContextMenu):
2623 def __init__(self, view):
2624 super(TreeContextMenu, self).__init__(view)
2626 def AddActions(self, menu):
2627 i = self.view.currentIndex()
2628 text = str(i.data()).strip()
2630 menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
2635 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2637 def __init__(self, glb, table_name, parent=None):
2638 super(TableWindow, self).__init__(parent)
2640 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2642 self.model = QSortFilterProxyModel()
2643 self.model.setSourceModel(self.data_model)
2645 self.view = QTableView()
2646 self.view.setModel(self.model)
2647 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2648 self.view.verticalHeader().setVisible(False)
2649 self.view.sortByColumn(-1, Qt.AscendingOrder)
2650 self.view.setSortingEnabled(True)
2651 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2652 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2654 self.ResizeColumnsToContents()
2656 self.context_menu = ContextMenu(self.view)
2658 self.find_bar = FindBar(self, self, True)
2660 self.finder = ChildDataItemFinder(self.data_model)
2662 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2664 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2666 self.setWidget(self.vbox.Widget())
2668 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2670 def Find(self, value, direction, pattern, context):
2671 self.view.setFocus()
2672 self.find_bar.Busy()
2673 self.finder.Find(value, direction, pattern, context, self.FindDone)
2675 def FindDone(self, row):
2676 self.find_bar.Idle()
2678 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2680 self.find_bar.NotFound()
2684 def GetTableList(glb):
2686 query = QSqlQuery(glb.db)
2687 if glb.dbref.is_sqlite3:
2688 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2690 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2692 tables.append(query.value(0))
2693 if glb.dbref.is_sqlite3:
2694 tables.append("sqlite_master")
2696 tables.append("information_schema.tables")
2697 tables.append("information_schema.views")
2698 tables.append("information_schema.columns")
2701 # Top Calls data model
2703 class TopCallsModel(SQLTableModel):
2705 def __init__(self, glb, report_vars, parent=None):
2707 if not glb.dbref.is_sqlite3:
2710 if len(report_vars.limit):
2711 limit = " LIMIT " + report_vars.limit
2712 sql = ("SELECT comm, pid, tid, name,"
2714 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2717 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2719 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2720 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2721 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2725 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2726 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2727 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2728 " INNER JOIN comms ON calls.comm_id = comms.id"
2729 " INNER JOIN threads ON calls.thread_id = threads.id" +
2730 report_vars.where_clause +
2731 " ORDER BY elapsed_time DESC" +
2734 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2735 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2736 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2738 def columnAlignment(self, column):
2739 return self.alignment[column]
2741 # Top Calls report creation dialog
2743 class TopCallsDialog(ReportDialogBase):
2745 def __init__(self, glb, parent=None):
2746 title = "Top Calls by Elapsed Time"
2747 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2748 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2749 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2750 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2751 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2752 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2753 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2754 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2755 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2759 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2761 def __init__(self, glb, report_vars, parent=None):
2762 super(TopCallsWindow, self).__init__(parent)
2764 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2765 self.model = self.data_model
2767 self.view = QTableView()
2768 self.view.setModel(self.model)
2769 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2770 self.view.verticalHeader().setVisible(False)
2771 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2772 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2774 self.context_menu = ContextMenu(self.view)
2776 self.ResizeColumnsToContents()
2778 self.find_bar = FindBar(self, self, True)
2780 self.finder = ChildDataItemFinder(self.model)
2782 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2784 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2786 self.setWidget(self.vbox.Widget())
2788 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2790 def Find(self, value, direction, pattern, context):
2791 self.view.setFocus()
2792 self.find_bar.Busy()
2793 self.finder.Find(value, direction, pattern, context, self.FindDone)
2795 def FindDone(self, row):
2796 self.find_bar.Idle()
2798 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2800 self.find_bar.NotFound()
2804 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2805 action = QAction(label, parent)
2806 if shortcut != None:
2807 action.setShortcuts(shortcut)
2808 action.setStatusTip(tip)
2809 action.triggered.connect(callback)
2812 # Typical application actions
2814 def CreateExitAction(app, parent=None):
2815 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2817 # Typical MDI actions
2819 def CreateCloseActiveWindowAction(mdi_area):
2820 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2822 def CreateCloseAllWindowsAction(mdi_area):
2823 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2825 def CreateTileWindowsAction(mdi_area):
2826 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2828 def CreateCascadeWindowsAction(mdi_area):
2829 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2831 def CreateNextWindowAction(mdi_area):
2832 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2834 def CreatePreviousWindowAction(mdi_area):
2835 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2837 # Typical MDI window menu
2841 def __init__(self, mdi_area, menu):
2842 self.mdi_area = mdi_area
2843 self.window_menu = menu.addMenu("&Windows")
2844 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2845 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2846 self.tile_windows = CreateTileWindowsAction(mdi_area)
2847 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2848 self.next_window = CreateNextWindowAction(mdi_area)
2849 self.previous_window = CreatePreviousWindowAction(mdi_area)
2850 self.window_menu.aboutToShow.connect(self.Update)
2853 self.window_menu.clear()
2854 sub_window_count = len(self.mdi_area.subWindowList())
2855 have_sub_windows = sub_window_count != 0
2856 self.close_active_window.setEnabled(have_sub_windows)
2857 self.close_all_windows.setEnabled(have_sub_windows)
2858 self.tile_windows.setEnabled(have_sub_windows)
2859 self.cascade_windows.setEnabled(have_sub_windows)
2860 self.next_window.setEnabled(have_sub_windows)
2861 self.previous_window.setEnabled(have_sub_windows)
2862 self.window_menu.addAction(self.close_active_window)
2863 self.window_menu.addAction(self.close_all_windows)
2864 self.window_menu.addSeparator()
2865 self.window_menu.addAction(self.tile_windows)
2866 self.window_menu.addAction(self.cascade_windows)
2867 self.window_menu.addSeparator()
2868 self.window_menu.addAction(self.next_window)
2869 self.window_menu.addAction(self.previous_window)
2870 if sub_window_count == 0:
2872 self.window_menu.addSeparator()
2874 for sub_window in self.mdi_area.subWindowList():
2875 label = str(nr) + " " + sub_window.name
2878 action = self.window_menu.addAction(label)
2879 action.setCheckable(True)
2880 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2881 action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
2882 self.window_menu.addAction(action)
2885 def setActiveSubWindow(self, nr):
2886 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2901 <p class=c1><a href=#reports>1. Reports</a></p>
2902 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2903 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2904 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
2905 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2906 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2907 <p class=c1><a href=#tables>2. Tables</a></p>
2908 <h1 id=reports>1. Reports</h1>
2909 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2910 The result is a GUI window with a tree representing a context-sensitive
2911 call-graph. Expanding a couple of levels of the tree and adjusting column
2912 widths to suit will display something like:
2914 Call Graph: pt_example
2915 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2918 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2919 |- unknown unknown 1 13198 0.1 1 0.0
2920 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2921 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2922 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2923 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2924 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2925 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2926 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2927 v- main ls 1 8182043 99.6 180254 99.9
2929 <h3>Points to note:</h3>
2931 <li>The top level is a command name (comm)</li>
2932 <li>The next level is a thread (pid:tid)</li>
2933 <li>Subsequent levels are functions</li>
2934 <li>'Count' is the number of calls</li>
2935 <li>'Time' is the elapsed time until the function returns</li>
2936 <li>Percentages are relative to the level above</li>
2937 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2940 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2941 The pattern matching symbols are ? for any character and * for zero or more characters.
2942 <h2 id=calltree>1.2 Call Tree</h2>
2943 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2944 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2945 <h2 id=allbranches>1.3 All branches</h2>
2946 The All branches report displays all branches in chronological order.
2947 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2948 <h3>Disassembly</h3>
2949 Open a branch to display disassembly. This only works if:
2951 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2952 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2953 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2954 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2955 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2957 <h4 id=xed>Intel XED Setup</h4>
2958 To use Intel XED, libxed.so must be present. To build and install libxed.so:
2960 git clone https://github.com/intelxed/mbuild.git mbuild
2961 git clone https://github.com/intelxed/xed
2964 sudo ./mfile.py --prefix=/usr/local install
2967 <h3>Instructions per Cycle (IPC)</h3>
2968 If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
2969 <p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
2970 Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
2971 In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
2972 since the previous displayed 'IPC'.
2974 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2975 Refer to Python documentation for the regular expression syntax.
2976 All columns are searched, but only currently fetched rows are searched.
2977 <h2 id=selectedbranches>1.4 Selected branches</h2>
2978 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2979 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2980 <h3>1.4.1 Time ranges</h3>
2981 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2982 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
2984 81073085947329-81073085958238 From 81073085947329 to 81073085958238
2985 100us-200us From 100us to 200us
2986 10ms- From 10ms to the end
2987 -100ns The first 100ns
2988 -10ms- The last 10ms
2990 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2991 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2992 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.
2993 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2994 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
2995 <h1 id=tables>2. Tables</h1>
2996 The Tables menu shows all tables and views in the database. Most tables have an associated view
2997 which displays the information in a more friendly way. Not all data for large tables is fetched
2998 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2999 but that can be slow for large tables.
3000 <p>There are also tables of database meta-information.
3001 For SQLite3 databases, the sqlite_master table is included.
3002 For PostgreSQL databases, information_schema.tables/views/columns are included.
3004 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3005 Refer to Python documentation for the regular expression syntax.
3006 All columns are searched, but only currently fetched rows are searched.
3007 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
3008 will go to the next/previous result in id order, instead of display order.
3013 class HelpWindow(QMdiSubWindow):
3015 def __init__(self, glb, parent=None):
3016 super(HelpWindow, self).__init__(parent)
3018 self.text = QTextBrowser()
3019 self.text.setHtml(glb_help_text)
3020 self.text.setReadOnly(True)
3021 self.text.setOpenExternalLinks(True)
3023 self.setWidget(self.text)
3025 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
3027 # Main window that only displays the help text
3029 class HelpOnlyWindow(QMainWindow):
3031 def __init__(self, parent=None):
3032 super(HelpOnlyWindow, self).__init__(parent)
3034 self.setMinimumSize(200, 100)
3035 self.resize(800, 600)
3036 self.setWindowTitle("Exported SQL Viewer Help")
3037 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
3039 self.text = QTextBrowser()
3040 self.text.setHtml(glb_help_text)
3041 self.text.setReadOnly(True)
3042 self.text.setOpenExternalLinks(True)
3044 self.setCentralWidget(self.text)
3046 # PostqreSQL server version
3048 def PostqreSQLServerVersion(db):
3049 query = QSqlQuery(db)
3050 QueryExec(query, "SELECT VERSION()")
3052 v_str = query.value(0)
3053 v_list = v_str.strip().split(" ")
3054 if v_list[0] == "PostgreSQL" and v_list[2] == "on":
3061 def SQLiteVersion(db):
3062 query = QSqlQuery(db)
3063 QueryExec(query, "SELECT sqlite_version()")
3065 return query.value(0)
3070 class AboutDialog(QDialog):
3072 def __init__(self, glb, parent=None):
3073 super(AboutDialog, self).__init__(parent)
3075 self.setWindowTitle("About Exported SQL Viewer")
3076 self.setMinimumWidth(300)
3078 pyside_version = "1" if pyside_version_1 else "2"
3081 text += "Python version: " + sys.version.split(" ")[0] + "\n"
3082 text += "PySide version: " + pyside_version + "\n"
3083 text += "Qt version: " + qVersion() + "\n"
3084 if glb.dbref.is_sqlite3:
3085 text += "SQLite version: " + SQLiteVersion(glb.db) + "\n"
3087 text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
3090 self.text = QTextBrowser()
3091 self.text.setHtml(text)
3092 self.text.setReadOnly(True)
3093 self.text.setOpenExternalLinks(True)
3095 self.vbox = QVBoxLayout()
3096 self.vbox.addWidget(self.text)
3098 self.setLayout(self.vbox);
3102 def ResizeFont(widget, diff):
3103 font = widget.font()
3104 sz = font.pointSize()
3105 font.setPointSize(sz + diff)
3106 widget.setFont(font)
3108 def ShrinkFont(widget):
3109 ResizeFont(widget, -1)
3111 def EnlargeFont(widget):
3112 ResizeFont(widget, 1)
3114 # Unique name for sub-windows
3116 def NumberedWindowName(name, nr):
3118 name += " <" + str(nr) + ">"
3121 def UniqueSubWindowName(mdi_area, name):
3124 unique_name = NumberedWindowName(name, nr)
3126 for sub_window in mdi_area.subWindowList():
3127 if sub_window.name == unique_name:
3136 def AddSubWindow(mdi_area, sub_window, name):
3137 unique_name = UniqueSubWindowName(mdi_area, name)
3138 sub_window.setMinimumSize(200, 100)
3139 sub_window.resize(800, 600)
3140 sub_window.setWindowTitle(unique_name)
3141 sub_window.setAttribute(Qt.WA_DeleteOnClose)
3142 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
3143 sub_window.name = unique_name
3144 mdi_area.addSubWindow(sub_window)
3149 class MainWindow(QMainWindow):
3151 def __init__(self, glb, parent=None):
3152 super(MainWindow, self).__init__(parent)
3156 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3157 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3158 self.setMinimumSize(200, 100)
3160 self.mdi_area = QMdiArea()
3161 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3162 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3164 self.setCentralWidget(self.mdi_area)
3166 menu = self.menuBar()
3168 file_menu = menu.addMenu("&File")
3169 file_menu.addAction(CreateExitAction(glb.app, self))
3171 edit_menu = menu.addMenu("&Edit")
3172 edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
3173 edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3174 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
3175 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
3176 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
3177 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3179 reports_menu = menu.addMenu("&Reports")
3180 if IsSelectable(glb.db, "calls"):
3181 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
3183 if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3184 reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3186 self.EventMenu(GetEventList(glb.db), reports_menu)
3188 if IsSelectable(glb.db, "calls"):
3189 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3191 self.TableMenu(GetTableList(glb), menu)
3193 self.window_menu = WindowMenu(self.mdi_area, menu)
3195 help_menu = menu.addMenu("&Help")
3196 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3197 help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
3200 win = self.mdi_area.activeSubWindow()
3207 def CopyToClipboard(self):
3208 self.Try(CopyCellsToClipboardHdr)
3210 def CopyToClipboardCSV(self):
3211 self.Try(CopyCellsToClipboardCSV)
3214 win = self.mdi_area.activeSubWindow()
3217 win.find_bar.Activate()
3221 def FetchMoreRecords(self):
3222 win = self.mdi_area.activeSubWindow()
3225 win.fetch_bar.Activate()
3229 def ShrinkFont(self):
3230 self.Try(ShrinkFont)
3232 def EnlargeFont(self):
3233 self.Try(EnlargeFont)
3235 def EventMenu(self, events, reports_menu):
3237 for event in events:
3238 event = event.split(":")[0]
3239 if event == "branches":
3240 branches_events += 1
3242 for event in events:
3244 event = event.split(":")[0]
3245 if event == "branches":
3246 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3247 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
3248 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3249 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
3251 def TableMenu(self, tables, menu):
3252 table_menu = menu.addMenu("&Tables")
3253 for table in tables:
3254 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
3256 def NewCallGraph(self):
3257 CallGraphWindow(self.glb, self)
3259 def NewCallTree(self):
3260 CallTreeWindow(self.glb, self)
3262 def NewTopCalls(self):
3263 dialog = TopCallsDialog(self.glb, self)
3264 ret = dialog.exec_()
3266 TopCallsWindow(self.glb, dialog.report_vars, self)
3268 def NewBranchView(self, event_id):
3269 BranchWindow(self.glb, event_id, ReportVars(), self)
3271 def NewSelectedBranchView(self, event_id):
3272 dialog = SelectedBranchDialog(self.glb, self)
3273 ret = dialog.exec_()
3275 BranchWindow(self.glb, event_id, dialog.report_vars, self)
3277 def NewTableView(self, table_name):
3278 TableWindow(self.glb, table_name, self)
3281 HelpWindow(self.glb, self)
3284 dialog = AboutDialog(self.glb, self)
3289 class xed_state_t(Structure):
3296 class XEDInstruction():
3298 def __init__(self, libxed):
3299 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3300 xedd_t = c_byte * 512
3301 self.xedd = xedd_t()
3302 self.xedp = addressof(self.xedd)
3303 libxed.xed_decoded_inst_zero(self.xedp)
3304 self.state = xed_state_t()
3305 self.statep = addressof(self.state)
3306 # Buffer for disassembled instruction text
3307 self.buffer = create_string_buffer(256)
3308 self.bufferp = addressof(self.buffer)
3314 self.libxed = CDLL("libxed.so")
3318 self.libxed = CDLL("/usr/local/lib/libxed.so")
3320 self.xed_tables_init = self.libxed.xed_tables_init
3321 self.xed_tables_init.restype = None
3322 self.xed_tables_init.argtypes = []
3324 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3325 self.xed_decoded_inst_zero.restype = None
3326 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3328 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3329 self.xed_operand_values_set_mode.restype = None
3330 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3332 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3333 self.xed_decoded_inst_zero_keep_mode.restype = None
3334 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3336 self.xed_decode = self.libxed.xed_decode
3337 self.xed_decode.restype = c_int
3338 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3340 self.xed_format_context = self.libxed.xed_format_context
3341 self.xed_format_context.restype = c_uint
3342 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3344 self.xed_tables_init()
3346 def Instruction(self):
3347 return XEDInstruction(self)
3349 def SetMode(self, inst, mode):
3351 inst.state.mode = 4 # 32-bit
3352 inst.state.width = 4 # 4 bytes
3354 inst.state.mode = 1 # 64-bit
3355 inst.state.width = 8 # 8 bytes
3356 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3358 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3359 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3360 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3363 # Use AT&T mode (2), alternative is Intel (3)
3364 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3367 if sys.version_info[0] == 2:
3368 result = inst.buffer.value
3370 result = inst.buffer.value.decode()
3371 # Return instruction length and the disassembled instruction text
3372 # For now, assume the length is in byte 166
3373 return inst.xedd[166], result
3375 def TryOpen(file_name):
3377 return open(file_name, "rb")
3382 result = sizeof(c_void_p)
3389 if sys.version_info[0] == 2:
3390 eclass = ord(header[4])
3391 encoding = ord(header[5])
3392 version = ord(header[6])
3395 encoding = header[5]
3397 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3398 result = True if eclass == 2 else False
3405 def __init__(self, dbref, db, dbname):
3408 self.dbname = dbname
3409 self.home_dir = os.path.expanduser("~")
3410 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3411 if self.buildid_dir:
3412 self.buildid_dir += "/.build-id/"
3414 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3416 self.mainwindow = None
3417 self.instances_to_shutdown_on_exit = weakref.WeakSet()
3419 self.disassembler = LibXED()
3420 self.have_disassembler = True
3422 self.have_disassembler = False
3424 def FileFromBuildId(self, build_id):
3425 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3426 return TryOpen(file_name)
3428 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3429 # Assume current machine i.e. no support for virtualization
3430 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3431 file_name = os.getenv("PERF_KCORE")
3432 f = TryOpen(file_name) if file_name else None
3435 # For now, no special handling if long_name is /proc/kcore
3436 f = TryOpen(long_name)
3439 f = self.FileFromBuildId(build_id)
3444 def AddInstanceToShutdownOnExit(self, instance):
3445 self.instances_to_shutdown_on_exit.add(instance)
3447 # Shutdown any background processes or threads
3448 def ShutdownInstances(self):
3449 for x in self.instances_to_shutdown_on_exit:
3455 # Database reference
3459 def __init__(self, is_sqlite3, dbname):
3460 self.is_sqlite3 = is_sqlite3
3461 self.dbname = dbname
3463 def Open(self, connection_name):
3464 dbname = self.dbname
3466 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3468 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3469 opts = dbname.split()
3472 opt = opt.split("=")
3473 if opt[0] == "hostname":
3474 db.setHostName(opt[1])
3475 elif opt[0] == "port":
3476 db.setPort(int(opt[1]))
3477 elif opt[0] == "username":
3478 db.setUserName(opt[1])
3479 elif opt[0] == "password":
3480 db.setPassword(opt[1])
3481 elif opt[0] == "dbname":
3486 db.setDatabaseName(dbname)
3488 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3494 usage_str = "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
3495 " or: exported-sql-viewer.py --help-only"
3496 ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
3497 ap.add_argument("--pyside-version-1", action='store_true')
3498 ap.add_argument("dbname", nargs="?")
3499 ap.add_argument("--help-only", action='store_true')
3500 args = ap.parse_args()
3503 app = QApplication(sys.argv)
3504 mainwindow = HelpOnlyWindow()
3509 dbname = args.dbname
3512 print("Too few arguments")
3517 f = open(dbname, "rb")
3518 if f.read(15) == b'SQLite format 3':
3524 dbref = DBRef(is_sqlite3, dbname)
3525 db, dbname = dbref.Open("main")
3526 glb = Glb(dbref, db, dbname)
3527 app = QApplication(sys.argv)
3529 mainwindow = MainWindow(glb)
3530 glb.mainwindow = mainwindow
3533 glb.ShutdownInstances()
3537 if __name__ == "__main__":