aboutsummaryrefslogtreecommitdiff
path: root/tools/perf/scripts/python
diff options
context:
space:
mode:
authorIan Rogers <irogers@google.com>2026-09-25 23:20:11 -0700
committerArnaldo Carvalho de Melo <acme@redhat.com>2026-09-26 20:49:31 +0200
commit3df7d85a242fb93cfa63466b8fc9fc44aad19b92 (patch)
treeb26f5c940ea3effbcdd36509d475f4b84e03e38c /tools/perf/scripts/python
parentb1f968c9656a8a874f566340ae501f888e700ad0 (diff)
perf python: Move and clean up exported-sql-viewer.py
Move exported-sql-viewer.py from tools/perf/scripts/python/ to tools/perf/python/ as it is a standalone PySide/PyQt GUI application for viewing SQLite and PostgreSQL databases exported by perf script. Improvements and cleanups compared to the legacy script: - Add support for PySide6 (falling back to PySide2 and PySide) loaded dynamically via importlib, and provide stub base classes (_QtBase, _QtWidget) so static analysis (mypy and pylint) and --help-only execution succeed when Qt bindings are not installed. - Fix a Python 3 NameError crash in HBoxLayout where expanded_mark and not_expanded_mark called Python 2's removed unicode() builtin instead of chr(0x25BC) and chr(0x25B6). - Replace instance-level method monkey-patching (self.mousePressEvent = self.MousePressEvent) on SwitchGraphView with proper class methods gated by self.rb_enabled so Qt virtual method dispatch works cleanly across PySide2 and PySide6. - Remove Python 2 constructs (cPickle, xrange), wildcard imports, bare 'except:' clauses, and variable names shadowing Python builtins. Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Ian Rogers <irogers@google.com> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/scripts/python')
-rwxr-xr-xtools/perf/scripts/python/exported-sql-viewer.py5030
1 files changed, 0 insertions, 5030 deletions
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
deleted file mode 100755
index e0b2e7268ef6..000000000000
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ /dev/null
@@ -1,5030 +0,0 @@
-#!/usr/bin/env python
-# SPDX-License-Identifier: GPL-2.0
-# exported-sql-viewer.py: view data from sql database
-# Copyright (c) 2014-2018, Intel Corporation.
-
-# To use this script you will need to have exported data using either the
-# export-to-sqlite.py or the export-to-postgresql.py script. Refer to those
-# scripts for details.
-#
-# Following on from the example in the export scripts, a
-# call-graph can be displayed for the pt_example database like this:
-#
-# python tools/perf/scripts/python/exported-sql-viewer.py pt_example
-#
-# Note that for PostgreSQL, this script supports connecting to remote databases
-# by setting hostname, port, username, password, and dbname e.g.
-#
-# python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
-#
-# The result is a GUI window with a tree representing a context-sensitive
-# call-graph. Expanding a couple of levels of the tree and adjusting column
-# widths to suit will display something like:
-#
-# Call Graph: pt_example
-# Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
-# v- ls
-# v- 2638:2638
-# v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
-# |- unknown unknown 1 13198 0.1 1 0.0
-# >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
-# >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
-# v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
-# >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
-# >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
-# >- __libc_csu_init ls 1 10354 0.1 10 0.0
-# |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
-# v- main ls 1 8182043 99.6 180254 99.9
-#
-# Points to note:
-# The top level is a command name (comm)
-# The next level is a thread (pid:tid)
-# Subsequent levels are functions
-# 'Count' is the number of calls
-# 'Time' is the elapsed time until the function returns
-# Percentages are relative to the level above
-# 'Branch Count' is the total number of branches for that function and all
-# functions that it calls
-
-# There is also a "All branches" report, which displays branches and
-# possibly disassembly. However, presently, the only supported disassembler is
-# Intel XED, and additionally the object code must be present in perf build ID
-# cache. To use Intel XED, libxed.so must be present. To build and install
-# libxed.so:
-# git clone https://github.com/intelxed/mbuild.git mbuild
-# git clone https://github.com/intelxed/xed
-# cd xed
-# ./mfile.py --share
-# sudo ./mfile.py --prefix=/usr/local install
-# sudo ldconfig
-#
-# Example report:
-#
-# Time CPU Command PID TID Branch Type In Tx Branch
-# 8107675239590 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
-# 7fab593ea260 48 89 e7 mov %rsp, %rdi
-# 8107675239899 2 ls 22011 22011 hardware interrupt No 7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
-# 8107675241900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
-# 7fab593ea260 48 89 e7 mov %rsp, %rdi
-# 7fab593ea263 e8 c8 06 00 00 callq 0x7fab593ea930
-# 8107675241900 2 ls 22011 22011 call No 7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
-# 7fab593ea930 55 pushq %rbp
-# 7fab593ea931 48 89 e5 mov %rsp, %rbp
-# 7fab593ea934 41 57 pushq %r15
-# 7fab593ea936 41 56 pushq %r14
-# 7fab593ea938 41 55 pushq %r13
-# 7fab593ea93a 41 54 pushq %r12
-# 7fab593ea93c 53 pushq %rbx
-# 7fab593ea93d 48 89 fb mov %rdi, %rbx
-# 7fab593ea940 48 83 ec 68 sub $0x68, %rsp
-# 7fab593ea944 0f 31 rdtsc
-# 7fab593ea946 48 c1 e2 20 shl $0x20, %rdx
-# 7fab593ea94a 89 c0 mov %eax, %eax
-# 7fab593ea94c 48 09 c2 or %rax, %rdx
-# 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
-# 8107675242232 2 ls 22011 22011 hardware interrupt No 7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
-# 8107675242900 2 ls 22011 22011 return from interrupt No ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
-# 7fab593ea94f 48 8b 05 1a 15 22 00 movq 0x22151a(%rip), %rax
-# 7fab593ea956 48 89 15 3b 13 22 00 movq %rdx, 0x22133b(%rip)
-# 8107675243232 2 ls 22011 22011 hardware interrupt No 7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
-
-from __future__ import print_function
-
-import sys
-# Only change warnings if the python -W option was not used
-if not sys.warnoptions:
- import warnings
- # PySide2 causes deprecation warnings, ignore them.
- warnings.filterwarnings("ignore", category=DeprecationWarning)
-import argparse
-import weakref
-import threading
-import string
-try:
- # Python2
- import cPickle as pickle
- # size of pickled integer big enough for record size
- glb_nsz = 8
-except ImportError:
- import pickle
- glb_nsz = 16
-import re
-import os
-import random
-import copy
-import math
-from libxed import LibXED
-
-pyside_version_1 = True
-if not "--pyside-version-1" in sys.argv:
- try:
- from PySide2.QtCore import *
- from PySide2.QtGui import *
- from PySide2.QtSql import *
- from PySide2.QtWidgets import *
- pyside_version_1 = False
- except:
- pass
-
-if pyside_version_1:
- from PySide.QtCore import *
- from PySide.QtGui import *
- from PySide.QtSql import *
-
-from decimal import Decimal, ROUND_HALF_UP
-from ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \
- c_void_p, c_bool, c_byte, c_char, c_int, c_uint, c_longlong, c_ulonglong
-from multiprocessing import Process, Array, Value, Event
-
-# xrange is range in Python3
-try:
- xrange
-except NameError:
- xrange = range
-
-def printerr(*args, **keyword_args):
- print(*args, file=sys.stderr, **keyword_args)
-
-# Data formatting helpers
-
-def tohex(ip):
- if ip < 0:
- ip += 1 << 64
- return "%x" % ip
-
-def offstr(offset):
- if offset:
- return "+0x%x" % offset
- return ""
-
-def dsoname(name):
- if name == "[kernel.kallsyms]":
- return "[kernel]"
- return name
-
-def findnth(s, sub, n, offs=0):
- pos = s.find(sub)
- if pos < 0:
- return pos
- if n <= 1:
- return offs + pos
- return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
-
-# Percent to one decimal place
-
-def PercentToOneDP(n, d):
- if not d:
- return "0.0"
- x = (n * Decimal(100)) / d
- return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
-
-# Helper for queries that must not fail
-
-def QueryExec(query, stmt):
- ret = query.exec_(stmt)
- if not ret:
- raise Exception("Query failed: " + query.lastError().text())
-
-# Background thread
-
-class Thread(QThread):
-
- done = Signal(object)
-
- def __init__(self, task, param=None, parent=None):
- super(Thread, self).__init__(parent)
- self.task = task
- self.param = param
-
- def run(self):
- while True:
- if self.param is None:
- done, result = self.task()
- else:
- done, result = self.task(self.param)
- self.done.emit(result)
- if done:
- break
-
-# Tree data model
-
-class TreeModel(QAbstractItemModel):
-
- def __init__(self, glb, params, parent=None):
- super(TreeModel, self).__init__(parent)
- self.glb = glb
- self.params = params
- self.root = self.GetRoot()
- self.last_row_read = 0
-
- def Item(self, parent):
- if parent.isValid():
- return parent.internalPointer()
- else:
- return self.root
-
- def rowCount(self, parent):
- result = self.Item(parent).childCount()
- if result < 0:
- result = 0
- self.dataChanged.emit(parent, parent)
- return result
-
- def hasChildren(self, parent):
- return self.Item(parent).hasChildren()
-
- def headerData(self, section, orientation, role):
- if role == Qt.TextAlignmentRole:
- return self.columnAlignment(section)
- if role != Qt.DisplayRole:
- return None
- if orientation != Qt.Horizontal:
- return None
- return self.columnHeader(section)
-
- def parent(self, child):
- child_item = child.internalPointer()
- if child_item is self.root:
- return QModelIndex()
- parent_item = child_item.getParentItem()
- return self.createIndex(parent_item.getRow(), 0, parent_item)
-
- def index(self, row, column, parent):
- child_item = self.Item(parent).getChildItem(row)
- return self.createIndex(row, column, child_item)
-
- def DisplayData(self, item, index):
- return item.getData(index.column())
-
- def FetchIfNeeded(self, row):
- if row > self.last_row_read:
- self.last_row_read = row
- if row + 10 >= self.root.child_count:
- self.fetcher.Fetch(glb_chunk_sz)
-
- def columnAlignment(self, column):
- return Qt.AlignLeft
-
- def columnFont(self, column):
- return None
-
- def data(self, index, role):
- if role == Qt.TextAlignmentRole:
- return self.columnAlignment(index.column())
- if role == Qt.FontRole:
- return self.columnFont(index.column())
- if role != Qt.DisplayRole:
- return None
- item = index.internalPointer()
- return self.DisplayData(item, index)
-
-# Table data model
-
-class TableModel(QAbstractTableModel):
-
- def __init__(self, parent=None):
- super(TableModel, self).__init__(parent)
- self.child_count = 0
- self.child_items = []
- self.last_row_read = 0
-
- def Item(self, parent):
- if parent.isValid():
- return parent.internalPointer()
- else:
- return self
-
- def rowCount(self, parent):
- return self.child_count
-
- def headerData(self, section, orientation, role):
- if role == Qt.TextAlignmentRole:
- return self.columnAlignment(section)
- if role != Qt.DisplayRole:
- return None
- if orientation != Qt.Horizontal:
- return None
- return self.columnHeader(section)
-
- def index(self, row, column, parent):
- return self.createIndex(row, column, self.child_items[row])
-
- def DisplayData(self, item, index):
- return item.getData(index.column())
-
- def FetchIfNeeded(self, row):
- if row > self.last_row_read:
- self.last_row_read = row
- if row + 10 >= self.child_count:
- self.fetcher.Fetch(glb_chunk_sz)
-
- def columnAlignment(self, column):
- return Qt.AlignLeft
-
- def columnFont(self, column):
- return None
-
- def data(self, index, role):
- if role == Qt.TextAlignmentRole:
- return self.columnAlignment(index.column())
- if role == Qt.FontRole:
- return self.columnFont(index.column())
- if role != Qt.DisplayRole:
- return None
- item = index.internalPointer()
- return self.DisplayData(item, index)
-
-# Model cache
-
-model_cache = weakref.WeakValueDictionary()
-model_cache_lock = threading.Lock()
-
-def LookupCreateModel(model_name, create_fn):
- model_cache_lock.acquire()
- try:
- model = model_cache[model_name]
- except:
- model = None
- if model is None:
- model = create_fn()
- model_cache[model_name] = model
- model_cache_lock.release()
- return model
-
-def LookupModel(model_name):
- model_cache_lock.acquire()
- try:
- model = model_cache[model_name]
- except:
- model = None
- model_cache_lock.release()
- return model
-
-# Find bar
-
-class FindBar():
-
- def __init__(self, parent, finder, is_reg_expr=False):
- self.finder = finder
- self.context = []
- self.last_value = None
- self.last_pattern = None
-
- label = QLabel("Find:")
- label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
-
- self.textbox = QComboBox()
- self.textbox.setEditable(True)
- self.textbox.currentIndexChanged.connect(self.ValueChanged)
-
- self.progress = QProgressBar()
- self.progress.setRange(0, 0)
- self.progress.hide()
-
- if is_reg_expr:
- self.pattern = QCheckBox("Regular Expression")
- else:
- self.pattern = QCheckBox("Pattern")
- self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
-
- self.next_button = QToolButton()
- self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
- self.next_button.released.connect(lambda: self.NextPrev(1))
-
- self.prev_button = QToolButton()
- self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
- self.prev_button.released.connect(lambda: self.NextPrev(-1))
-
- self.close_button = QToolButton()
- self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
- self.close_button.released.connect(self.Deactivate)
-
- self.hbox = QHBoxLayout()
- self.hbox.setContentsMargins(0, 0, 0, 0)
-
- self.hbox.addWidget(label)
- self.hbox.addWidget(self.textbox)
- self.hbox.addWidget(self.progress)
- self.hbox.addWidget(self.pattern)
- self.hbox.addWidget(self.next_button)
- self.hbox.addWidget(self.prev_button)
- self.hbox.addWidget(self.close_button)
-
- self.bar = QWidget()
- self.bar.setLayout(self.hbox)
- self.bar.hide()
-
- def Widget(self):
- return self.bar
-
- def Activate(self):
- self.bar.show()
- self.textbox.lineEdit().selectAll()
- self.textbox.setFocus()
-
- def Deactivate(self):
- self.bar.hide()
-
- def Busy(self):
- self.textbox.setEnabled(False)
- self.pattern.hide()
- self.next_button.hide()
- self.prev_button.hide()
- self.progress.show()
-
- def Idle(self):
- self.textbox.setEnabled(True)
- self.progress.hide()
- self.pattern.show()
- self.next_button.show()
- self.prev_button.show()
-
- def Find(self, direction):
- value = self.textbox.currentText()
- pattern = self.pattern.isChecked()
- self.last_value = value
- self.last_pattern = pattern
- self.finder.Find(value, direction, pattern, self.context)
-
- def ValueChanged(self):
- value = self.textbox.currentText()
- pattern = self.pattern.isChecked()
- index = self.textbox.currentIndex()
- data = self.textbox.itemData(index)
- # Store the pattern in the combo box to keep it with the text value
- if data == None:
- self.textbox.setItemData(index, pattern)
- else:
- self.pattern.setChecked(data)
- self.Find(0)
-
- def NextPrev(self, direction):
- value = self.textbox.currentText()
- pattern = self.pattern.isChecked()
- if value != self.last_value:
- index = self.textbox.findText(value)
- # Allow for a button press before the value has been added to the combo box
- if index < 0:
- index = self.textbox.count()
- self.textbox.addItem(value, pattern)
- self.textbox.setCurrentIndex(index)
- return
- else:
- self.textbox.setItemData(index, pattern)
- elif pattern != self.last_pattern:
- # Keep the pattern recorded in the combo box up to date
- index = self.textbox.currentIndex()
- self.textbox.setItemData(index, pattern)
- self.Find(direction)
-
- def NotFound(self):
- QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
-
-# Context-sensitive call graph data model item base
-
-class CallGraphLevelItemBase(object):
-
- def __init__(self, glb, params, row, parent_item):
- self.glb = glb
- self.params = params
- self.row = row
- self.parent_item = parent_item
- self.query_done = False
- self.child_count = 0
- self.child_items = []
- if parent_item:
- self.level = parent_item.level + 1
- else:
- self.level = 0
-
- def getChildItem(self, row):
- return self.child_items[row]
-
- def getParentItem(self):
- return self.parent_item
-
- def getRow(self):
- return self.row
-
- def childCount(self):
- if not self.query_done:
- self.Select()
- if not self.child_count:
- return -1
- return self.child_count
-
- def hasChildren(self):
- if not self.query_done:
- return True
- return self.child_count > 0
-
- def getData(self, column):
- return self.data[column]
-
-# Context-sensitive call graph data model level 2+ item base
-
-class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
-
- def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
- super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
- self.comm_id = comm_id
- self.thread_id = thread_id
- self.call_path_id = call_path_id
- self.insn_cnt = insn_cnt
- self.cyc_cnt = cyc_cnt
- self.branch_count = branch_count
- self.time = time
-
- def Select(self):
- self.query_done = True
- query = QSqlQuery(self.glb.db)
- if self.params.have_ipc:
- ipc_str = ", SUM(insn_count), SUM(cyc_count)"
- else:
- ipc_str = ""
- QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
- " FROM calls"
- " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
- " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
- " INNER JOIN dsos ON symbols.dso_id = dsos.id"
- " WHERE parent_call_path_id = " + str(self.call_path_id) +
- " AND comm_id = " + str(self.comm_id) +
- " AND thread_id = " + str(self.thread_id) +
- " GROUP BY call_path_id, name, short_name"
- " ORDER BY call_path_id")
- while query.next():
- if self.params.have_ipc:
- insn_cnt = int(query.value(5))
- cyc_cnt = int(query.value(6))
- branch_count = int(query.value(7))
- else:
- insn_cnt = 0
- cyc_cnt = 0
- branch_count = int(query.value(5))
- child_item = CallGraphLevelThreeItem(self.glb, self.params, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), insn_cnt, cyc_cnt, branch_count, self)
- self.child_items.append(child_item)
- self.child_count += 1
-
-# Context-sensitive call graph data model level three item
-
-class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
-
- def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item):
- super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
- dso = dsoname(dso)
- if self.params.have_ipc:
- insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
- cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
- br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
- ipc = CalcIPC(cyc_cnt, insn_cnt)
- self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
- else:
- self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
- self.dbid = call_path_id
-
-# Context-sensitive call graph data model level two item
-
-class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
-
- def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
- super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
- if self.params.have_ipc:
- self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
- else:
- self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
- self.dbid = thread_id
-
- def Select(self):
- super(CallGraphLevelTwoItem, self).Select()
- for child_item in self.child_items:
- self.time += child_item.time
- self.insn_cnt += child_item.insn_cnt
- self.cyc_cnt += child_item.cyc_cnt
- self.branch_count += child_item.branch_count
- for child_item in self.child_items:
- child_item.data[4] = PercentToOneDP(child_item.time, self.time)
- if self.params.have_ipc:
- child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
- child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
- child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
- else:
- child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
-
-# Context-sensitive call graph data model level one item
-
-class CallGraphLevelOneItem(CallGraphLevelItemBase):
-
- def __init__(self, glb, params, row, comm_id, comm, parent_item):
- super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
- if self.params.have_ipc:
- self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
- else:
- self.data = [comm, "", "", "", "", "", ""]
- self.dbid = comm_id
-
- def Select(self):
- self.query_done = True
- query = QSqlQuery(self.glb.db)
- QueryExec(query, "SELECT thread_id, pid, tid"
- " FROM comm_threads"
- " INNER JOIN threads ON thread_id = threads.id"
- " WHERE comm_id = " + str(self.dbid))
- while query.next():
- child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
- self.child_items.append(child_item)
- self.child_count += 1
-
-# Context-sensitive call graph data model root item
-
-class CallGraphRootItem(CallGraphLevelItemBase):
-
- def __init__(self, glb, params):
- super(CallGraphRootItem, self).__init__(glb, params, 0, None)
- self.dbid = 0
- self.query_done = True
- if_has_calls = ""
- if IsSelectable(glb.db, "comms", columns = "has_calls"):
- if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
- query = QSqlQuery(glb.db)
- QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
- while query.next():
- if not query.value(0):
- continue
- child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
- self.child_items.append(child_item)
- self.child_count += 1
-
-# Call graph model parameters
-
-class CallGraphModelParams():
-
- def __init__(self, glb, parent=None):
- self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
-
-# Context-sensitive call graph data model base
-
-class CallGraphModelBase(TreeModel):
-
- def __init__(self, glb, parent=None):
- super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
-
- def FindSelect(self, value, pattern, query):
- if pattern:
- # postgresql and sqlite pattern patching differences:
- # postgresql LIKE is case sensitive but sqlite LIKE is not
- # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
- # postgresql supports ILIKE which is case insensitive
- # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
- if not self.glb.dbref.is_sqlite3:
- # Escape % and _
- s = value.replace("%", "\\%")
- s = s.replace("_", "\\_")
- # Translate * and ? into SQL LIKE pattern characters % and _
- if sys.version_info[0] == 3:
- trans = str.maketrans("*?", "%_")
- else:
- trans = string.maketrans("*?", "%_")
- match = " LIKE '" + str(s).translate(trans) + "'"
- else:
- match = " GLOB '" + str(value) + "'"
- else:
- match = " = '" + str(value) + "'"
- self.DoFindSelect(query, match)
-
- def Found(self, query, found):
- if found:
- return self.FindPath(query)
- return []
-
- def FindValue(self, value, pattern, query, last_value, last_pattern):
- if last_value == value and pattern == last_pattern:
- found = query.first()
- else:
- self.FindSelect(value, pattern, query)
- found = query.next()
- return self.Found(query, found)
-
- def FindNext(self, query):
- found = query.next()
- if not found:
- found = query.first()
- return self.Found(query, found)
-
- def FindPrev(self, query):
- found = query.previous()
- if not found:
- found = query.last()
- return self.Found(query, found)
-
- def FindThread(self, c):
- if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
- ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
- elif c.direction > 0:
- ids = self.FindNext(c.query)
- else:
- ids = self.FindPrev(c.query)
- return (True, ids)
-
- def Find(self, value, direction, pattern, context, callback):
- class Context():
- def __init__(self, *x):
- self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
- def Update(self, *x):
- self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
- if len(context):
- context[0].Update(value, direction, pattern)
- else:
- context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
- # Use a thread so the UI is not blocked during the SELECT
- thread = Thread(self.FindThread, context[0])
- thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
- thread.start()
-
- def FindDone(self, thread, callback, ids):
- callback(ids)
-
-# Context-sensitive call graph data model
-
-class CallGraphModel(CallGraphModelBase):
-
- def __init__(self, glb, parent=None):
- super(CallGraphModel, self).__init__(glb, parent)
-
- def GetRoot(self):
- return CallGraphRootItem(self.glb, self.params)
-
- def columnCount(self, parent=None):
- if self.params.have_ipc:
- return 12
- else:
- return 7
-
- def columnHeader(self, column):
- if self.params.have_ipc:
- headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
- else:
- headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
- return headers[column]
-
- def columnAlignment(self, column):
- if self.params.have_ipc:
- alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
- else:
- alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
- return alignment[column]
-
- def DoFindSelect(self, query, match):
- QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
- " FROM calls"
- " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
- " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
- " WHERE calls.id <> 0"
- " AND symbols.name" + match +
- " GROUP BY comm_id, thread_id, call_path_id"
- " ORDER BY comm_id, thread_id, call_path_id")
-
- def FindPath(self, query):
- # Turn the query result into a list of ids that the tree view can walk
- # to open the tree at the right place.
- ids = []
- parent_id = query.value(0)
- while parent_id:
- ids.insert(0, parent_id)
- q2 = QSqlQuery(self.glb.db)
- QueryExec(q2, "SELECT parent_id"
- " FROM call_paths"
- " WHERE id = " + str(parent_id))
- if not q2.next():
- break
- parent_id = q2.value(0)
- # The call path root is not used
- if ids[0] == 1:
- del ids[0]
- ids.insert(0, query.value(2))
- ids.insert(0, query.value(1))
- return ids
-
-# Call tree data model level 2+ item base
-
-class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
-
- def __init__(self, glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
- super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
- self.comm_id = comm_id
- self.thread_id = thread_id
- self.calls_id = calls_id
- self.call_time = call_time
- self.time = time
- self.insn_cnt = insn_cnt
- self.cyc_cnt = cyc_cnt
- self.branch_count = branch_count
-
- def Select(self):
- self.query_done = True
- if self.calls_id == 0:
- comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
- else:
- comm_thread = ""
- if self.params.have_ipc:
- ipc_str = ", insn_count, cyc_count"
- else:
- ipc_str = ""
- query = QSqlQuery(self.glb.db)
- QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
- " FROM calls"
- " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
- " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
- " INNER JOIN dsos ON symbols.dso_id = dsos.id"
- " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
- " ORDER BY call_time, calls.id")
- while query.next():
- if self.params.have_ipc:
- insn_cnt = int(query.value(5))
- cyc_cnt = int(query.value(6))
- branch_count = int(query.value(7))
- else:
- insn_cnt = 0
- cyc_cnt = 0
- branch_count = int(query.value(5))
- child_item = CallTreeLevelThreeItem(self.glb, self.params, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), insn_cnt, cyc_cnt, branch_count, self)
- self.child_items.append(child_item)
- self.child_count += 1
-
-# Call tree data model level three item
-
-class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
-
- def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
- super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item)
- dso = dsoname(dso)
- if self.params.have_ipc:
- insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
- cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
- br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
- ipc = CalcIPC(cyc_cnt, insn_cnt)
- self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
- else:
- self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
- self.dbid = calls_id
-
-# Call tree data model level two item
-
-class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
-
- def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
- super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, 0, parent_item)
- if self.params.have_ipc:
- self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
- else:
- self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
- self.dbid = thread_id
-
- def Select(self):
- super(CallTreeLevelTwoItem, self).Select()
- for child_item in self.child_items:
- self.time += child_item.time
- self.insn_cnt += child_item.insn_cnt
- self.cyc_cnt += child_item.cyc_cnt
- self.branch_count += child_item.branch_count
- for child_item in self.child_items:
- child_item.data[4] = PercentToOneDP(child_item.time, self.time)
- if self.params.have_ipc:
- child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
- child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
- child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
- else:
- child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
-
-# Call tree data model level one item
-
-class CallTreeLevelOneItem(CallGraphLevelItemBase):
-
- def __init__(self, glb, params, row, comm_id, comm, parent_item):
- super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
- if self.params.have_ipc:
- self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
- else:
- self.data = [comm, "", "", "", "", "", ""]
- self.dbid = comm_id
-
- def Select(self):
- self.query_done = True
- query = QSqlQuery(self.glb.db)
- QueryExec(query, "SELECT thread_id, pid, tid"
- " FROM comm_threads"
- " INNER JOIN threads ON thread_id = threads.id"
- " WHERE comm_id = " + str(self.dbid))
- while query.next():
- child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
- self.child_items.append(child_item)
- self.child_count += 1
-
-# Call tree data model root item
-
-class CallTreeRootItem(CallGraphLevelItemBase):
-
- def __init__(self, glb, params):
- super(CallTreeRootItem, self).__init__(glb, params, 0, None)
- self.dbid = 0
- self.query_done = True
- if_has_calls = ""
- if IsSelectable(glb.db, "comms", columns = "has_calls"):
- if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
- query