#!/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 ifnot 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 ifnot"--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 run(self): whileTrue: if self.param isNone:
done, result = self.task() else:
done, result = self.task(self.param)
self.done.emit(result) if done: break
def headerData(self, section, orientation, role): if role == Qt.TextAlignmentRole: return self.columnAlignment(section) if role != Qt.DisplayRole: returnNone if orientation != Qt.Horizontal: returnNone 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 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: returnNone
item = index.internalPointer() return self.DisplayData(item, index)
def headerData(self, section, orientation, role): if role == Qt.TextAlignmentRole: return self.columnAlignment(section) if role != Qt.DisplayRole: returnNone if orientation != Qt.Horizontal: returnNone return self.columnHeader(section)
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: returnNone
item = index.internalPointer() return self.DisplayData(item, index)
def LookupCreateModel(model_name, create_fn):
model_cache_lock.acquire() try:
model = model_cache[model_name] except:
model = None if model isNone:
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
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 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 ifnot 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() ifnot found:
found = query.first() return self.Found(query, found)
def FindPrev(self, query):
found = query.previous() ifnot found:
found = query.last() return self.Found(query, found)
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)) ifnot 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 DoFindSelect(self, query, match):
QueryExec(query, "SELECT calls.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 + " ORDER BY comm_id, thread_id, call_time, calls.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 calls" " WHERE id = " + str(parent_id)) ifnot q2.next(): break
parent_id = q2.value(0)
ids.insert(0, query.value(2))
ids.insert(0, query.value(1)) return ids
self.layout().setContentsMargins(0, 0, 0, 0) for child in children: if child.isWidgetType():
self.layout().addWidget(child) else:
self.layout().addLayout(child)
self.layout().setContentsMargins(0, 0, 0, 0) for child in children: if child.isWidgetType():
self.layout().addWidget(child) else:
self.layout().addLayout(child)
if thread_at_time:
self.DisplayThreadAtTime(*thread_at_time)
def DisplayThreadAtTime(self, comm_id, thread_id, time):
parent = QModelIndex() for dbid in (comm_id, thread_id):
found = False
n = self.model.rowCount(parent) for row in xrange(n):
child = self.model.index(row, 0, parent) if child.internalPointer().dbid == dbid:
found = True
self.view.setExpanded(parent, True)
self.view.setCurrentIndex(child)
parent = child break ifnot found: return
found = False whileTrue:
n = self.model.rowCount(parent) ifnot n: return
last_child = None for row in xrange(n):
self.view.setExpanded(parent, True)
child = self.model.index(row, 0, parent)
child_call_time = child.internalPointer().call_time if child_call_time < time:
last_child = child elif child_call_time == time:
self.view.setCurrentIndex(child) return elif child_call_time > time: break ifnot last_child: ifnot found:
child = self.model.index(0, 0, parent)
self.view.setExpanded(parent, True)
self.view.setCurrentIndex(child) return
found = True
self.view.setExpanded(parent, True)
self.view.setCurrentIndex(last_child)
parent = last_child
# ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name
def ExecComm(db, thread_id, time):
query = QSqlQuery(db)
QueryExec(query, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag" " FROM comm_threads" " INNER JOIN comms ON comms.id = comm_threads.comm_id" " WHERE comm_threads.thread_id = " + str(thread_id) + " ORDER BY comms.c_time, comms.id")
first = None
last = None while query.next(): if first isNone:
first = query.value(0) if query.value(2) and Decimal(query.value(1)) <= Decimal(time):
last = query.value(0) ifnot(last isNone): return last return first
# Container for (x, y) data
class XY(): def __init__(self, x=0, y=0):
self.x = x
self.y = y
def paint(self, painter, option, widget):
last = None for point in self.data.points:
self.PaintPoint(painter, last, point.x) if point.x > self.attrs.subrange.x.hi: break;
last = point
self.PaintPoint(painter, last, self.attrs.subrange.x.hi + 1)
def Step(self):
attrs = self.parentItem().attrs
subrange = attrs.subrange.x
t = subrange.hi - subrange.lo
s = (3.0 * t) / self.width
n = 1.0 while s > n:
n = n * 10.0 return n
def PaintMarks(self, painter, at_y, lo, hi, step, i):
attrs = self.parentItem().attrs
x = lo while x <= hi:
xp = attrs.XToPixel(x) if i % 10: if i % 5:
sz = 1 else:
sz = 2 else:
sz = self.max_mark_sz
i = 0
painter.drawLine(xp, at_y, xp, at_y + sz)
x += step
i += 1
def paint(self, painter, option, widget): # Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1
painter.drawLine(0, 0, self.width - 1, 0)
n = self.Step()
attrs = self.parentItem().attrs
subrange = attrs.subrange.x if subrange.lo:
x_offset = n - (subrange.lo % n) else:
x_offset = 0.0
x = subrange.lo + x_offset
i = (x / n) % 10
self.PaintMarks(painter, 0, x, subrange.hi, n, i)
def ScaleDimensions(self):
n = self.Step()
attrs = self.parentItem().attrs
lo = attrs.subrange.x.lo
hi = (n * 10.0) + lo
width = attrs.XToPixel(hi) if width > 500:
width = 0 return (n, lo, hi, width)
def Text(self):
unit = self.Unit() if unit >= 1000000000:
unit = int(unit / 1000000000)
us = "s" elif unit >= 1000000:
unit = int(unit / 1000000)
us = "ms" elif unit >= 1000:
unit = int(unit / 1000)
us = "us" else:
unit = int(unit)
us = "ns" return" = " + str(unit) + " " + us
# Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data
def RubberBandX(self, event):
x = event.pos().toPoint().x() if x < self.rb_xlo:
x = self.rb_xlo elif x > self.rb_xhi:
x = self.rb_xhi else:
self.rb_in_view = True return x
def RubberBandRect(self, x): if self.rb_origin.x() <= x:
width = x - self.rb_origin.x()
rect = QRect(self.rb_origin, QSize(width, self.height)) else:
width = self.rb_origin.x() - x
top_left = QPoint(self.rb_origin.x() - width, self.rb_origin.y())
rect = QRect(top_left, QSize(width, self.height)) return rect
def MousePressEvent(self, event):
self.rb_in_view = False
x = self.RubberBandX(event)
self.rb_origin = QPoint(x, self.top) if self.rubber_band isNone:
self.rubber_band = QRubberBand(QRubberBand.Rectangle, self.RubberBandParent())
self.RubberBandSetGeometry(QRect(self.rb_origin, QSize(0, self.height))) if self.rb_in_view:
self.rubber_band.show()
self.rb_event_handler.RBMoveEvent(x, x) else:
self.rubber_band.hide()
def MouseMoveEvent(self, event):
x = self.RubberBandX(event)
rect = self.RubberBandRect(x)
self.RubberBandSetGeometry(rect) if self.rb_in_view:
self.rubber_band.show()
self.rb_event_handler.RBMoveEvent(self.rb_origin.x(), x)
def MouseReleaseEvent(self, event):
x = self.RubberBandX(event) if self.rb_in_view:
selection_state = self.RubberBandRect(x) else:
selection_state = None
self.rb_event_handler.RBReleaseEvent(self.rb_origin.x(), x, selection_state)
# Switch graph legend data model
class SwitchGraphLegendModel(QAbstractTableModel):
def data(self, index, role): if role == Qt.BackgroundRole:
child = self.child_items[index.row()] if child in self.highlight_set: return self.region_attributes[child.key].colour returnNone if role == Qt.ForegroundRole:
child = self.child_items[index.row()] if child in self.highlight_set: return QColor(255, 255, 255) return self.region_attributes[child.key].colour if role != Qt.DisplayRole: returnNone
hregion = self.child_items[index.row()]
col = index.column() if col == 0: return hregion.pid if col == 1: return hregion.tid if col == 2: return hregion.comm returnNone
def Highlight(self, highlight_set): for row in xrange(self.child_count):
child = self.child_items[row] if child in self.highlight_set: if child notin highlight_set:
self.SetHighlight(row, False) elif child in highlight_set:
self.SetHighlight(row, True)
self.highlight_set = highlight_set
def changeEvent(self, event): if event.type() == QEvent.FontChange:
self.view.resizeRowsToContents()
self.view.resizeColumnsToContents() # Need to resize rows again after column resize
self.view.resizeRowsToContents()
super(SwitchGraphLegend, self).changeEvent(event)
# Random colour generation
def RGBColourTooLight(r, g, b): if g > 230: returnTrue if g <= 160: returnFalse if r <= 180 and g <= 180: returnFalse if r < 60: returnFalse returnTrue
def GenerateColours(x):
cs = [0] for i in xrange(1, x):
cs.append(int((255.0 / i) + 0.5))
colours = [] for r in cs: for g in cs: for b in cs: # Exclude black and colours that look too light against a white background
--> --------------------
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.