1b3a67546SAdrian Hunter#!/usr/bin/env python2
2031c2a00SAdrian Hunter# SPDX-License-Identifier: GPL-2.0
3031c2a00SAdrian Hunter# exported-sql-viewer.py: view data from sql database
4031c2a00SAdrian Hunter# Copyright (c) 2014-2018, Intel Corporation.
5031c2a00SAdrian Hunter
6031c2a00SAdrian Hunter# To use this script you will need to have exported data using either the
7031c2a00SAdrian Hunter# export-to-sqlite.py or the export-to-postgresql.py script.  Refer to those
8031c2a00SAdrian Hunter# scripts for details.
9031c2a00SAdrian Hunter#
10031c2a00SAdrian Hunter# Following on from the example in the export scripts, a
11031c2a00SAdrian Hunter# call-graph can be displayed for the pt_example database like this:
12031c2a00SAdrian Hunter#
13031c2a00SAdrian Hunter#	python tools/perf/scripts/python/exported-sql-viewer.py pt_example
14031c2a00SAdrian Hunter#
15031c2a00SAdrian Hunter# Note that for PostgreSQL, this script supports connecting to remote databases
16031c2a00SAdrian Hunter# by setting hostname, port, username, password, and dbname e.g.
17031c2a00SAdrian Hunter#
18031c2a00SAdrian Hunter#	python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
19031c2a00SAdrian Hunter#
20031c2a00SAdrian Hunter# The result is a GUI window with a tree representing a context-sensitive
21031c2a00SAdrian Hunter# call-graph.  Expanding a couple of levels of the tree and adjusting column
22031c2a00SAdrian Hunter# widths to suit will display something like:
23031c2a00SAdrian Hunter#
24031c2a00SAdrian Hunter#                                         Call Graph: pt_example
25031c2a00SAdrian Hunter# Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
26031c2a00SAdrian Hunter# v- ls
27031c2a00SAdrian Hunter#     v- 2638:2638
28031c2a00SAdrian Hunter#         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
29031c2a00SAdrian Hunter#           |- unknown               unknown       1        13198     0.1              1              0.0
30031c2a00SAdrian Hunter#           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
31031c2a00SAdrian Hunter#           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
32031c2a00SAdrian Hunter#           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
33031c2a00SAdrian Hunter#              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
34031c2a00SAdrian Hunter#              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
35031c2a00SAdrian Hunter#              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
36031c2a00SAdrian Hunter#              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
37031c2a00SAdrian Hunter#              v- main               ls            1      8182043    99.6         180254             99.9
38031c2a00SAdrian Hunter#
39031c2a00SAdrian Hunter# Points to note:
40031c2a00SAdrian Hunter#	The top level is a command name (comm)
41031c2a00SAdrian Hunter#	The next level is a thread (pid:tid)
42031c2a00SAdrian Hunter#	Subsequent levels are functions
43031c2a00SAdrian Hunter#	'Count' is the number of calls
44031c2a00SAdrian Hunter#	'Time' is the elapsed time until the function returns
45031c2a00SAdrian Hunter#	Percentages are relative to the level above
46031c2a00SAdrian Hunter#	'Branch Count' is the total number of branches for that function and all
47031c2a00SAdrian Hunter#       functions that it calls
48031c2a00SAdrian Hunter
4976099f98SAdrian Hunter# There is also a "All branches" report, which displays branches and
5076099f98SAdrian Hunter# possibly disassembly.  However, presently, the only supported disassembler is
5176099f98SAdrian Hunter# Intel XED, and additionally the object code must be present in perf build ID
5276099f98SAdrian Hunter# cache. To use Intel XED, libxed.so must be present. To build and install
5376099f98SAdrian Hunter# libxed.so:
5476099f98SAdrian Hunter#            git clone https://github.com/intelxed/mbuild.git mbuild
5576099f98SAdrian Hunter#            git clone https://github.com/intelxed/xed
5676099f98SAdrian Hunter#            cd xed
5776099f98SAdrian Hunter#            ./mfile.py --share
5876099f98SAdrian Hunter#            sudo ./mfile.py --prefix=/usr/local install
5976099f98SAdrian Hunter#            sudo ldconfig
6076099f98SAdrian Hunter#
6176099f98SAdrian Hunter# Example report:
6276099f98SAdrian Hunter#
6376099f98SAdrian Hunter# Time           CPU  Command  PID    TID    Branch Type            In Tx  Branch
6476099f98SAdrian Hunter# 8107675239590  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
6576099f98SAdrian Hunter#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
6676099f98SAdrian Hunter# 8107675239899  2    ls       22011  22011  hardware interrupt     No         7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
6776099f98SAdrian Hunter# 8107675241900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
6876099f98SAdrian Hunter#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
6976099f98SAdrian Hunter#                                                                              7fab593ea263 e8 c8 06 00 00                                  callq  0x7fab593ea930
7076099f98SAdrian Hunter# 8107675241900  2    ls       22011  22011  call                   No         7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
7176099f98SAdrian Hunter#                                                                              7fab593ea930 55                                              pushq  %rbp
7276099f98SAdrian Hunter#                                                                              7fab593ea931 48 89 e5                                        mov %rsp, %rbp
7376099f98SAdrian Hunter#                                                                              7fab593ea934 41 57                                           pushq  %r15
7476099f98SAdrian Hunter#                                                                              7fab593ea936 41 56                                           pushq  %r14
7576099f98SAdrian Hunter#                                                                              7fab593ea938 41 55                                           pushq  %r13
7676099f98SAdrian Hunter#                                                                              7fab593ea93a 41 54                                           pushq  %r12
7776099f98SAdrian Hunter#                                                                              7fab593ea93c 53                                              pushq  %rbx
7876099f98SAdrian Hunter#                                                                              7fab593ea93d 48 89 fb                                        mov %rdi, %rbx
7976099f98SAdrian Hunter#                                                                              7fab593ea940 48 83 ec 68                                     sub $0x68, %rsp
8076099f98SAdrian Hunter#                                                                              7fab593ea944 0f 31                                           rdtsc
8176099f98SAdrian Hunter#                                                                              7fab593ea946 48 c1 e2 20                                     shl $0x20, %rdx
8276099f98SAdrian Hunter#                                                                              7fab593ea94a 89 c0                                           mov %eax, %eax
8376099f98SAdrian Hunter#                                                                              7fab593ea94c 48 09 c2                                        or %rax, %rdx
8476099f98SAdrian Hunter#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
8576099f98SAdrian Hunter# 8107675242232  2    ls       22011  22011  hardware interrupt     No         7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
8676099f98SAdrian Hunter# 8107675242900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
8776099f98SAdrian Hunter#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
8876099f98SAdrian Hunter#                                                                              7fab593ea956 48 89 15 3b 13 22 00                            movq  %rdx, 0x22133b(%rip)
8976099f98SAdrian Hunter# 8107675243232  2    ls       22011  22011  hardware interrupt     No         7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
9076099f98SAdrian Hunter
91beda0e72STony Jonesfrom __future__ import print_function
92beda0e72STony Jones
93031c2a00SAdrian Hunterimport sys
941beb5c7bSAdrian Hunterimport weakref
951beb5c7bSAdrian Hunterimport threading
96ebd70c7dSAdrian Hunterimport string
97beda0e72STony Jonestry:
98beda0e72STony Jones	# Python2
99beda0e72STony Jones	import cPickle as pickle
100beda0e72STony Jones	# size of pickled integer big enough for record size
101beda0e72STony Jones	glb_nsz = 8
102beda0e72STony Jonesexcept ImportError:
103beda0e72STony Jones	import pickle
104beda0e72STony Jones	glb_nsz = 16
1058392b74bSAdrian Hunterimport re
1068392b74bSAdrian Hunterimport os
107031c2a00SAdrian Hunterfrom PySide.QtCore import *
108031c2a00SAdrian Hunterfrom PySide.QtGui import *
109031c2a00SAdrian Hunterfrom PySide.QtSql import *
1108453c936SAdrian Hunterpyside_version_1 = True
111031c2a00SAdrian Hunterfrom decimal import *
1128392b74bSAdrian Hunterfrom ctypes import *
1138392b74bSAdrian Hunterfrom multiprocessing import Process, Array, Value, Event
114031c2a00SAdrian Hunter
115beda0e72STony Jones# xrange is range in Python3
116beda0e72STony Jonestry:
117beda0e72STony Jones	xrange
118beda0e72STony Jonesexcept NameError:
119beda0e72STony Jones	xrange = range
120beda0e72STony Jones
121beda0e72STony Jonesdef printerr(*args, **keyword_args):
122beda0e72STony Jones	print(*args, file=sys.stderr, **keyword_args)
123beda0e72STony Jones
124031c2a00SAdrian Hunter# Data formatting helpers
125031c2a00SAdrian Hunter
12676099f98SAdrian Hunterdef tohex(ip):
12776099f98SAdrian Hunter	if ip < 0:
12876099f98SAdrian Hunter		ip += 1 << 64
12976099f98SAdrian Hunter	return "%x" % ip
13076099f98SAdrian Hunter
13176099f98SAdrian Hunterdef offstr(offset):
13276099f98SAdrian Hunter	if offset:
13376099f98SAdrian Hunter		return "+0x%x" % offset
13476099f98SAdrian Hunter	return ""
13576099f98SAdrian Hunter
136031c2a00SAdrian Hunterdef dsoname(name):
137031c2a00SAdrian Hunter	if name == "[kernel.kallsyms]":
138031c2a00SAdrian Hunter		return "[kernel]"
139031c2a00SAdrian Hunter	return name
140031c2a00SAdrian Hunter
141210cf1f9SAdrian Hunterdef findnth(s, sub, n, offs=0):
142210cf1f9SAdrian Hunter	pos = s.find(sub)
143210cf1f9SAdrian Hunter	if pos < 0:
144210cf1f9SAdrian Hunter		return pos
145210cf1f9SAdrian Hunter	if n <= 1:
146210cf1f9SAdrian Hunter		return offs + pos
147210cf1f9SAdrian Hunter	return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
148210cf1f9SAdrian Hunter
149031c2a00SAdrian Hunter# Percent to one decimal place
150031c2a00SAdrian Hunter
151031c2a00SAdrian Hunterdef PercentToOneDP(n, d):
152031c2a00SAdrian Hunter	if not d:
153031c2a00SAdrian Hunter		return "0.0"
154031c2a00SAdrian Hunter	x = (n * Decimal(100)) / d
155031c2a00SAdrian Hunter	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
156031c2a00SAdrian Hunter
157031c2a00SAdrian Hunter# Helper for queries that must not fail
158031c2a00SAdrian Hunter
159031c2a00SAdrian Hunterdef QueryExec(query, stmt):
160031c2a00SAdrian Hunter	ret = query.exec_(stmt)
161031c2a00SAdrian Hunter	if not ret:
162031c2a00SAdrian Hunter		raise Exception("Query failed: " + query.lastError().text())
163031c2a00SAdrian Hunter
164ebd70c7dSAdrian Hunter# Background thread
165ebd70c7dSAdrian Hunter
166ebd70c7dSAdrian Hunterclass Thread(QThread):
167ebd70c7dSAdrian Hunter
168ebd70c7dSAdrian Hunter	done = Signal(object)
169ebd70c7dSAdrian Hunter
170ebd70c7dSAdrian Hunter	def __init__(self, task, param=None, parent=None):
171ebd70c7dSAdrian Hunter		super(Thread, self).__init__(parent)
172ebd70c7dSAdrian Hunter		self.task = task
173ebd70c7dSAdrian Hunter		self.param = param
174ebd70c7dSAdrian Hunter
175ebd70c7dSAdrian Hunter	def run(self):
176ebd70c7dSAdrian Hunter		while True:
177ebd70c7dSAdrian Hunter			if self.param is None:
178ebd70c7dSAdrian Hunter				done, result = self.task()
179ebd70c7dSAdrian Hunter			else:
180ebd70c7dSAdrian Hunter				done, result = self.task(self.param)
181ebd70c7dSAdrian Hunter			self.done.emit(result)
182ebd70c7dSAdrian Hunter			if done:
183ebd70c7dSAdrian Hunter				break
184ebd70c7dSAdrian Hunter
185031c2a00SAdrian Hunter# Tree data model
186031c2a00SAdrian Hunter
187031c2a00SAdrian Hunterclass TreeModel(QAbstractItemModel):
188031c2a00SAdrian Hunter
189a448ba23SAdrian Hunter	def __init__(self, glb, parent=None):
190031c2a00SAdrian Hunter		super(TreeModel, self).__init__(parent)
191a448ba23SAdrian Hunter		self.glb = glb
192a448ba23SAdrian Hunter		self.root = self.GetRoot()
193031c2a00SAdrian Hunter		self.last_row_read = 0
194031c2a00SAdrian Hunter
195031c2a00SAdrian Hunter	def Item(self, parent):
196031c2a00SAdrian Hunter		if parent.isValid():
197031c2a00SAdrian Hunter			return parent.internalPointer()
198031c2a00SAdrian Hunter		else:
199031c2a00SAdrian Hunter			return self.root
200031c2a00SAdrian Hunter
201031c2a00SAdrian Hunter	def rowCount(self, parent):
202031c2a00SAdrian Hunter		result = self.Item(parent).childCount()
203031c2a00SAdrian Hunter		if result < 0:
204031c2a00SAdrian Hunter			result = 0
205031c2a00SAdrian Hunter			self.dataChanged.emit(parent, parent)
206031c2a00SAdrian Hunter		return result
207031c2a00SAdrian Hunter
208031c2a00SAdrian Hunter	def hasChildren(self, parent):
209031c2a00SAdrian Hunter		return self.Item(parent).hasChildren()
210031c2a00SAdrian Hunter
211031c2a00SAdrian Hunter	def headerData(self, section, orientation, role):
212031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
213031c2a00SAdrian Hunter			return self.columnAlignment(section)
214031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
215031c2a00SAdrian Hunter			return None
216031c2a00SAdrian Hunter		if orientation != Qt.Horizontal:
217031c2a00SAdrian Hunter			return None
218031c2a00SAdrian Hunter		return self.columnHeader(section)
219031c2a00SAdrian Hunter
220031c2a00SAdrian Hunter	def parent(self, child):
221031c2a00SAdrian Hunter		child_item = child.internalPointer()
222031c2a00SAdrian Hunter		if child_item is self.root:
223031c2a00SAdrian Hunter			return QModelIndex()
224031c2a00SAdrian Hunter		parent_item = child_item.getParentItem()
225031c2a00SAdrian Hunter		return self.createIndex(parent_item.getRow(), 0, parent_item)
226031c2a00SAdrian Hunter
227031c2a00SAdrian Hunter	def index(self, row, column, parent):
228031c2a00SAdrian Hunter		child_item = self.Item(parent).getChildItem(row)
229031c2a00SAdrian Hunter		return self.createIndex(row, column, child_item)
230031c2a00SAdrian Hunter
231031c2a00SAdrian Hunter	def DisplayData(self, item, index):
232031c2a00SAdrian Hunter		return item.getData(index.column())
233031c2a00SAdrian Hunter
2348392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2358392b74bSAdrian Hunter		if row > self.last_row_read:
2368392b74bSAdrian Hunter			self.last_row_read = row
2378392b74bSAdrian Hunter			if row + 10 >= self.root.child_count:
2388392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2398392b74bSAdrian Hunter
2408392b74bSAdrian Hunter	def columnAlignment(self, column):
2418392b74bSAdrian Hunter		return Qt.AlignLeft
2428392b74bSAdrian Hunter
2438392b74bSAdrian Hunter	def columnFont(self, column):
2448392b74bSAdrian Hunter		return None
2458392b74bSAdrian Hunter
2468392b74bSAdrian Hunter	def data(self, index, role):
2478392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2488392b74bSAdrian Hunter			return self.columnAlignment(index.column())
2498392b74bSAdrian Hunter		if role == Qt.FontRole:
2508392b74bSAdrian Hunter			return self.columnFont(index.column())
2518392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2528392b74bSAdrian Hunter			return None
2538392b74bSAdrian Hunter		item = index.internalPointer()
2548392b74bSAdrian Hunter		return self.DisplayData(item, index)
2558392b74bSAdrian Hunter
2568392b74bSAdrian Hunter# Table data model
2578392b74bSAdrian Hunter
2588392b74bSAdrian Hunterclass TableModel(QAbstractTableModel):
2598392b74bSAdrian Hunter
2608392b74bSAdrian Hunter	def __init__(self, parent=None):
2618392b74bSAdrian Hunter		super(TableModel, self).__init__(parent)
2628392b74bSAdrian Hunter		self.child_count = 0
2638392b74bSAdrian Hunter		self.child_items = []
2648392b74bSAdrian Hunter		self.last_row_read = 0
2658392b74bSAdrian Hunter
2668392b74bSAdrian Hunter	def Item(self, parent):
2678392b74bSAdrian Hunter		if parent.isValid():
2688392b74bSAdrian Hunter			return parent.internalPointer()
2698392b74bSAdrian Hunter		else:
2708392b74bSAdrian Hunter			return self
2718392b74bSAdrian Hunter
2728392b74bSAdrian Hunter	def rowCount(self, parent):
2738392b74bSAdrian Hunter		return self.child_count
2748392b74bSAdrian Hunter
2758392b74bSAdrian Hunter	def headerData(self, section, orientation, role):
2768392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2778392b74bSAdrian Hunter			return self.columnAlignment(section)
2788392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2798392b74bSAdrian Hunter			return None
2808392b74bSAdrian Hunter		if orientation != Qt.Horizontal:
2818392b74bSAdrian Hunter			return None
2828392b74bSAdrian Hunter		return self.columnHeader(section)
2838392b74bSAdrian Hunter
2848392b74bSAdrian Hunter	def index(self, row, column, parent):
2858392b74bSAdrian Hunter		return self.createIndex(row, column, self.child_items[row])
2868392b74bSAdrian Hunter
2878392b74bSAdrian Hunter	def DisplayData(self, item, index):
2888392b74bSAdrian Hunter		return item.getData(index.column())
2898392b74bSAdrian Hunter
2908392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2918392b74bSAdrian Hunter		if row > self.last_row_read:
2928392b74bSAdrian Hunter			self.last_row_read = row
2938392b74bSAdrian Hunter			if row + 10 >= self.child_count:
2948392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2958392b74bSAdrian Hunter
296031c2a00SAdrian Hunter	def columnAlignment(self, column):
297031c2a00SAdrian Hunter		return Qt.AlignLeft
298031c2a00SAdrian Hunter
299031c2a00SAdrian Hunter	def columnFont(self, column):
300031c2a00SAdrian Hunter		return None
301031c2a00SAdrian Hunter
302031c2a00SAdrian Hunter	def data(self, index, role):
303031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
304031c2a00SAdrian Hunter			return self.columnAlignment(index.column())
305031c2a00SAdrian Hunter		if role == Qt.FontRole:
306031c2a00SAdrian Hunter			return self.columnFont(index.column())
307031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
308031c2a00SAdrian Hunter			return None
309031c2a00SAdrian Hunter		item = index.internalPointer()
310031c2a00SAdrian Hunter		return self.DisplayData(item, index)
311031c2a00SAdrian Hunter
3121beb5c7bSAdrian Hunter# Model cache
3131beb5c7bSAdrian Hunter
3141beb5c7bSAdrian Huntermodel_cache = weakref.WeakValueDictionary()
3151beb5c7bSAdrian Huntermodel_cache_lock = threading.Lock()
3161beb5c7bSAdrian Hunter
3171beb5c7bSAdrian Hunterdef LookupCreateModel(model_name, create_fn):
3181beb5c7bSAdrian Hunter	model_cache_lock.acquire()
3191beb5c7bSAdrian Hunter	try:
3201beb5c7bSAdrian Hunter		model = model_cache[model_name]
3211beb5c7bSAdrian Hunter	except:
3221beb5c7bSAdrian Hunter		model = None
3231beb5c7bSAdrian Hunter	if model is None:
3241beb5c7bSAdrian Hunter		model = create_fn()
3251beb5c7bSAdrian Hunter		model_cache[model_name] = model
3261beb5c7bSAdrian Hunter	model_cache_lock.release()
3271beb5c7bSAdrian Hunter	return model
3281beb5c7bSAdrian Hunter
329ebd70c7dSAdrian Hunter# Find bar
330ebd70c7dSAdrian Hunter
331ebd70c7dSAdrian Hunterclass FindBar():
332ebd70c7dSAdrian Hunter
333ebd70c7dSAdrian Hunter	def __init__(self, parent, finder, is_reg_expr=False):
334ebd70c7dSAdrian Hunter		self.finder = finder
335ebd70c7dSAdrian Hunter		self.context = []
336ebd70c7dSAdrian Hunter		self.last_value = None
337ebd70c7dSAdrian Hunter		self.last_pattern = None
338ebd70c7dSAdrian Hunter
339ebd70c7dSAdrian Hunter		label = QLabel("Find:")
340ebd70c7dSAdrian Hunter		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
341ebd70c7dSAdrian Hunter
342ebd70c7dSAdrian Hunter		self.textbox = QComboBox()
343ebd70c7dSAdrian Hunter		self.textbox.setEditable(True)
344ebd70c7dSAdrian Hunter		self.textbox.currentIndexChanged.connect(self.ValueChanged)
345ebd70c7dSAdrian Hunter
346ebd70c7dSAdrian Hunter		self.progress = QProgressBar()
347ebd70c7dSAdrian Hunter		self.progress.setRange(0, 0)
348ebd70c7dSAdrian Hunter		self.progress.hide()
349ebd70c7dSAdrian Hunter
350ebd70c7dSAdrian Hunter		if is_reg_expr:
351ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Regular Expression")
352ebd70c7dSAdrian Hunter		else:
353ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Pattern")
354ebd70c7dSAdrian Hunter		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
355ebd70c7dSAdrian Hunter
356ebd70c7dSAdrian Hunter		self.next_button = QToolButton()
357ebd70c7dSAdrian Hunter		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
358ebd70c7dSAdrian Hunter		self.next_button.released.connect(lambda: self.NextPrev(1))
359ebd70c7dSAdrian Hunter
360ebd70c7dSAdrian Hunter		self.prev_button = QToolButton()
361ebd70c7dSAdrian Hunter		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
362ebd70c7dSAdrian Hunter		self.prev_button.released.connect(lambda: self.NextPrev(-1))
363ebd70c7dSAdrian Hunter
364ebd70c7dSAdrian Hunter		self.close_button = QToolButton()
365ebd70c7dSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
366ebd70c7dSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
367ebd70c7dSAdrian Hunter
368ebd70c7dSAdrian Hunter		self.hbox = QHBoxLayout()
369ebd70c7dSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
370ebd70c7dSAdrian Hunter
371ebd70c7dSAdrian Hunter		self.hbox.addWidget(label)
372ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.textbox)
373ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.progress)
374ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.pattern)
375ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.next_button)
376ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.prev_button)
377ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.close_button)
378ebd70c7dSAdrian Hunter
379ebd70c7dSAdrian Hunter		self.bar = QWidget()
380ebd70c7dSAdrian Hunter		self.bar.setLayout(self.hbox);
381ebd70c7dSAdrian Hunter		self.bar.hide()
382ebd70c7dSAdrian Hunter
383ebd70c7dSAdrian Hunter	def Widget(self):
384ebd70c7dSAdrian Hunter		return self.bar
385ebd70c7dSAdrian Hunter
386ebd70c7dSAdrian Hunter	def Activate(self):
387ebd70c7dSAdrian Hunter		self.bar.show()
388ebd70c7dSAdrian Hunter		self.textbox.setFocus()
389ebd70c7dSAdrian Hunter
390ebd70c7dSAdrian Hunter	def Deactivate(self):
391ebd70c7dSAdrian Hunter		self.bar.hide()
392ebd70c7dSAdrian Hunter
393ebd70c7dSAdrian Hunter	def Busy(self):
394ebd70c7dSAdrian Hunter		self.textbox.setEnabled(False)
395ebd70c7dSAdrian Hunter		self.pattern.hide()
396ebd70c7dSAdrian Hunter		self.next_button.hide()
397ebd70c7dSAdrian Hunter		self.prev_button.hide()
398ebd70c7dSAdrian Hunter		self.progress.show()
399ebd70c7dSAdrian Hunter
400ebd70c7dSAdrian Hunter	def Idle(self):
401ebd70c7dSAdrian Hunter		self.textbox.setEnabled(True)
402ebd70c7dSAdrian Hunter		self.progress.hide()
403ebd70c7dSAdrian Hunter		self.pattern.show()
404ebd70c7dSAdrian Hunter		self.next_button.show()
405ebd70c7dSAdrian Hunter		self.prev_button.show()
406ebd70c7dSAdrian Hunter
407ebd70c7dSAdrian Hunter	def Find(self, direction):
408ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
409ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
410ebd70c7dSAdrian Hunter		self.last_value = value
411ebd70c7dSAdrian Hunter		self.last_pattern = pattern
412ebd70c7dSAdrian Hunter		self.finder.Find(value, direction, pattern, self.context)
413ebd70c7dSAdrian Hunter
414ebd70c7dSAdrian Hunter	def ValueChanged(self):
415ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
416ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
417ebd70c7dSAdrian Hunter		index = self.textbox.currentIndex()
418ebd70c7dSAdrian Hunter		data = self.textbox.itemData(index)
419ebd70c7dSAdrian Hunter		# Store the pattern in the combo box to keep it with the text value
420ebd70c7dSAdrian Hunter		if data == None:
421ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
422ebd70c7dSAdrian Hunter		else:
423ebd70c7dSAdrian Hunter			self.pattern.setChecked(data)
424ebd70c7dSAdrian Hunter		self.Find(0)
425ebd70c7dSAdrian Hunter
426ebd70c7dSAdrian Hunter	def NextPrev(self, direction):
427ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
428ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
429ebd70c7dSAdrian Hunter		if value != self.last_value:
430ebd70c7dSAdrian Hunter			index = self.textbox.findText(value)
431ebd70c7dSAdrian Hunter			# Allow for a button press before the value has been added to the combo box
432ebd70c7dSAdrian Hunter			if index < 0:
433ebd70c7dSAdrian Hunter				index = self.textbox.count()
434ebd70c7dSAdrian Hunter				self.textbox.addItem(value, pattern)
435ebd70c7dSAdrian Hunter				self.textbox.setCurrentIndex(index)
436ebd70c7dSAdrian Hunter				return
437ebd70c7dSAdrian Hunter			else:
438ebd70c7dSAdrian Hunter				self.textbox.setItemData(index, pattern)
439ebd70c7dSAdrian Hunter		elif pattern != self.last_pattern:
440ebd70c7dSAdrian Hunter			# Keep the pattern recorded in the combo box up to date
441ebd70c7dSAdrian Hunter			index = self.textbox.currentIndex()
442ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
443ebd70c7dSAdrian Hunter		self.Find(direction)
444ebd70c7dSAdrian Hunter
445ebd70c7dSAdrian Hunter	def NotFound(self):
446ebd70c7dSAdrian Hunter		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
447ebd70c7dSAdrian Hunter
448031c2a00SAdrian Hunter# Context-sensitive call graph data model item base
449031c2a00SAdrian Hunter
450031c2a00SAdrian Hunterclass CallGraphLevelItemBase(object):
451031c2a00SAdrian Hunter
452031c2a00SAdrian Hunter	def __init__(self, glb, row, parent_item):
453031c2a00SAdrian Hunter		self.glb = glb
454031c2a00SAdrian Hunter		self.row = row
455031c2a00SAdrian Hunter		self.parent_item = parent_item
456031c2a00SAdrian Hunter		self.query_done = False;
457031c2a00SAdrian Hunter		self.child_count = 0
458031c2a00SAdrian Hunter		self.child_items = []
459031c2a00SAdrian Hunter
460031c2a00SAdrian Hunter	def getChildItem(self, row):
461031c2a00SAdrian Hunter		return self.child_items[row]
462031c2a00SAdrian Hunter
463031c2a00SAdrian Hunter	def getParentItem(self):
464031c2a00SAdrian Hunter		return self.parent_item
465031c2a00SAdrian Hunter
466031c2a00SAdrian Hunter	def getRow(self):
467031c2a00SAdrian Hunter		return self.row
468031c2a00SAdrian Hunter
469031c2a00SAdrian Hunter	def childCount(self):
470031c2a00SAdrian Hunter		if not self.query_done:
471031c2a00SAdrian Hunter			self.Select()
472031c2a00SAdrian Hunter			if not self.child_count:
473031c2a00SAdrian Hunter				return -1
474031c2a00SAdrian Hunter		return self.child_count
475031c2a00SAdrian Hunter
476031c2a00SAdrian Hunter	def hasChildren(self):
477031c2a00SAdrian Hunter		if not self.query_done:
478031c2a00SAdrian Hunter			return True
479031c2a00SAdrian Hunter		return self.child_count > 0
480031c2a00SAdrian Hunter
481031c2a00SAdrian Hunter	def getData(self, column):
482031c2a00SAdrian Hunter		return self.data[column]
483031c2a00SAdrian Hunter
484031c2a00SAdrian Hunter# Context-sensitive call graph data model level 2+ item base
485031c2a00SAdrian Hunter
486031c2a00SAdrian Hunterclass CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
487031c2a00SAdrian Hunter
488031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
489031c2a00SAdrian Hunter		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
490031c2a00SAdrian Hunter		self.comm_id = comm_id
491031c2a00SAdrian Hunter		self.thread_id = thread_id
492031c2a00SAdrian Hunter		self.call_path_id = call_path_id
493031c2a00SAdrian Hunter		self.branch_count = branch_count
494031c2a00SAdrian Hunter		self.time = time
495031c2a00SAdrian Hunter
496031c2a00SAdrian Hunter	def Select(self):
497031c2a00SAdrian Hunter		self.query_done = True;
498031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
499031c2a00SAdrian Hunter		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
500031c2a00SAdrian Hunter					" FROM calls"
501031c2a00SAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
502031c2a00SAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
503031c2a00SAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
504031c2a00SAdrian Hunter					" WHERE parent_call_path_id = " + str(self.call_path_id) +
505031c2a00SAdrian Hunter					" AND comm_id = " + str(self.comm_id) +
506031c2a00SAdrian Hunter					" AND thread_id = " + str(self.thread_id) +
507031c2a00SAdrian Hunter					" GROUP BY call_path_id, name, short_name"
508031c2a00SAdrian Hunter					" ORDER BY call_path_id")
509031c2a00SAdrian Hunter		while query.next():
510031c2a00SAdrian Hunter			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)
511031c2a00SAdrian Hunter			self.child_items.append(child_item)
512031c2a00SAdrian Hunter			self.child_count += 1
513031c2a00SAdrian Hunter
514031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item
515031c2a00SAdrian Hunter
516031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
517031c2a00SAdrian Hunter
518031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
519031c2a00SAdrian Hunter		super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
520031c2a00SAdrian Hunter		dso = dsoname(dso)
521031c2a00SAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
522031c2a00SAdrian Hunter		self.dbid = call_path_id
523031c2a00SAdrian Hunter
524031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item
525031c2a00SAdrian Hunter
526031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
527031c2a00SAdrian Hunter
528031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
529031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
530031c2a00SAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
531031c2a00SAdrian Hunter		self.dbid = thread_id
532031c2a00SAdrian Hunter
533031c2a00SAdrian Hunter	def Select(self):
534031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).Select()
535031c2a00SAdrian Hunter		for child_item in self.child_items:
536031c2a00SAdrian Hunter			self.time += child_item.time
537031c2a00SAdrian Hunter			self.branch_count += child_item.branch_count
538031c2a00SAdrian Hunter		for child_item in self.child_items:
539031c2a00SAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
540031c2a00SAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
541031c2a00SAdrian Hunter
542031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item
543031c2a00SAdrian Hunter
544031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase):
545031c2a00SAdrian Hunter
546031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, comm, parent_item):
547031c2a00SAdrian Hunter		super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
548031c2a00SAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
549031c2a00SAdrian Hunter		self.dbid = comm_id
550031c2a00SAdrian Hunter
551031c2a00SAdrian Hunter	def Select(self):
552031c2a00SAdrian Hunter		self.query_done = True;
553031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
554031c2a00SAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
555031c2a00SAdrian Hunter					" FROM comm_threads"
556031c2a00SAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
557031c2a00SAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
558031c2a00SAdrian Hunter		while query.next():
559031c2a00SAdrian Hunter			child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
560031c2a00SAdrian Hunter			self.child_items.append(child_item)
561031c2a00SAdrian Hunter			self.child_count += 1
562031c2a00SAdrian Hunter
563031c2a00SAdrian Hunter# Context-sensitive call graph data model root item
564031c2a00SAdrian Hunter
565031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase):
566031c2a00SAdrian Hunter
567031c2a00SAdrian Hunter	def __init__(self, glb):
568031c2a00SAdrian Hunter		super(CallGraphRootItem, self).__init__(glb, 0, None)
569031c2a00SAdrian Hunter		self.dbid = 0
570031c2a00SAdrian Hunter		self.query_done = True;
571031c2a00SAdrian Hunter		query = QSqlQuery(glb.db)
572031c2a00SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
573031c2a00SAdrian Hunter		while query.next():
574031c2a00SAdrian Hunter			if not query.value(0):
575031c2a00SAdrian Hunter				continue
576031c2a00SAdrian Hunter			child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
577031c2a00SAdrian Hunter			self.child_items.append(child_item)
578031c2a00SAdrian Hunter			self.child_count += 1
579031c2a00SAdrian Hunter
580254c0d82SAdrian Hunter# Context-sensitive call graph data model base
581031c2a00SAdrian Hunter
582254c0d82SAdrian Hunterclass CallGraphModelBase(TreeModel):
583031c2a00SAdrian Hunter
584031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
585254c0d82SAdrian Hunter		super(CallGraphModelBase, self).__init__(glb, parent)
586031c2a00SAdrian Hunter
587ebd70c7dSAdrian Hunter	def FindSelect(self, value, pattern, query):
588ebd70c7dSAdrian Hunter		if pattern:
589ebd70c7dSAdrian Hunter			# postgresql and sqlite pattern patching differences:
590ebd70c7dSAdrian Hunter			#   postgresql LIKE is case sensitive but sqlite LIKE is not
591ebd70c7dSAdrian Hunter			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
592ebd70c7dSAdrian Hunter			#   postgresql supports ILIKE which is case insensitive
593ebd70c7dSAdrian Hunter			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
594ebd70c7dSAdrian Hunter			if not self.glb.dbref.is_sqlite3:
595ebd70c7dSAdrian Hunter				# Escape % and _
596ebd70c7dSAdrian Hunter				s = value.replace("%", "\%")
597ebd70c7dSAdrian Hunter				s = s.replace("_", "\_")
598ebd70c7dSAdrian Hunter				# Translate * and ? into SQL LIKE pattern characters % and _
599ebd70c7dSAdrian Hunter				trans = string.maketrans("*?", "%_")
600ebd70c7dSAdrian Hunter				match = " LIKE '" + str(s).translate(trans) + "'"
601ebd70c7dSAdrian Hunter			else:
602ebd70c7dSAdrian Hunter				match = " GLOB '" + str(value) + "'"
603ebd70c7dSAdrian Hunter		else:
604ebd70c7dSAdrian Hunter			match = " = '" + str(value) + "'"
605254c0d82SAdrian Hunter		self.DoFindSelect(query, match)
606ebd70c7dSAdrian Hunter
607ebd70c7dSAdrian Hunter	def Found(self, query, found):
608ebd70c7dSAdrian Hunter		if found:
609ebd70c7dSAdrian Hunter			return self.FindPath(query)
610ebd70c7dSAdrian Hunter		return []
611ebd70c7dSAdrian Hunter
612ebd70c7dSAdrian Hunter	def FindValue(self, value, pattern, query, last_value, last_pattern):
613ebd70c7dSAdrian Hunter		if last_value == value and pattern == last_pattern:
614ebd70c7dSAdrian Hunter			found = query.first()
615ebd70c7dSAdrian Hunter		else:
616ebd70c7dSAdrian Hunter			self.FindSelect(value, pattern, query)
617ebd70c7dSAdrian Hunter			found = query.next()
618ebd70c7dSAdrian Hunter		return self.Found(query, found)
619ebd70c7dSAdrian Hunter
620ebd70c7dSAdrian Hunter	def FindNext(self, query):
621ebd70c7dSAdrian Hunter		found = query.next()
622ebd70c7dSAdrian Hunter		if not found:
623ebd70c7dSAdrian Hunter			found = query.first()
624ebd70c7dSAdrian Hunter		return self.Found(query, found)
625ebd70c7dSAdrian Hunter
626ebd70c7dSAdrian Hunter	def FindPrev(self, query):
627ebd70c7dSAdrian Hunter		found = query.previous()
628ebd70c7dSAdrian Hunter		if not found:
629ebd70c7dSAdrian Hunter			found = query.last()
630ebd70c7dSAdrian Hunter		return self.Found(query, found)
631ebd70c7dSAdrian Hunter
632ebd70c7dSAdrian Hunter	def FindThread(self, c):
633ebd70c7dSAdrian Hunter		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
634ebd70c7dSAdrian Hunter			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
635ebd70c7dSAdrian Hunter		elif c.direction > 0:
636ebd70c7dSAdrian Hunter			ids = self.FindNext(c.query)
637ebd70c7dSAdrian Hunter		else:
638ebd70c7dSAdrian Hunter			ids = self.FindPrev(c.query)
639ebd70c7dSAdrian Hunter		return (True, ids)
640ebd70c7dSAdrian Hunter
641ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
642ebd70c7dSAdrian Hunter		class Context():
643ebd70c7dSAdrian Hunter			def __init__(self, *x):
644ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
645ebd70c7dSAdrian Hunter			def Update(self, *x):
646ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
647ebd70c7dSAdrian Hunter		if len(context):
648ebd70c7dSAdrian Hunter			context[0].Update(value, direction, pattern)
649ebd70c7dSAdrian Hunter		else:
650ebd70c7dSAdrian Hunter			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
651ebd70c7dSAdrian Hunter		# Use a thread so the UI is not blocked during the SELECT
652ebd70c7dSAdrian Hunter		thread = Thread(self.FindThread, context[0])
653ebd70c7dSAdrian Hunter		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
654ebd70c7dSAdrian Hunter		thread.start()
655ebd70c7dSAdrian Hunter
656ebd70c7dSAdrian Hunter	def FindDone(self, thread, callback, ids):
657ebd70c7dSAdrian Hunter		callback(ids)
658ebd70c7dSAdrian Hunter
659254c0d82SAdrian Hunter# Context-sensitive call graph data model
660254c0d82SAdrian Hunter
661254c0d82SAdrian Hunterclass CallGraphModel(CallGraphModelBase):
662254c0d82SAdrian Hunter
663254c0d82SAdrian Hunter	def __init__(self, glb, parent=None):
664254c0d82SAdrian Hunter		super(CallGraphModel, self).__init__(glb, parent)
665254c0d82SAdrian Hunter
666254c0d82SAdrian Hunter	def GetRoot(self):
667254c0d82SAdrian Hunter		return CallGraphRootItem(self.glb)
668254c0d82SAdrian Hunter
669254c0d82SAdrian Hunter	def columnCount(self, parent=None):
670254c0d82SAdrian Hunter		return 7
671254c0d82SAdrian Hunter
672254c0d82SAdrian Hunter	def columnHeader(self, column):
673254c0d82SAdrian Hunter		headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
674254c0d82SAdrian Hunter		return headers[column]
675254c0d82SAdrian Hunter
676254c0d82SAdrian Hunter	def columnAlignment(self, column):
677254c0d82SAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
678254c0d82SAdrian Hunter		return alignment[column]
679254c0d82SAdrian Hunter
680254c0d82SAdrian Hunter	def DoFindSelect(self, query, match):
681254c0d82SAdrian Hunter		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
682254c0d82SAdrian Hunter						" FROM calls"
683254c0d82SAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
684254c0d82SAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
685254c0d82SAdrian Hunter						" WHERE symbols.name" + match +
686254c0d82SAdrian Hunter						" GROUP BY comm_id, thread_id, call_path_id"
687254c0d82SAdrian Hunter						" ORDER BY comm_id, thread_id, call_path_id")
688254c0d82SAdrian Hunter
689254c0d82SAdrian Hunter	def FindPath(self, query):
690254c0d82SAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
691254c0d82SAdrian Hunter		# to open the tree at the right place.
692254c0d82SAdrian Hunter		ids = []
693254c0d82SAdrian Hunter		parent_id = query.value(0)
694254c0d82SAdrian Hunter		while parent_id:
695254c0d82SAdrian Hunter			ids.insert(0, parent_id)
696254c0d82SAdrian Hunter			q2 = QSqlQuery(self.glb.db)
697254c0d82SAdrian Hunter			QueryExec(q2, "SELECT parent_id"
698254c0d82SAdrian Hunter					" FROM call_paths"
699254c0d82SAdrian Hunter					" WHERE id = " + str(parent_id))
700254c0d82SAdrian Hunter			if not q2.next():
701254c0d82SAdrian Hunter				break
702254c0d82SAdrian Hunter			parent_id = q2.value(0)
703254c0d82SAdrian Hunter		# The call path root is not used
704254c0d82SAdrian Hunter		if ids[0] == 1:
705254c0d82SAdrian Hunter			del ids[0]
706254c0d82SAdrian Hunter		ids.insert(0, query.value(2))
707254c0d82SAdrian Hunter		ids.insert(0, query.value(1))
708254c0d82SAdrian Hunter		return ids
709254c0d82SAdrian Hunter
710ae8b887cSAdrian Hunter# Call tree data model level 2+ item base
711ae8b887cSAdrian Hunter
712ae8b887cSAdrian Hunterclass CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
713ae8b887cSAdrian Hunter
714ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
715ae8b887cSAdrian Hunter		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
716ae8b887cSAdrian Hunter		self.comm_id = comm_id
717ae8b887cSAdrian Hunter		self.thread_id = thread_id
718ae8b887cSAdrian Hunter		self.calls_id = calls_id
719ae8b887cSAdrian Hunter		self.branch_count = branch_count
720ae8b887cSAdrian Hunter		self.time = time
721ae8b887cSAdrian Hunter
722ae8b887cSAdrian Hunter	def Select(self):
723ae8b887cSAdrian Hunter		self.query_done = True;
724ae8b887cSAdrian Hunter		if self.calls_id == 0:
725ae8b887cSAdrian Hunter			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
726ae8b887cSAdrian Hunter		else:
727ae8b887cSAdrian Hunter			comm_thread = ""
728ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
729ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
730ae8b887cSAdrian Hunter					" FROM calls"
731ae8b887cSAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
732ae8b887cSAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
733ae8b887cSAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
734ae8b887cSAdrian Hunter					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
735ae8b887cSAdrian Hunter					" ORDER BY call_time, calls.id")
736ae8b887cSAdrian Hunter		while query.next():
737ae8b887cSAdrian Hunter			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)
738ae8b887cSAdrian Hunter			self.child_items.append(child_item)
739ae8b887cSAdrian Hunter			self.child_count += 1
740ae8b887cSAdrian Hunter
741ae8b887cSAdrian Hunter# Call tree data model level three item
742ae8b887cSAdrian Hunter
743ae8b887cSAdrian Hunterclass CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
744ae8b887cSAdrian Hunter
745ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
746ae8b887cSAdrian Hunter		super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
747ae8b887cSAdrian Hunter		dso = dsoname(dso)
748ae8b887cSAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
749ae8b887cSAdrian Hunter		self.dbid = calls_id
750ae8b887cSAdrian Hunter
751ae8b887cSAdrian Hunter# Call tree data model level two item
752ae8b887cSAdrian Hunter
753ae8b887cSAdrian Hunterclass CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
754ae8b887cSAdrian Hunter
755ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
756ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item)
757ae8b887cSAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
758ae8b887cSAdrian Hunter		self.dbid = thread_id
759ae8b887cSAdrian Hunter
760ae8b887cSAdrian Hunter	def Select(self):
761ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).Select()
762ae8b887cSAdrian Hunter		for child_item in self.child_items:
763ae8b887cSAdrian Hunter			self.time += child_item.time
764ae8b887cSAdrian Hunter			self.branch_count += child_item.branch_count
765ae8b887cSAdrian Hunter		for child_item in self.child_items:
766ae8b887cSAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
767ae8b887cSAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
768ae8b887cSAdrian Hunter
769ae8b887cSAdrian Hunter# Call tree data model level one item
770ae8b887cSAdrian Hunter
771ae8b887cSAdrian Hunterclass CallTreeLevelOneItem(CallGraphLevelItemBase):
772ae8b887cSAdrian Hunter
773ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, comm, parent_item):
774ae8b887cSAdrian Hunter		super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item)
775ae8b887cSAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
776ae8b887cSAdrian Hunter		self.dbid = comm_id
777ae8b887cSAdrian Hunter
778ae8b887cSAdrian Hunter	def Select(self):
779ae8b887cSAdrian Hunter		self.query_done = True;
780ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
781ae8b887cSAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
782ae8b887cSAdrian Hunter					" FROM comm_threads"
783ae8b887cSAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
784ae8b887cSAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
785ae8b887cSAdrian Hunter		while query.next():
786ae8b887cSAdrian Hunter			child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
787ae8b887cSAdrian Hunter			self.child_items.append(child_item)
788ae8b887cSAdrian Hunter			self.child_count += 1
789ae8b887cSAdrian Hunter
790ae8b887cSAdrian Hunter# Call tree data model root item
791ae8b887cSAdrian Hunter
792ae8b887cSAdrian Hunterclass CallTreeRootItem(CallGraphLevelItemBase):
793ae8b887cSAdrian Hunter
794ae8b887cSAdrian Hunter	def __init__(self, glb):
795ae8b887cSAdrian Hunter		super(CallTreeRootItem, self).__init__(glb, 0, None)
796ae8b887cSAdrian Hunter		self.dbid = 0
797ae8b887cSAdrian Hunter		self.query_done = True;
798ae8b887cSAdrian Hunter		query = QSqlQuery(glb.db)
799ae8b887cSAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
800ae8b887cSAdrian Hunter		while query.next():
801ae8b887cSAdrian Hunter			if not query.value(0):
802ae8b887cSAdrian Hunter				continue
803ae8b887cSAdrian Hunter			child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
804ae8b887cSAdrian Hunter			self.child_items.append(child_item)
805ae8b887cSAdrian Hunter			self.child_count += 1
806ae8b887cSAdrian Hunter
807ae8b887cSAdrian Hunter# Call Tree data model
808ae8b887cSAdrian Hunter
809ae8b887cSAdrian Hunterclass CallTreeModel(CallGraphModelBase):
810ae8b887cSAdrian Hunter
811ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
812ae8b887cSAdrian Hunter		super(CallTreeModel, self).__init__(glb, parent)
813ae8b887cSAdrian Hunter
814ae8b887cSAdrian Hunter	def GetRoot(self):
815ae8b887cSAdrian Hunter		return CallTreeRootItem(self.glb)
816ae8b887cSAdrian Hunter
817ae8b887cSAdrian Hunter	def columnCount(self, parent=None):
818ae8b887cSAdrian Hunter		return 7
819ae8b887cSAdrian Hunter
820ae8b887cSAdrian Hunter	def columnHeader(self, column):
821ae8b887cSAdrian Hunter		headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
822ae8b887cSAdrian Hunter		return headers[column]
823ae8b887cSAdrian Hunter
824ae8b887cSAdrian Hunter	def columnAlignment(self, column):
825ae8b887cSAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
826ae8b887cSAdrian Hunter		return alignment[column]
827ae8b887cSAdrian Hunter
828ae8b887cSAdrian Hunter	def DoFindSelect(self, query, match):
829ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
830ae8b887cSAdrian Hunter						" FROM calls"
831ae8b887cSAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
832ae8b887cSAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
833ae8b887cSAdrian Hunter						" WHERE symbols.name" + match +
834ae8b887cSAdrian Hunter						" ORDER BY comm_id, thread_id, call_time, calls.id")
835ae8b887cSAdrian Hunter
836ae8b887cSAdrian Hunter	def FindPath(self, query):
837ae8b887cSAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
838ae8b887cSAdrian Hunter		# to open the tree at the right place.
839ae8b887cSAdrian Hunter		ids = []
840ae8b887cSAdrian Hunter		parent_id = query.value(0)
841ae8b887cSAdrian Hunter		while parent_id:
842ae8b887cSAdrian Hunter			ids.insert(0, parent_id)
843ae8b887cSAdrian Hunter			q2 = QSqlQuery(self.glb.db)
844ae8b887cSAdrian Hunter			QueryExec(q2, "SELECT parent_id"
845ae8b887cSAdrian Hunter					" FROM calls"
846ae8b887cSAdrian Hunter					" WHERE id = " + str(parent_id))
847ae8b887cSAdrian Hunter			if not q2.next():
848ae8b887cSAdrian Hunter				break
849ae8b887cSAdrian Hunter			parent_id = q2.value(0)
850ae8b887cSAdrian Hunter		ids.insert(0, query.value(2))
851ae8b887cSAdrian Hunter		ids.insert(0, query.value(1))
852ae8b887cSAdrian Hunter		return ids
853ae8b887cSAdrian Hunter
854ebd70c7dSAdrian Hunter# Vertical widget layout
855ebd70c7dSAdrian Hunter
856ebd70c7dSAdrian Hunterclass VBox():
857ebd70c7dSAdrian Hunter
858ebd70c7dSAdrian Hunter	def __init__(self, w1, w2, w3=None):
859ebd70c7dSAdrian Hunter		self.vbox = QWidget()
860ebd70c7dSAdrian Hunter		self.vbox.setLayout(QVBoxLayout());
861ebd70c7dSAdrian Hunter
862ebd70c7dSAdrian Hunter		self.vbox.layout().setContentsMargins(0, 0, 0, 0)
863ebd70c7dSAdrian Hunter
864ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w1)
865ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w2)
866ebd70c7dSAdrian Hunter		if w3:
867ebd70c7dSAdrian Hunter			self.vbox.layout().addWidget(w3)
868ebd70c7dSAdrian Hunter
869ebd70c7dSAdrian Hunter	def Widget(self):
870ebd70c7dSAdrian Hunter		return self.vbox
871ebd70c7dSAdrian Hunter
872a731cc4cSAdrian Hunter# Tree window base
8731beb5c7bSAdrian Hunter
874a731cc4cSAdrian Hunterclass TreeWindowBase(QMdiSubWindow):
8751beb5c7bSAdrian Hunter
876a731cc4cSAdrian Hunter	def __init__(self, parent=None):
877a731cc4cSAdrian Hunter		super(TreeWindowBase, self).__init__(parent)
8781beb5c7bSAdrian Hunter
879a731cc4cSAdrian Hunter		self.model = None
880a731cc4cSAdrian Hunter		self.view = None
881a731cc4cSAdrian Hunter		self.find_bar = None
8821beb5c7bSAdrian Hunter
883ebd70c7dSAdrian Hunter	def DisplayFound(self, ids):
884ebd70c7dSAdrian Hunter		if not len(ids):
885ebd70c7dSAdrian Hunter			return False
886ebd70c7dSAdrian Hunter		parent = QModelIndex()
887ebd70c7dSAdrian Hunter		for dbid in ids:
888ebd70c7dSAdrian Hunter			found = False
889ebd70c7dSAdrian Hunter			n = self.model.rowCount(parent)
890ebd70c7dSAdrian Hunter			for row in xrange(n):
891ebd70c7dSAdrian Hunter				child = self.model.index(row, 0, parent)
892ebd70c7dSAdrian Hunter				if child.internalPointer().dbid == dbid:
893ebd70c7dSAdrian Hunter					found = True
894ebd70c7dSAdrian Hunter					self.view.setCurrentIndex(child)
895ebd70c7dSAdrian Hunter					parent = child
896ebd70c7dSAdrian Hunter					break
897ebd70c7dSAdrian Hunter			if not found:
898ebd70c7dSAdrian Hunter				break
899ebd70c7dSAdrian Hunter		return found
900ebd70c7dSAdrian Hunter
901ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context):
902ebd70c7dSAdrian Hunter		self.view.setFocus()
903ebd70c7dSAdrian Hunter		self.find_bar.Busy()
904ebd70c7dSAdrian Hunter		self.model.Find(value, direction, pattern, context, self.FindDone)
905ebd70c7dSAdrian Hunter
906ebd70c7dSAdrian Hunter	def FindDone(self, ids):
907ebd70c7dSAdrian Hunter		found = True
908ebd70c7dSAdrian Hunter		if not self.DisplayFound(ids):
909ebd70c7dSAdrian Hunter			found = False
910ebd70c7dSAdrian Hunter		self.find_bar.Idle()
911ebd70c7dSAdrian Hunter		if not found:
912ebd70c7dSAdrian Hunter			self.find_bar.NotFound()
913ebd70c7dSAdrian Hunter
914a731cc4cSAdrian Hunter
915a731cc4cSAdrian Hunter# Context-sensitive call graph window
916a731cc4cSAdrian Hunter
917a731cc4cSAdrian Hunterclass CallGraphWindow(TreeWindowBase):
918a731cc4cSAdrian Hunter
919a731cc4cSAdrian Hunter	def __init__(self, glb, parent=None):
920a731cc4cSAdrian Hunter		super(CallGraphWindow, self).__init__(parent)
921a731cc4cSAdrian Hunter
922a731cc4cSAdrian Hunter		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
923a731cc4cSAdrian Hunter
924a731cc4cSAdrian Hunter		self.view = QTreeView()
925a731cc4cSAdrian Hunter		self.view.setModel(self.model)
926a731cc4cSAdrian Hunter
927a731cc4cSAdrian Hunter		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
928a731cc4cSAdrian Hunter			self.view.setColumnWidth(c, w)
929a731cc4cSAdrian Hunter
930a731cc4cSAdrian Hunter		self.find_bar = FindBar(self, self)
931a731cc4cSAdrian Hunter
932a731cc4cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
933a731cc4cSAdrian Hunter
934a731cc4cSAdrian Hunter		self.setWidget(self.vbox.Widget())
935a731cc4cSAdrian Hunter
936a731cc4cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
937a731cc4cSAdrian Hunter
938ae8b887cSAdrian Hunter# Call tree window
939ae8b887cSAdrian Hunter
940ae8b887cSAdrian Hunterclass CallTreeWindow(TreeWindowBase):
941ae8b887cSAdrian Hunter
942ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
943ae8b887cSAdrian Hunter		super(CallTreeWindow, self).__init__(parent)
944ae8b887cSAdrian Hunter
945ae8b887cSAdrian Hunter		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
946ae8b887cSAdrian Hunter
947ae8b887cSAdrian Hunter		self.view = QTreeView()
948ae8b887cSAdrian Hunter		self.view.setModel(self.model)
949ae8b887cSAdrian Hunter
950ae8b887cSAdrian Hunter		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
951ae8b887cSAdrian Hunter			self.view.setColumnWidth(c, w)
952ae8b887cSAdrian Hunter
953ae8b887cSAdrian Hunter		self.find_bar = FindBar(self, self)
954ae8b887cSAdrian Hunter
955ae8b887cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
956ae8b887cSAdrian Hunter
957ae8b887cSAdrian Hunter		self.setWidget(self.vbox.Widget())
958ae8b887cSAdrian Hunter
959ae8b887cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
960ae8b887cSAdrian Hunter
9618392b74bSAdrian Hunter# Child data item  finder
9628392b74bSAdrian Hunter
9638392b74bSAdrian Hunterclass ChildDataItemFinder():
9648392b74bSAdrian Hunter
9658392b74bSAdrian Hunter	def __init__(self, root):
9668392b74bSAdrian Hunter		self.root = root
9678392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
9688392b74bSAdrian Hunter		self.rows = []
9698392b74bSAdrian Hunter		self.pos = 0
9708392b74bSAdrian Hunter
9718392b74bSAdrian Hunter	def FindSelect(self):
9728392b74bSAdrian Hunter		self.rows = []
9738392b74bSAdrian Hunter		if self.pattern:
9748392b74bSAdrian Hunter			pattern = re.compile(self.value)
9758392b74bSAdrian Hunter			for child in self.root.child_items:
9768392b74bSAdrian Hunter				for column_data in child.data:
9778392b74bSAdrian Hunter					if re.search(pattern, str(column_data)) is not None:
9788392b74bSAdrian Hunter						self.rows.append(child.row)
9798392b74bSAdrian Hunter						break
9808392b74bSAdrian Hunter		else:
9818392b74bSAdrian Hunter			for child in self.root.child_items:
9828392b74bSAdrian Hunter				for column_data in child.data:
9838392b74bSAdrian Hunter					if self.value in str(column_data):
9848392b74bSAdrian Hunter						self.rows.append(child.row)
9858392b74bSAdrian Hunter						break
9868392b74bSAdrian Hunter
9878392b74bSAdrian Hunter	def FindValue(self):
9888392b74bSAdrian Hunter		self.pos = 0
9898392b74bSAdrian Hunter		if self.last_value != self.value or self.pattern != self.last_pattern:
9908392b74bSAdrian Hunter			self.FindSelect()
9918392b74bSAdrian Hunter		if not len(self.rows):
9928392b74bSAdrian Hunter			return -1
9938392b74bSAdrian Hunter		return self.rows[self.pos]
9948392b74bSAdrian Hunter
9958392b74bSAdrian Hunter	def FindThread(self):
9968392b74bSAdrian Hunter		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
9978392b74bSAdrian Hunter			row = self.FindValue()
9988392b74bSAdrian Hunter		elif len(self.rows):
9998392b74bSAdrian Hunter			if self.direction > 0:
10008392b74bSAdrian Hunter				self.pos += 1
10018392b74bSAdrian Hunter				if self.pos >= len(self.rows):
10028392b74bSAdrian Hunter					self.pos = 0
10038392b74bSAdrian Hunter			else:
10048392b74bSAdrian Hunter				self.pos -= 1
10058392b74bSAdrian Hunter				if self.pos < 0:
10068392b74bSAdrian Hunter					self.pos = len(self.rows) - 1
10078392b74bSAdrian Hunter			row = self.rows[self.pos]
10088392b74bSAdrian Hunter		else:
10098392b74bSAdrian Hunter			row = -1
10108392b74bSAdrian Hunter		return (True, row)
10118392b74bSAdrian Hunter
10128392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
10138392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
10148392b74bSAdrian Hunter		# Use a thread so the UI is not blocked
10158392b74bSAdrian Hunter		thread = Thread(self.FindThread)
10168392b74bSAdrian Hunter		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
10178392b74bSAdrian Hunter		thread.start()
10188392b74bSAdrian Hunter
10198392b74bSAdrian Hunter	def FindDone(self, thread, callback, row):
10208392b74bSAdrian Hunter		callback(row)
10218392b74bSAdrian Hunter
10228392b74bSAdrian Hunter# Number of database records to fetch in one go
10238392b74bSAdrian Hunter
10248392b74bSAdrian Hunterglb_chunk_sz = 10000
10258392b74bSAdrian Hunter
10268392b74bSAdrian Hunter# Background process for SQL data fetcher
10278392b74bSAdrian Hunter
10288392b74bSAdrian Hunterclass SQLFetcherProcess():
10298392b74bSAdrian Hunter
10308392b74bSAdrian Hunter	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
10318392b74bSAdrian Hunter		# Need a unique connection name
10328392b74bSAdrian Hunter		conn_name = "SQLFetcher" + str(os.getpid())
10338392b74bSAdrian Hunter		self.db, dbname = dbref.Open(conn_name)
10348392b74bSAdrian Hunter		self.sql = sql
10358392b74bSAdrian Hunter		self.buffer = buffer
10368392b74bSAdrian Hunter		self.head = head
10378392b74bSAdrian Hunter		self.tail = tail
10388392b74bSAdrian Hunter		self.fetch_count = fetch_count
10398392b74bSAdrian Hunter		self.fetching_done = fetching_done
10408392b74bSAdrian Hunter		self.process_target = process_target
10418392b74bSAdrian Hunter		self.wait_event = wait_event
10428392b74bSAdrian Hunter		self.fetched_event = fetched_event
10438392b74bSAdrian Hunter		self.prep = prep
10448392b74bSAdrian Hunter		self.query = QSqlQuery(self.db)
10458392b74bSAdrian Hunter		self.query_limit = 0 if "$$last_id$$" in sql else 2
10468392b74bSAdrian Hunter		self.last_id = -1
10478392b74bSAdrian Hunter		self.fetched = 0
10488392b74bSAdrian Hunter		self.more = True
10498392b74bSAdrian Hunter		self.local_head = self.head.value
10508392b74bSAdrian Hunter		self.local_tail = self.tail.value
10518392b74bSAdrian Hunter
10528392b74bSAdrian Hunter	def Select(self):
10538392b74bSAdrian Hunter		if self.query_limit:
10548392b74bSAdrian Hunter			if self.query_limit == 1:
10558392b74bSAdrian Hunter				return
10568392b74bSAdrian Hunter			self.query_limit -= 1
10578392b74bSAdrian Hunter		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
10588392b74bSAdrian Hunter		QueryExec(self.query, stmt)
10598392b74bSAdrian Hunter
10608392b74bSAdrian Hunter	def Next(self):
10618392b74bSAdrian Hunter		if not self.query.next():
10628392b74bSAdrian Hunter			self.Select()
10638392b74bSAdrian Hunter			if not self.query.next():
10648392b74bSAdrian Hunter				return None
10658392b74bSAdrian Hunter		self.last_id = self.query.value(0)
10668392b74bSAdrian Hunter		return self.prep(self.query)
10678392b74bSAdrian Hunter
10688392b74bSAdrian Hunter	def WaitForTarget(self):
10698392b74bSAdrian Hunter		while True:
10708392b74bSAdrian Hunter			self.wait_event.clear()
10718392b74bSAdrian Hunter			target = self.process_target.value
10728392b74bSAdrian Hunter			if target > self.fetched or target < 0:
10738392b74bSAdrian Hunter				break
10748392b74bSAdrian Hunter			self.wait_event.wait()
10758392b74bSAdrian Hunter		return target
10768392b74bSAdrian Hunter
10778392b74bSAdrian Hunter	def HasSpace(self, sz):
10788392b74bSAdrian Hunter		if self.local_tail <= self.local_head:
10798392b74bSAdrian Hunter			space = len(self.buffer) - self.local_head
10808392b74bSAdrian Hunter			if space > sz:
10818392b74bSAdrian Hunter				return True
10828392b74bSAdrian Hunter			if space >= glb_nsz:
10838392b74bSAdrian Hunter				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1084beda0e72STony Jones				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
10858392b74bSAdrian Hunter				self.buffer[self.local_head : self.local_head + len(nd)] = nd
10868392b74bSAdrian Hunter			self.local_head = 0
10878392b74bSAdrian Hunter		if self.local_tail - self.local_head > sz:
10888392b74bSAdrian Hunter			return True
10898392b74bSAdrian Hunter		return False
10908392b74bSAdrian Hunter
10918392b74bSAdrian Hunter	def WaitForSpace(self, sz):
10928392b74bSAdrian Hunter		if self.HasSpace(sz):
10938392b74bSAdrian Hunter			return
10948392b74bSAdrian Hunter		while True:
10958392b74bSAdrian Hunter			self.wait_event.clear()
10968392b74bSAdrian Hunter			self.local_tail = self.tail.value
10978392b74bSAdrian Hunter			if self.HasSpace(sz):
10988392b74bSAdrian Hunter				return
10998392b74bSAdrian Hunter			self.wait_event.wait()
11008392b74bSAdrian Hunter
11018392b74bSAdrian Hunter	def AddToBuffer(self, obj):
1102beda0e72STony Jones		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
11038392b74bSAdrian Hunter		n = len(d)
1104beda0e72STony Jones		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
11058392b74bSAdrian Hunter		sz = n + glb_nsz
11068392b74bSAdrian Hunter		self.WaitForSpace(sz)
11078392b74bSAdrian Hunter		pos = self.local_head
11088392b74bSAdrian Hunter		self.buffer[pos : pos + len(nd)] = nd
11098392b74bSAdrian Hunter		self.buffer[pos + glb_nsz : pos + sz] = d
11108392b74bSAdrian Hunter		self.local_head += sz
11118392b74bSAdrian Hunter
11128392b74bSAdrian Hunter	def FetchBatch(self, batch_size):
11138392b74bSAdrian Hunter		fetched = 0
11148392b74bSAdrian Hunter		while batch_size > fetched:
11158392b74bSAdrian Hunter			obj = self.Next()
11168392b74bSAdrian Hunter			if obj is None:
11178392b74bSAdrian Hunter				self.more = False
11188392b74bSAdrian Hunter				break
11198392b74bSAdrian Hunter			self.AddToBuffer(obj)
11208392b74bSAdrian Hunter			fetched += 1
11218392b74bSAdrian Hunter		if fetched:
11228392b74bSAdrian Hunter			self.fetched += fetched
11238392b74bSAdrian Hunter			with self.fetch_count.get_lock():
11248392b74bSAdrian Hunter				self.fetch_count.value += fetched
11258392b74bSAdrian Hunter			self.head.value = self.local_head
11268392b74bSAdrian Hunter			self.fetched_event.set()
11278392b74bSAdrian Hunter
11288392b74bSAdrian Hunter	def Run(self):
11298392b74bSAdrian Hunter		while self.more:
11308392b74bSAdrian Hunter			target = self.WaitForTarget()
11318392b74bSAdrian Hunter			if target < 0:
11328392b74bSAdrian Hunter				break
11338392b74bSAdrian Hunter			batch_size = min(glb_chunk_sz, target - self.fetched)
11348392b74bSAdrian Hunter			self.FetchBatch(batch_size)
11358392b74bSAdrian Hunter		self.fetching_done.value = True
11368392b74bSAdrian Hunter		self.fetched_event.set()
11378392b74bSAdrian Hunter
11388392b74bSAdrian Hunterdef SQLFetcherFn(*x):
11398392b74bSAdrian Hunter	process = SQLFetcherProcess(*x)
11408392b74bSAdrian Hunter	process.Run()
11418392b74bSAdrian Hunter
11428392b74bSAdrian Hunter# SQL data fetcher
11438392b74bSAdrian Hunter
11448392b74bSAdrian Hunterclass SQLFetcher(QObject):
11458392b74bSAdrian Hunter
11468392b74bSAdrian Hunter	done = Signal(object)
11478392b74bSAdrian Hunter
11488392b74bSAdrian Hunter	def __init__(self, glb, sql, prep, process_data, parent=None):
11498392b74bSAdrian Hunter		super(SQLFetcher, self).__init__(parent)
11508392b74bSAdrian Hunter		self.process_data = process_data
11518392b74bSAdrian Hunter		self.more = True
11528392b74bSAdrian Hunter		self.target = 0
11538392b74bSAdrian Hunter		self.last_target = 0
11548392b74bSAdrian Hunter		self.fetched = 0
11558392b74bSAdrian Hunter		self.buffer_size = 16 * 1024 * 1024
11568392b74bSAdrian Hunter		self.buffer = Array(c_char, self.buffer_size, lock=False)
11578392b74bSAdrian Hunter		self.head = Value(c_longlong)
11588392b74bSAdrian Hunter		self.tail = Value(c_longlong)
11598392b74bSAdrian Hunter		self.local_tail = 0
11608392b74bSAdrian Hunter		self.fetch_count = Value(c_longlong)
11618392b74bSAdrian Hunter		self.fetching_done = Value(c_bool)
11628392b74bSAdrian Hunter		self.last_count = 0
11638392b74bSAdrian Hunter		self.process_target = Value(c_longlong)
11648392b74bSAdrian Hunter		self.wait_event = Event()
11658392b74bSAdrian Hunter		self.fetched_event = Event()
11668392b74bSAdrian Hunter		glb.AddInstanceToShutdownOnExit(self)
11678392b74bSAdrian Hunter		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))
11688392b74bSAdrian Hunter		self.process.start()
11698392b74bSAdrian Hunter		self.thread = Thread(self.Thread)
11708392b74bSAdrian Hunter		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
11718392b74bSAdrian Hunter		self.thread.start()
11728392b74bSAdrian Hunter
11738392b74bSAdrian Hunter	def Shutdown(self):
11748392b74bSAdrian Hunter		# Tell the thread and process to exit
11758392b74bSAdrian Hunter		self.process_target.value = -1
11768392b74bSAdrian Hunter		self.wait_event.set()
11778392b74bSAdrian Hunter		self.more = False
11788392b74bSAdrian Hunter		self.fetching_done.value = True
11798392b74bSAdrian Hunter		self.fetched_event.set()
11808392b74bSAdrian Hunter
11818392b74bSAdrian Hunter	def Thread(self):
11828392b74bSAdrian Hunter		if not self.more:
11838392b74bSAdrian Hunter			return True, 0
11848392b74bSAdrian Hunter		while True:
11858392b74bSAdrian Hunter			self.fetched_event.clear()
11868392b74bSAdrian Hunter			fetch_count = self.fetch_count.value
11878392b74bSAdrian Hunter			if fetch_count != self.last_count:
11888392b74bSAdrian Hunter				break
11898392b74bSAdrian Hunter			if self.fetching_done.value:
11908392b74bSAdrian Hunter				self.more = False
11918392b74bSAdrian Hunter				return True, 0
11928392b74bSAdrian Hunter			self.fetched_event.wait()
11938392b74bSAdrian Hunter		count = fetch_count - self.last_count
11948392b74bSAdrian Hunter		self.last_count = fetch_count
11958392b74bSAdrian Hunter		self.fetched += count
11968392b74bSAdrian Hunter		return False, count
11978392b74bSAdrian Hunter
11988392b74bSAdrian Hunter	def Fetch(self, nr):
11998392b74bSAdrian Hunter		if not self.more:
12008392b74bSAdrian Hunter			# -1 inidcates there are no more
12018392b74bSAdrian Hunter			return -1
12028392b74bSAdrian Hunter		result = self.fetched
12038392b74bSAdrian Hunter		extra = result + nr - self.target
12048392b74bSAdrian Hunter		if extra > 0:
12058392b74bSAdrian Hunter			self.target += extra
12068392b74bSAdrian Hunter			# process_target < 0 indicates shutting down
12078392b74bSAdrian Hunter			if self.process_target.value >= 0:
12088392b74bSAdrian Hunter				self.process_target.value = self.target
12098392b74bSAdrian Hunter			self.wait_event.set()
12108392b74bSAdrian Hunter		return result
12118392b74bSAdrian Hunter
12128392b74bSAdrian Hunter	def RemoveFromBuffer(self):
12138392b74bSAdrian Hunter		pos = self.local_tail
12148392b74bSAdrian Hunter		if len(self.buffer) - pos < glb_nsz:
12158392b74bSAdrian Hunter			pos = 0
1216beda0e72STony Jones		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
12178392b74bSAdrian Hunter		if n == 0:
12188392b74bSAdrian Hunter			pos = 0
1219beda0e72STony Jones			n = pickle.loads(self.buffer[0 : glb_nsz])
12208392b74bSAdrian Hunter		pos += glb_nsz
1221beda0e72STony Jones		obj = pickle.loads(self.buffer[pos : pos + n])
12228392b74bSAdrian Hunter		self.local_tail = pos + n
12238392b74bSAdrian Hunter		return obj
12248392b74bSAdrian Hunter
12258392b74bSAdrian Hunter	def ProcessData(self, count):
12268392b74bSAdrian Hunter		for i in xrange(count):
12278392b74bSAdrian Hunter			obj = self.RemoveFromBuffer()
12288392b74bSAdrian Hunter			self.process_data(obj)
12298392b74bSAdrian Hunter		self.tail.value = self.local_tail
12308392b74bSAdrian Hunter		self.wait_event.set()
12318392b74bSAdrian Hunter		self.done.emit(count)
12328392b74bSAdrian Hunter
12338392b74bSAdrian Hunter# Fetch more records bar
12348392b74bSAdrian Hunter
12358392b74bSAdrian Hunterclass FetchMoreRecordsBar():
12368392b74bSAdrian Hunter
12378392b74bSAdrian Hunter	def __init__(self, model, parent):
12388392b74bSAdrian Hunter		self.model = model
12398392b74bSAdrian Hunter
12408392b74bSAdrian Hunter		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
12418392b74bSAdrian Hunter		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12428392b74bSAdrian Hunter
12438392b74bSAdrian Hunter		self.fetch_count = QSpinBox()
12448392b74bSAdrian Hunter		self.fetch_count.setRange(1, 1000000)
12458392b74bSAdrian Hunter		self.fetch_count.setValue(10)
12468392b74bSAdrian Hunter		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12478392b74bSAdrian Hunter
12488392b74bSAdrian Hunter		self.fetch = QPushButton("Go!")
12498392b74bSAdrian Hunter		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12508392b74bSAdrian Hunter		self.fetch.released.connect(self.FetchMoreRecords)
12518392b74bSAdrian Hunter
12528392b74bSAdrian Hunter		self.progress = QProgressBar()
12538392b74bSAdrian Hunter		self.progress.setRange(0, 100)
12548392b74bSAdrian Hunter		self.progress.hide()
12558392b74bSAdrian Hunter
12568392b74bSAdrian Hunter		self.done_label = QLabel("All records fetched")
12578392b74bSAdrian Hunter		self.done_label.hide()
12588392b74bSAdrian Hunter
12598392b74bSAdrian Hunter		self.spacer = QLabel("")
12608392b74bSAdrian Hunter
12618392b74bSAdrian Hunter		self.close_button = QToolButton()
12628392b74bSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
12638392b74bSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
12648392b74bSAdrian Hunter
12658392b74bSAdrian Hunter		self.hbox = QHBoxLayout()
12668392b74bSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
12678392b74bSAdrian Hunter
12688392b74bSAdrian Hunter		self.hbox.addWidget(self.label)
12698392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch_count)
12708392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch)
12718392b74bSAdrian Hunter		self.hbox.addWidget(self.spacer)
12728392b74bSAdrian Hunter		self.hbox.addWidget(self.progress)
12738392b74bSAdrian Hunter		self.hbox.addWidget(self.done_label)
12748392b74bSAdrian Hunter		self.hbox.addWidget(self.close_button)
12758392b74bSAdrian Hunter
12768392b74bSAdrian Hunter		self.bar = QWidget()
12778392b74bSAdrian Hunter		self.bar.setLayout(self.hbox);
12788392b74bSAdrian Hunter		self.bar.show()
12798392b74bSAdrian Hunter
12808392b74bSAdrian Hunter		self.in_progress = False
12818392b74bSAdrian Hunter		self.model.progress.connect(self.Progress)
12828392b74bSAdrian Hunter
12838392b74bSAdrian Hunter		self.done = False
12848392b74bSAdrian Hunter
12858392b74bSAdrian Hunter		if not model.HasMoreRecords():
12868392b74bSAdrian Hunter			self.Done()
12878392b74bSAdrian Hunter
12888392b74bSAdrian Hunter	def Widget(self):
12898392b74bSAdrian Hunter		return self.bar
12908392b74bSAdrian Hunter
12918392b74bSAdrian Hunter	def Activate(self):
12928392b74bSAdrian Hunter		self.bar.show()
12938392b74bSAdrian Hunter		self.fetch.setFocus()
12948392b74bSAdrian Hunter
12958392b74bSAdrian Hunter	def Deactivate(self):
12968392b74bSAdrian Hunter		self.bar.hide()
12978392b74bSAdrian Hunter
12988392b74bSAdrian Hunter	def Enable(self, enable):
12998392b74bSAdrian Hunter		self.fetch.setEnabled(enable)
13008392b74bSAdrian Hunter		self.fetch_count.setEnabled(enable)
13018392b74bSAdrian Hunter
13028392b74bSAdrian Hunter	def Busy(self):
13038392b74bSAdrian Hunter		self.Enable(False)
13048392b74bSAdrian Hunter		self.fetch.hide()
13058392b74bSAdrian Hunter		self.spacer.hide()
13068392b74bSAdrian Hunter		self.progress.show()
13078392b74bSAdrian Hunter
13088392b74bSAdrian Hunter	def Idle(self):
13098392b74bSAdrian Hunter		self.in_progress = False
13108392b74bSAdrian Hunter		self.Enable(True)
13118392b74bSAdrian Hunter		self.progress.hide()
13128392b74bSAdrian Hunter		self.fetch.show()
13138392b74bSAdrian Hunter		self.spacer.show()
13148392b74bSAdrian Hunter
13158392b74bSAdrian Hunter	def Target(self):
13168392b74bSAdrian Hunter		return self.fetch_count.value() * glb_chunk_sz
13178392b74bSAdrian Hunter
13188392b74bSAdrian Hunter	def Done(self):
13198392b74bSAdrian Hunter		self.done = True
13208392b74bSAdrian Hunter		self.Idle()
13218392b74bSAdrian Hunter		self.label.hide()
13228392b74bSAdrian Hunter		self.fetch_count.hide()
13238392b74bSAdrian Hunter		self.fetch.hide()
13248392b74bSAdrian Hunter		self.spacer.hide()
13258392b74bSAdrian Hunter		self.done_label.show()
13268392b74bSAdrian Hunter
13278392b74bSAdrian Hunter	def Progress(self, count):
13288392b74bSAdrian Hunter		if self.in_progress:
13298392b74bSAdrian Hunter			if count:
13308392b74bSAdrian Hunter				percent = ((count - self.start) * 100) / self.Target()
13318392b74bSAdrian Hunter				if percent >= 100:
13328392b74bSAdrian Hunter					self.Idle()
13338392b74bSAdrian Hunter				else:
13348392b74bSAdrian Hunter					self.progress.setValue(percent)
13358392b74bSAdrian Hunter		if not count:
13368392b74bSAdrian Hunter			# Count value of zero means no more records
13378392b74bSAdrian Hunter			self.Done()
13388392b74bSAdrian Hunter
13398392b74bSAdrian Hunter	def FetchMoreRecords(self):
13408392b74bSAdrian Hunter		if self.done:
13418392b74bSAdrian Hunter			return
13428392b74bSAdrian Hunter		self.progress.setValue(0)
13438392b74bSAdrian Hunter		self.Busy()
13448392b74bSAdrian Hunter		self.in_progress = True
13458392b74bSAdrian Hunter		self.start = self.model.FetchMoreRecords(self.Target())
13468392b74bSAdrian Hunter
134776099f98SAdrian Hunter# Brance data model level two item
134876099f98SAdrian Hunter
134976099f98SAdrian Hunterclass BranchLevelTwoItem():
135076099f98SAdrian Hunter
135176099f98SAdrian Hunter	def __init__(self, row, text, parent_item):
135276099f98SAdrian Hunter		self.row = row
135376099f98SAdrian Hunter		self.parent_item = parent_item
135476099f98SAdrian Hunter		self.data = [""] * 8
135576099f98SAdrian Hunter		self.data[7] = text
135676099f98SAdrian Hunter		self.level = 2
135776099f98SAdrian Hunter
135876099f98SAdrian Hunter	def getParentItem(self):
135976099f98SAdrian Hunter		return self.parent_item
136076099f98SAdrian Hunter
136176099f98SAdrian Hunter	def getRow(self):
136276099f98SAdrian Hunter		return self.row
136376099f98SAdrian Hunter
136476099f98SAdrian Hunter	def childCount(self):
136576099f98SAdrian Hunter		return 0
136676099f98SAdrian Hunter
136776099f98SAdrian Hunter	def hasChildren(self):
136876099f98SAdrian Hunter		return False
136976099f98SAdrian Hunter
137076099f98SAdrian Hunter	def getData(self, column):
137176099f98SAdrian Hunter		return self.data[column]
137276099f98SAdrian Hunter
137376099f98SAdrian Hunter# Brance data model level one item
137476099f98SAdrian Hunter
137576099f98SAdrian Hunterclass BranchLevelOneItem():
137676099f98SAdrian Hunter
137776099f98SAdrian Hunter	def __init__(self, glb, row, data, parent_item):
137876099f98SAdrian Hunter		self.glb = glb
137976099f98SAdrian Hunter		self.row = row
138076099f98SAdrian Hunter		self.parent_item = parent_item
138176099f98SAdrian Hunter		self.child_count = 0
138276099f98SAdrian Hunter		self.child_items = []
138376099f98SAdrian Hunter		self.data = data[1:]
138476099f98SAdrian Hunter		self.dbid = data[0]
138576099f98SAdrian Hunter		self.level = 1
138676099f98SAdrian Hunter		self.query_done = False
138776099f98SAdrian Hunter
138876099f98SAdrian Hunter	def getChildItem(self, row):
138976099f98SAdrian Hunter		return self.child_items[row]
139076099f98SAdrian Hunter
139176099f98SAdrian Hunter	def getParentItem(self):
139276099f98SAdrian Hunter		return self.parent_item
139376099f98SAdrian Hunter
139476099f98SAdrian Hunter	def getRow(self):
139576099f98SAdrian Hunter		return self.row
139676099f98SAdrian Hunter
139776099f98SAdrian Hunter	def Select(self):
139876099f98SAdrian Hunter		self.query_done = True
139976099f98SAdrian Hunter
140076099f98SAdrian Hunter		if not self.glb.have_disassembler:
140176099f98SAdrian Hunter			return
140276099f98SAdrian Hunter
140376099f98SAdrian Hunter		query = QSqlQuery(self.glb.db)
140476099f98SAdrian Hunter
140576099f98SAdrian Hunter		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
140676099f98SAdrian Hunter				  " FROM samples"
140776099f98SAdrian Hunter				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
140876099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
140976099f98SAdrian Hunter				  " WHERE samples.id = " + str(self.dbid))
141076099f98SAdrian Hunter		if not query.next():
141176099f98SAdrian Hunter			return
141276099f98SAdrian Hunter		cpu = query.value(0)
141376099f98SAdrian Hunter		dso = query.value(1)
141476099f98SAdrian Hunter		sym = query.value(2)
141576099f98SAdrian Hunter		if dso == 0 or sym == 0:
141676099f98SAdrian Hunter			return
141776099f98SAdrian Hunter		off = query.value(3)
141876099f98SAdrian Hunter		short_name = query.value(4)
141976099f98SAdrian Hunter		long_name = query.value(5)
142076099f98SAdrian Hunter		build_id = query.value(6)
142176099f98SAdrian Hunter		sym_start = query.value(7)
142276099f98SAdrian Hunter		ip = query.value(8)
142376099f98SAdrian Hunter
142476099f98SAdrian Hunter		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
142576099f98SAdrian Hunter				  " FROM samples"
142676099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
142776099f98SAdrian Hunter				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
142876099f98SAdrian Hunter				  " ORDER BY samples.id"
142976099f98SAdrian Hunter				  " LIMIT 1")
143076099f98SAdrian Hunter		if not query.next():
143176099f98SAdrian Hunter			return
143276099f98SAdrian Hunter		if query.value(0) != dso:
143376099f98SAdrian Hunter			# Cannot disassemble from one dso to another
143476099f98SAdrian Hunter			return
143576099f98SAdrian Hunter		bsym = query.value(1)
143676099f98SAdrian Hunter		boff = query.value(2)
143776099f98SAdrian Hunter		bsym_start = query.value(3)
143876099f98SAdrian Hunter		if bsym == 0:
143976099f98SAdrian Hunter			return
144076099f98SAdrian Hunter		tot = bsym_start + boff + 1 - sym_start - off
144176099f98SAdrian Hunter		if tot <= 0 or tot > 16384:
144276099f98SAdrian Hunter			return
144376099f98SAdrian Hunter
144476099f98SAdrian Hunter		inst = self.glb.disassembler.Instruction()
144576099f98SAdrian Hunter		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
144676099f98SAdrian Hunter		if not f:
144776099f98SAdrian Hunter			return
144876099f98SAdrian Hunter		mode = 0 if Is64Bit(f) else 1
144976099f98SAdrian Hunter		self.glb.disassembler.SetMode(inst, mode)
145076099f98SAdrian Hunter
145176099f98SAdrian Hunter		buf_sz = tot + 16
145276099f98SAdrian Hunter		buf = create_string_buffer(tot + 16)
145376099f98SAdrian Hunter		f.seek(sym_start + off)
145476099f98SAdrian Hunter		buf.value = f.read(buf_sz)
145576099f98SAdrian Hunter		buf_ptr = addressof(buf)
145676099f98SAdrian Hunter		i = 0
145776099f98SAdrian Hunter		while tot > 0:
145876099f98SAdrian Hunter			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
145976099f98SAdrian Hunter			if cnt:
146076099f98SAdrian Hunter				byte_str = tohex(ip).rjust(16)
146176099f98SAdrian Hunter				for k in xrange(cnt):
146276099f98SAdrian Hunter					byte_str += " %02x" % ord(buf[i])
146376099f98SAdrian Hunter					i += 1
146476099f98SAdrian Hunter				while k < 15:
146576099f98SAdrian Hunter					byte_str += "   "
146676099f98SAdrian Hunter					k += 1
146776099f98SAdrian Hunter				self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
146876099f98SAdrian Hunter				self.child_count += 1
146976099f98SAdrian Hunter			else:
147076099f98SAdrian Hunter				return
147176099f98SAdrian Hunter			buf_ptr += cnt
147276099f98SAdrian Hunter			tot -= cnt
147376099f98SAdrian Hunter			buf_sz -= cnt
147476099f98SAdrian Hunter			ip += cnt
147576099f98SAdrian Hunter
147676099f98SAdrian Hunter	def childCount(self):
147776099f98SAdrian Hunter		if not self.query_done:
147876099f98SAdrian Hunter			self.Select()
147976099f98SAdrian Hunter			if not self.child_count:
148076099f98SAdrian Hunter				return -1
148176099f98SAdrian Hunter		return self.child_count
148276099f98SAdrian Hunter
148376099f98SAdrian Hunter	def hasChildren(self):
148476099f98SAdrian Hunter		if not self.query_done:
148576099f98SAdrian Hunter			return True
148676099f98SAdrian Hunter		return self.child_count > 0
148776099f98SAdrian Hunter
148876099f98SAdrian Hunter	def getData(self, column):
148976099f98SAdrian Hunter		return self.data[column]
149076099f98SAdrian Hunter
149176099f98SAdrian Hunter# Brance data model root item
149276099f98SAdrian Hunter
149376099f98SAdrian Hunterclass BranchRootItem():
149476099f98SAdrian Hunter
149576099f98SAdrian Hunter	def __init__(self):
149676099f98SAdrian Hunter		self.child_count = 0
149776099f98SAdrian Hunter		self.child_items = []
149876099f98SAdrian Hunter		self.level = 0
149976099f98SAdrian Hunter
150076099f98SAdrian Hunter	def getChildItem(self, row):
150176099f98SAdrian Hunter		return self.child_items[row]
150276099f98SAdrian Hunter
150376099f98SAdrian Hunter	def getParentItem(self):
150476099f98SAdrian Hunter		return None
150576099f98SAdrian Hunter
150676099f98SAdrian Hunter	def getRow(self):
150776099f98SAdrian Hunter		return 0
150876099f98SAdrian Hunter
150976099f98SAdrian Hunter	def childCount(self):
151076099f98SAdrian Hunter		return self.child_count
151176099f98SAdrian Hunter
151276099f98SAdrian Hunter	def hasChildren(self):
151376099f98SAdrian Hunter		return self.child_count > 0
151476099f98SAdrian Hunter
151576099f98SAdrian Hunter	def getData(self, column):
151676099f98SAdrian Hunter		return ""
151776099f98SAdrian Hunter
151876099f98SAdrian Hunter# Branch data preparation
151976099f98SAdrian Hunter
152076099f98SAdrian Hunterdef BranchDataPrep(query):
152176099f98SAdrian Hunter	data = []
152276099f98SAdrian Hunter	for i in xrange(0, 8):
152376099f98SAdrian Hunter		data.append(query.value(i))
152476099f98SAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
152576099f98SAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
152676099f98SAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
152776099f98SAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
152876099f98SAdrian Hunter	return data
152976099f98SAdrian Hunter
15308453c936SAdrian Hunterdef BranchDataPrepWA(query):
15318453c936SAdrian Hunter	data = []
15328453c936SAdrian Hunter	data.append(query.value(0))
15338453c936SAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
15348453c936SAdrian Hunter	data.append("{:>19}".format(query.value(1)))
15358453c936SAdrian Hunter	for i in xrange(2, 8):
15368453c936SAdrian Hunter		data.append(query.value(i))
15378453c936SAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
15388453c936SAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
15398453c936SAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
15408453c936SAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
15418453c936SAdrian Hunter	return data
15428453c936SAdrian Hunter
154376099f98SAdrian Hunter# Branch data model
154476099f98SAdrian Hunter
154576099f98SAdrian Hunterclass BranchModel(TreeModel):
154676099f98SAdrian Hunter
154776099f98SAdrian Hunter	progress = Signal(object)
154876099f98SAdrian Hunter
154976099f98SAdrian Hunter	def __init__(self, glb, event_id, where_clause, parent=None):
1550a448ba23SAdrian Hunter		super(BranchModel, self).__init__(glb, parent)
155176099f98SAdrian Hunter		self.event_id = event_id
155276099f98SAdrian Hunter		self.more = True
155376099f98SAdrian Hunter		self.populated = 0
155476099f98SAdrian Hunter		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
155576099f98SAdrian Hunter			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
155676099f98SAdrian Hunter			" ip, symbols.name, sym_offset, dsos.short_name,"
155776099f98SAdrian Hunter			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
155876099f98SAdrian Hunter			" FROM samples"
155976099f98SAdrian Hunter			" INNER JOIN comms ON comm_id = comms.id"
156076099f98SAdrian Hunter			" INNER JOIN threads ON thread_id = threads.id"
156176099f98SAdrian Hunter			" INNER JOIN branch_types ON branch_type = branch_types.id"
156276099f98SAdrian Hunter			" INNER JOIN symbols ON symbol_id = symbols.id"
156376099f98SAdrian Hunter			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
156476099f98SAdrian Hunter			" INNER JOIN dsos ON samples.dso_id = dsos.id"
156576099f98SAdrian Hunter			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
156676099f98SAdrian Hunter			" WHERE samples.id > $$last_id$$" + where_clause +
156776099f98SAdrian Hunter			" AND evsel_id = " + str(self.event_id) +
156876099f98SAdrian Hunter			" ORDER BY samples.id"
156976099f98SAdrian Hunter			" LIMIT " + str(glb_chunk_sz))
15708453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
15718453c936SAdrian Hunter			prep = BranchDataPrepWA
15728453c936SAdrian Hunter		else:
15738453c936SAdrian Hunter			prep = BranchDataPrep
15748453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
157576099f98SAdrian Hunter		self.fetcher.done.connect(self.Update)
157676099f98SAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
157776099f98SAdrian Hunter
1578a448ba23SAdrian Hunter	def GetRoot(self):
1579a448ba23SAdrian Hunter		return BranchRootItem()
1580a448ba23SAdrian Hunter
158176099f98SAdrian Hunter	def columnCount(self, parent=None):
158276099f98SAdrian Hunter		return 8
158376099f98SAdrian Hunter
158476099f98SAdrian Hunter	def columnHeader(self, column):
158576099f98SAdrian Hunter		return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
158676099f98SAdrian Hunter
158776099f98SAdrian Hunter	def columnFont(self, column):
158876099f98SAdrian Hunter		if column != 7:
158976099f98SAdrian Hunter			return None
159076099f98SAdrian Hunter		return QFont("Monospace")
159176099f98SAdrian Hunter
159276099f98SAdrian Hunter	def DisplayData(self, item, index):
159376099f98SAdrian Hunter		if item.level == 1:
159476099f98SAdrian Hunter			self.FetchIfNeeded(item.row)
159576099f98SAdrian Hunter		return item.getData(index.column())
159676099f98SAdrian Hunter
159776099f98SAdrian Hunter	def AddSample(self, data):
159876099f98SAdrian Hunter		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
159976099f98SAdrian Hunter		self.root.child_items.append(child)
160076099f98SAdrian Hunter		self.populated += 1
160176099f98SAdrian Hunter
160276099f98SAdrian Hunter	def Update(self, fetched):
160376099f98SAdrian Hunter		if not fetched:
160476099f98SAdrian Hunter			self.more = False
160576099f98SAdrian Hunter			self.progress.emit(0)
160676099f98SAdrian Hunter		child_count = self.root.child_count
160776099f98SAdrian Hunter		count = self.populated - child_count
160876099f98SAdrian Hunter		if count > 0:
160976099f98SAdrian Hunter			parent = QModelIndex()
161076099f98SAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
161176099f98SAdrian Hunter			self.insertRows(child_count, count, parent)
161276099f98SAdrian Hunter			self.root.child_count += count
161376099f98SAdrian Hunter			self.endInsertRows()
161476099f98SAdrian Hunter			self.progress.emit(self.root.child_count)
161576099f98SAdrian Hunter
161676099f98SAdrian Hunter	def FetchMoreRecords(self, count):
161776099f98SAdrian Hunter		current = self.root.child_count
161876099f98SAdrian Hunter		if self.more:
161976099f98SAdrian Hunter			self.fetcher.Fetch(count)
162076099f98SAdrian Hunter		else:
162176099f98SAdrian Hunter			self.progress.emit(0)
162276099f98SAdrian Hunter		return current
162376099f98SAdrian Hunter
162476099f98SAdrian Hunter	def HasMoreRecords(self):
162576099f98SAdrian Hunter		return self.more
162676099f98SAdrian Hunter
16270bf0947aSAdrian Hunter# Report Variables
16280bf0947aSAdrian Hunter
16290bf0947aSAdrian Hunterclass ReportVars():
16300bf0947aSAdrian Hunter
1631cd358012SAdrian Hunter	def __init__(self, name = "", where_clause = "", limit = ""):
1632947cc38dSAdrian Hunter		self.name = name
16330bf0947aSAdrian Hunter		self.where_clause = where_clause
1634cd358012SAdrian Hunter		self.limit = limit
16350bf0947aSAdrian Hunter
16360bf0947aSAdrian Hunter	def UniqueId(self):
1637cd358012SAdrian Hunter		return str(self.where_clause + ";" + self.limit)
16380bf0947aSAdrian Hunter
163976099f98SAdrian Hunter# Branch window
164076099f98SAdrian Hunter
164176099f98SAdrian Hunterclass BranchWindow(QMdiSubWindow):
164276099f98SAdrian Hunter
1643947cc38dSAdrian Hunter	def __init__(self, glb, event_id, report_vars, parent=None):
164476099f98SAdrian Hunter		super(BranchWindow, self).__init__(parent)
164576099f98SAdrian Hunter
16460bf0947aSAdrian Hunter		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
164776099f98SAdrian Hunter
16480bf0947aSAdrian Hunter		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
164976099f98SAdrian Hunter
165076099f98SAdrian Hunter		self.view = QTreeView()
165176099f98SAdrian Hunter		self.view.setUniformRowHeights(True)
165276099f98SAdrian Hunter		self.view.setModel(self.model)
165376099f98SAdrian Hunter
165476099f98SAdrian Hunter		self.ResizeColumnsToContents()
165576099f98SAdrian Hunter
165676099f98SAdrian Hunter		self.find_bar = FindBar(self, self, True)
165776099f98SAdrian Hunter
165876099f98SAdrian Hunter		self.finder = ChildDataItemFinder(self.model.root)
165976099f98SAdrian Hunter
166076099f98SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
166176099f98SAdrian Hunter
166276099f98SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
166376099f98SAdrian Hunter
166476099f98SAdrian Hunter		self.setWidget(self.vbox.Widget())
166576099f98SAdrian Hunter
1666947cc38dSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
166776099f98SAdrian Hunter
166876099f98SAdrian Hunter	def ResizeColumnToContents(self, column, n):
166976099f98SAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
167076099f98SAdrian Hunter		# so implement a crude alternative
167176099f98SAdrian Hunter		mm = "MM" if column else "MMMM"
167276099f98SAdrian Hunter		font = self.view.font()
167376099f98SAdrian Hunter		metrics = QFontMetrics(font)
167476099f98SAdrian Hunter		max = 0
167576099f98SAdrian Hunter		for row in xrange(n):
167676099f98SAdrian Hunter			val = self.model.root.child_items[row].data[column]
167776099f98SAdrian Hunter			len = metrics.width(str(val) + mm)
167876099f98SAdrian Hunter			max = len if len > max else max
167976099f98SAdrian Hunter		val = self.model.columnHeader(column)
168076099f98SAdrian Hunter		len = metrics.width(str(val) + mm)
168176099f98SAdrian Hunter		max = len if len > max else max
168276099f98SAdrian Hunter		self.view.setColumnWidth(column, max)
168376099f98SAdrian Hunter
168476099f98SAdrian Hunter	def ResizeColumnsToContents(self):
168576099f98SAdrian Hunter		n = min(self.model.root.child_count, 100)
168676099f98SAdrian Hunter		if n < 1:
168776099f98SAdrian Hunter			# No data yet, so connect a signal to notify when there is
168876099f98SAdrian Hunter			self.model.rowsInserted.connect(self.UpdateColumnWidths)
168976099f98SAdrian Hunter			return
169076099f98SAdrian Hunter		columns = self.model.columnCount()
169176099f98SAdrian Hunter		for i in xrange(columns):
169276099f98SAdrian Hunter			self.ResizeColumnToContents(i, n)
169376099f98SAdrian Hunter
169476099f98SAdrian Hunter	def UpdateColumnWidths(self, *x):
169576099f98SAdrian Hunter		# This only needs to be done once, so disconnect the signal now
169676099f98SAdrian Hunter		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
169776099f98SAdrian Hunter		self.ResizeColumnsToContents()
169876099f98SAdrian Hunter
169976099f98SAdrian Hunter	def Find(self, value, direction, pattern, context):
170076099f98SAdrian Hunter		self.view.setFocus()
170176099f98SAdrian Hunter		self.find_bar.Busy()
170276099f98SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
170376099f98SAdrian Hunter
170476099f98SAdrian Hunter	def FindDone(self, row):
170576099f98SAdrian Hunter		self.find_bar.Idle()
170676099f98SAdrian Hunter		if row >= 0:
170776099f98SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
170876099f98SAdrian Hunter		else:
170976099f98SAdrian Hunter			self.find_bar.NotFound()
171076099f98SAdrian Hunter
17111c3ca1b3SAdrian Hunter# Line edit data item
17121c3ca1b3SAdrian Hunter
17131c3ca1b3SAdrian Hunterclass LineEditDataItem(object):
17141c3ca1b3SAdrian Hunter
1715cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
17161c3ca1b3SAdrian Hunter		self.glb = glb
17171c3ca1b3SAdrian Hunter		self.label = label
17181c3ca1b3SAdrian Hunter		self.placeholder_text = placeholder_text
17191c3ca1b3SAdrian Hunter		self.parent = parent
17201c3ca1b3SAdrian Hunter		self.id = id
17211c3ca1b3SAdrian Hunter
1722cd358012SAdrian Hunter		self.value = default
17231c3ca1b3SAdrian Hunter
1724cd358012SAdrian Hunter		self.widget = QLineEdit(default)
17251c3ca1b3SAdrian Hunter		self.widget.editingFinished.connect(self.Validate)
17261c3ca1b3SAdrian Hunter		self.widget.textChanged.connect(self.Invalidate)
17271c3ca1b3SAdrian Hunter		self.red = False
17281c3ca1b3SAdrian Hunter		self.error = ""
17291c3ca1b3SAdrian Hunter		self.validated = True
17301c3ca1b3SAdrian Hunter
17311c3ca1b3SAdrian Hunter		if placeholder_text:
17321c3ca1b3SAdrian Hunter			self.widget.setPlaceholderText(placeholder_text)
17331c3ca1b3SAdrian Hunter
17341c3ca1b3SAdrian Hunter	def TurnTextRed(self):
17351c3ca1b3SAdrian Hunter		if not self.red:
17361c3ca1b3SAdrian Hunter			palette = QPalette()
17371c3ca1b3SAdrian Hunter			palette.setColor(QPalette.Text,Qt.red)
17381c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
17391c3ca1b3SAdrian Hunter			self.red = True
17401c3ca1b3SAdrian Hunter
17411c3ca1b3SAdrian Hunter	def TurnTextNormal(self):
17421c3ca1b3SAdrian Hunter		if self.red:
17431c3ca1b3SAdrian Hunter			palette = QPalette()
17441c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
17451c3ca1b3SAdrian Hunter			self.red = False
17461c3ca1b3SAdrian Hunter
17471c3ca1b3SAdrian Hunter	def InvalidValue(self, value):
17481c3ca1b3SAdrian Hunter		self.value = ""
17491c3ca1b3SAdrian Hunter		self.TurnTextRed()
17501c3ca1b3SAdrian Hunter		self.error = self.label + " invalid value '" + value + "'"
17511c3ca1b3SAdrian Hunter		self.parent.ShowMessage(self.error)
17521c3ca1b3SAdrian Hunter
17531c3ca1b3SAdrian Hunter	def Invalidate(self):
17541c3ca1b3SAdrian Hunter		self.validated = False
17551c3ca1b3SAdrian Hunter
17561c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
17571c3ca1b3SAdrian Hunter		self.value = input_string.strip()
17581c3ca1b3SAdrian Hunter
17591c3ca1b3SAdrian Hunter	def Validate(self):
17601c3ca1b3SAdrian Hunter		self.validated = True
17611c3ca1b3SAdrian Hunter		self.error = ""
17621c3ca1b3SAdrian Hunter		self.TurnTextNormal()
17631c3ca1b3SAdrian Hunter		self.parent.ClearMessage()
17641c3ca1b3SAdrian Hunter		input_string = self.widget.text()
17651c3ca1b3SAdrian Hunter		if not len(input_string.strip()):
17661c3ca1b3SAdrian Hunter			self.value = ""
17671c3ca1b3SAdrian Hunter			return
17681c3ca1b3SAdrian Hunter		self.DoValidate(input_string)
17691c3ca1b3SAdrian Hunter
17701c3ca1b3SAdrian Hunter	def IsValid(self):
17711c3ca1b3SAdrian Hunter		if not self.validated:
17721c3ca1b3SAdrian Hunter			self.Validate()
17731c3ca1b3SAdrian Hunter		if len(self.error):
17741c3ca1b3SAdrian Hunter			self.parent.ShowMessage(self.error)
17751c3ca1b3SAdrian Hunter			return False
17761c3ca1b3SAdrian Hunter		return True
17771c3ca1b3SAdrian Hunter
17781c3ca1b3SAdrian Hunter	def IsNumber(self, value):
17791c3ca1b3SAdrian Hunter		try:
17801c3ca1b3SAdrian Hunter			x = int(value)
17811c3ca1b3SAdrian Hunter		except:
17821c3ca1b3SAdrian Hunter			x = 0
17831c3ca1b3SAdrian Hunter		return str(x) == value
17841c3ca1b3SAdrian Hunter
17851c3ca1b3SAdrian Hunter# Non-negative integer ranges dialog data item
17861c3ca1b3SAdrian Hunter
17871c3ca1b3SAdrian Hunterclass NonNegativeIntegerRangesDataItem(LineEditDataItem):
17881c3ca1b3SAdrian Hunter
17891c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
17901c3ca1b3SAdrian Hunter		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
17911c3ca1b3SAdrian Hunter
17921c3ca1b3SAdrian Hunter		self.column_name = column_name
17931c3ca1b3SAdrian Hunter
17941c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
17951c3ca1b3SAdrian Hunter		singles = []
17961c3ca1b3SAdrian Hunter		ranges = []
17971c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
17981c3ca1b3SAdrian Hunter			if "-" in value:
17991c3ca1b3SAdrian Hunter				vrange = value.split("-")
18001c3ca1b3SAdrian Hunter				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
18011c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
18021c3ca1b3SAdrian Hunter				ranges.append(vrange)
18031c3ca1b3SAdrian Hunter			else:
18041c3ca1b3SAdrian Hunter				if not self.IsNumber(value):
18051c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
18061c3ca1b3SAdrian Hunter				singles.append(value)
18071c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
18081c3ca1b3SAdrian Hunter		if len(singles):
18091c3ca1b3SAdrian Hunter			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
18101c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
18111c3ca1b3SAdrian Hunter
1812cd358012SAdrian Hunter# Positive integer dialog data item
1813cd358012SAdrian Hunter
1814cd358012SAdrian Hunterclass PositiveIntegerDataItem(LineEditDataItem):
1815cd358012SAdrian Hunter
1816cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1817cd358012SAdrian Hunter		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1818cd358012SAdrian Hunter
1819cd358012SAdrian Hunter	def DoValidate(self, input_string):
1820cd358012SAdrian Hunter		if not self.IsNumber(input_string.strip()):
1821cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1822cd358012SAdrian Hunter		value = int(input_string.strip())
1823cd358012SAdrian Hunter		if value <= 0:
1824cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1825cd358012SAdrian Hunter		self.value = str(value)
1826cd358012SAdrian Hunter
18271c3ca1b3SAdrian Hunter# Dialog data item converted and validated using a SQL table
18281c3ca1b3SAdrian Hunter
18291c3ca1b3SAdrian Hunterclass SQLTableDataItem(LineEditDataItem):
18301c3ca1b3SAdrian Hunter
18311c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
18321c3ca1b3SAdrian Hunter		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
18331c3ca1b3SAdrian Hunter
18341c3ca1b3SAdrian Hunter		self.table_name = table_name
18351c3ca1b3SAdrian Hunter		self.match_column = match_column
18361c3ca1b3SAdrian Hunter		self.column_name1 = column_name1
18371c3ca1b3SAdrian Hunter		self.column_name2 = column_name2
18381c3ca1b3SAdrian Hunter
18391c3ca1b3SAdrian Hunter	def ValueToIds(self, value):
18401c3ca1b3SAdrian Hunter		ids = []
18411c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
18421c3ca1b3SAdrian Hunter		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
18431c3ca1b3SAdrian Hunter		ret = query.exec_(stmt)
18441c3ca1b3SAdrian Hunter		if ret:
18451c3ca1b3SAdrian Hunter			while query.next():
18461c3ca1b3SAdrian Hunter				ids.append(str(query.value(0)))
18471c3ca1b3SAdrian Hunter		return ids
18481c3ca1b3SAdrian Hunter
18491c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
18501c3ca1b3SAdrian Hunter		all_ids = []
18511c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
18521c3ca1b3SAdrian Hunter			ids = self.ValueToIds(value)
18531c3ca1b3SAdrian Hunter			if len(ids):
18541c3ca1b3SAdrian Hunter				all_ids.extend(ids)
18551c3ca1b3SAdrian Hunter			else:
18561c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
18571c3ca1b3SAdrian Hunter		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
18581c3ca1b3SAdrian Hunter		if self.column_name2:
18591c3ca1b3SAdrian Hunter			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
18601c3ca1b3SAdrian Hunter
18611c3ca1b3SAdrian Hunter# Sample time ranges dialog data item converted and validated using 'samples' SQL table
18621c3ca1b3SAdrian Hunter
18631c3ca1b3SAdrian Hunterclass SampleTimeRangesDataItem(LineEditDataItem):
18641c3ca1b3SAdrian Hunter
18651c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
18661c3ca1b3SAdrian Hunter		self.column_name = column_name
18671c3ca1b3SAdrian Hunter
18681c3ca1b3SAdrian Hunter		self.last_id = 0
18691c3ca1b3SAdrian Hunter		self.first_time = 0
18701c3ca1b3SAdrian Hunter		self.last_time = 2 ** 64
18711c3ca1b3SAdrian Hunter
18721c3ca1b3SAdrian Hunter		query = QSqlQuery(glb.db)
18731c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
18741c3ca1b3SAdrian Hunter		if query.next():
18751c3ca1b3SAdrian Hunter			self.last_id = int(query.value(0))
18761c3ca1b3SAdrian Hunter			self.last_time = int(query.value(1))
18771c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
18781c3ca1b3SAdrian Hunter		if query.next():
18791c3ca1b3SAdrian Hunter			self.first_time = int(query.value(0))
18801c3ca1b3SAdrian Hunter		if placeholder_text:
18811c3ca1b3SAdrian Hunter			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
18821c3ca1b3SAdrian Hunter
18831c3ca1b3SAdrian Hunter		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
18841c3ca1b3SAdrian Hunter
18851c3ca1b3SAdrian Hunter	def IdBetween(self, query, lower_id, higher_id, order):
18861c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
18871c3ca1b3SAdrian Hunter		if query.next():
18881c3ca1b3SAdrian Hunter			return True, int(query.value(0))
18891c3ca1b3SAdrian Hunter		else:
18901c3ca1b3SAdrian Hunter			return False, 0
18911c3ca1b3SAdrian Hunter
18921c3ca1b3SAdrian Hunter	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
18931c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
18941c3ca1b3SAdrian Hunter		while True:
18951c3ca1b3SAdrian Hunter			next_id = int((lower_id + higher_id) / 2)
18961c3ca1b3SAdrian Hunter			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
18971c3ca1b3SAdrian Hunter			if not query.next():
18981c3ca1b3SAdrian Hunter				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
18991c3ca1b3SAdrian Hunter				if not ok:
19001c3ca1b3SAdrian Hunter					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
19011c3ca1b3SAdrian Hunter					if not ok:
19021c3ca1b3SAdrian Hunter						return str(higher_id)
19031c3ca1b3SAdrian Hunter				next_id = dbid
19041c3ca1b3SAdrian Hunter				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
19051c3ca1b3SAdrian Hunter			next_time = int(query.value(0))
19061c3ca1b3SAdrian Hunter			if get_floor:
19071c3ca1b3SAdrian Hunter				if target_time > next_time:
19081c3ca1b3SAdrian Hunter					lower_id = next_id
19091c3ca1b3SAdrian Hunter				else:
19101c3ca1b3SAdrian Hunter					higher_id = next_id
19111c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
19121c3ca1b3SAdrian Hunter					return str(higher_id)
19131c3ca1b3SAdrian Hunter			else:
19141c3ca1b3SAdrian Hunter				if target_time >= next_time:
19151c3ca1b3SAdrian Hunter					lower_id = next_id
19161c3ca1b3SAdrian Hunter				else:
19171c3ca1b3SAdrian Hunter					higher_id = next_id
19181c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
19191c3ca1b3SAdrian Hunter					return str(lower_id)
19201c3ca1b3SAdrian Hunter
19211c3ca1b3SAdrian Hunter	def ConvertRelativeTime(self, val):
19221c3ca1b3SAdrian Hunter		mult = 1
19231c3ca1b3SAdrian Hunter		suffix = val[-2:]
19241c3ca1b3SAdrian Hunter		if suffix == "ms":
19251c3ca1b3SAdrian Hunter			mult = 1000000
19261c3ca1b3SAdrian Hunter		elif suffix == "us":
19271c3ca1b3SAdrian Hunter			mult = 1000
19281c3ca1b3SAdrian Hunter		elif suffix == "ns":
19291c3ca1b3SAdrian Hunter			mult = 1
19301c3ca1b3SAdrian Hunter		else:
19311c3ca1b3SAdrian Hunter			return val
19321c3ca1b3SAdrian Hunter		val = val[:-2].strip()
19331c3ca1b3SAdrian Hunter		if not self.IsNumber(val):
19341c3ca1b3SAdrian Hunter			return val
19351c3ca1b3SAdrian Hunter		val = int(val) * mult
19361c3ca1b3SAdrian Hunter		if val >= 0:
19371c3ca1b3SAdrian Hunter			val += self.first_time
19381c3ca1b3SAdrian Hunter		else:
19391c3ca1b3SAdrian Hunter			val += self.last_time
19401c3ca1b3SAdrian Hunter		return str(val)
19411c3ca1b3SAdrian Hunter
19421c3ca1b3SAdrian Hunter	def ConvertTimeRange(self, vrange):
19431c3ca1b3SAdrian Hunter		if vrange[0] == "":
19441c3ca1b3SAdrian Hunter			vrange[0] = str(self.first_time)
19451c3ca1b3SAdrian Hunter		if vrange[1] == "":
19461c3ca1b3SAdrian Hunter			vrange[1] = str(self.last_time)
19471c3ca1b3SAdrian Hunter		vrange[0] = self.ConvertRelativeTime(vrange[0])
19481c3ca1b3SAdrian Hunter		vrange[1] = self.ConvertRelativeTime(vrange[1])
19491c3ca1b3SAdrian Hunter		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
19501c3ca1b3SAdrian Hunter			return False
19511c3ca1b3SAdrian Hunter		beg_range = max(int(vrange[0]), self.first_time)
19521c3ca1b3SAdrian Hunter		end_range = min(int(vrange[1]), self.last_time)
19531c3ca1b3SAdrian Hunter		if beg_range > self.last_time or end_range < self.first_time:
19541c3ca1b3SAdrian Hunter			return False
19551c3ca1b3SAdrian Hunter		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
19561c3ca1b3SAdrian Hunter		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
19571c3ca1b3SAdrian Hunter		return True
19581c3ca1b3SAdrian Hunter
19591c3ca1b3SAdrian Hunter	def AddTimeRange(self, value, ranges):
19601c3ca1b3SAdrian Hunter		n = value.count("-")
19611c3ca1b3SAdrian Hunter		if n == 1:
19621c3ca1b3SAdrian Hunter			pass
19631c3ca1b3SAdrian Hunter		elif n == 2:
19641c3ca1b3SAdrian Hunter			if value.split("-")[1].strip() == "":
19651c3ca1b3SAdrian Hunter				n = 1
19661c3ca1b3SAdrian Hunter		elif n == 3:
19671c3ca1b3SAdrian Hunter			n = 2
19681c3ca1b3SAdrian Hunter		else:
19691c3ca1b3SAdrian Hunter			return False
19701c3ca1b3SAdrian Hunter		pos = findnth(value, "-", n)
19711c3ca1b3SAdrian Hunter		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
19721c3ca1b3SAdrian Hunter		if self.ConvertTimeRange(vrange):
19731c3ca1b3SAdrian Hunter			ranges.append(vrange)
19741c3ca1b3SAdrian Hunter			return True
19751c3ca1b3SAdrian Hunter		return False
19761c3ca1b3SAdrian Hunter
19771c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
19781c3ca1b3SAdrian Hunter		ranges = []
19791c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
19801c3ca1b3SAdrian Hunter			if not self.AddTimeRange(value, ranges):
19811c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
19821c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
19831c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
19841c3ca1b3SAdrian Hunter
19850924cd68SAdrian Hunter# Report Dialog Base
1986210cf1f9SAdrian Hunter
19870924cd68SAdrian Hunterclass ReportDialogBase(QDialog):
1988210cf1f9SAdrian Hunter
19890924cd68SAdrian Hunter	def __init__(self, glb, title, items, partial, parent=None):
19900924cd68SAdrian Hunter		super(ReportDialogBase, self).__init__(parent)
1991210cf1f9SAdrian Hunter
1992210cf1f9SAdrian Hunter		self.glb = glb
1993210cf1f9SAdrian Hunter
19940bf0947aSAdrian Hunter		self.report_vars = ReportVars()
1995210cf1f9SAdrian Hunter
19960924cd68SAdrian Hunter		self.setWindowTitle(title)
1997210cf1f9SAdrian Hunter		self.setMinimumWidth(600)
1998210cf1f9SAdrian Hunter
19991c3ca1b3SAdrian Hunter		self.data_items = [x(glb, self) for x in items]
2000210cf1f9SAdrian Hunter
20010924cd68SAdrian Hunter		self.partial = partial
20020924cd68SAdrian Hunter
2003210cf1f9SAdrian Hunter		self.grid = QGridLayout()
2004210cf1f9SAdrian Hunter
2005210cf1f9SAdrian Hunter		for row in xrange(len(self.data_items)):
2006210cf1f9SAdrian Hunter			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2007210cf1f9SAdrian Hunter			self.grid.addWidget(self.data_items[row].widget, row, 1)
2008210cf1f9SAdrian Hunter
2009210cf1f9SAdrian Hunter		self.status = QLabel()
2010210cf1f9SAdrian Hunter
2011210cf1f9SAdrian Hunter		self.ok_button = QPushButton("Ok", self)
2012210cf1f9SAdrian Hunter		self.ok_button.setDefault(True)
2013210cf1f9SAdrian Hunter		self.ok_button.released.connect(self.Ok)
2014210cf1f9SAdrian Hunter		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2015210cf1f9SAdrian Hunter
2016210cf1f9SAdrian Hunter		self.cancel_button = QPushButton("Cancel", self)
2017210cf1f9SAdrian Hunter		self.cancel_button.released.connect(self.reject)
2018210cf1f9SAdrian Hunter		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2019210cf1f9SAdrian Hunter
2020210cf1f9SAdrian Hunter		self.hbox = QHBoxLayout()
2021210cf1f9SAdrian Hunter		#self.hbox.addStretch()
2022210cf1f9SAdrian Hunter		self.hbox.addWidget(self.status)
2023210cf1f9SAdrian Hunter		self.hbox.addWidget(self.ok_button)
2024210cf1f9SAdrian Hunter		self.hbox.addWidget(self.cancel_button)
2025210cf1f9SAdrian Hunter
2026210cf1f9SAdrian Hunter		self.vbox = QVBoxLayout()
2027210cf1f9SAdrian Hunter		self.vbox.addLayout(self.grid)
2028210cf1f9SAdrian Hunter		self.vbox.addLayout(self.hbox)
2029210cf1f9SAdrian Hunter
2030210cf1f9SAdrian Hunter		self.setLayout(self.vbox);
2031210cf1f9SAdrian Hunter
2032210cf1f9SAdrian Hunter	def Ok(self):
20330bf0947aSAdrian Hunter		vars = self.report_vars
20341c3ca1b3SAdrian Hunter		for d in self.data_items:
20351c3ca1b3SAdrian Hunter			if d.id == "REPORTNAME":
20361c3ca1b3SAdrian Hunter				vars.name = d.value
2037947cc38dSAdrian Hunter		if not vars.name:
2038210cf1f9SAdrian Hunter			self.ShowMessage("Report name is required")
2039210cf1f9SAdrian Hunter			return
2040210cf1f9SAdrian Hunter		for d in self.data_items:
2041210cf1f9SAdrian Hunter			if not d.IsValid():
2042210cf1f9SAdrian Hunter				return
2043210cf1f9SAdrian Hunter		for d in self.data_items[1:]:
2044cd358012SAdrian Hunter			if d.id == "LIMIT":
2045cd358012SAdrian Hunter				vars.limit = d.value
2046cd358012SAdrian Hunter			elif len(d.value):
20470bf0947aSAdrian Hunter				if len(vars.where_clause):
20480bf0947aSAdrian Hunter					vars.where_clause += " AND "
20490bf0947aSAdrian Hunter				vars.where_clause += d.value
20500bf0947aSAdrian Hunter		if len(vars.where_clause):
20510924cd68SAdrian Hunter			if self.partial:
20520bf0947aSAdrian Hunter				vars.where_clause = " AND ( " + vars.where_clause + " ) "
2053210cf1f9SAdrian Hunter			else:
20540bf0947aSAdrian Hunter				vars.where_clause = " WHERE " + vars.where_clause + " "
2055210cf1f9SAdrian Hunter		self.accept()
2056210cf1f9SAdrian Hunter
2057210cf1f9SAdrian Hunter	def ShowMessage(self, msg):
2058210cf1f9SAdrian Hunter		self.status.setText("<font color=#FF0000>" + msg)
2059210cf1f9SAdrian Hunter
2060210cf1f9SAdrian Hunter	def ClearMessage(self):
2061210cf1f9SAdrian Hunter		self.status.setText("")
2062210cf1f9SAdrian Hunter
20630924cd68SAdrian Hunter# Selected branch report creation dialog
20640924cd68SAdrian Hunter
20650924cd68SAdrian Hunterclass SelectedBranchDialog(ReportDialogBase):
20660924cd68SAdrian Hunter
20670924cd68SAdrian Hunter	def __init__(self, glb, parent=None):
20680924cd68SAdrian Hunter		title = "Selected Branches"
20691c3ca1b3SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
20701c3ca1b3SAdrian Hunter			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
20711c3ca1b3SAdrian Hunter			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
20721c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
20731c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
20741c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
20751c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
20761c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
20771c3ca1b3SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
20780924cd68SAdrian Hunter		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
20790924cd68SAdrian Hunter
208076099f98SAdrian Hunter# Event list
208176099f98SAdrian Hunter
208276099f98SAdrian Hunterdef GetEventList(db):
208376099f98SAdrian Hunter	events = []
208476099f98SAdrian Hunter	query = QSqlQuery(db)
208576099f98SAdrian Hunter	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
208676099f98SAdrian Hunter	while query.next():
208776099f98SAdrian Hunter		events.append(query.value(0))
208876099f98SAdrian Hunter	return events
208976099f98SAdrian Hunter
2090655cb952SAdrian Hunter# Is a table selectable
2091655cb952SAdrian Hunter
2092ae8b887cSAdrian Hunterdef IsSelectable(db, table, sql = ""):
2093655cb952SAdrian Hunter	query = QSqlQuery(db)
2094655cb952SAdrian Hunter	try:
2095ae8b887cSAdrian Hunter		QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1")
2096655cb952SAdrian Hunter	except:
2097655cb952SAdrian Hunter		return False
2098655cb952SAdrian Hunter	return True
2099655cb952SAdrian Hunter
21008392b74bSAdrian Hunter# SQL table data model item
21018392b74bSAdrian Hunter
21028392b74bSAdrian Hunterclass SQLTableItem():
21038392b74bSAdrian Hunter
21048392b74bSAdrian Hunter	def __init__(self, row, data):
21058392b74bSAdrian Hunter		self.row = row
21068392b74bSAdrian Hunter		self.data = data
21078392b74bSAdrian Hunter
21088392b74bSAdrian Hunter	def getData(self, column):
21098392b74bSAdrian Hunter		return self.data[column]
21108392b74bSAdrian Hunter
21118392b74bSAdrian Hunter# SQL table data model
21128392b74bSAdrian Hunter
21138392b74bSAdrian Hunterclass SQLTableModel(TableModel):
21148392b74bSAdrian Hunter
21158392b74bSAdrian Hunter	progress = Signal(object)
21168392b74bSAdrian Hunter
21178c90fef9SAdrian Hunter	def __init__(self, glb, sql, column_headers, parent=None):
21188392b74bSAdrian Hunter		super(SQLTableModel, self).__init__(parent)
21198392b74bSAdrian Hunter		self.glb = glb
21208392b74bSAdrian Hunter		self.more = True
21218392b74bSAdrian Hunter		self.populated = 0
21228c90fef9SAdrian Hunter		self.column_headers = column_headers
21238453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
21248392b74bSAdrian Hunter		self.fetcher.done.connect(self.Update)
21258392b74bSAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
21268392b74bSAdrian Hunter
21278392b74bSAdrian Hunter	def DisplayData(self, item, index):
21288392b74bSAdrian Hunter		self.FetchIfNeeded(item.row)
21298392b74bSAdrian Hunter		return item.getData(index.column())
21308392b74bSAdrian Hunter
21318392b74bSAdrian Hunter	def AddSample(self, data):
21328392b74bSAdrian Hunter		child = SQLTableItem(self.populated, data)
21338392b74bSAdrian Hunter		self.child_items.append(child)
21348392b74bSAdrian Hunter		self.populated += 1
21358392b74bSAdrian Hunter
21368392b74bSAdrian Hunter	def Update(self, fetched):
21378392b74bSAdrian Hunter		if not fetched:
21388392b74bSAdrian Hunter			self.more = False
21398392b74bSAdrian Hunter			self.progress.emit(0)
21408392b74bSAdrian Hunter		child_count = self.child_count
21418392b74bSAdrian Hunter		count = self.populated - child_count
21428392b74bSAdrian Hunter		if count > 0:
21438392b74bSAdrian Hunter			parent = QModelIndex()
21448392b74bSAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
21458392b74bSAdrian Hunter			self.insertRows(child_count, count, parent)
21468392b74bSAdrian Hunter			self.child_count += count
21478392b74bSAdrian Hunter			self.endInsertRows()
21488392b74bSAdrian Hunter			self.progress.emit(self.child_count)
21498392b74bSAdrian Hunter
21508392b74bSAdrian Hunter	def FetchMoreRecords(self, count):
21518392b74bSAdrian Hunter		current = self.child_count
21528392b74bSAdrian Hunter		if self.more:
21538392b74bSAdrian Hunter			self.fetcher.Fetch(count)
21548392b74bSAdrian Hunter		else:
21558392b74bSAdrian Hunter			self.progress.emit(0)
21568392b74bSAdrian Hunter		return current
21578392b74bSAdrian Hunter
21588392b74bSAdrian Hunter	def HasMoreRecords(self):
21598392b74bSAdrian Hunter		return self.more
21608392b74bSAdrian Hunter
21618c90fef9SAdrian Hunter	def columnCount(self, parent=None):
21628c90fef9SAdrian Hunter		return len(self.column_headers)
21638c90fef9SAdrian Hunter
21648c90fef9SAdrian Hunter	def columnHeader(self, column):
21658c90fef9SAdrian Hunter		return self.column_headers[column]
21668c90fef9SAdrian Hunter
21678453c936SAdrian Hunter	def SQLTableDataPrep(self, query, count):
21688453c936SAdrian Hunter		data = []
21698453c936SAdrian Hunter		for i in xrange(count):
21708453c936SAdrian Hunter			data.append(query.value(i))
21718453c936SAdrian Hunter		return data
21728453c936SAdrian Hunter
21738392b74bSAdrian Hunter# SQL automatic table data model
21748392b74bSAdrian Hunter
21758392b74bSAdrian Hunterclass SQLAutoTableModel(SQLTableModel):
21768392b74bSAdrian Hunter
21778392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
21788392b74bSAdrian Hunter		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
21798392b74bSAdrian Hunter		if table_name == "comm_threads_view":
21808392b74bSAdrian Hunter			# For now, comm_threads_view has no id column
21818392b74bSAdrian Hunter			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
21828c90fef9SAdrian Hunter		column_headers = []
21838392b74bSAdrian Hunter		query = QSqlQuery(glb.db)
21848392b74bSAdrian Hunter		if glb.dbref.is_sqlite3:
21858392b74bSAdrian Hunter			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
21868392b74bSAdrian Hunter			while query.next():
21878c90fef9SAdrian Hunter				column_headers.append(query.value(1))
21888392b74bSAdrian Hunter			if table_name == "sqlite_master":
21898392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
21908392b74bSAdrian Hunter		else:
21918392b74bSAdrian Hunter			if table_name[:19] == "information_schema.":
21928392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
21938392b74bSAdrian Hunter				select_table_name = table_name[19:]
21948392b74bSAdrian Hunter				schema = "information_schema"
21958392b74bSAdrian Hunter			else:
21968392b74bSAdrian Hunter				select_table_name = table_name
21978392b74bSAdrian Hunter				schema = "public"
21988392b74bSAdrian Hunter			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
21998392b74bSAdrian Hunter			while query.next():
22008c90fef9SAdrian Hunter				column_headers.append(query.value(0))
22018453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
22028453c936SAdrian Hunter			if table_name == "samples_view":
22038453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_view_DataPrep
22048453c936SAdrian Hunter			if table_name == "samples":
22058453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_DataPrep
22068c90fef9SAdrian Hunter		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
22078392b74bSAdrian Hunter
22088453c936SAdrian Hunter	def samples_view_DataPrep(self, query, count):
22098453c936SAdrian Hunter		data = []
22108453c936SAdrian Hunter		data.append(query.value(0))
22118453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
22128453c936SAdrian Hunter		data.append("{:>19}".format(query.value(1)))
22138453c936SAdrian Hunter		for i in xrange(2, count):
22148453c936SAdrian Hunter			data.append(query.value(i))
22158453c936SAdrian Hunter		return data
22168453c936SAdrian Hunter
22178453c936SAdrian Hunter	def samples_DataPrep(self, query, count):
22188453c936SAdrian Hunter		data = []
22198453c936SAdrian Hunter		for i in xrange(9):
22208453c936SAdrian Hunter			data.append(query.value(i))
22218453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
22228453c936SAdrian Hunter		data.append("{:>19}".format(query.value(9)))
22238453c936SAdrian Hunter		for i in xrange(10, count):
22248453c936SAdrian Hunter			data.append(query.value(i))
22258453c936SAdrian Hunter		return data
22268453c936SAdrian Hunter
22278392b74bSAdrian Hunter# Base class for custom ResizeColumnsToContents
22288392b74bSAdrian Hunter
22298392b74bSAdrian Hunterclass ResizeColumnsToContentsBase(QObject):
22308392b74bSAdrian Hunter
22318392b74bSAdrian Hunter	def __init__(self, parent=None):
22328392b74bSAdrian Hunter		super(ResizeColumnsToContentsBase, self).__init__(parent)
22338392b74bSAdrian Hunter
22348392b74bSAdrian Hunter	def ResizeColumnToContents(self, column, n):
22358392b74bSAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
22368392b74bSAdrian Hunter		# so implement a crude alternative
22378392b74bSAdrian Hunter		font = self.view.font()
22388392b74bSAdrian Hunter		metrics = QFontMetrics(font)
22398392b74bSAdrian Hunter		max = 0
22408392b74bSAdrian Hunter		for row in xrange(n):
22418392b74bSAdrian Hunter			val = self.data_model.child_items[row].data[column]
22428392b74bSAdrian Hunter			len = metrics.width(str(val) + "MM")
22438392b74bSAdrian Hunter			max = len if len > max else max
22448392b74bSAdrian Hunter		val = self.data_model.columnHeader(column)
22458392b74bSAdrian Hunter		len = metrics.width(str(val) + "MM")
22468392b74bSAdrian Hunter		max = len if len > max else max
22478392b74bSAdrian Hunter		self.view.setColumnWidth(column, max)
22488392b74bSAdrian Hunter
22498392b74bSAdrian Hunter	def ResizeColumnsToContents(self):
22508392b74bSAdrian Hunter		n = min(self.data_model.child_count, 100)
22518392b74bSAdrian Hunter		if n < 1:
22528392b74bSAdrian Hunter			# No data yet, so connect a signal to notify when there is
22538392b74bSAdrian Hunter			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
22548392b74bSAdrian Hunter			return
22558392b74bSAdrian Hunter		columns = self.data_model.columnCount()
22568392b74bSAdrian Hunter		for i in xrange(columns):
22578392b74bSAdrian Hunter			self.ResizeColumnToContents(i, n)
22588392b74bSAdrian Hunter
22598392b74bSAdrian Hunter	def UpdateColumnWidths(self, *x):
22608392b74bSAdrian Hunter		# This only needs to be done once, so disconnect the signal now
22618392b74bSAdrian Hunter		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
22628392b74bSAdrian Hunter		self.ResizeColumnsToContents()
22638392b74bSAdrian Hunter
22648392b74bSAdrian Hunter# Table window
22658392b74bSAdrian Hunter
22668392b74bSAdrian Hunterclass TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
22678392b74bSAdrian Hunter
22688392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
22698392b74bSAdrian Hunter		super(TableWindow, self).__init__(parent)
22708392b74bSAdrian Hunter
22718392b74bSAdrian Hunter		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
22728392b74bSAdrian Hunter
22738392b74bSAdrian Hunter		self.model = QSortFilterProxyModel()
22748392b74bSAdrian Hunter		self.model.setSourceModel(self.data_model)
22758392b74bSAdrian Hunter
22768392b74bSAdrian Hunter		self.view = QTableView()
22778392b74bSAdrian Hunter		self.view.setModel(self.model)
22788392b74bSAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
22798392b74bSAdrian Hunter		self.view.verticalHeader().setVisible(False)
22808392b74bSAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
22818392b74bSAdrian Hunter		self.view.setSortingEnabled(True)
22828392b74bSAdrian Hunter
22838392b74bSAdrian Hunter		self.ResizeColumnsToContents()
22848392b74bSAdrian Hunter
22858392b74bSAdrian Hunter		self.find_bar = FindBar(self, self, True)
22868392b74bSAdrian Hunter
22878392b74bSAdrian Hunter		self.finder = ChildDataItemFinder(self.data_model)
22888392b74bSAdrian Hunter
22898392b74bSAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
22908392b74bSAdrian Hunter
22918392b74bSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
22928392b74bSAdrian Hunter
22938392b74bSAdrian Hunter		self.setWidget(self.vbox.Widget())
22948392b74bSAdrian Hunter
22958392b74bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
22968392b74bSAdrian Hunter
22978392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context):
22988392b74bSAdrian Hunter		self.view.setFocus()
22998392b74bSAdrian Hunter		self.find_bar.Busy()
23008392b74bSAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
23018392b74bSAdrian Hunter
23028392b74bSAdrian Hunter	def FindDone(self, row):
23038392b74bSAdrian Hunter		self.find_bar.Idle()
23048392b74bSAdrian Hunter		if row >= 0:
230535fa1ceeSAdrian Hunter			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
23068392b74bSAdrian Hunter		else:
23078392b74bSAdrian Hunter			self.find_bar.NotFound()
23088392b74bSAdrian Hunter
23098392b74bSAdrian Hunter# Table list
23108392b74bSAdrian Hunter
23118392b74bSAdrian Hunterdef GetTableList(glb):
23128392b74bSAdrian Hunter	tables = []
23138392b74bSAdrian Hunter	query = QSqlQuery(glb.db)
23148392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
23158392b74bSAdrian Hunter		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
23168392b74bSAdrian Hunter	else:
23178392b74bSAdrian Hunter		QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
23188392b74bSAdrian Hunter	while query.next():
23198392b74bSAdrian Hunter		tables.append(query.value(0))
23208392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
23218392b74bSAdrian Hunter		tables.append("sqlite_master")
23228392b74bSAdrian Hunter	else:
23238392b74bSAdrian Hunter		tables.append("information_schema.tables")
23248392b74bSAdrian Hunter		tables.append("information_schema.views")
23258392b74bSAdrian Hunter		tables.append("information_schema.columns")
23268392b74bSAdrian Hunter	return tables
23278392b74bSAdrian Hunter
2328cd358012SAdrian Hunter# Top Calls data model
2329cd358012SAdrian Hunter
2330cd358012SAdrian Hunterclass TopCallsModel(SQLTableModel):
2331cd358012SAdrian Hunter
2332cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2333cd358012SAdrian Hunter		text = ""
2334cd358012SAdrian Hunter		if not glb.dbref.is_sqlite3:
2335cd358012SAdrian Hunter			text = "::text"
2336cd358012SAdrian Hunter		limit = ""
2337cd358012SAdrian Hunter		if len(report_vars.limit):
2338cd358012SAdrian Hunter			limit = " LIMIT " + report_vars.limit
2339cd358012SAdrian Hunter		sql = ("SELECT comm, pid, tid, name,"
2340cd358012SAdrian Hunter			" CASE"
2341cd358012SAdrian Hunter			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2342cd358012SAdrian Hunter			" ELSE short_name"
2343cd358012SAdrian Hunter			" END AS dso,"
2344cd358012SAdrian Hunter			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2345cd358012SAdrian Hunter			" CASE"
2346cd358012SAdrian Hunter			" WHEN (calls.flags = 1) THEN 'no call'" + text +
2347cd358012SAdrian Hunter			" WHEN (calls.flags = 2) THEN 'no return'" + text +
2348cd358012SAdrian Hunter			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2349cd358012SAdrian Hunter			" ELSE ''" + text +
2350cd358012SAdrian Hunter			" END AS flags"
2351cd358012SAdrian Hunter			" FROM calls"
2352cd358012SAdrian Hunter			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2353cd358012SAdrian Hunter			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2354cd358012SAdrian Hunter			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
2355cd358012SAdrian Hunter			" INNER JOIN comms ON calls.comm_id = comms.id"
2356cd358012SAdrian Hunter			" INNER JOIN threads ON calls.thread_id = threads.id" +
2357cd358012SAdrian Hunter			report_vars.where_clause +
2358cd358012SAdrian Hunter			" ORDER BY elapsed_time DESC" +
2359cd358012SAdrian Hunter			limit
2360cd358012SAdrian Hunter			)
2361cd358012SAdrian Hunter		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2362cd358012SAdrian Hunter		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2363cd358012SAdrian Hunter		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2364cd358012SAdrian Hunter
2365cd358012SAdrian Hunter	def columnAlignment(self, column):
2366cd358012SAdrian Hunter		return self.alignment[column]
2367cd358012SAdrian Hunter
2368cd358012SAdrian Hunter# Top Calls report creation dialog
2369cd358012SAdrian Hunter
2370cd358012SAdrian Hunterclass TopCallsDialog(ReportDialogBase):
2371cd358012SAdrian Hunter
2372cd358012SAdrian Hunter	def __init__(self, glb, parent=None):
2373cd358012SAdrian Hunter		title = "Top Calls by Elapsed Time"
2374cd358012SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2375cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2376cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2377cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2378cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2379cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2380cd358012SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2381cd358012SAdrian Hunter			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2382cd358012SAdrian Hunter		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2383cd358012SAdrian Hunter
2384cd358012SAdrian Hunter# Top Calls window
2385cd358012SAdrian Hunter
2386cd358012SAdrian Hunterclass TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2387cd358012SAdrian Hunter
2388cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2389cd358012SAdrian Hunter		super(TopCallsWindow, self).__init__(parent)
2390cd358012SAdrian Hunter
2391cd358012SAdrian Hunter		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2392cd358012SAdrian Hunter		self.model = self.data_model
2393cd358012SAdrian Hunter
2394cd358012SAdrian Hunter		self.view = QTableView()
2395cd358012SAdrian Hunter		self.view.setModel(self.model)
2396cd358012SAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2397cd358012SAdrian Hunter		self.view.verticalHeader().setVisible(False)
2398cd358012SAdrian Hunter
2399cd358012SAdrian Hunter		self.ResizeColumnsToContents()
2400cd358012SAdrian Hunter
2401cd358012SAdrian Hunter		self.find_bar = FindBar(self, self, True)
2402cd358012SAdrian Hunter
2403cd358012SAdrian Hunter		self.finder = ChildDataItemFinder(self.model)
2404cd358012SAdrian Hunter
2405cd358012SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2406cd358012SAdrian Hunter
2407cd358012SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2408cd358012SAdrian Hunter
2409cd358012SAdrian Hunter		self.setWidget(self.vbox.Widget())
2410cd358012SAdrian Hunter
2411cd358012SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2412cd358012SAdrian Hunter
2413cd358012SAdrian Hunter	def Find(self, value, direction, pattern, context):
2414cd358012SAdrian Hunter		self.view.setFocus()
2415cd358012SAdrian Hunter		self.find_bar.Busy()
2416cd358012SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
2417cd358012SAdrian Hunter
2418cd358012SAdrian Hunter	def FindDone(self, row):
2419cd358012SAdrian Hunter		self.find_bar.Idle()
2420cd358012SAdrian Hunter		if row >= 0:
2421cd358012SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2422cd358012SAdrian Hunter		else:
2423cd358012SAdrian Hunter			self.find_bar.NotFound()
2424cd358012SAdrian Hunter
24251beb5c7bSAdrian Hunter# Action Definition
24261beb5c7bSAdrian Hunter
24271beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None):
24281beb5c7bSAdrian Hunter	action = QAction(label, parent)
24291beb5c7bSAdrian Hunter	if shortcut != None:
24301beb5c7bSAdrian Hunter		action.setShortcuts(shortcut)
24311beb5c7bSAdrian Hunter	action.setStatusTip(tip)
24321beb5c7bSAdrian Hunter	action.triggered.connect(callback)
24331beb5c7bSAdrian Hunter	return action
24341beb5c7bSAdrian Hunter
24351beb5c7bSAdrian Hunter# Typical application actions
24361beb5c7bSAdrian Hunter
24371beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None):
24381beb5c7bSAdrian Hunter	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
24391beb5c7bSAdrian Hunter
24401beb5c7bSAdrian Hunter# Typical MDI actions
24411beb5c7bSAdrian Hunter
24421beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area):
24431beb5c7bSAdrian Hunter	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
24441beb5c7bSAdrian Hunter
24451beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area):
24461beb5c7bSAdrian Hunter	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
24471beb5c7bSAdrian Hunter
24481beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area):
24491beb5c7bSAdrian Hunter	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
24501beb5c7bSAdrian Hunter
24511beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area):
24521beb5c7bSAdrian Hunter	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
24531beb5c7bSAdrian Hunter
24541beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area):
24551beb5c7bSAdrian Hunter	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
24561beb5c7bSAdrian Hunter
24571beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area):
24581beb5c7bSAdrian Hunter	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
24591beb5c7bSAdrian Hunter
24601beb5c7bSAdrian Hunter# Typical MDI window menu
24611beb5c7bSAdrian Hunter
24621beb5c7bSAdrian Hunterclass WindowMenu():
24631beb5c7bSAdrian Hunter
24641beb5c7bSAdrian Hunter	def __init__(self, mdi_area, menu):
24651beb5c7bSAdrian Hunter		self.mdi_area = mdi_area
24661beb5c7bSAdrian Hunter		self.window_menu = menu.addMenu("&Windows")
24671beb5c7bSAdrian Hunter		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
24681beb5c7bSAdrian Hunter		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
24691beb5c7bSAdrian Hunter		self.tile_windows = CreateTileWindowsAction(mdi_area)
24701beb5c7bSAdrian Hunter		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
24711beb5c7bSAdrian Hunter		self.next_window = CreateNextWindowAction(mdi_area)
24721beb5c7bSAdrian Hunter		self.previous_window = CreatePreviousWindowAction(mdi_area)
24731beb5c7bSAdrian Hunter		self.window_menu.aboutToShow.connect(self.Update)
24741beb5c7bSAdrian Hunter
24751beb5c7bSAdrian Hunter	def Update(self):
24761beb5c7bSAdrian Hunter		self.window_menu.clear()
24771beb5c7bSAdrian Hunter		sub_window_count = len(self.mdi_area.subWindowList())
24781beb5c7bSAdrian Hunter		have_sub_windows = sub_window_count != 0
24791beb5c7bSAdrian Hunter		self.close_active_window.setEnabled(have_sub_windows)
24801beb5c7bSAdrian Hunter		self.close_all_windows.setEnabled(have_sub_windows)
24811beb5c7bSAdrian Hunter		self.tile_windows.setEnabled(have_sub_windows)
24821beb5c7bSAdrian Hunter		self.cascade_windows.setEnabled(have_sub_windows)
24831beb5c7bSAdrian Hunter		self.next_window.setEnabled(have_sub_windows)
24841beb5c7bSAdrian Hunter		self.previous_window.setEnabled(have_sub_windows)
24851beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_active_window)
24861beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_all_windows)
24871beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
24881beb5c7bSAdrian Hunter		self.window_menu.addAction(self.tile_windows)
24891beb5c7bSAdrian Hunter		self.window_menu.addAction(self.cascade_windows)
24901beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
24911beb5c7bSAdrian Hunter		self.window_menu.addAction(self.next_window)
24921beb5c7bSAdrian Hunter		self.window_menu.addAction(self.previous_window)
24931beb5c7bSAdrian Hunter		if sub_window_count == 0:
24941beb5c7bSAdrian Hunter			return
24951beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
24961beb5c7bSAdrian Hunter		nr = 1
24971beb5c7bSAdrian Hunter		for sub_window in self.mdi_area.subWindowList():
24981beb5c7bSAdrian Hunter			label = str(nr) + " " + sub_window.name
24991beb5c7bSAdrian Hunter			if nr < 10:
25001beb5c7bSAdrian Hunter				label = "&" + label
25011beb5c7bSAdrian Hunter			action = self.window_menu.addAction(label)
25021beb5c7bSAdrian Hunter			action.setCheckable(True)
25031beb5c7bSAdrian Hunter			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
25041beb5c7bSAdrian Hunter			action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
25051beb5c7bSAdrian Hunter			self.window_menu.addAction(action)
25061beb5c7bSAdrian Hunter			nr += 1
25071beb5c7bSAdrian Hunter
25081beb5c7bSAdrian Hunter	def setActiveSubWindow(self, nr):
25091beb5c7bSAdrian Hunter		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
25101beb5c7bSAdrian Hunter
251165b24292SAdrian Hunter# Help text
251265b24292SAdrian Hunter
251365b24292SAdrian Hunterglb_help_text = """
251465b24292SAdrian Hunter<h1>Contents</h1>
251565b24292SAdrian Hunter<style>
251665b24292SAdrian Hunterp.c1 {
251765b24292SAdrian Hunter    text-indent: 40px;
251865b24292SAdrian Hunter}
251965b24292SAdrian Hunterp.c2 {
252065b24292SAdrian Hunter    text-indent: 80px;
252165b24292SAdrian Hunter}
252265b24292SAdrian Hunter}
252365b24292SAdrian Hunter</style>
252465b24292SAdrian Hunter<p class=c1><a href=#reports>1. Reports</a></p>
252565b24292SAdrian Hunter<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2526ae8b887cSAdrian Hunter<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2527ae8b887cSAdrian Hunter<p class=c2><a href=#allbranches>1.3 All branches</a></p>
2528ae8b887cSAdrian Hunter<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2529ae8b887cSAdrian Hunter<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
253065b24292SAdrian Hunter<p class=c1><a href=#tables>2. Tables</a></p>
253165b24292SAdrian Hunter<h1 id=reports>1. Reports</h1>
253265b24292SAdrian Hunter<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
253365b24292SAdrian HunterThe result is a GUI window with a tree representing a context-sensitive
253465b24292SAdrian Huntercall-graph. Expanding a couple of levels of the tree and adjusting column
253565b24292SAdrian Hunterwidths to suit will display something like:
253665b24292SAdrian Hunter<pre>
253765b24292SAdrian Hunter                                         Call Graph: pt_example
253865b24292SAdrian HunterCall Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
253965b24292SAdrian Hunterv- ls
254065b24292SAdrian Hunter    v- 2638:2638
254165b24292SAdrian Hunter        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
254265b24292SAdrian Hunter          |- unknown               unknown       1        13198     0.1              1              0.0
254365b24292SAdrian Hunter          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
254465b24292SAdrian Hunter          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
254565b24292SAdrian Hunter          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
254665b24292SAdrian Hunter             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
254765b24292SAdrian Hunter             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
254865b24292SAdrian Hunter             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
254965b24292SAdrian Hunter             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
255065b24292SAdrian Hunter             v- main               ls            1      8182043    99.6         180254             99.9
255165b24292SAdrian Hunter</pre>
255265b24292SAdrian Hunter<h3>Points to note:</h3>
255365b24292SAdrian Hunter<ul>
255465b24292SAdrian Hunter<li>The top level is a command name (comm)</li>
255565b24292SAdrian Hunter<li>The next level is a thread (pid:tid)</li>
255665b24292SAdrian Hunter<li>Subsequent levels are functions</li>
255765b24292SAdrian Hunter<li>'Count' is the number of calls</li>
255865b24292SAdrian Hunter<li>'Time' is the elapsed time until the function returns</li>
255965b24292SAdrian Hunter<li>Percentages are relative to the level above</li>
256065b24292SAdrian Hunter<li>'Branch Count' is the total number of branches for that function and all functions that it calls
256165b24292SAdrian Hunter</ul>
256265b24292SAdrian Hunter<h3>Find</h3>
256365b24292SAdrian HunterCtrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
256465b24292SAdrian HunterThe pattern matching symbols are ? for any character and * for zero or more characters.
2565ae8b887cSAdrian Hunter<h2 id=calltree>1.2 Call Tree</h2>
2566ae8b887cSAdrian HunterThe Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2567ae8b887cSAdrian HunterAlso the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2568ae8b887cSAdrian Hunter<h2 id=allbranches>1.3 All branches</h2>
256965b24292SAdrian HunterThe All branches report displays all branches in chronological order.
257065b24292SAdrian HunterNot all data is fetched immediately. More records can be fetched using the Fetch bar provided.
257165b24292SAdrian Hunter<h3>Disassembly</h3>
257265b24292SAdrian HunterOpen a branch to display disassembly. This only works if:
257365b24292SAdrian Hunter<ol>
257465b24292SAdrian Hunter<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
257565b24292SAdrian Hunter<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
257665b24292SAdrian HunterThe default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
257765b24292SAdrian HunterOne exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
257865b24292SAdrian Hunteror alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
257965b24292SAdrian Hunter</ol>
258065b24292SAdrian Hunter<h4 id=xed>Intel XED Setup</h4>
258165b24292SAdrian HunterTo use Intel XED, libxed.so must be present.  To build and install libxed.so:
258265b24292SAdrian Hunter<pre>
258365b24292SAdrian Huntergit clone https://github.com/intelxed/mbuild.git mbuild
258465b24292SAdrian Huntergit clone https://github.com/intelxed/xed
258565b24292SAdrian Huntercd xed
258665b24292SAdrian Hunter./mfile.py --share
258765b24292SAdrian Huntersudo ./mfile.py --prefix=/usr/local install
258865b24292SAdrian Huntersudo ldconfig
258965b24292SAdrian Hunter</pre>
259065b24292SAdrian Hunter<h3>Find</h3>
259165b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
259265b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
259365b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
2594ae8b887cSAdrian Hunter<h2 id=selectedbranches>1.4 Selected branches</h2>
259565b24292SAdrian HunterThis is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
259665b24292SAdrian Hunterby various selection criteria. A dialog box displays available criteria which are AND'ed together.
2597ae8b887cSAdrian Hunter<h3>1.4.1 Time ranges</h3>
259865b24292SAdrian HunterThe time ranges hint text shows the total time range. Relative time ranges can also be entered in
259965b24292SAdrian Hunterms, us or ns. Also, negative values are relative to the end of trace.  Examples:
260065b24292SAdrian Hunter<pre>
260165b24292SAdrian Hunter	81073085947329-81073085958238	From 81073085947329 to 81073085958238
260265b24292SAdrian Hunter	100us-200us		From 100us to 200us
260365b24292SAdrian Hunter	10ms-			From 10ms to the end
260465b24292SAdrian Hunter	-100ns			The first 100ns
260565b24292SAdrian Hunter	-10ms-			The last 10ms
260665b24292SAdrian Hunter</pre>
260765b24292SAdrian HunterN.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2608ae8b887cSAdrian Hunter<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2609cd358012SAdrian HunterThe Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
2610cd358012SAdrian HunterThe data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2611cd358012SAdrian HunterIf not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
261265b24292SAdrian Hunter<h1 id=tables>2. Tables</h1>
261365b24292SAdrian HunterThe Tables menu shows all tables and views in the database. Most tables have an associated view
261465b24292SAdrian Hunterwhich displays the information in a more friendly way. Not all data for large tables is fetched
261565b24292SAdrian Hunterimmediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
261665b24292SAdrian Hunterbut that can be slow for large tables.
261765b24292SAdrian Hunter<p>There are also tables of database meta-information.
261865b24292SAdrian HunterFor SQLite3 databases, the sqlite_master table is included.
261965b24292SAdrian HunterFor PostgreSQL databases, information_schema.tables/views/columns are included.
262065b24292SAdrian Hunter<h3>Find</h3>
262165b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
262265b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
262365b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
262435fa1ceeSAdrian Hunter<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
262535fa1ceeSAdrian Hunterwill go to the next/previous result in id order, instead of display order.
262665b24292SAdrian Hunter"""
262765b24292SAdrian Hunter
262865b24292SAdrian Hunter# Help window
262965b24292SAdrian Hunter
263065b24292SAdrian Hunterclass HelpWindow(QMdiSubWindow):
263165b24292SAdrian Hunter
263265b24292SAdrian Hunter	def __init__(self, glb, parent=None):
263365b24292SAdrian Hunter		super(HelpWindow, self).__init__(parent)
263465b24292SAdrian Hunter
263565b24292SAdrian Hunter		self.text = QTextBrowser()
263665b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
263765b24292SAdrian Hunter		self.text.setReadOnly(True)
263865b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
263965b24292SAdrian Hunter
264065b24292SAdrian Hunter		self.setWidget(self.text)
264165b24292SAdrian Hunter
264265b24292SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
264365b24292SAdrian Hunter
264465b24292SAdrian Hunter# Main window that only displays the help text
264565b24292SAdrian Hunter
264665b24292SAdrian Hunterclass HelpOnlyWindow(QMainWindow):
264765b24292SAdrian Hunter
264865b24292SAdrian Hunter	def __init__(self, parent=None):
264965b24292SAdrian Hunter		super(HelpOnlyWindow, self).__init__(parent)
265065b24292SAdrian Hunter
265165b24292SAdrian Hunter		self.setMinimumSize(200, 100)
265265b24292SAdrian Hunter		self.resize(800, 600)
265365b24292SAdrian Hunter		self.setWindowTitle("Exported SQL Viewer Help")
265465b24292SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
265565b24292SAdrian Hunter
265665b24292SAdrian Hunter		self.text = QTextBrowser()
265765b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
265865b24292SAdrian Hunter		self.text.setReadOnly(True)
265965b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
266065b24292SAdrian Hunter
266165b24292SAdrian Hunter		self.setCentralWidget(self.text)
266265b24292SAdrian Hunter
266382f68e28SAdrian Hunter# Font resize
266482f68e28SAdrian Hunter
266582f68e28SAdrian Hunterdef ResizeFont(widget, diff):
266682f68e28SAdrian Hunter	font = widget.font()
266782f68e28SAdrian Hunter	sz = font.pointSize()
266882f68e28SAdrian Hunter	font.setPointSize(sz + diff)
266982f68e28SAdrian Hunter	widget.setFont(font)
267082f68e28SAdrian Hunter
267182f68e28SAdrian Hunterdef ShrinkFont(widget):
267282f68e28SAdrian Hunter	ResizeFont(widget, -1)
267382f68e28SAdrian Hunter
267482f68e28SAdrian Hunterdef EnlargeFont(widget):
267582f68e28SAdrian Hunter	ResizeFont(widget, 1)
267682f68e28SAdrian Hunter
26771beb5c7bSAdrian Hunter# Unique name for sub-windows
26781beb5c7bSAdrian Hunter
26791beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr):
26801beb5c7bSAdrian Hunter	if nr > 1:
26811beb5c7bSAdrian Hunter		name += " <" + str(nr) + ">"
26821beb5c7bSAdrian Hunter	return name
26831beb5c7bSAdrian Hunter
26841beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name):
26851beb5c7bSAdrian Hunter	nr = 1
26861beb5c7bSAdrian Hunter	while True:
26871beb5c7bSAdrian Hunter		unique_name = NumberedWindowName(name, nr)
26881beb5c7bSAdrian Hunter		ok = True
26891beb5c7bSAdrian Hunter		for sub_window in mdi_area.subWindowList():
26901beb5c7bSAdrian Hunter			if sub_window.name == unique_name:
26911beb5c7bSAdrian Hunter				ok = False
26921beb5c7bSAdrian Hunter				break
26931beb5c7bSAdrian Hunter		if ok:
26941beb5c7bSAdrian Hunter			return unique_name
26951beb5c7bSAdrian Hunter		nr += 1
26961beb5c7bSAdrian Hunter
26971beb5c7bSAdrian Hunter# Add a sub-window
26981beb5c7bSAdrian Hunter
26991beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name):
27001beb5c7bSAdrian Hunter	unique_name = UniqueSubWindowName(mdi_area, name)
27011beb5c7bSAdrian Hunter	sub_window.setMinimumSize(200, 100)
27021beb5c7bSAdrian Hunter	sub_window.resize(800, 600)
27031beb5c7bSAdrian Hunter	sub_window.setWindowTitle(unique_name)
27041beb5c7bSAdrian Hunter	sub_window.setAttribute(Qt.WA_DeleteOnClose)
27051beb5c7bSAdrian Hunter	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
27061beb5c7bSAdrian Hunter	sub_window.name = unique_name
27071beb5c7bSAdrian Hunter	mdi_area.addSubWindow(sub_window)
27081beb5c7bSAdrian Hunter	sub_window.show()
27091beb5c7bSAdrian Hunter
2710031c2a00SAdrian Hunter# Main window
2711031c2a00SAdrian Hunter
2712031c2a00SAdrian Hunterclass MainWindow(QMainWindow):
2713031c2a00SAdrian Hunter
2714031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
2715031c2a00SAdrian Hunter		super(MainWindow, self).__init__(parent)
2716031c2a00SAdrian Hunter
2717031c2a00SAdrian Hunter		self.glb = glb
2718031c2a00SAdrian Hunter
27191beb5c7bSAdrian Hunter		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
2720031c2a00SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
2721031c2a00SAdrian Hunter		self.setMinimumSize(200, 100)
2722031c2a00SAdrian Hunter
27231beb5c7bSAdrian Hunter		self.mdi_area = QMdiArea()
27241beb5c7bSAdrian Hunter		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
27251beb5c7bSAdrian Hunter		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2726031c2a00SAdrian Hunter
27271beb5c7bSAdrian Hunter		self.setCentralWidget(self.mdi_area)
2728031c2a00SAdrian Hunter
27291beb5c7bSAdrian Hunter		menu = self.menuBar()
2730031c2a00SAdrian Hunter
27311beb5c7bSAdrian Hunter		file_menu = menu.addMenu("&File")
27321beb5c7bSAdrian Hunter		file_menu.addAction(CreateExitAction(glb.app, self))
27331beb5c7bSAdrian Hunter
2734ebd70c7dSAdrian Hunter		edit_menu = menu.addMenu("&Edit")
2735ebd70c7dSAdrian Hunter		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
27368392b74bSAdrian Hunter		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
273782f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
273882f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
2739ebd70c7dSAdrian Hunter
27401beb5c7bSAdrian Hunter		reports_menu = menu.addMenu("&Reports")
2741655cb952SAdrian Hunter		if IsSelectable(glb.db, "calls"):
27421beb5c7bSAdrian Hunter			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
27431beb5c7bSAdrian Hunter
2744ae8b887cSAdrian Hunter		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
2745ae8b887cSAdrian Hunter			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
2746ae8b887cSAdrian Hunter
274776099f98SAdrian Hunter		self.EventMenu(GetEventList(glb.db), reports_menu)
274876099f98SAdrian Hunter
2749cd358012SAdrian Hunter		if IsSelectable(glb.db, "calls"):
2750cd358012SAdrian Hunter			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
2751cd358012SAdrian Hunter
27528392b74bSAdrian Hunter		self.TableMenu(GetTableList(glb), menu)
27538392b74bSAdrian Hunter
27541beb5c7bSAdrian Hunter		self.window_menu = WindowMenu(self.mdi_area, menu)
27551beb5c7bSAdrian Hunter
275665b24292SAdrian Hunter		help_menu = menu.addMenu("&Help")
275765b24292SAdrian Hunter		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
275865b24292SAdrian Hunter
2759ebd70c7dSAdrian Hunter	def Find(self):
2760ebd70c7dSAdrian Hunter		win = self.mdi_area.activeSubWindow()
2761ebd70c7dSAdrian Hunter		if win:
2762ebd70c7dSAdrian Hunter			try:
2763ebd70c7dSAdrian Hunter				win.find_bar.Activate()
2764ebd70c7dSAdrian Hunter			except:
2765ebd70c7dSAdrian Hunter				pass
2766ebd70c7dSAdrian Hunter
27678392b74bSAdrian Hunter	def FetchMoreRecords(self):
27688392b74bSAdrian Hunter		win = self.mdi_area.activeSubWindow()
27698392b74bSAdrian Hunter		if win:
27708392b74bSAdrian Hunter			try:
27718392b74bSAdrian Hunter				win.fetch_bar.Activate()
27728392b74bSAdrian Hunter			except:
27738392b74bSAdrian Hunter				pass
27748392b74bSAdrian Hunter
277582f68e28SAdrian Hunter	def ShrinkFont(self):
277682f68e28SAdrian Hunter		win = self.mdi_area.activeSubWindow()
277782f68e28SAdrian Hunter		ShrinkFont(win.view)
277882f68e28SAdrian Hunter
277982f68e28SAdrian Hunter	def EnlargeFont(self):
278082f68e28SAdrian Hunter		win = self.mdi_area.activeSubWindow()
278182f68e28SAdrian Hunter		EnlargeFont(win.view)
278282f68e28SAdrian Hunter
278376099f98SAdrian Hunter	def EventMenu(self, events, reports_menu):
278476099f98SAdrian Hunter		branches_events = 0
278576099f98SAdrian Hunter		for event in events:
278676099f98SAdrian Hunter			event = event.split(":")[0]
278776099f98SAdrian Hunter			if event == "branches":
278876099f98SAdrian Hunter				branches_events += 1
278976099f98SAdrian Hunter		dbid = 0
279076099f98SAdrian Hunter		for event in events:
279176099f98SAdrian Hunter			dbid += 1
279276099f98SAdrian Hunter			event = event.split(":")[0]
279376099f98SAdrian Hunter			if event == "branches":
279476099f98SAdrian Hunter				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
279576099f98SAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
2796210cf1f9SAdrian Hunter				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
2797210cf1f9SAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
279876099f98SAdrian Hunter
27998392b74bSAdrian Hunter	def TableMenu(self, tables, menu):
28008392b74bSAdrian Hunter		table_menu = menu.addMenu("&Tables")
28018392b74bSAdrian Hunter		for table in tables:
28028392b74bSAdrian Hunter			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
28038392b74bSAdrian Hunter
28041beb5c7bSAdrian Hunter	def NewCallGraph(self):
28051beb5c7bSAdrian Hunter		CallGraphWindow(self.glb, self)
2806031c2a00SAdrian Hunter
2807ae8b887cSAdrian Hunter	def NewCallTree(self):
2808ae8b887cSAdrian Hunter		CallTreeWindow(self.glb, self)
2809ae8b887cSAdrian Hunter
2810cd358012SAdrian Hunter	def NewTopCalls(self):
2811cd358012SAdrian Hunter		dialog = TopCallsDialog(self.glb, self)
2812cd358012SAdrian Hunter		ret = dialog.exec_()
2813cd358012SAdrian Hunter		if ret:
2814cd358012SAdrian Hunter			TopCallsWindow(self.glb, dialog.report_vars, self)
2815cd358012SAdrian Hunter
281676099f98SAdrian Hunter	def NewBranchView(self, event_id):
2817947cc38dSAdrian Hunter		BranchWindow(self.glb, event_id, ReportVars(), self)
281876099f98SAdrian Hunter
2819210cf1f9SAdrian Hunter	def NewSelectedBranchView(self, event_id):
2820210cf1f9SAdrian Hunter		dialog = SelectedBranchDialog(self.glb, self)
2821210cf1f9SAdrian Hunter		ret = dialog.exec_()
2822210cf1f9SAdrian Hunter		if ret:
2823947cc38dSAdrian Hunter			BranchWindow(self.glb, event_id, dialog.report_vars, self)
2824210cf1f9SAdrian Hunter
28258392b74bSAdrian Hunter	def NewTableView(self, table_name):
28268392b74bSAdrian Hunter		TableWindow(self.glb, table_name, self)
28278392b74bSAdrian Hunter
282865b24292SAdrian Hunter	def Help(self):
282965b24292SAdrian Hunter		HelpWindow(self.glb, self)
283065b24292SAdrian Hunter
283176099f98SAdrian Hunter# XED Disassembler
283276099f98SAdrian Hunter
283376099f98SAdrian Hunterclass xed_state_t(Structure):
283476099f98SAdrian Hunter
283576099f98SAdrian Hunter	_fields_ = [
283676099f98SAdrian Hunter		("mode", c_int),
283776099f98SAdrian Hunter		("width", c_int)
283876099f98SAdrian Hunter	]
283976099f98SAdrian Hunter
284076099f98SAdrian Hunterclass XEDInstruction():
284176099f98SAdrian Hunter
284276099f98SAdrian Hunter	def __init__(self, libxed):
284376099f98SAdrian Hunter		# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
284476099f98SAdrian Hunter		xedd_t = c_byte * 512
284576099f98SAdrian Hunter		self.xedd = xedd_t()
284676099f98SAdrian Hunter		self.xedp = addressof(self.xedd)
284776099f98SAdrian Hunter		libxed.xed_decoded_inst_zero(self.xedp)
284876099f98SAdrian Hunter		self.state = xed_state_t()
284976099f98SAdrian Hunter		self.statep = addressof(self.state)
285076099f98SAdrian Hunter		# Buffer for disassembled instruction text
285176099f98SAdrian Hunter		self.buffer = create_string_buffer(256)
285276099f98SAdrian Hunter		self.bufferp = addressof(self.buffer)
285376099f98SAdrian Hunter
285476099f98SAdrian Hunterclass LibXED():
285576099f98SAdrian Hunter
285676099f98SAdrian Hunter	def __init__(self):
28575ed4419dSAdrian Hunter		try:
285876099f98SAdrian Hunter			self.libxed = CDLL("libxed.so")
28595ed4419dSAdrian Hunter		except:
28605ed4419dSAdrian Hunter			self.libxed = None
28615ed4419dSAdrian Hunter		if not self.libxed:
28625ed4419dSAdrian Hunter			self.libxed = CDLL("/usr/local/lib/libxed.so")
286376099f98SAdrian Hunter
286476099f98SAdrian Hunter		self.xed_tables_init = self.libxed.xed_tables_init
286576099f98SAdrian Hunter		self.xed_tables_init.restype = None
286676099f98SAdrian Hunter		self.xed_tables_init.argtypes = []
286776099f98SAdrian Hunter
286876099f98SAdrian Hunter		self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
286976099f98SAdrian Hunter		self.xed_decoded_inst_zero.restype = None
287076099f98SAdrian Hunter		self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
287176099f98SAdrian Hunter
287276099f98SAdrian Hunter		self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
287376099f98SAdrian Hunter		self.xed_operand_values_set_mode.restype = None
287476099f98SAdrian Hunter		self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
287576099f98SAdrian Hunter
287676099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
287776099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.restype = None
287876099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
287976099f98SAdrian Hunter
288076099f98SAdrian Hunter		self.xed_decode = self.libxed.xed_decode
288176099f98SAdrian Hunter		self.xed_decode.restype = c_int
288276099f98SAdrian Hunter		self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
288376099f98SAdrian Hunter
288476099f98SAdrian Hunter		self.xed_format_context = self.libxed.xed_format_context
288576099f98SAdrian Hunter		self.xed_format_context.restype = c_uint
288676099f98SAdrian Hunter		self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
288776099f98SAdrian Hunter
288876099f98SAdrian Hunter		self.xed_tables_init()
288976099f98SAdrian Hunter
289076099f98SAdrian Hunter	def Instruction(self):
289176099f98SAdrian Hunter		return XEDInstruction(self)
289276099f98SAdrian Hunter
289376099f98SAdrian Hunter	def SetMode(self, inst, mode):
289476099f98SAdrian Hunter		if mode:
289576099f98SAdrian Hunter			inst.state.mode = 4 # 32-bit
289676099f98SAdrian Hunter			inst.state.width = 4 # 4 bytes
289776099f98SAdrian Hunter		else:
289876099f98SAdrian Hunter			inst.state.mode = 1 # 64-bit
289976099f98SAdrian Hunter			inst.state.width = 8 # 8 bytes
290076099f98SAdrian Hunter		self.xed_operand_values_set_mode(inst.xedp, inst.statep)
290176099f98SAdrian Hunter
290276099f98SAdrian Hunter	def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
290376099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode(inst.xedp)
290476099f98SAdrian Hunter		err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
290576099f98SAdrian Hunter		if err:
290676099f98SAdrian Hunter			return 0, ""
290776099f98SAdrian Hunter		# Use AT&T mode (2), alternative is Intel (3)
290876099f98SAdrian Hunter		ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
290976099f98SAdrian Hunter		if not ok:
291076099f98SAdrian Hunter			return 0, ""
2911*606bd60aSAdrian Hunter		if sys.version_info[0] == 2:
2912*606bd60aSAdrian Hunter			result = inst.buffer.value
2913*606bd60aSAdrian Hunter		else:
2914*606bd60aSAdrian Hunter			result = inst.buffer.value.decode()
291576099f98SAdrian Hunter		# Return instruction length and the disassembled instruction text
291676099f98SAdrian Hunter		# For now, assume the length is in byte 166
2917*606bd60aSAdrian Hunter		return inst.xedd[166], result
291876099f98SAdrian Hunter
291976099f98SAdrian Hunterdef TryOpen(file_name):
292076099f98SAdrian Hunter	try:
292176099f98SAdrian Hunter		return open(file_name, "rb")
292276099f98SAdrian Hunter	except:
292376099f98SAdrian Hunter		return None
292476099f98SAdrian Hunter
292576099f98SAdrian Hunterdef Is64Bit(f):
292676099f98SAdrian Hunter	result = sizeof(c_void_p)
292776099f98SAdrian Hunter	# ELF support only
292876099f98SAdrian Hunter	pos = f.tell()
292976099f98SAdrian Hunter	f.seek(0)
293076099f98SAdrian Hunter	header = f.read(7)
293176099f98SAdrian Hunter	f.seek(pos)
293276099f98SAdrian Hunter	magic = header[0:4]
2933*606bd60aSAdrian Hunter	if sys.version_info[0] == 2:
293476099f98SAdrian Hunter		eclass = ord(header[4])
293576099f98SAdrian Hunter		encoding = ord(header[5])
293676099f98SAdrian Hunter		version = ord(header[6])
2937*606bd60aSAdrian Hunter	else:
2938*606bd60aSAdrian Hunter		eclass = header[4]
2939*606bd60aSAdrian Hunter		encoding = header[5]
2940*606bd60aSAdrian Hunter		version = header[6]
294176099f98SAdrian Hunter	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
294276099f98SAdrian Hunter		result = True if eclass == 2 else False
294376099f98SAdrian Hunter	return result
294476099f98SAdrian Hunter
2945031c2a00SAdrian Hunter# Global data
2946031c2a00SAdrian Hunter
2947031c2a00SAdrian Hunterclass Glb():
2948031c2a00SAdrian Hunter
2949031c2a00SAdrian Hunter	def __init__(self, dbref, db, dbname):
2950031c2a00SAdrian Hunter		self.dbref = dbref
2951031c2a00SAdrian Hunter		self.db = db
2952031c2a00SAdrian Hunter		self.dbname = dbname
295376099f98SAdrian Hunter		self.home_dir = os.path.expanduser("~")
295476099f98SAdrian Hunter		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
295576099f98SAdrian Hunter		if self.buildid_dir:
295676099f98SAdrian Hunter			self.buildid_dir += "/.build-id/"
295776099f98SAdrian Hunter		else:
295876099f98SAdrian Hunter			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
2959031c2a00SAdrian Hunter		self.app = None
2960031c2a00SAdrian Hunter		self.mainwindow = None
29618392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit = weakref.WeakSet()
296276099f98SAdrian Hunter		try:
296376099f98SAdrian Hunter			self.disassembler = LibXED()
296476099f98SAdrian Hunter			self.have_disassembler = True
296576099f98SAdrian Hunter		except:
296676099f98SAdrian Hunter			self.have_disassembler = False
296776099f98SAdrian Hunter
296876099f98SAdrian Hunter	def FileFromBuildId(self, build_id):
296976099f98SAdrian Hunter		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
297076099f98SAdrian Hunter		return TryOpen(file_name)
297176099f98SAdrian Hunter
297276099f98SAdrian Hunter	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
297376099f98SAdrian Hunter		# Assume current machine i.e. no support for virtualization
297476099f98SAdrian Hunter		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
297576099f98SAdrian Hunter			file_name = os.getenv("PERF_KCORE")
297676099f98SAdrian Hunter			f = TryOpen(file_name) if file_name else None
297776099f98SAdrian Hunter			if f:
297876099f98SAdrian Hunter				return f
297976099f98SAdrian Hunter			# For now, no special handling if long_name is /proc/kcore
298076099f98SAdrian Hunter			f = TryOpen(long_name)
298176099f98SAdrian Hunter			if f:
298276099f98SAdrian Hunter				return f
298376099f98SAdrian Hunter		f = self.FileFromBuildId(build_id)
298476099f98SAdrian Hunter		if f:
298576099f98SAdrian Hunter			return f
298676099f98SAdrian Hunter		return None
29878392b74bSAdrian Hunter
29888392b74bSAdrian Hunter	def AddInstanceToShutdownOnExit(self, instance):
29898392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit.add(instance)
29908392b74bSAdrian Hunter
29918392b74bSAdrian Hunter	# Shutdown any background processes or threads
29928392b74bSAdrian Hunter	def ShutdownInstances(self):
29938392b74bSAdrian Hunter		for x in self.instances_to_shutdown_on_exit:
29948392b74bSAdrian Hunter			try:
29958392b74bSAdrian Hunter				x.Shutdown()
29968392b74bSAdrian Hunter			except:
29978392b74bSAdrian Hunter				pass
2998031c2a00SAdrian Hunter
2999031c2a00SAdrian Hunter# Database reference
3000031c2a00SAdrian Hunter
3001031c2a00SAdrian Hunterclass DBRef():
3002031c2a00SAdrian Hunter
3003031c2a00SAdrian Hunter	def __init__(self, is_sqlite3, dbname):
3004031c2a00SAdrian Hunter		self.is_sqlite3 = is_sqlite3
3005031c2a00SAdrian Hunter		self.dbname = dbname
3006031c2a00SAdrian Hunter
3007031c2a00SAdrian Hunter	def Open(self, connection_name):
3008031c2a00SAdrian Hunter		dbname = self.dbname
3009031c2a00SAdrian Hunter		if self.is_sqlite3:
3010031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3011031c2a00SAdrian Hunter		else:
3012031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3013031c2a00SAdrian Hunter			opts = dbname.split()
3014031c2a00SAdrian Hunter			for opt in opts:
3015031c2a00SAdrian Hunter				if "=" in opt:
3016031c2a00SAdrian Hunter					opt = opt.split("=")
3017031c2a00SAdrian Hunter					if opt[0] == "hostname":
3018031c2a00SAdrian Hunter						db.setHostName(opt[1])
3019031c2a00SAdrian Hunter					elif opt[0] == "port":
3020031c2a00SAdrian Hunter						db.setPort(int(opt[1]))
3021031c2a00SAdrian Hunter					elif opt[0] == "username":
3022031c2a00SAdrian Hunter						db.setUserName(opt[1])
3023031c2a00SAdrian Hunter					elif opt[0] == "password":
3024031c2a00SAdrian Hunter						db.setPassword(opt[1])
3025031c2a00SAdrian Hunter					elif opt[0] == "dbname":
3026031c2a00SAdrian Hunter						dbname = opt[1]
3027031c2a00SAdrian Hunter				else:
3028031c2a00SAdrian Hunter					dbname = opt
3029031c2a00SAdrian Hunter
3030031c2a00SAdrian Hunter		db.setDatabaseName(dbname)
3031031c2a00SAdrian Hunter		if not db.open():
3032031c2a00SAdrian Hunter			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3033031c2a00SAdrian Hunter		return db, dbname
3034031c2a00SAdrian Hunter
3035031c2a00SAdrian Hunter# Main
3036031c2a00SAdrian Hunter
3037031c2a00SAdrian Hunterdef Main():
3038031c2a00SAdrian Hunter	if (len(sys.argv) < 2):
3039beda0e72STony Jones		printerr("Usage is: exported-sql-viewer.py {<database name> | --help-only}");
3040031c2a00SAdrian Hunter		raise Exception("Too few arguments")
3041031c2a00SAdrian Hunter
3042031c2a00SAdrian Hunter	dbname = sys.argv[1]
304365b24292SAdrian Hunter	if dbname == "--help-only":
304465b24292SAdrian Hunter		app = QApplication(sys.argv)
304565b24292SAdrian Hunter		mainwindow = HelpOnlyWindow()
304665b24292SAdrian Hunter		mainwindow.show()
304765b24292SAdrian Hunter		err = app.exec_()
304865b24292SAdrian Hunter		sys.exit(err)
3049031c2a00SAdrian Hunter
3050031c2a00SAdrian Hunter	is_sqlite3 = False
3051031c2a00SAdrian Hunter	try:
3052beda0e72STony Jones		f = open(dbname, "rb")
3053beda0e72STony Jones		if f.read(15) == b'SQLite format 3':
3054031c2a00SAdrian Hunter			is_sqlite3 = True
3055031c2a00SAdrian Hunter		f.close()
3056031c2a00SAdrian Hunter	except:
3057031c2a00SAdrian Hunter		pass
3058031c2a00SAdrian Hunter
3059031c2a00SAdrian Hunter	dbref = DBRef(is_sqlite3, dbname)
3060031c2a00SAdrian Hunter	db, dbname = dbref.Open("main")
3061031c2a00SAdrian Hunter	glb = Glb(dbref, db, dbname)
3062031c2a00SAdrian Hunter	app = QApplication(sys.argv)
3063031c2a00SAdrian Hunter	glb.app = app
3064031c2a00SAdrian Hunter	mainwindow = MainWindow(glb)
3065031c2a00SAdrian Hunter	glb.mainwindow = mainwindow
3066031c2a00SAdrian Hunter	mainwindow.show()
3067031c2a00SAdrian Hunter	err = app.exec_()
30688392b74bSAdrian Hunter	glb.ShutdownInstances()
3069031c2a00SAdrian Hunter	db.close()
3070031c2a00SAdrian Hunter	sys.exit(err)
3071031c2a00SAdrian Hunter
3072031c2a00SAdrian Hunterif __name__ == "__main__":
3073031c2a00SAdrian Hunter	Main()
3074