2 # SPDX-License-Identifier: GPL-2.0
3 # exported-sql-viewer.py: view data from sql database
4 # Copyright (c) 2014-2018, Intel Corporation.
6 # To use this script you will need to have exported data using either the
7 # export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
10 # Following on from the example in the export scripts, a
11 # call-graph can be displayed for the pt_example database like this:
13 # python tools/perf/scripts/python/exported-sql-viewer.py pt_example
15 # Note that for PostgreSQL, this script supports connecting to remote databases
16 # by setting hostname, port, username, password, and dbname e.g.
18 # python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
20 # The result is a GUI window with a tree representing a context-sensitive
21 # call-graph. Expanding a couple of levels of the tree and adjusting column
22 # widths to suit will display something like:
24 # Call Graph: pt_example
25 # Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
28 # v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
29 # |- unknown unknown 1 13198 0.1 1 0.0
30 # >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
31 # >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
32 # v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
33 # >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
34 # >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
35 # >- __libc_csu_init ls 1 10354 0.1 10 0.0
36 # |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
37 # v- main ls 1 8182043 99.6 180254 99.9
40 # The top level is a command name (comm)
41 # The next level is a thread (pid:tid)
42 # Subsequent levels are functions
43 # 'Count' is the number of calls
44 # 'Time' is the elapsed time until the function returns
45 # Percentages are relative to the level above
46 # 'Branch Count' is the total number of branches for that function and all
47 # functions that it calls
49 # There is also a "All branches" report, which displays branches and
50 # possibly disassembly. However, presently, the only supported disassembler is
51 # Intel XED, and additionally the object code must be present in perf build ID
52 # cache. To use Intel XED, libxed.so must be present. To build and install
54 # git clone https://github.com/intelxed/mbuild.git mbuild
55 # git clone https://github.com/intelxed/xed
58 # sudo ./mfile.py --prefix=/usr/local install
63 # Time CPU Command PID TID Branch Type In Tx Branch
64 # 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
65 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
66 # 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
67 # 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
68 # 7fab593ea260 48 89 e7 mov %rsp, %rdi
69 # 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930
70 # 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
71 # 7fab593ea930 55 pushq %rbp
72 # 7fab593ea931 48 89 e5 mov %rsp, %rbp
73 # 7fab593ea934 41 57 pushq %r15
74 # 7fab593ea936 41 56 pushq %r14
75 # 7fab593ea938 41 55 pushq %r13
76 # 7fab593ea93a 41 54 pushq %r12
77 # 7fab593ea93c 53 pushq %rbx
78 # 7fab593ea93d 48 89 fb mov %rdi, %rbx
79 # 7fab593ea940 48 83 ec 68 sub $0x68, %rsp
80 # 7fab593ea944 0f 31 rdtsc
81 # 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx
82 # 7fab593ea94a 89 c0 mov %eax, %eax
83 # 7fab593ea94c 48 09 c2 or %rax, %rdx
84 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
85 # 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
86 # 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
87 # 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
88 # 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip)
89 # 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
91 from __future__ import print_function
99 import cPickle as pickle
100 # size of pickled integer big enough for record size
107 from PySide.QtCore import *
108 from PySide.QtGui import *
109 from PySide.QtSql import *
110 pyside_version_1 = True
111 from decimal import *
113 from multiprocessing import Process, Array, Value, Event
115 # xrange is range in Python3
121 def printerr(*args, **keyword_args):
122 print(*args, file=sys.stderr, **keyword_args)
124 # Data formatting helpers
133 return "+0x%x" % offset
137 if name == "[kernel.kallsyms]":
141 def findnth(s, sub, n, offs=0):
147 return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
149 # Percent to one decimal place
151 def PercentToOneDP(n, d):
154 x = (n * Decimal(100)) / d
155 return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
157 # Helper for queries that must not fail
159 def QueryExec(query, stmt):
160 ret = query.exec_(stmt)
162 raise Exception("Query failed: " + query.lastError().text())
166 class Thread(QThread):
168 done = Signal(object)
170 def __init__(self, task, param=None, parent=None):
171 super(Thread, self).__init__(parent)
177 if self.param is None:
178 done, result = self.task()
180 done, result = self.task(self.param)
181 self.done.emit(result)
187 class TreeModel(QAbstractItemModel):
189 def __init__(self, glb, parent=None):
190 super(TreeModel, self).__init__(parent)
192 self.root = self.GetRoot()
193 self.last_row_read = 0
195 def Item(self, parent):
197 return parent.internalPointer()
201 def rowCount(self, parent):
202 result = self.Item(parent).childCount()
205 self.dataChanged.emit(parent, parent)
208 def hasChildren(self, parent):
209 return self.Item(parent).hasChildren()
211 def headerData(self, section, orientation, role):
212 if role == Qt.TextAlignmentRole:
213 return self.columnAlignment(section)
214 if role != Qt.DisplayRole:
216 if orientation != Qt.Horizontal:
218 return self.columnHeader(section)
220 def parent(self, child):
221 child_item = child.internalPointer()
222 if child_item is self.root:
224 parent_item = child_item.getParentItem()
225 return self.createIndex(parent_item.getRow(), 0, parent_item)
227 def index(self, row, column, parent):
228 child_item = self.Item(parent).getChildItem(row)
229 return self.createIndex(row, column, child_item)
231 def DisplayData(self, item, index):
232 return item.getData(index.column())
234 def FetchIfNeeded(self, row):
235 if row > self.last_row_read:
236 self.last_row_read = row
237 if row + 10 >= self.root.child_count:
238 self.fetcher.Fetch(glb_chunk_sz)
240 def columnAlignment(self, column):
243 def columnFont(self, column):
246 def data(self, index, role):
247 if role == Qt.TextAlignmentRole:
248 return self.columnAlignment(index.column())
249 if role == Qt.FontRole:
250 return self.columnFont(index.column())
251 if role != Qt.DisplayRole:
253 item = index.internalPointer()
254 return self.DisplayData(item, index)
258 class TableModel(QAbstractTableModel):
260 def __init__(self, parent=None):
261 super(TableModel, self).__init__(parent)
263 self.child_items = []
264 self.last_row_read = 0
266 def Item(self, parent):
268 return parent.internalPointer()
272 def rowCount(self, parent):
273 return self.child_count
275 def headerData(self, section, orientation, role):
276 if role == Qt.TextAlignmentRole:
277 return self.columnAlignment(section)
278 if role != Qt.DisplayRole:
280 if orientation != Qt.Horizontal:
282 return self.columnHeader(section)
284 def index(self, row, column, parent):
285 return self.createIndex(row, column, self.child_items[row])
287 def DisplayData(self, item, index):
288 return item.getData(index.column())
290 def FetchIfNeeded(self, row):
291 if row > self.last_row_read:
292 self.last_row_read = row
293 if row + 10 >= self.child_count:
294 self.fetcher.Fetch(glb_chunk_sz)
296 def columnAlignment(self, column):
299 def columnFont(self, column):
302 def data(self, index, role):
303 if role == Qt.TextAlignmentRole:
304 return self.columnAlignment(index.column())
305 if role == Qt.FontRole:
306 return self.columnFont(index.column())
307 if role != Qt.DisplayRole:
309 item = index.internalPointer()
310 return self.DisplayData(item, index)
314 model_cache = weakref.WeakValueDictionary()
315 model_cache_lock = threading.Lock()
317 def LookupCreateModel(model_name, create_fn):
318 model_cache_lock.acquire()
320 model = model_cache[model_name]
325 model_cache[model_name] = model
326 model_cache_lock.release()
333 def __init__(self, parent, finder, is_reg_expr=False):
336 self.last_value = None
337 self.last_pattern = None
339 label = QLabel("Find:")
340 label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
342 self.textbox = QComboBox()
343 self.textbox.setEditable(True)
344 self.textbox.currentIndexChanged.connect(self.ValueChanged)
346 self.progress = QProgressBar()
347 self.progress.setRange(0, 0)
351 self.pattern = QCheckBox("Regular Expression")
353 self.pattern = QCheckBox("Pattern")
354 self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
356 self.next_button = QToolButton()
357 self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
358 self.next_button.released.connect(lambda: self.NextPrev(1))
360 self.prev_button = QToolButton()
361 self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
362 self.prev_button.released.connect(lambda: self.NextPrev(-1))
364 self.close_button = QToolButton()
365 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
366 self.close_button.released.connect(self.Deactivate)
368 self.hbox = QHBoxLayout()
369 self.hbox.setContentsMargins(0, 0, 0, 0)
371 self.hbox.addWidget(label)
372 self.hbox.addWidget(self.textbox)
373 self.hbox.addWidget(self.progress)
374 self.hbox.addWidget(self.pattern)
375 self.hbox.addWidget(self.next_button)
376 self.hbox.addWidget(self.prev_button)
377 self.hbox.addWidget(self.close_button)
380 self.bar.setLayout(self.hbox);
388 self.textbox.setFocus()
390 def Deactivate(self):
394 self.textbox.setEnabled(False)
396 self.next_button.hide()
397 self.prev_button.hide()
401 self.textbox.setEnabled(True)
404 self.next_button.show()
405 self.prev_button.show()
407 def Find(self, direction):
408 value = self.textbox.currentText()
409 pattern = self.pattern.isChecked()
410 self.last_value = value
411 self.last_pattern = pattern
412 self.finder.Find(value, direction, pattern, self.context)
414 def ValueChanged(self):
415 value = self.textbox.currentText()
416 pattern = self.pattern.isChecked()
417 index = self.textbox.currentIndex()
418 data = self.textbox.itemData(index)
419 # Store the pattern in the combo box to keep it with the text value
421 self.textbox.setItemData(index, pattern)
423 self.pattern.setChecked(data)
426 def NextPrev(self, direction):
427 value = self.textbox.currentText()
428 pattern = self.pattern.isChecked()
429 if value != self.last_value:
430 index = self.textbox.findText(value)
431 # Allow for a button press before the value has been added to the combo box
433 index = self.textbox.count()
434 self.textbox.addItem(value, pattern)
435 self.textbox.setCurrentIndex(index)
438 self.textbox.setItemData(index, pattern)
439 elif pattern != self.last_pattern:
440 # Keep the pattern recorded in the combo box up to date
441 index = self.textbox.currentIndex()
442 self.textbox.setItemData(index, pattern)
446 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
448 # Context-sensitive call graph data model item base
450 class CallGraphLevelItemBase(object):
452 def __init__(self, glb, row, parent_item):
455 self.parent_item = parent_item
456 self.query_done = False;
458 self.child_items = []
460 self.level = parent_item.level + 1
464 def getChildItem(self, row):
465 return self.child_items[row]
467 def getParentItem(self):
468 return self.parent_item
473 def childCount(self):
474 if not self.query_done:
476 if not self.child_count:
478 return self.child_count
480 def hasChildren(self):
481 if not self.query_done:
483 return self.child_count > 0
485 def getData(self, column):
486 return self.data[column]
488 # Context-sensitive call graph data model level 2+ item base
490 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
492 def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
493 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
494 self.comm_id = comm_id
495 self.thread_id = thread_id
496 self.call_path_id = call_path_id
497 self.branch_count = branch_count
501 self.query_done = True;
502 query = QSqlQuery(self.glb.db)
503 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
505 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
506 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
507 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
508 " WHERE parent_call_path_id = " + str(self.call_path_id) +
509 " AND comm_id = " + str(self.comm_id) +
510 " AND thread_id = " + str(self.thread_id) +
511 " GROUP BY call_path_id, name, short_name"
512 " ORDER BY call_path_id")
514 child_item = CallGraphLevelThreeItem(self.glb, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), int(query.value(5)), self)
515 self.child_items.append(child_item)
516 self.child_count += 1
518 # Context-sensitive call graph data model level three item
520 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
522 def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
523 super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
525 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
526 self.dbid = call_path_id
528 # Context-sensitive call graph data model level two item
530 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
532 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
533 super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
534 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
535 self.dbid = thread_id
538 super(CallGraphLevelTwoItem, self).Select()
539 for child_item in self.child_items:
540 self.time += child_item.time
541 self.branch_count += child_item.branch_count
542 for child_item in self.child_items:
543 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
544 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
546 # Context-sensitive call graph data model level one item
548 class CallGraphLevelOneItem(CallGraphLevelItemBase):
550 def __init__(self, glb, row, comm_id, comm, parent_item):
551 super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
552 self.data = [comm, "", "", "", "", "", ""]
556 self.query_done = True;
557 query = QSqlQuery(self.glb.db)
558 QueryExec(query, "SELECT thread_id, pid, tid"
560 " INNER JOIN threads ON thread_id = threads.id"
561 " WHERE comm_id = " + str(self.dbid))
563 child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
564 self.child_items.append(child_item)
565 self.child_count += 1
567 # Context-sensitive call graph data model root item
569 class CallGraphRootItem(CallGraphLevelItemBase):
571 def __init__(self, glb):
572 super(CallGraphRootItem, self).__init__(glb, 0, None)
574 self.query_done = True;
575 query = QSqlQuery(glb.db)
576 QueryExec(query, "SELECT id, comm FROM comms")
578 if not query.value(0):
580 child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
581 self.child_items.append(child_item)
582 self.child_count += 1
584 # Context-sensitive call graph data model base
586 class CallGraphModelBase(TreeModel):
588 def __init__(self, glb, parent=None):
589 super(CallGraphModelBase, self).__init__(glb, parent)
591 def FindSelect(self, value, pattern, query):
593 # postgresql and sqlite pattern patching differences:
594 # postgresql LIKE is case sensitive but sqlite LIKE is not
595 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
596 # postgresql supports ILIKE which is case insensitive
597 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
598 if not self.glb.dbref.is_sqlite3:
600 s = value.replace("%", "\%")
601 s = s.replace("_", "\_")
602 # Translate * and ? into SQL LIKE pattern characters % and _
603 trans = string.maketrans("*?", "%_")
604 match = " LIKE '" + str(s).translate(trans) + "'"
606 match = " GLOB '" + str(value) + "'"
608 match = " = '" + str(value) + "'"
609 self.DoFindSelect(query, match)
611 def Found(self, query, found):
613 return self.FindPath(query)
616 def FindValue(self, value, pattern, query, last_value, last_pattern):
617 if last_value == value and pattern == last_pattern:
618 found = query.first()
620 self.FindSelect(value, pattern, query)
622 return self.Found(query, found)
624 def FindNext(self, query):
627 found = query.first()
628 return self.Found(query, found)
630 def FindPrev(self, query):
631 found = query.previous()
634 return self.Found(query, found)
636 def FindThread(self, c):
637 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
638 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
639 elif c.direction > 0:
640 ids = self.FindNext(c.query)
642 ids = self.FindPrev(c.query)
645 def Find(self, value, direction, pattern, context, callback):
647 def __init__(self, *x):
648 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
649 def Update(self, *x):
650 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
652 context[0].Update(value, direction, pattern)
654 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
655 # Use a thread so the UI is not blocked during the SELECT
656 thread = Thread(self.FindThread, context[0])
657 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
660 def FindDone(self, thread, callback, ids):
663 # Context-sensitive call graph data model
665 class CallGraphModel(CallGraphModelBase):
667 def __init__(self, glb, parent=None):
668 super(CallGraphModel, self).__init__(glb, parent)
671 return CallGraphRootItem(self.glb)
673 def columnCount(self, parent=None):
676 def columnHeader(self, column):
677 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
678 return headers[column]
680 def columnAlignment(self, column):
681 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
682 return alignment[column]
684 def DoFindSelect(self, query, match):
685 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
687 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
688 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
689 " WHERE symbols.name" + match +
690 " GROUP BY comm_id, thread_id, call_path_id"
691 " ORDER BY comm_id, thread_id, call_path_id")
693 def FindPath(self, query):
694 # Turn the query result into a list of ids that the tree view can walk
695 # to open the tree at the right place.
697 parent_id = query.value(0)
699 ids.insert(0, parent_id)
700 q2 = QSqlQuery(self.glb.db)
701 QueryExec(q2, "SELECT parent_id"
703 " WHERE id = " + str(parent_id))
706 parent_id = q2.value(0)
707 # The call path root is not used
710 ids.insert(0, query.value(2))
711 ids.insert(0, query.value(1))
714 # Call tree data model level 2+ item base
716 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
718 def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
719 super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
720 self.comm_id = comm_id
721 self.thread_id = thread_id
722 self.calls_id = calls_id
723 self.branch_count = branch_count
727 self.query_done = True;
728 if self.calls_id == 0:
729 comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
732 query = QSqlQuery(self.glb.db)
733 QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
735 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
736 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
737 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
738 " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
739 " ORDER BY call_time, calls.id")
741 child_item = CallTreeLevelThreeItem(self.glb, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), int(query.value(5)), self)
742 self.child_items.append(child_item)
743 self.child_count += 1
745 # Call tree data model level three item
747 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
749 def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
750 super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
752 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
755 # Call tree data model level two item
757 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
759 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
760 super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item)
761 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
762 self.dbid = thread_id
765 super(CallTreeLevelTwoItem, self).Select()
766 for child_item in self.child_items:
767 self.time += child_item.time
768 self.branch_count += child_item.branch_count
769 for child_item in self.child_items:
770 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
771 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
773 # Call tree data model level one item
775 class CallTreeLevelOneItem(CallGraphLevelItemBase):
777 def __init__(self, glb, row, comm_id, comm, parent_item):
778 super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item)
779 self.data = [comm, "", "", "", "", "", ""]
783 self.query_done = True;
784 query = QSqlQuery(self.glb.db)
785 QueryExec(query, "SELECT thread_id, pid, tid"
787 " INNER JOIN threads ON thread_id = threads.id"
788 " WHERE comm_id = " + str(self.dbid))
790 child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
791 self.child_items.append(child_item)
792 self.child_count += 1
794 # Call tree data model root item
796 class CallTreeRootItem(CallGraphLevelItemBase):
798 def __init__(self, glb):
799 super(CallTreeRootItem, self).__init__(glb, 0, None)
801 self.query_done = True;
802 query = QSqlQuery(glb.db)
803 QueryExec(query, "SELECT id, comm FROM comms")
805 if not query.value(0):
807 child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
808 self.child_items.append(child_item)
809 self.child_count += 1
811 # Call Tree data model
813 class CallTreeModel(CallGraphModelBase):
815 def __init__(self, glb, parent=None):
816 super(CallTreeModel, self).__init__(glb, parent)
819 return CallTreeRootItem(self.glb)
821 def columnCount(self, parent=None):
824 def columnHeader(self, column):
825 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
826 return headers[column]
828 def columnAlignment(self, column):
829 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
830 return alignment[column]
832 def DoFindSelect(self, query, match):
833 QueryExec(query, "SELECT calls.id, comm_id, thread_id"
835 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
836 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
837 " WHERE symbols.name" + match +
838 " ORDER BY comm_id, thread_id, call_time, calls.id")
840 def FindPath(self, query):
841 # Turn the query result into a list of ids that the tree view can walk
842 # to open the tree at the right place.
844 parent_id = query.value(0)
846 ids.insert(0, parent_id)
847 q2 = QSqlQuery(self.glb.db)
848 QueryExec(q2, "SELECT parent_id"
850 " WHERE id = " + str(parent_id))
853 parent_id = q2.value(0)
854 ids.insert(0, query.value(2))
855 ids.insert(0, query.value(1))
858 # Vertical widget layout
862 def __init__(self, w1, w2, w3=None):
863 self.vbox = QWidget()
864 self.vbox.setLayout(QVBoxLayout());
866 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
868 self.vbox.layout().addWidget(w1)
869 self.vbox.layout().addWidget(w2)
871 self.vbox.layout().addWidget(w3)
878 class TreeWindowBase(QMdiSubWindow):
880 def __init__(self, parent=None):
881 super(TreeWindowBase, self).__init__(parent)
886 self.view = QTreeView()
887 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
888 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
890 def DisplayFound(self, ids):
893 parent = QModelIndex()
896 n = self.model.rowCount(parent)
897 for row in xrange(n):
898 child = self.model.index(row, 0, parent)
899 if child.internalPointer().dbid == dbid:
901 self.view.setCurrentIndex(child)
908 def Find(self, value, direction, pattern, context):
911 self.model.Find(value, direction, pattern, context, self.FindDone)
913 def FindDone(self, ids):
915 if not self.DisplayFound(ids):
919 self.find_bar.NotFound()
922 # Context-sensitive call graph window
924 class CallGraphWindow(TreeWindowBase):
926 def __init__(self, glb, parent=None):
927 super(CallGraphWindow, self).__init__(parent)
929 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
931 self.view.setModel(self.model)
933 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
934 self.view.setColumnWidth(c, w)
936 self.find_bar = FindBar(self, self)
938 self.vbox = VBox(self.view, self.find_bar.Widget())
940 self.setWidget(self.vbox.Widget())
942 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
946 class CallTreeWindow(TreeWindowBase):
948 def __init__(self, glb, parent=None):
949 super(CallTreeWindow, self).__init__(parent)
951 self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
953 self.view.setModel(self.model)
955 for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
956 self.view.setColumnWidth(c, w)
958 self.find_bar = FindBar(self, self)
960 self.vbox = VBox(self.view, self.find_bar.Widget())
962 self.setWidget(self.vbox.Widget())
964 AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
966 # Child data item finder
968 class ChildDataItemFinder():
970 def __init__(self, root):
972 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
976 def FindSelect(self):
979 pattern = re.compile(self.value)
980 for child in self.root.child_items:
981 for column_data in child.data:
982 if re.search(pattern, str(column_data)) is not None:
983 self.rows.append(child.row)
986 for child in self.root.child_items:
987 for column_data in child.data:
988 if self.value in str(column_data):
989 self.rows.append(child.row)
994 if self.last_value != self.value or self.pattern != self.last_pattern:
996 if not len(self.rows):
998 return self.rows[self.pos]
1000 def FindThread(self):
1001 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1002 row = self.FindValue()
1003 elif len(self.rows):
1004 if self.direction > 0:
1006 if self.pos >= len(self.rows):
1011 self.pos = len(self.rows) - 1
1012 row = self.rows[self.pos]
1017 def Find(self, value, direction, pattern, context, callback):
1018 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1019 # Use a thread so the UI is not blocked
1020 thread = Thread(self.FindThread)
1021 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1024 def FindDone(self, thread, callback, row):
1027 # Number of database records to fetch in one go
1029 glb_chunk_sz = 10000
1031 # Background process for SQL data fetcher
1033 class SQLFetcherProcess():
1035 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1036 # Need a unique connection name
1037 conn_name = "SQLFetcher" + str(os.getpid())
1038 self.db, dbname = dbref.Open(conn_name)
1040 self.buffer = buffer
1043 self.fetch_count = fetch_count
1044 self.fetching_done = fetching_done
1045 self.process_target = process_target
1046 self.wait_event = wait_event
1047 self.fetched_event = fetched_event
1049 self.query = QSqlQuery(self.db)
1050 self.query_limit = 0 if "$$last_id$$" in sql else 2
1054 self.local_head = self.head.value
1055 self.local_tail = self.tail.value
1058 if self.query_limit:
1059 if self.query_limit == 1:
1061 self.query_limit -= 1
1062 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1063 QueryExec(self.query, stmt)
1066 if not self.query.next():
1068 if not self.query.next():
1070 self.last_id = self.query.value(0)
1071 return self.prep(self.query)
1073 def WaitForTarget(self):
1075 self.wait_event.clear()
1076 target = self.process_target.value
1077 if target > self.fetched or target < 0:
1079 self.wait_event.wait()
1082 def HasSpace(self, sz):
1083 if self.local_tail <= self.local_head:
1084 space = len(self.buffer) - self.local_head
1087 if space >= glb_nsz:
1088 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1089 nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
1090 self.buffer[self.local_head : self.local_head + len(nd)] = nd
1092 if self.local_tail - self.local_head > sz:
1096 def WaitForSpace(self, sz):
1097 if self.HasSpace(sz):
1100 self.wait_event.clear()
1101 self.local_tail = self.tail.value
1102 if self.HasSpace(sz):
1104 self.wait_event.wait()
1106 def AddToBuffer(self, obj):
1107 d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
1109 nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
1111 self.WaitForSpace(sz)
1112 pos = self.local_head
1113 self.buffer[pos : pos + len(nd)] = nd
1114 self.buffer[pos + glb_nsz : pos + sz] = d
1115 self.local_head += sz
1117 def FetchBatch(self, batch_size):
1119 while batch_size > fetched:
1124 self.AddToBuffer(obj)
1127 self.fetched += fetched
1128 with self.fetch_count.get_lock():
1129 self.fetch_count.value += fetched
1130 self.head.value = self.local_head
1131 self.fetched_event.set()
1135 target = self.WaitForTarget()
1138 batch_size = min(glb_chunk_sz, target - self.fetched)
1139 self.FetchBatch(batch_size)
1140 self.fetching_done.value = True
1141 self.fetched_event.set()
1143 def SQLFetcherFn(*x):
1144 process = SQLFetcherProcess(*x)
1149 class SQLFetcher(QObject):
1151 done = Signal(object)
1153 def __init__(self, glb, sql, prep, process_data, parent=None):
1154 super(SQLFetcher, self).__init__(parent)
1155 self.process_data = process_data
1158 self.last_target = 0
1160 self.buffer_size = 16 * 1024 * 1024
1161 self.buffer = Array(c_char, self.buffer_size, lock=False)
1162 self.head = Value(c_longlong)
1163 self.tail = Value(c_longlong)
1165 self.fetch_count = Value(c_longlong)
1166 self.fetching_done = Value(c_bool)
1168 self.process_target = Value(c_longlong)
1169 self.wait_event = Event()
1170 self.fetched_event = Event()
1171 glb.AddInstanceToShutdownOnExit(self)
1172 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))
1173 self.process.start()
1174 self.thread = Thread(self.Thread)
1175 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1179 # Tell the thread and process to exit
1180 self.process_target.value = -1
1181 self.wait_event.set()
1183 self.fetching_done.value = True
1184 self.fetched_event.set()
1190 self.fetched_event.clear()
1191 fetch_count = self.fetch_count.value
1192 if fetch_count != self.last_count:
1194 if self.fetching_done.value:
1197 self.fetched_event.wait()
1198 count = fetch_count - self.last_count
1199 self.last_count = fetch_count
1200 self.fetched += count
1203 def Fetch(self, nr):
1205 # -1 inidcates there are no more
1207 result = self.fetched
1208 extra = result + nr - self.target
1210 self.target += extra
1211 # process_target < 0 indicates shutting down
1212 if self.process_target.value >= 0:
1213 self.process_target.value = self.target
1214 self.wait_event.set()
1217 def RemoveFromBuffer(self):
1218 pos = self.local_tail
1219 if len(self.buffer) - pos < glb_nsz:
1221 n = pickle.loads(self.buffer[pos : pos + glb_nsz])
1224 n = pickle.loads(self.buffer[0 : glb_nsz])
1226 obj = pickle.loads(self.buffer[pos : pos + n])
1227 self.local_tail = pos + n
1230 def ProcessData(self, count):
1231 for i in xrange(count):
1232 obj = self.RemoveFromBuffer()
1233 self.process_data(obj)
1234 self.tail.value = self.local_tail
1235 self.wait_event.set()
1236 self.done.emit(count)
1238 # Fetch more records bar
1240 class FetchMoreRecordsBar():
1242 def __init__(self, model, parent):
1245 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1246 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1248 self.fetch_count = QSpinBox()
1249 self.fetch_count.setRange(1, 1000000)
1250 self.fetch_count.setValue(10)
1251 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1253 self.fetch = QPushButton("Go!")
1254 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1255 self.fetch.released.connect(self.FetchMoreRecords)
1257 self.progress = QProgressBar()
1258 self.progress.setRange(0, 100)
1259 self.progress.hide()
1261 self.done_label = QLabel("All records fetched")
1262 self.done_label.hide()
1264 self.spacer = QLabel("")
1266 self.close_button = QToolButton()
1267 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1268 self.close_button.released.connect(self.Deactivate)
1270 self.hbox = QHBoxLayout()
1271 self.hbox.setContentsMargins(0, 0, 0, 0)
1273 self.hbox.addWidget(self.label)
1274 self.hbox.addWidget(self.fetch_count)
1275 self.hbox.addWidget(self.fetch)
1276 self.hbox.addWidget(self.spacer)
1277 self.hbox.addWidget(self.progress)
1278 self.hbox.addWidget(self.done_label)
1279 self.hbox.addWidget(self.close_button)
1281 self.bar = QWidget()
1282 self.bar.setLayout(self.hbox);
1285 self.in_progress = False
1286 self.model.progress.connect(self.Progress)
1290 if not model.HasMoreRecords():
1298 self.fetch.setFocus()
1300 def Deactivate(self):
1303 def Enable(self, enable):
1304 self.fetch.setEnabled(enable)
1305 self.fetch_count.setEnabled(enable)
1311 self.progress.show()
1314 self.in_progress = False
1316 self.progress.hide()
1321 return self.fetch_count.value() * glb_chunk_sz
1327 self.fetch_count.hide()
1330 self.done_label.show()
1332 def Progress(self, count):
1333 if self.in_progress:
1335 percent = ((count - self.start) * 100) / self.Target()
1339 self.progress.setValue(percent)
1341 # Count value of zero means no more records
1344 def FetchMoreRecords(self):
1347 self.progress.setValue(0)
1349 self.in_progress = True
1350 self.start = self.model.FetchMoreRecords(self.Target())
1352 # Brance data model level two item
1354 class BranchLevelTwoItem():
1356 def __init__(self, row, text, parent_item):
1358 self.parent_item = parent_item
1359 self.data = [""] * 8
1363 def getParentItem(self):
1364 return self.parent_item
1369 def childCount(self):
1372 def hasChildren(self):
1375 def getData(self, column):
1376 return self.data[column]
1378 # Brance data model level one item
1380 class BranchLevelOneItem():
1382 def __init__(self, glb, row, data, parent_item):
1385 self.parent_item = parent_item
1386 self.child_count = 0
1387 self.child_items = []
1388 self.data = data[1:]
1391 self.query_done = False
1393 def getChildItem(self, row):
1394 return self.child_items[row]
1396 def getParentItem(self):
1397 return self.parent_item
1403 self.query_done = True
1405 if not self.glb.have_disassembler:
1408 query = QSqlQuery(self.glb.db)
1410 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1412 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1413 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1414 " WHERE samples.id = " + str(self.dbid))
1415 if not query.next():
1417 cpu = query.value(0)
1418 dso = query.value(1)
1419 sym = query.value(2)
1420 if dso == 0 or sym == 0:
1422 off = query.value(3)
1423 short_name = query.value(4)
1424 long_name = query.value(5)
1425 build_id = query.value(6)
1426 sym_start = query.value(7)
1429 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1431 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1432 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1433 " ORDER BY samples.id"
1435 if not query.next():
1437 if query.value(0) != dso:
1438 # Cannot disassemble from one dso to another
1440 bsym = query.value(1)
1441 boff = query.value(2)
1442 bsym_start = query.value(3)
1445 tot = bsym_start + boff + 1 - sym_start - off
1446 if tot <= 0 or tot > 16384:
1449 inst = self.glb.disassembler.Instruction()
1450 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1453 mode = 0 if Is64Bit(f) else 1
1454 self.glb.disassembler.SetMode(inst, mode)
1457 buf = create_string_buffer(tot + 16)
1458 f.seek(sym_start + off)
1459 buf.value = f.read(buf_sz)
1460 buf_ptr = addressof(buf)
1463 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1465 byte_str = tohex(ip).rjust(16)
1466 for k in xrange(cnt):
1467 byte_str += " %02x" % ord(buf[i])
1472 self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
1473 self.child_count += 1
1481 def childCount(self):
1482 if not self.query_done:
1484 if not self.child_count:
1486 return self.child_count
1488 def hasChildren(self):
1489 if not self.query_done:
1491 return self.child_count > 0
1493 def getData(self, column):
1494 return self.data[column]
1496 # Brance data model root item
1498 class BranchRootItem():
1501 self.child_count = 0
1502 self.child_items = []
1505 def getChildItem(self, row):
1506 return self.child_items[row]
1508 def getParentItem(self):
1514 def childCount(self):
1515 return self.child_count
1517 def hasChildren(self):
1518 return self.child_count > 0
1520 def getData(self, column):
1523 # Branch data preparation
1525 def BranchDataPrep(query):
1527 for i in xrange(0, 8):
1528 data.append(query.value(i))
1529 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1530 " (" + dsoname(query.value(11)) + ")" + " -> " +
1531 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1532 " (" + dsoname(query.value(15)) + ")")
1535 def BranchDataPrepWA(query):
1537 data.append(query.value(0))
1538 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1539 data.append("{:>19}".format(query.value(1)))
1540 for i in xrange(2, 8):
1541 data.append(query.value(i))
1542 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1543 " (" + dsoname(query.value(11)) + ")" + " -> " +
1544 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1545 " (" + dsoname(query.value(15)) + ")")
1550 class BranchModel(TreeModel):
1552 progress = Signal(object)
1554 def __init__(self, glb, event_id, where_clause, parent=None):
1555 super(BranchModel, self).__init__(glb, parent)
1556 self.event_id = event_id
1559 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1560 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1561 " ip, symbols.name, sym_offset, dsos.short_name,"
1562 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1564 " INNER JOIN comms ON comm_id = comms.id"
1565 " INNER JOIN threads ON thread_id = threads.id"
1566 " INNER JOIN branch_types ON branch_type = branch_types.id"
1567 " INNER JOIN symbols ON symbol_id = symbols.id"
1568 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1569 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1570 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1571 " WHERE samples.id > $$last_id$$" + where_clause +
1572 " AND evsel_id = " + str(self.event_id) +
1573 " ORDER BY samples.id"
1574 " LIMIT " + str(glb_chunk_sz))
1575 if pyside_version_1 and sys.version_info[0] == 3:
1576 prep = BranchDataPrepWA
1578 prep = BranchDataPrep
1579 self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
1580 self.fetcher.done.connect(self.Update)
1581 self.fetcher.Fetch(glb_chunk_sz)
1584 return BranchRootItem()
1586 def columnCount(self, parent=None):
1589 def columnHeader(self, column):
1590 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1592 def columnFont(self, column):
1595 return QFont("Monospace")
1597 def DisplayData(self, item, index):
1599 self.FetchIfNeeded(item.row)
1600 return item.getData(index.column())
1602 def AddSample(self, data):
1603 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1604 self.root.child_items.append(child)
1607 def Update(self, fetched):
1610 self.progress.emit(0)
1611 child_count = self.root.child_count
1612 count = self.populated - child_count
1614 parent = QModelIndex()
1615 self.beginInsertRows(parent, child_count, child_count + count - 1)
1616 self.insertRows(child_count, count, parent)
1617 self.root.child_count += count
1618 self.endInsertRows()
1619 self.progress.emit(self.root.child_count)
1621 def FetchMoreRecords(self, count):
1622 current = self.root.child_count
1624 self.fetcher.Fetch(count)
1626 self.progress.emit(0)
1629 def HasMoreRecords(self):
1636 def __init__(self, name = "", where_clause = "", limit = ""):
1638 self.where_clause = where_clause
1642 return str(self.where_clause + ";" + self.limit)
1646 class BranchWindow(QMdiSubWindow):
1648 def __init__(self, glb, event_id, report_vars, parent=None):
1649 super(BranchWindow, self).__init__(parent)
1651 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
1653 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1655 self.view = QTreeView()
1656 self.view.setUniformRowHeights(True)
1657 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1658 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1659 self.view.setModel(self.model)
1661 self.ResizeColumnsToContents()
1663 self.find_bar = FindBar(self, self, True)
1665 self.finder = ChildDataItemFinder(self.model.root)
1667 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1669 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1671 self.setWidget(self.vbox.Widget())
1673 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1675 def ResizeColumnToContents(self, column, n):
1676 # Using the view's resizeColumnToContents() here is extrememly slow
1677 # so implement a crude alternative
1678 mm = "MM" if column else "MMMM"
1679 font = self.view.font()
1680 metrics = QFontMetrics(font)
1682 for row in xrange(n):
1683 val = self.model.root.child_items[row].data[column]
1684 len = metrics.width(str(val) + mm)
1685 max = len if len > max else max
1686 val = self.model.columnHeader(column)
1687 len = metrics.width(str(val) + mm)
1688 max = len if len > max else max
1689 self.view.setColumnWidth(column, max)
1691 def ResizeColumnsToContents(self):
1692 n = min(self.model.root.child_count, 100)
1694 # No data yet, so connect a signal to notify when there is
1695 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1697 columns = self.model.columnCount()
1698 for i in xrange(columns):
1699 self.ResizeColumnToContents(i, n)
1701 def UpdateColumnWidths(self, *x):
1702 # This only needs to be done once, so disconnect the signal now
1703 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1704 self.ResizeColumnsToContents()
1706 def Find(self, value, direction, pattern, context):
1707 self.view.setFocus()
1708 self.find_bar.Busy()
1709 self.finder.Find(value, direction, pattern, context, self.FindDone)
1711 def FindDone(self, row):
1712 self.find_bar.Idle()
1714 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1716 self.find_bar.NotFound()
1718 # Line edit data item
1720 class LineEditDataItem(object):
1722 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1725 self.placeholder_text = placeholder_text
1726 self.parent = parent
1729 self.value = default
1731 self.widget = QLineEdit(default)
1732 self.widget.editingFinished.connect(self.Validate)
1733 self.widget.textChanged.connect(self.Invalidate)
1736 self.validated = True
1738 if placeholder_text:
1739 self.widget.setPlaceholderText(placeholder_text)
1741 def TurnTextRed(self):
1743 palette = QPalette()
1744 palette.setColor(QPalette.Text,Qt.red)
1745 self.widget.setPalette(palette)
1748 def TurnTextNormal(self):
1750 palette = QPalette()
1751 self.widget.setPalette(palette)
1754 def InvalidValue(self, value):
1757 self.error = self.label + " invalid value '" + value + "'"
1758 self.parent.ShowMessage(self.error)
1760 def Invalidate(self):
1761 self.validated = False
1763 def DoValidate(self, input_string):
1764 self.value = input_string.strip()
1767 self.validated = True
1769 self.TurnTextNormal()
1770 self.parent.ClearMessage()
1771 input_string = self.widget.text()
1772 if not len(input_string.strip()):
1775 self.DoValidate(input_string)
1778 if not self.validated:
1781 self.parent.ShowMessage(self.error)
1785 def IsNumber(self, value):
1790 return str(x) == value
1792 # Non-negative integer ranges dialog data item
1794 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1796 def __init__(self, glb, label, placeholder_text, column_name, parent):
1797 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1799 self.column_name = column_name
1801 def DoValidate(self, input_string):
1804 for value in [x.strip() for x in input_string.split(",")]:
1806 vrange = value.split("-")
1807 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1808 return self.InvalidValue(value)
1809 ranges.append(vrange)
1811 if not self.IsNumber(value):
1812 return self.InvalidValue(value)
1813 singles.append(value)
1814 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1816 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1817 self.value = " OR ".join(ranges)
1819 # Positive integer dialog data item
1821 class PositiveIntegerDataItem(LineEditDataItem):
1823 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1824 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1826 def DoValidate(self, input_string):
1827 if not self.IsNumber(input_string.strip()):
1828 return self.InvalidValue(input_string)
1829 value = int(input_string.strip())
1831 return self.InvalidValue(input_string)
1832 self.value = str(value)
1834 # Dialog data item converted and validated using a SQL table
1836 class SQLTableDataItem(LineEditDataItem):
1838 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1839 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
1841 self.table_name = table_name
1842 self.match_column = match_column
1843 self.column_name1 = column_name1
1844 self.column_name2 = column_name2
1846 def ValueToIds(self, value):
1848 query = QSqlQuery(self.glb.db)
1849 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1850 ret = query.exec_(stmt)
1853 ids.append(str(query.value(0)))
1856 def DoValidate(self, input_string):
1858 for value in [x.strip() for x in input_string.split(",")]:
1859 ids = self.ValueToIds(value)
1863 return self.InvalidValue(value)
1864 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1865 if self.column_name2:
1866 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1868 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
1870 class SampleTimeRangesDataItem(LineEditDataItem):
1872 def __init__(self, glb, label, placeholder_text, column_name, parent):
1873 self.column_name = column_name
1877 self.last_time = 2 ** 64
1879 query = QSqlQuery(glb.db)
1880 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1882 self.last_id = int(query.value(0))
1883 self.last_time = int(query.value(1))
1884 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1886 self.first_time = int(query.value(0))
1887 if placeholder_text:
1888 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1890 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1892 def IdBetween(self, query, lower_id, higher_id, order):
1893 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1895 return True, int(query.value(0))
1899 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1900 query = QSqlQuery(self.glb.db)
1902 next_id = int((lower_id + higher_id) / 2)
1903 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1904 if not query.next():
1905 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1907 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1909 return str(higher_id)
1911 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1912 next_time = int(query.value(0))
1914 if target_time > next_time:
1918 if higher_id <= lower_id + 1:
1919 return str(higher_id)
1921 if target_time >= next_time:
1925 if higher_id <= lower_id + 1:
1926 return str(lower_id)
1928 def ConvertRelativeTime(self, val):
1933 elif suffix == "us":
1935 elif suffix == "ns":
1939 val = val[:-2].strip()
1940 if not self.IsNumber(val):
1942 val = int(val) * mult
1944 val += self.first_time
1946 val += self.last_time
1949 def ConvertTimeRange(self, vrange):
1951 vrange[0] = str(self.first_time)
1953 vrange[1] = str(self.last_time)
1954 vrange[0] = self.ConvertRelativeTime(vrange[0])
1955 vrange[1] = self.ConvertRelativeTime(vrange[1])
1956 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1958 beg_range = max(int(vrange[0]), self.first_time)
1959 end_range = min(int(vrange[1]), self.last_time)
1960 if beg_range > self.last_time or end_range < self.first_time:
1962 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
1963 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
1966 def AddTimeRange(self, value, ranges):
1967 n = value.count("-")
1971 if value.split("-")[1].strip() == "":
1977 pos = findnth(value, "-", n)
1978 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
1979 if self.ConvertTimeRange(vrange):
1980 ranges.append(vrange)
1984 def DoValidate(self, input_string):
1986 for value in [x.strip() for x in input_string.split(",")]:
1987 if not self.AddTimeRange(value, ranges):
1988 return self.InvalidValue(value)
1989 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1990 self.value = " OR ".join(ranges)
1992 # Report Dialog Base
1994 class ReportDialogBase(QDialog):
1996 def __init__(self, glb, title, items, partial, parent=None):
1997 super(ReportDialogBase, self).__init__(parent)
2001 self.report_vars = ReportVars()
2003 self.setWindowTitle(title)
2004 self.setMinimumWidth(600)
2006 self.data_items = [x(glb, self) for x in items]
2008 self.partial = partial
2010 self.grid = QGridLayout()
2012 for row in xrange(len(self.data_items)):
2013 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2014 self.grid.addWidget(self.data_items[row].widget, row, 1)
2016 self.status = QLabel()
2018 self.ok_button = QPushButton("Ok", self)
2019 self.ok_button.setDefault(True)
2020 self.ok_button.released.connect(self.Ok)
2021 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2023 self.cancel_button = QPushButton("Cancel", self)
2024 self.cancel_button.released.connect(self.reject)
2025 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2027 self.hbox = QHBoxLayout()
2028 #self.hbox.addStretch()
2029 self.hbox.addWidget(self.status)
2030 self.hbox.addWidget(self.ok_button)
2031 self.hbox.addWidget(self.cancel_button)
2033 self.vbox = QVBoxLayout()
2034 self.vbox.addLayout(self.grid)
2035 self.vbox.addLayout(self.hbox)
2037 self.setLayout(self.vbox);
2040 vars = self.report_vars
2041 for d in self.data_items:
2042 if d.id == "REPORTNAME":
2045 self.ShowMessage("Report name is required")
2047 for d in self.data_items:
2050 for d in self.data_items[1:]:
2052 vars.limit = d.value
2054 if len(vars.where_clause):
2055 vars.where_clause += " AND "
2056 vars.where_clause += d.value
2057 if len(vars.where_clause):
2059 vars.where_clause = " AND ( " + vars.where_clause + " ) "
2061 vars.where_clause = " WHERE " + vars.where_clause + " "
2064 def ShowMessage(self, msg):
2065 self.status.setText("<font color=#FF0000>" + msg)
2067 def ClearMessage(self):
2068 self.status.setText("")
2070 # Selected branch report creation dialog
2072 class SelectedBranchDialog(ReportDialogBase):
2074 def __init__(self, glb, parent=None):
2075 title = "Selected Branches"
2076 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2077 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2078 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2079 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2080 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2081 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2082 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2083 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2084 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2085 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2089 def GetEventList(db):
2091 query = QSqlQuery(db)
2092 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2094 events.append(query.value(0))
2097 # Is a table selectable
2099 def IsSelectable(db, table, sql = ""):
2100 query = QSqlQuery(db)
2102 QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1")
2107 # SQL table data model item
2109 class SQLTableItem():
2111 def __init__(self, row, data):
2115 def getData(self, column):
2116 return self.data[column]
2118 # SQL table data model
2120 class SQLTableModel(TableModel):
2122 progress = Signal(object)
2124 def __init__(self, glb, sql, column_headers, parent=None):
2125 super(SQLTableModel, self).__init__(parent)
2129 self.column_headers = column_headers
2130 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
2131 self.fetcher.done.connect(self.Update)
2132 self.fetcher.Fetch(glb_chunk_sz)
2134 def DisplayData(self, item, index):
2135 self.FetchIfNeeded(item.row)
2136 return item.getData(index.column())
2138 def AddSample(self, data):
2139 child = SQLTableItem(self.populated, data)
2140 self.child_items.append(child)
2143 def Update(self, fetched):
2146 self.progress.emit(0)
2147 child_count = self.child_count
2148 count = self.populated - child_count
2150 parent = QModelIndex()
2151 self.beginInsertRows(parent, child_count, child_count + count - 1)
2152 self.insertRows(child_count, count, parent)
2153 self.child_count += count
2154 self.endInsertRows()
2155 self.progress.emit(self.child_count)
2157 def FetchMoreRecords(self, count):
2158 current = self.child_count
2160 self.fetcher.Fetch(count)
2162 self.progress.emit(0)
2165 def HasMoreRecords(self):
2168 def columnCount(self, parent=None):
2169 return len(self.column_headers)
2171 def columnHeader(self, column):
2172 return self.column_headers[column]
2174 def SQLTableDataPrep(self, query, count):
2176 for i in xrange(count):
2177 data.append(query.value(i))
2180 # SQL automatic table data model
2182 class SQLAutoTableModel(SQLTableModel):
2184 def __init__(self, glb, table_name, parent=None):
2185 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2186 if table_name == "comm_threads_view":
2187 # For now, comm_threads_view has no id column
2188 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2190 query = QSqlQuery(glb.db)
2191 if glb.dbref.is_sqlite3:
2192 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2194 column_headers.append(query.value(1))
2195 if table_name == "sqlite_master":
2196 sql = "SELECT * FROM " + table_name
2198 if table_name[:19] == "information_schema.":
2199 sql = "SELECT * FROM " + table_name
2200 select_table_name = table_name[19:]
2201 schema = "information_schema"
2203 select_table_name = table_name
2205 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2207 column_headers.append(query.value(0))
2208 if pyside_version_1 and sys.version_info[0] == 3:
2209 if table_name == "samples_view":
2210 self.SQLTableDataPrep = self.samples_view_DataPrep
2211 if table_name == "samples":
2212 self.SQLTableDataPrep = self.samples_DataPrep
2213 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2215 def samples_view_DataPrep(self, query, count):
2217 data.append(query.value(0))
2218 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2219 data.append("{:>19}".format(query.value(1)))
2220 for i in xrange(2, count):
2221 data.append(query.value(i))
2224 def samples_DataPrep(self, query, count):
2227 data.append(query.value(i))
2228 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2229 data.append("{:>19}".format(query.value(9)))
2230 for i in xrange(10, count):
2231 data.append(query.value(i))
2234 # Base class for custom ResizeColumnsToContents
2236 class ResizeColumnsToContentsBase(QObject):
2238 def __init__(self, parent=None):
2239 super(ResizeColumnsToContentsBase, self).__init__(parent)
2241 def ResizeColumnToContents(self, column, n):
2242 # Using the view's resizeColumnToContents() here is extrememly slow
2243 # so implement a crude alternative
2244 font = self.view.font()
2245 metrics = QFontMetrics(font)
2247 for row in xrange(n):
2248 val = self.data_model.child_items[row].data[column]
2249 len = metrics.width(str(val) + "MM")
2250 max = len if len > max else max
2251 val = self.data_model.columnHeader(column)
2252 len = metrics.width(str(val) + "MM")
2253 max = len if len > max else max
2254 self.view.setColumnWidth(column, max)
2256 def ResizeColumnsToContents(self):
2257 n = min(self.data_model.child_count, 100)
2259 # No data yet, so connect a signal to notify when there is
2260 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2262 columns = self.data_model.columnCount()
2263 for i in xrange(columns):
2264 self.ResizeColumnToContents(i, n)
2266 def UpdateColumnWidths(self, *x):
2267 # This only needs to be done once, so disconnect the signal now
2268 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2269 self.ResizeColumnsToContents()
2271 # Convert value to CSV
2275 val = val.replace('"', '""')
2276 if "," in val or '"' in val:
2277 val = '"' + val + '"'
2280 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2284 def RowColumnKey(a):
2285 return a.row() * glb_max_cols + a.column()
2287 # Copy selected table cells to clipboard
2289 def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2290 indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2291 idx_cnt = len(indexes)
2296 min_row = indexes[0].row()
2297 max_row = indexes[0].row()
2298 min_col = indexes[0].column()
2299 max_col = indexes[0].column()
2301 min_row = min(min_row, i.row())
2302 max_row = max(max_row, i.row())
2303 min_col = min(min_col, i.column())
2304 max_col = max(max_col, i.column())
2305 if max_col > glb_max_cols:
2306 raise RuntimeError("glb_max_cols is too low")
2307 max_width = [0] * (1 + max_col - min_col)
2309 c = i.column() - min_col
2310 max_width[c] = max(max_width[c], len(str(i.data())))
2315 model = indexes[0].model()
2316 for col in range(min_col, max_col + 1):
2317 val = model.headerData(col, Qt.Horizontal)
2319 text += sep + ToCSValue(val)
2323 max_width[c] = max(max_width[c], len(val))
2324 width = max_width[c]
2325 align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2326 if align & Qt.AlignRight:
2327 val = val.rjust(width)
2328 text += pad + sep + val
2329 pad = " " * (width - len(val))
2336 if i.row() > last_row:
2342 text += sep + ToCSValue(str(i.data()))
2345 width = max_width[i.column() - min_col]
2346 if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2347 val = str(i.data()).rjust(width)
2350 text += pad + sep + val
2351 pad = " " * (width - len(val))
2353 QApplication.clipboard().setText(text)
2355 def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2356 indexes = view.selectedIndexes()
2357 if not len(indexes):
2360 selection = view.selectionModel()
2364 above = view.indexAbove(i)
2365 if not selection.isSelected(above):
2370 raise RuntimeError("CopyTreeCellsToClipboard internal error")
2372 model = first.model()
2374 col_cnt = model.columnCount(first)
2375 max_width = [0] * col_cnt
2378 indent_str = " " * indent_sz
2380 expanded_mark_sz = 2
2381 if sys.version_info[0] == 3:
2382 expanded_mark = "\u25BC "
2383 not_expanded_mark = "\u25B6 "
2385 expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2386 not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2394 for c in range(col_cnt):
2395 i = pos.sibling(row, c)
2397 n = len(str(i.data()))
2399 n = len(str(i.data()).strip())
2400 n += (i.internalPointer().level - 1) * indent_sz
2401 n += expanded_mark_sz
2402 max_width[c] = max(max_width[c], n)
2403 pos = view.indexBelow(pos)
2404 if not selection.isSelected(pos):
2411 for c in range(col_cnt):
2412 val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2414 text += sep + ToCSValue(val)
2417 max_width[c] = max(max_width[c], len(val))
2418 width = max_width[c]
2419 align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2420 if align & Qt.AlignRight:
2421 val = val.rjust(width)
2422 text += pad + sep + val
2423 pad = " " * (width - len(val))
2432 for c in range(col_cnt):
2433 i = pos.sibling(row, c)
2436 if model.hasChildren(i):
2437 if view.isExpanded(i):
2438 mark = expanded_mark
2440 mark = not_expanded_mark
2443 val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2445 text += sep + ToCSValue(val)
2448 width = max_width[c]
2449 if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2450 val = val.rjust(width)
2451 text += pad + sep + val
2452 pad = " " * (width - len(val))
2454 pos = view.indexBelow(pos)
2455 if not selection.isSelected(pos):
2457 text = text.rstrip() + "\n"
2461 QApplication.clipboard().setText(text)
2463 def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2464 view.CopyCellsToClipboard(view, as_csv, with_hdr)
2466 def CopyCellsToClipboardHdr(view):
2467 CopyCellsToClipboard(view, False, True)
2469 def CopyCellsToClipboardCSV(view):
2470 CopyCellsToClipboard(view, True, True)
2474 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2476 def __init__(self, glb, table_name, parent=None):
2477 super(TableWindow, self).__init__(parent)
2479 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2481 self.model = QSortFilterProxyModel()
2482 self.model.setSourceModel(self.data_model)
2484 self.view = QTableView()
2485 self.view.setModel(self.model)
2486 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2487 self.view.verticalHeader().setVisible(False)
2488 self.view.sortByColumn(-1, Qt.AscendingOrder)
2489 self.view.setSortingEnabled(True)
2490 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2491 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2493 self.ResizeColumnsToContents()
2495 self.find_bar = FindBar(self, self, True)
2497 self.finder = ChildDataItemFinder(self.data_model)
2499 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2501 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2503 self.setWidget(self.vbox.Widget())
2505 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2507 def Find(self, value, direction, pattern, context):
2508 self.view.setFocus()
2509 self.find_bar.Busy()
2510 self.finder.Find(value, direction, pattern, context, self.FindDone)
2512 def FindDone(self, row):
2513 self.find_bar.Idle()
2515 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2517 self.find_bar.NotFound()
2521 def GetTableList(glb):
2523 query = QSqlQuery(glb.db)
2524 if glb.dbref.is_sqlite3:
2525 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2527 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2529 tables.append(query.value(0))
2530 if glb.dbref.is_sqlite3:
2531 tables.append("sqlite_master")
2533 tables.append("information_schema.tables")
2534 tables.append("information_schema.views")
2535 tables.append("information_schema.columns")
2538 # Top Calls data model
2540 class TopCallsModel(SQLTableModel):
2542 def __init__(self, glb, report_vars, parent=None):
2544 if not glb.dbref.is_sqlite3:
2547 if len(report_vars.limit):
2548 limit = " LIMIT " + report_vars.limit
2549 sql = ("SELECT comm, pid, tid, name,"
2551 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2554 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2556 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2557 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2558 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2562 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2563 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2564 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2565 " INNER JOIN comms ON calls.comm_id = comms.id"
2566 " INNER JOIN threads ON calls.thread_id = threads.id" +
2567 report_vars.where_clause +
2568 " ORDER BY elapsed_time DESC" +
2571 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2572 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2573 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2575 def columnAlignment(self, column):
2576 return self.alignment[column]
2578 # Top Calls report creation dialog
2580 class TopCallsDialog(ReportDialogBase):
2582 def __init__(self, glb, parent=None):
2583 title = "Top Calls by Elapsed Time"
2584 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2585 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2586 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2587 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2588 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2589 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2590 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2591 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2592 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2596 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2598 def __init__(self, glb, report_vars, parent=None):
2599 super(TopCallsWindow, self).__init__(parent)
2601 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2602 self.model = self.data_model
2604 self.view = QTableView()
2605 self.view.setModel(self.model)
2606 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2607 self.view.verticalHeader().setVisible(False)
2608 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2609 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2611 self.ResizeColumnsToContents()
2613 self.find_bar = FindBar(self, self, True)
2615 self.finder = ChildDataItemFinder(self.model)
2617 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2619 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2621 self.setWidget(self.vbox.Widget())
2623 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2625 def Find(self, value, direction, pattern, context):
2626 self.view.setFocus()
2627 self.find_bar.Busy()
2628 self.finder.Find(value, direction, pattern, context, self.FindDone)
2630 def FindDone(self, row):
2631 self.find_bar.Idle()
2633 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2635 self.find_bar.NotFound()
2639 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2640 action = QAction(label, parent)
2641 if shortcut != None:
2642 action.setShortcuts(shortcut)
2643 action.setStatusTip(tip)
2644 action.triggered.connect(callback)
2647 # Typical application actions
2649 def CreateExitAction(app, parent=None):
2650 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2652 # Typical MDI actions
2654 def CreateCloseActiveWindowAction(mdi_area):
2655 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2657 def CreateCloseAllWindowsAction(mdi_area):
2658 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2660 def CreateTileWindowsAction(mdi_area):
2661 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2663 def CreateCascadeWindowsAction(mdi_area):
2664 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2666 def CreateNextWindowAction(mdi_area):
2667 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2669 def CreatePreviousWindowAction(mdi_area):
2670 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2672 # Typical MDI window menu
2676 def __init__(self, mdi_area, menu):
2677 self.mdi_area = mdi_area
2678 self.window_menu = menu.addMenu("&Windows")
2679 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2680 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2681 self.tile_windows = CreateTileWindowsAction(mdi_area)
2682 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2683 self.next_window = CreateNextWindowAction(mdi_area)
2684 self.previous_window = CreatePreviousWindowAction(mdi_area)
2685 self.window_menu.aboutToShow.connect(self.Update)
2688 self.window_menu.clear()
2689 sub_window_count = len(self.mdi_area.subWindowList())
2690 have_sub_windows = sub_window_count != 0
2691 self.close_active_window.setEnabled(have_sub_windows)
2692 self.close_all_windows.setEnabled(have_sub_windows)
2693 self.tile_windows.setEnabled(have_sub_windows)
2694 self.cascade_windows.setEnabled(have_sub_windows)
2695 self.next_window.setEnabled(have_sub_windows)
2696 self.previous_window.setEnabled(have_sub_windows)
2697 self.window_menu.addAction(self.close_active_window)
2698 self.window_menu.addAction(self.close_all_windows)
2699 self.window_menu.addSeparator()
2700 self.window_menu.addAction(self.tile_windows)
2701 self.window_menu.addAction(self.cascade_windows)
2702 self.window_menu.addSeparator()
2703 self.window_menu.addAction(self.next_window)
2704 self.window_menu.addAction(self.previous_window)
2705 if sub_window_count == 0:
2707 self.window_menu.addSeparator()
2709 for sub_window in self.mdi_area.subWindowList():
2710 label = str(nr) + " " + sub_window.name
2713 action = self.window_menu.addAction(label)
2714 action.setCheckable(True)
2715 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2716 action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2717 self.window_menu.addAction(action)
2720 def setActiveSubWindow(self, nr):
2721 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2736 <p class=c1><a href=#reports>1. Reports</a></p>
2737 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2738 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2739 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
2740 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2741 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2742 <p class=c1><a href=#tables>2. Tables</a></p>
2743 <h1 id=reports>1. Reports</h1>
2744 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2745 The result is a GUI window with a tree representing a context-sensitive
2746 call-graph. Expanding a couple of levels of the tree and adjusting column
2747 widths to suit will display something like:
2749 Call Graph: pt_example
2750 Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2753 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2754 |- unknown unknown 1 13198 0.1 1 0.0
2755 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2756 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2757 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2758 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2759 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2760 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2761 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2762 v- main ls 1 8182043 99.6 180254 99.9
2764 <h3>Points to note:</h3>
2766 <li>The top level is a command name (comm)</li>
2767 <li>The next level is a thread (pid:tid)</li>
2768 <li>Subsequent levels are functions</li>
2769 <li>'Count' is the number of calls</li>
2770 <li>'Time' is the elapsed time until the function returns</li>
2771 <li>Percentages are relative to the level above</li>
2772 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2775 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2776 The pattern matching symbols are ? for any character and * for zero or more characters.
2777 <h2 id=calltree>1.2 Call Tree</h2>
2778 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2779 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2780 <h2 id=allbranches>1.3 All branches</h2>
2781 The All branches report displays all branches in chronological order.
2782 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2783 <h3>Disassembly</h3>
2784 Open a branch to display disassembly. This only works if:
2786 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2787 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2788 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2789 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2790 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2792 <h4 id=xed>Intel XED Setup</h4>
2793 To use Intel XED, libxed.so must be present. To build and install libxed.so:
2795 git clone https://github.com/intelxed/mbuild.git mbuild
2796 git clone https://github.com/intelxed/xed
2799 sudo ./mfile.py --prefix=/usr/local install
2803 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2804 Refer to Python documentation for the regular expression syntax.
2805 All columns are searched, but only currently fetched rows are searched.
2806 <h2 id=selectedbranches>1.4 Selected branches</h2>
2807 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2808 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2809 <h3>1.4.1 Time ranges</h3>
2810 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2811 ms, us or ns. Also, negative values are relative to the end of trace. Examples:
2813 81073085947329-81073085958238 From 81073085947329 to 81073085958238
2814 100us-200us From 100us to 200us
2815 10ms- From 10ms to the end
2816 -100ns The first 100ns
2817 -10ms- The last 10ms
2819 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2820 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2821 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.
2822 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2823 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
2824 <h1 id=tables>2. Tables</h1>
2825 The Tables menu shows all tables and views in the database. Most tables have an associated view
2826 which displays the information in a more friendly way. Not all data for large tables is fetched
2827 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2828 but that can be slow for large tables.
2829 <p>There are also tables of database meta-information.
2830 For SQLite3 databases, the sqlite_master table is included.
2831 For PostgreSQL databases, information_schema.tables/views/columns are included.
2833 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2834 Refer to Python documentation for the regular expression syntax.
2835 All columns are searched, but only currently fetched rows are searched.
2836 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2837 will go to the next/previous result in id order, instead of display order.
2842 class HelpWindow(QMdiSubWindow):
2844 def __init__(self, glb, parent=None):
2845 super(HelpWindow, self).__init__(parent)
2847 self.text = QTextBrowser()
2848 self.text.setHtml(glb_help_text)
2849 self.text.setReadOnly(True)
2850 self.text.setOpenExternalLinks(True)
2852 self.setWidget(self.text)
2854 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2856 # Main window that only displays the help text
2858 class HelpOnlyWindow(QMainWindow):
2860 def __init__(self, parent=None):
2861 super(HelpOnlyWindow, self).__init__(parent)
2863 self.setMinimumSize(200, 100)
2864 self.resize(800, 600)
2865 self.setWindowTitle("Exported SQL Viewer Help")
2866 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2868 self.text = QTextBrowser()
2869 self.text.setHtml(glb_help_text)
2870 self.text.setReadOnly(True)
2871 self.text.setOpenExternalLinks(True)
2873 self.setCentralWidget(self.text)
2877 def ResizeFont(widget, diff):
2878 font = widget.font()
2879 sz = font.pointSize()
2880 font.setPointSize(sz + diff)
2881 widget.setFont(font)
2883 def ShrinkFont(widget):
2884 ResizeFont(widget, -1)
2886 def EnlargeFont(widget):
2887 ResizeFont(widget, 1)
2889 # Unique name for sub-windows
2891 def NumberedWindowName(name, nr):
2893 name += " <" + str(nr) + ">"
2896 def UniqueSubWindowName(mdi_area, name):
2899 unique_name = NumberedWindowName(name, nr)
2901 for sub_window in mdi_area.subWindowList():
2902 if sub_window.name == unique_name:
2911 def AddSubWindow(mdi_area, sub_window, name):
2912 unique_name = UniqueSubWindowName(mdi_area, name)
2913 sub_window.setMinimumSize(200, 100)
2914 sub_window.resize(800, 600)
2915 sub_window.setWindowTitle(unique_name)
2916 sub_window.setAttribute(Qt.WA_DeleteOnClose)
2917 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
2918 sub_window.name = unique_name
2919 mdi_area.addSubWindow(sub_window)
2924 class MainWindow(QMainWindow):
2926 def __init__(self, glb, parent=None):
2927 super(MainWindow, self).__init__(parent)
2931 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
2932 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
2933 self.setMinimumSize(200, 100)
2935 self.mdi_area = QMdiArea()
2936 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2937 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2939 self.setCentralWidget(self.mdi_area)
2941 menu = self.menuBar()
2943 file_menu = menu.addMenu("&File")
2944 file_menu.addAction(CreateExitAction(glb.app, self))
2946 edit_menu = menu.addMenu("&Edit")
2947 edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
2948 edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
2949 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
2950 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
2951 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
2952 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
2954 reports_menu = menu.addMenu("&Reports")
2955 if IsSelectable(glb.db, "calls"):
2956 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
2958 if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
2959 reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
2961 self.EventMenu(GetEventList(glb.db), reports_menu)
2963 if IsSelectable(glb.db, "calls"):
2964 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
2966 self.TableMenu(GetTableList(glb), menu)
2968 self.window_menu = WindowMenu(self.mdi_area, menu)
2970 help_menu = menu.addMenu("&Help")
2971 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
2974 win = self.mdi_area.activeSubWindow()
2981 def CopyToClipboard(self):
2982 self.Try(CopyCellsToClipboardHdr)
2984 def CopyToClipboardCSV(self):
2985 self.Try(CopyCellsToClipboardCSV)
2988 win = self.mdi_area.activeSubWindow()
2991 win.find_bar.Activate()
2995 def FetchMoreRecords(self):
2996 win = self.mdi_area.activeSubWindow()
2999 win.fetch_bar.Activate()
3003 def ShrinkFont(self):
3004 self.Try(ShrinkFont)
3006 def EnlargeFont(self):
3007 self.Try(EnlargeFont)
3009 def EventMenu(self, events, reports_menu):
3011 for event in events:
3012 event = event.split(":")[0]
3013 if event == "branches":
3014 branches_events += 1
3016 for event in events:
3018 event = event.split(":")[0]
3019 if event == "branches":
3020 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3021 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
3022 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3023 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
3025 def TableMenu(self, tables, menu):
3026 table_menu = menu.addMenu("&Tables")
3027 for table in tables:
3028 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
3030 def NewCallGraph(self):
3031 CallGraphWindow(self.glb, self)
3033 def NewCallTree(self):
3034 CallTreeWindow(self.glb, self)
3036 def NewTopCalls(self):
3037 dialog = TopCallsDialog(self.glb, self)
3038 ret = dialog.exec_()
3040 TopCallsWindow(self.glb, dialog.report_vars, self)
3042 def NewBranchView(self, event_id):
3043 BranchWindow(self.glb, event_id, ReportVars(), self)
3045 def NewSelectedBranchView(self, event_id):
3046 dialog = SelectedBranchDialog(self.glb, self)
3047 ret = dialog.exec_()
3049 BranchWindow(self.glb, event_id, dialog.report_vars, self)
3051 def NewTableView(self, table_name):
3052 TableWindow(self.glb, table_name, self)
3055 HelpWindow(self.glb, self)
3059 class xed_state_t(Structure):
3066 class XEDInstruction():
3068 def __init__(self, libxed):
3069 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3070 xedd_t = c_byte * 512
3071 self.xedd = xedd_t()
3072 self.xedp = addressof(self.xedd)
3073 libxed.xed_decoded_inst_zero(self.xedp)
3074 self.state = xed_state_t()
3075 self.statep = addressof(self.state)
3076 # Buffer for disassembled instruction text
3077 self.buffer = create_string_buffer(256)
3078 self.bufferp = addressof(self.buffer)
3084 self.libxed = CDLL("libxed.so")
3088 self.libxed = CDLL("/usr/local/lib/libxed.so")
3090 self.xed_tables_init = self.libxed.xed_tables_init
3091 self.xed_tables_init.restype = None
3092 self.xed_tables_init.argtypes = []
3094 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3095 self.xed_decoded_inst_zero.restype = None
3096 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3098 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3099 self.xed_operand_values_set_mode.restype = None
3100 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3102 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3103 self.xed_decoded_inst_zero_keep_mode.restype = None
3104 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3106 self.xed_decode = self.libxed.xed_decode
3107 self.xed_decode.restype = c_int
3108 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3110 self.xed_format_context = self.libxed.xed_format_context
3111 self.xed_format_context.restype = c_uint
3112 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3114 self.xed_tables_init()
3116 def Instruction(self):
3117 return XEDInstruction(self)
3119 def SetMode(self, inst, mode):
3121 inst.state.mode = 4 # 32-bit
3122 inst.state.width = 4 # 4 bytes
3124 inst.state.mode = 1 # 64-bit
3125 inst.state.width = 8 # 8 bytes
3126 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3128 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3129 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3130 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3133 # Use AT&T mode (2), alternative is Intel (3)
3134 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3137 if sys.version_info[0] == 2:
3138 result = inst.buffer.value
3140 result = inst.buffer.value.decode()
3141 # Return instruction length and the disassembled instruction text
3142 # For now, assume the length is in byte 166
3143 return inst.xedd[166], result
3145 def TryOpen(file_name):
3147 return open(file_name, "rb")
3152 result = sizeof(c_void_p)
3159 if sys.version_info[0] == 2:
3160 eclass = ord(header[4])
3161 encoding = ord(header[5])
3162 version = ord(header[6])
3165 encoding = header[5]
3167 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3168 result = True if eclass == 2 else False
3175 def __init__(self, dbref, db, dbname):
3178 self.dbname = dbname
3179 self.home_dir = os.path.expanduser("~")
3180 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3181 if self.buildid_dir:
3182 self.buildid_dir += "/.build-id/"
3184 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3186 self.mainwindow = None
3187 self.instances_to_shutdown_on_exit = weakref.WeakSet()
3189 self.disassembler = LibXED()
3190 self.have_disassembler = True
3192 self.have_disassembler = False
3194 def FileFromBuildId(self, build_id):
3195 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3196 return TryOpen(file_name)
3198 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3199 # Assume current machine i.e. no support for virtualization
3200 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3201 file_name = os.getenv("PERF_KCORE")
3202 f = TryOpen(file_name) if file_name else None
3205 # For now, no special handling if long_name is /proc/kcore
3206 f = TryOpen(long_name)
3209 f = self.FileFromBuildId(build_id)
3214 def AddInstanceToShutdownOnExit(self, instance):
3215 self.instances_to_shutdown_on_exit.add(instance)
3217 # Shutdown any background processes or threads
3218 def ShutdownInstances(self):
3219 for x in self.instances_to_shutdown_on_exit:
3225 # Database reference
3229 def __init__(self, is_sqlite3, dbname):
3230 self.is_sqlite3 = is_sqlite3
3231 self.dbname = dbname
3233 def Open(self, connection_name):
3234 dbname = self.dbname
3236 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3238 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3239 opts = dbname.split()
3242 opt = opt.split("=")
3243 if opt[0] == "hostname":
3244 db.setHostName(opt[1])
3245 elif opt[0] == "port":
3246 db.setPort(int(opt[1]))
3247 elif opt[0] == "username":
3248 db.setUserName(opt[1])
3249 elif opt[0] == "password":
3250 db.setPassword(opt[1])
3251 elif opt[0] == "dbname":
3256 db.setDatabaseName(dbname)
3258 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3264 if (len(sys.argv) < 2):
3265 printerr("Usage is: exported-sql-viewer.py {<database name> | --help-only}");
3266 raise Exception("Too few arguments")
3268 dbname = sys.argv[1]
3269 if dbname == "--help-only":
3270 app = QApplication(sys.argv)
3271 mainwindow = HelpOnlyWindow()
3278 f = open(dbname, "rb")
3279 if f.read(15) == b'SQLite format 3':
3285 dbref = DBRef(is_sqlite3, dbname)
3286 db, dbname = dbref.Open("main")
3287 glb = Glb(dbref, db, dbname)
3288 app = QApplication(sys.argv)
3290 mainwindow = MainWindow(glb)
3291 glb.mainwindow = mainwindow
3294 glb.ShutdownInstances()
3298 if __name__ == "__main__":