1c6aba1bfSAdrian Hunter#!/usr/bin/env python
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
94*1ed7f47fSAdrian Hunterimport argparse
951beb5c7bSAdrian Hunterimport weakref
961beb5c7bSAdrian Hunterimport threading
97ebd70c7dSAdrian Hunterimport string
98beda0e72STony Jonestry:
99beda0e72STony Jones	# Python2
100beda0e72STony Jones	import cPickle as pickle
101beda0e72STony Jones	# size of pickled integer big enough for record size
102beda0e72STony Jones	glb_nsz = 8
103beda0e72STony Jonesexcept ImportError:
104beda0e72STony Jones	import pickle
105beda0e72STony Jones	glb_nsz = 16
1068392b74bSAdrian Hunterimport re
1078392b74bSAdrian Hunterimport os
108031c2a00SAdrian Hunterfrom PySide.QtCore import *
109031c2a00SAdrian Hunterfrom PySide.QtGui import *
110031c2a00SAdrian Hunterfrom PySide.QtSql import *
1118453c936SAdrian Hunterpyside_version_1 = True
112031c2a00SAdrian Hunterfrom decimal import *
1138392b74bSAdrian Hunterfrom ctypes import *
1148392b74bSAdrian Hunterfrom multiprocessing import Process, Array, Value, Event
115031c2a00SAdrian Hunter
116beda0e72STony Jones# xrange is range in Python3
117beda0e72STony Jonestry:
118beda0e72STony Jones	xrange
119beda0e72STony Jonesexcept NameError:
120beda0e72STony Jones	xrange = range
121beda0e72STony Jones
122beda0e72STony Jonesdef printerr(*args, **keyword_args):
123beda0e72STony Jones	print(*args, file=sys.stderr, **keyword_args)
124beda0e72STony Jones
125031c2a00SAdrian Hunter# Data formatting helpers
126031c2a00SAdrian Hunter
12776099f98SAdrian Hunterdef tohex(ip):
12876099f98SAdrian Hunter	if ip < 0:
12976099f98SAdrian Hunter		ip += 1 << 64
13076099f98SAdrian Hunter	return "%x" % ip
13176099f98SAdrian Hunter
13276099f98SAdrian Hunterdef offstr(offset):
13376099f98SAdrian Hunter	if offset:
13476099f98SAdrian Hunter		return "+0x%x" % offset
13576099f98SAdrian Hunter	return ""
13676099f98SAdrian Hunter
137031c2a00SAdrian Hunterdef dsoname(name):
138031c2a00SAdrian Hunter	if name == "[kernel.kallsyms]":
139031c2a00SAdrian Hunter		return "[kernel]"
140031c2a00SAdrian Hunter	return name
141031c2a00SAdrian Hunter
142210cf1f9SAdrian Hunterdef findnth(s, sub, n, offs=0):
143210cf1f9SAdrian Hunter	pos = s.find(sub)
144210cf1f9SAdrian Hunter	if pos < 0:
145210cf1f9SAdrian Hunter		return pos
146210cf1f9SAdrian Hunter	if n <= 1:
147210cf1f9SAdrian Hunter		return offs + pos
148210cf1f9SAdrian Hunter	return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
149210cf1f9SAdrian Hunter
150031c2a00SAdrian Hunter# Percent to one decimal place
151031c2a00SAdrian Hunter
152031c2a00SAdrian Hunterdef PercentToOneDP(n, d):
153031c2a00SAdrian Hunter	if not d:
154031c2a00SAdrian Hunter		return "0.0"
155031c2a00SAdrian Hunter	x = (n * Decimal(100)) / d
156031c2a00SAdrian Hunter	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
157031c2a00SAdrian Hunter
158031c2a00SAdrian Hunter# Helper for queries that must not fail
159031c2a00SAdrian Hunter
160031c2a00SAdrian Hunterdef QueryExec(query, stmt):
161031c2a00SAdrian Hunter	ret = query.exec_(stmt)
162031c2a00SAdrian Hunter	if not ret:
163031c2a00SAdrian Hunter		raise Exception("Query failed: " + query.lastError().text())
164031c2a00SAdrian Hunter
165ebd70c7dSAdrian Hunter# Background thread
166ebd70c7dSAdrian Hunter
167ebd70c7dSAdrian Hunterclass Thread(QThread):
168ebd70c7dSAdrian Hunter
169ebd70c7dSAdrian Hunter	done = Signal(object)
170ebd70c7dSAdrian Hunter
171ebd70c7dSAdrian Hunter	def __init__(self, task, param=None, parent=None):
172ebd70c7dSAdrian Hunter		super(Thread, self).__init__(parent)
173ebd70c7dSAdrian Hunter		self.task = task
174ebd70c7dSAdrian Hunter		self.param = param
175ebd70c7dSAdrian Hunter
176ebd70c7dSAdrian Hunter	def run(self):
177ebd70c7dSAdrian Hunter		while True:
178ebd70c7dSAdrian Hunter			if self.param is None:
179ebd70c7dSAdrian Hunter				done, result = self.task()
180ebd70c7dSAdrian Hunter			else:
181ebd70c7dSAdrian Hunter				done, result = self.task(self.param)
182ebd70c7dSAdrian Hunter			self.done.emit(result)
183ebd70c7dSAdrian Hunter			if done:
184ebd70c7dSAdrian Hunter				break
185ebd70c7dSAdrian Hunter
186031c2a00SAdrian Hunter# Tree data model
187031c2a00SAdrian Hunter
188031c2a00SAdrian Hunterclass TreeModel(QAbstractItemModel):
189031c2a00SAdrian Hunter
190a448ba23SAdrian Hunter	def __init__(self, glb, parent=None):
191031c2a00SAdrian Hunter		super(TreeModel, self).__init__(parent)
192a448ba23SAdrian Hunter		self.glb = glb
193a448ba23SAdrian Hunter		self.root = self.GetRoot()
194031c2a00SAdrian Hunter		self.last_row_read = 0
195031c2a00SAdrian Hunter
196031c2a00SAdrian Hunter	def Item(self, parent):
197031c2a00SAdrian Hunter		if parent.isValid():
198031c2a00SAdrian Hunter			return parent.internalPointer()
199031c2a00SAdrian Hunter		else:
200031c2a00SAdrian Hunter			return self.root
201031c2a00SAdrian Hunter
202031c2a00SAdrian Hunter	def rowCount(self, parent):
203031c2a00SAdrian Hunter		result = self.Item(parent).childCount()
204031c2a00SAdrian Hunter		if result < 0:
205031c2a00SAdrian Hunter			result = 0
206031c2a00SAdrian Hunter			self.dataChanged.emit(parent, parent)
207031c2a00SAdrian Hunter		return result
208031c2a00SAdrian Hunter
209031c2a00SAdrian Hunter	def hasChildren(self, parent):
210031c2a00SAdrian Hunter		return self.Item(parent).hasChildren()
211031c2a00SAdrian Hunter
212031c2a00SAdrian Hunter	def headerData(self, section, orientation, role):
213031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
214031c2a00SAdrian Hunter			return self.columnAlignment(section)
215031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
216031c2a00SAdrian Hunter			return None
217031c2a00SAdrian Hunter		if orientation != Qt.Horizontal:
218031c2a00SAdrian Hunter			return None
219031c2a00SAdrian Hunter		return self.columnHeader(section)
220031c2a00SAdrian Hunter
221031c2a00SAdrian Hunter	def parent(self, child):
222031c2a00SAdrian Hunter		child_item = child.internalPointer()
223031c2a00SAdrian Hunter		if child_item is self.root:
224031c2a00SAdrian Hunter			return QModelIndex()
225031c2a00SAdrian Hunter		parent_item = child_item.getParentItem()
226031c2a00SAdrian Hunter		return self.createIndex(parent_item.getRow(), 0, parent_item)
227031c2a00SAdrian Hunter
228031c2a00SAdrian Hunter	def index(self, row, column, parent):
229031c2a00SAdrian Hunter		child_item = self.Item(parent).getChildItem(row)
230031c2a00SAdrian Hunter		return self.createIndex(row, column, child_item)
231031c2a00SAdrian Hunter
232031c2a00SAdrian Hunter	def DisplayData(self, item, index):
233031c2a00SAdrian Hunter		return item.getData(index.column())
234031c2a00SAdrian Hunter
2358392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2368392b74bSAdrian Hunter		if row > self.last_row_read:
2378392b74bSAdrian Hunter			self.last_row_read = row
2388392b74bSAdrian Hunter			if row + 10 >= self.root.child_count:
2398392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2408392b74bSAdrian Hunter
2418392b74bSAdrian Hunter	def columnAlignment(self, column):
2428392b74bSAdrian Hunter		return Qt.AlignLeft
2438392b74bSAdrian Hunter
2448392b74bSAdrian Hunter	def columnFont(self, column):
2458392b74bSAdrian Hunter		return None
2468392b74bSAdrian Hunter
2478392b74bSAdrian Hunter	def data(self, index, role):
2488392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2498392b74bSAdrian Hunter			return self.columnAlignment(index.column())
2508392b74bSAdrian Hunter		if role == Qt.FontRole:
2518392b74bSAdrian Hunter			return self.columnFont(index.column())
2528392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2538392b74bSAdrian Hunter			return None
2548392b74bSAdrian Hunter		item = index.internalPointer()
2558392b74bSAdrian Hunter		return self.DisplayData(item, index)
2568392b74bSAdrian Hunter
2578392b74bSAdrian Hunter# Table data model
2588392b74bSAdrian Hunter
2598392b74bSAdrian Hunterclass TableModel(QAbstractTableModel):
2608392b74bSAdrian Hunter
2618392b74bSAdrian Hunter	def __init__(self, parent=None):
2628392b74bSAdrian Hunter		super(TableModel, self).__init__(parent)
2638392b74bSAdrian Hunter		self.child_count = 0
2648392b74bSAdrian Hunter		self.child_items = []
2658392b74bSAdrian Hunter		self.last_row_read = 0
2668392b74bSAdrian Hunter
2678392b74bSAdrian Hunter	def Item(self, parent):
2688392b74bSAdrian Hunter		if parent.isValid():
2698392b74bSAdrian Hunter			return parent.internalPointer()
2708392b74bSAdrian Hunter		else:
2718392b74bSAdrian Hunter			return self
2728392b74bSAdrian Hunter
2738392b74bSAdrian Hunter	def rowCount(self, parent):
2748392b74bSAdrian Hunter		return self.child_count
2758392b74bSAdrian Hunter
2768392b74bSAdrian Hunter	def headerData(self, section, orientation, role):
2778392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2788392b74bSAdrian Hunter			return self.columnAlignment(section)
2798392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2808392b74bSAdrian Hunter			return None
2818392b74bSAdrian Hunter		if orientation != Qt.Horizontal:
2828392b74bSAdrian Hunter			return None
2838392b74bSAdrian Hunter		return self.columnHeader(section)
2848392b74bSAdrian Hunter
2858392b74bSAdrian Hunter	def index(self, row, column, parent):
2868392b74bSAdrian Hunter		return self.createIndex(row, column, self.child_items[row])
2878392b74bSAdrian Hunter
2888392b74bSAdrian Hunter	def DisplayData(self, item, index):
2898392b74bSAdrian Hunter		return item.getData(index.column())
2908392b74bSAdrian Hunter
2918392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2928392b74bSAdrian Hunter		if row > self.last_row_read:
2938392b74bSAdrian Hunter			self.last_row_read = row
2948392b74bSAdrian Hunter			if row + 10 >= self.child_count:
2958392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2968392b74bSAdrian Hunter
297031c2a00SAdrian Hunter	def columnAlignment(self, column):
298031c2a00SAdrian Hunter		return Qt.AlignLeft
299031c2a00SAdrian Hunter
300031c2a00SAdrian Hunter	def columnFont(self, column):
301031c2a00SAdrian Hunter		return None
302031c2a00SAdrian Hunter
303031c2a00SAdrian Hunter	def data(self, index, role):
304031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
305031c2a00SAdrian Hunter			return self.columnAlignment(index.column())
306031c2a00SAdrian Hunter		if role == Qt.FontRole:
307031c2a00SAdrian Hunter			return self.columnFont(index.column())
308031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
309031c2a00SAdrian Hunter			return None
310031c2a00SAdrian Hunter		item = index.internalPointer()
311031c2a00SAdrian Hunter		return self.DisplayData(item, index)
312031c2a00SAdrian Hunter
3131beb5c7bSAdrian Hunter# Model cache
3141beb5c7bSAdrian Hunter
3151beb5c7bSAdrian Huntermodel_cache = weakref.WeakValueDictionary()
3161beb5c7bSAdrian Huntermodel_cache_lock = threading.Lock()
3171beb5c7bSAdrian Hunter
3181beb5c7bSAdrian Hunterdef LookupCreateModel(model_name, create_fn):
3191beb5c7bSAdrian Hunter	model_cache_lock.acquire()
3201beb5c7bSAdrian Hunter	try:
3211beb5c7bSAdrian Hunter		model = model_cache[model_name]
3221beb5c7bSAdrian Hunter	except:
3231beb5c7bSAdrian Hunter		model = None
3241beb5c7bSAdrian Hunter	if model is None:
3251beb5c7bSAdrian Hunter		model = create_fn()
3261beb5c7bSAdrian Hunter		model_cache[model_name] = model
3271beb5c7bSAdrian Hunter	model_cache_lock.release()
3281beb5c7bSAdrian Hunter	return model
3291beb5c7bSAdrian Hunter
330ebd70c7dSAdrian Hunter# Find bar
331ebd70c7dSAdrian Hunter
332ebd70c7dSAdrian Hunterclass FindBar():
333ebd70c7dSAdrian Hunter
334ebd70c7dSAdrian Hunter	def __init__(self, parent, finder, is_reg_expr=False):
335ebd70c7dSAdrian Hunter		self.finder = finder
336ebd70c7dSAdrian Hunter		self.context = []
337ebd70c7dSAdrian Hunter		self.last_value = None
338ebd70c7dSAdrian Hunter		self.last_pattern = None
339ebd70c7dSAdrian Hunter
340ebd70c7dSAdrian Hunter		label = QLabel("Find:")
341ebd70c7dSAdrian Hunter		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
342ebd70c7dSAdrian Hunter
343ebd70c7dSAdrian Hunter		self.textbox = QComboBox()
344ebd70c7dSAdrian Hunter		self.textbox.setEditable(True)
345ebd70c7dSAdrian Hunter		self.textbox.currentIndexChanged.connect(self.ValueChanged)
346ebd70c7dSAdrian Hunter
347ebd70c7dSAdrian Hunter		self.progress = QProgressBar()
348ebd70c7dSAdrian Hunter		self.progress.setRange(0, 0)
349ebd70c7dSAdrian Hunter		self.progress.hide()
350ebd70c7dSAdrian Hunter
351ebd70c7dSAdrian Hunter		if is_reg_expr:
352ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Regular Expression")
353ebd70c7dSAdrian Hunter		else:
354ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Pattern")
355ebd70c7dSAdrian Hunter		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
356ebd70c7dSAdrian Hunter
357ebd70c7dSAdrian Hunter		self.next_button = QToolButton()
358ebd70c7dSAdrian Hunter		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
359ebd70c7dSAdrian Hunter		self.next_button.released.connect(lambda: self.NextPrev(1))
360ebd70c7dSAdrian Hunter
361ebd70c7dSAdrian Hunter		self.prev_button = QToolButton()
362ebd70c7dSAdrian Hunter		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
363ebd70c7dSAdrian Hunter		self.prev_button.released.connect(lambda: self.NextPrev(-1))
364ebd70c7dSAdrian Hunter
365ebd70c7dSAdrian Hunter		self.close_button = QToolButton()
366ebd70c7dSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
367ebd70c7dSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
368ebd70c7dSAdrian Hunter
369ebd70c7dSAdrian Hunter		self.hbox = QHBoxLayout()
370ebd70c7dSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
371ebd70c7dSAdrian Hunter
372ebd70c7dSAdrian Hunter		self.hbox.addWidget(label)
373ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.textbox)
374ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.progress)
375ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.pattern)
376ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.next_button)
377ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.prev_button)
378ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.close_button)
379ebd70c7dSAdrian Hunter
380ebd70c7dSAdrian Hunter		self.bar = QWidget()
381ebd70c7dSAdrian Hunter		self.bar.setLayout(self.hbox);
382ebd70c7dSAdrian Hunter		self.bar.hide()
383ebd70c7dSAdrian Hunter
384ebd70c7dSAdrian Hunter	def Widget(self):
385ebd70c7dSAdrian Hunter		return self.bar
386ebd70c7dSAdrian Hunter
387ebd70c7dSAdrian Hunter	def Activate(self):
388ebd70c7dSAdrian Hunter		self.bar.show()
389ebd70c7dSAdrian Hunter		self.textbox.setFocus()
390ebd70c7dSAdrian Hunter
391ebd70c7dSAdrian Hunter	def Deactivate(self):
392ebd70c7dSAdrian Hunter		self.bar.hide()
393ebd70c7dSAdrian Hunter
394ebd70c7dSAdrian Hunter	def Busy(self):
395ebd70c7dSAdrian Hunter		self.textbox.setEnabled(False)
396ebd70c7dSAdrian Hunter		self.pattern.hide()
397ebd70c7dSAdrian Hunter		self.next_button.hide()
398ebd70c7dSAdrian Hunter		self.prev_button.hide()
399ebd70c7dSAdrian Hunter		self.progress.show()
400ebd70c7dSAdrian Hunter
401ebd70c7dSAdrian Hunter	def Idle(self):
402ebd70c7dSAdrian Hunter		self.textbox.setEnabled(True)
403ebd70c7dSAdrian Hunter		self.progress.hide()
404ebd70c7dSAdrian Hunter		self.pattern.show()
405ebd70c7dSAdrian Hunter		self.next_button.show()
406ebd70c7dSAdrian Hunter		self.prev_button.show()
407ebd70c7dSAdrian Hunter
408ebd70c7dSAdrian Hunter	def Find(self, direction):
409ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
410ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
411ebd70c7dSAdrian Hunter		self.last_value = value
412ebd70c7dSAdrian Hunter		self.last_pattern = pattern
413ebd70c7dSAdrian Hunter		self.finder.Find(value, direction, pattern, self.context)
414ebd70c7dSAdrian Hunter
415ebd70c7dSAdrian Hunter	def ValueChanged(self):
416ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
417ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
418ebd70c7dSAdrian Hunter		index = self.textbox.currentIndex()
419ebd70c7dSAdrian Hunter		data = self.textbox.itemData(index)
420ebd70c7dSAdrian Hunter		# Store the pattern in the combo box to keep it with the text value
421ebd70c7dSAdrian Hunter		if data == None:
422ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
423ebd70c7dSAdrian Hunter		else:
424ebd70c7dSAdrian Hunter			self.pattern.setChecked(data)
425ebd70c7dSAdrian Hunter		self.Find(0)
426ebd70c7dSAdrian Hunter
427ebd70c7dSAdrian Hunter	def NextPrev(self, direction):
428ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
429ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
430ebd70c7dSAdrian Hunter		if value != self.last_value:
431ebd70c7dSAdrian Hunter			index = self.textbox.findText(value)
432ebd70c7dSAdrian Hunter			# Allow for a button press before the value has been added to the combo box
433ebd70c7dSAdrian Hunter			if index < 0:
434ebd70c7dSAdrian Hunter				index = self.textbox.count()
435ebd70c7dSAdrian Hunter				self.textbox.addItem(value, pattern)
436ebd70c7dSAdrian Hunter				self.textbox.setCurrentIndex(index)
437ebd70c7dSAdrian Hunter				return
438ebd70c7dSAdrian Hunter			else:
439ebd70c7dSAdrian Hunter				self.textbox.setItemData(index, pattern)
440ebd70c7dSAdrian Hunter		elif pattern != self.last_pattern:
441ebd70c7dSAdrian Hunter			# Keep the pattern recorded in the combo box up to date
442ebd70c7dSAdrian Hunter			index = self.textbox.currentIndex()
443ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
444ebd70c7dSAdrian Hunter		self.Find(direction)
445ebd70c7dSAdrian Hunter
446ebd70c7dSAdrian Hunter	def NotFound(self):
447ebd70c7dSAdrian Hunter		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
448ebd70c7dSAdrian Hunter
449031c2a00SAdrian Hunter# Context-sensitive call graph data model item base
450031c2a00SAdrian Hunter
451031c2a00SAdrian Hunterclass CallGraphLevelItemBase(object):
452031c2a00SAdrian Hunter
453031c2a00SAdrian Hunter	def __init__(self, glb, row, parent_item):
454031c2a00SAdrian Hunter		self.glb = glb
455031c2a00SAdrian Hunter		self.row = row
456031c2a00SAdrian Hunter		self.parent_item = parent_item
457031c2a00SAdrian Hunter		self.query_done = False;
458031c2a00SAdrian Hunter		self.child_count = 0
459031c2a00SAdrian Hunter		self.child_items = []
4603ac641f4SAdrian Hunter		if parent_item:
4613ac641f4SAdrian Hunter			self.level = parent_item.level + 1
4623ac641f4SAdrian Hunter		else:
4633ac641f4SAdrian Hunter			self.level = 0
464031c2a00SAdrian Hunter
465031c2a00SAdrian Hunter	def getChildItem(self, row):
466031c2a00SAdrian Hunter		return self.child_items[row]
467031c2a00SAdrian Hunter
468031c2a00SAdrian Hunter	def getParentItem(self):
469031c2a00SAdrian Hunter		return self.parent_item
470031c2a00SAdrian Hunter
471031c2a00SAdrian Hunter	def getRow(self):
472031c2a00SAdrian Hunter		return self.row
473031c2a00SAdrian Hunter
474031c2a00SAdrian Hunter	def childCount(self):
475031c2a00SAdrian Hunter		if not self.query_done:
476031c2a00SAdrian Hunter			self.Select()
477031c2a00SAdrian Hunter			if not self.child_count:
478031c2a00SAdrian Hunter				return -1
479031c2a00SAdrian Hunter		return self.child_count
480031c2a00SAdrian Hunter
481031c2a00SAdrian Hunter	def hasChildren(self):
482031c2a00SAdrian Hunter		if not self.query_done:
483031c2a00SAdrian Hunter			return True
484031c2a00SAdrian Hunter		return self.child_count > 0
485031c2a00SAdrian Hunter
486031c2a00SAdrian Hunter	def getData(self, column):
487031c2a00SAdrian Hunter		return self.data[column]
488031c2a00SAdrian Hunter
489031c2a00SAdrian Hunter# Context-sensitive call graph data model level 2+ item base
490031c2a00SAdrian Hunter
491031c2a00SAdrian Hunterclass CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
492031c2a00SAdrian Hunter
493031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
494031c2a00SAdrian Hunter		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
495031c2a00SAdrian Hunter		self.comm_id = comm_id
496031c2a00SAdrian Hunter		self.thread_id = thread_id
497031c2a00SAdrian Hunter		self.call_path_id = call_path_id
498031c2a00SAdrian Hunter		self.branch_count = branch_count
499031c2a00SAdrian Hunter		self.time = time
500031c2a00SAdrian Hunter
501031c2a00SAdrian Hunter	def Select(self):
502031c2a00SAdrian Hunter		self.query_done = True;
503031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
504031c2a00SAdrian Hunter		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
505031c2a00SAdrian Hunter					" FROM calls"
506031c2a00SAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
507031c2a00SAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
508031c2a00SAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
509031c2a00SAdrian Hunter					" WHERE parent_call_path_id = " + str(self.call_path_id) +
510031c2a00SAdrian Hunter					" AND comm_id = " + str(self.comm_id) +
511031c2a00SAdrian Hunter					" AND thread_id = " + str(self.thread_id) +
512031c2a00SAdrian Hunter					" GROUP BY call_path_id, name, short_name"
513031c2a00SAdrian Hunter					" ORDER BY call_path_id")
514031c2a00SAdrian Hunter		while query.next():
515031c2a00SAdrian 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)
516031c2a00SAdrian Hunter			self.child_items.append(child_item)
517031c2a00SAdrian Hunter			self.child_count += 1
518031c2a00SAdrian Hunter
519031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item
520031c2a00SAdrian Hunter
521031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
522031c2a00SAdrian Hunter
523031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
524031c2a00SAdrian Hunter		super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
525031c2a00SAdrian Hunter		dso = dsoname(dso)
526031c2a00SAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
527031c2a00SAdrian Hunter		self.dbid = call_path_id
528031c2a00SAdrian Hunter
529031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item
530031c2a00SAdrian Hunter
531031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
532031c2a00SAdrian Hunter
533031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
534031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
535031c2a00SAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
536031c2a00SAdrian Hunter		self.dbid = thread_id
537031c2a00SAdrian Hunter
538031c2a00SAdrian Hunter	def Select(self):
539031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).Select()
540031c2a00SAdrian Hunter		for child_item in self.child_items:
541031c2a00SAdrian Hunter			self.time += child_item.time
542031c2a00SAdrian Hunter			self.branch_count += child_item.branch_count
543031c2a00SAdrian Hunter		for child_item in self.child_items:
544031c2a00SAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
545031c2a00SAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
546031c2a00SAdrian Hunter
547031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item
548031c2a00SAdrian Hunter
549031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase):
550031c2a00SAdrian Hunter
551031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, comm, parent_item):
552031c2a00SAdrian Hunter		super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
553031c2a00SAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
554031c2a00SAdrian Hunter		self.dbid = comm_id
555031c2a00SAdrian Hunter
556031c2a00SAdrian Hunter	def Select(self):
557031c2a00SAdrian Hunter		self.query_done = True;
558031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
559031c2a00SAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
560031c2a00SAdrian Hunter					" FROM comm_threads"
561031c2a00SAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
562031c2a00SAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
563031c2a00SAdrian Hunter		while query.next():
564031c2a00SAdrian Hunter			child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
565031c2a00SAdrian Hunter			self.child_items.append(child_item)
566031c2a00SAdrian Hunter			self.child_count += 1
567031c2a00SAdrian Hunter
568031c2a00SAdrian Hunter# Context-sensitive call graph data model root item
569031c2a00SAdrian Hunter
570031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase):
571031c2a00SAdrian Hunter
572031c2a00SAdrian Hunter	def __init__(self, glb):
573031c2a00SAdrian Hunter		super(CallGraphRootItem, self).__init__(glb, 0, None)
574031c2a00SAdrian Hunter		self.dbid = 0
575031c2a00SAdrian Hunter		self.query_done = True;
576031c2a00SAdrian Hunter		query = QSqlQuery(glb.db)
577031c2a00SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
578031c2a00SAdrian Hunter		while query.next():
579031c2a00SAdrian Hunter			if not query.value(0):
580031c2a00SAdrian Hunter				continue
581031c2a00SAdrian Hunter			child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
582031c2a00SAdrian Hunter			self.child_items.append(child_item)
583031c2a00SAdrian Hunter			self.child_count += 1
584031c2a00SAdrian Hunter
585254c0d82SAdrian Hunter# Context-sensitive call graph data model base
586031c2a00SAdrian Hunter
587254c0d82SAdrian Hunterclass CallGraphModelBase(TreeModel):
588031c2a00SAdrian Hunter
589031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
590254c0d82SAdrian Hunter		super(CallGraphModelBase, self).__init__(glb, parent)
591031c2a00SAdrian Hunter
592ebd70c7dSAdrian Hunter	def FindSelect(self, value, pattern, query):
593ebd70c7dSAdrian Hunter		if pattern:
594ebd70c7dSAdrian Hunter			# postgresql and sqlite pattern patching differences:
595ebd70c7dSAdrian Hunter			#   postgresql LIKE is case sensitive but sqlite LIKE is not
596ebd70c7dSAdrian Hunter			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
597ebd70c7dSAdrian Hunter			#   postgresql supports ILIKE which is case insensitive
598ebd70c7dSAdrian Hunter			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
599ebd70c7dSAdrian Hunter			if not self.glb.dbref.is_sqlite3:
600ebd70c7dSAdrian Hunter				# Escape % and _
601ebd70c7dSAdrian Hunter				s = value.replace("%", "\%")
602ebd70c7dSAdrian Hunter				s = s.replace("_", "\_")
603ebd70c7dSAdrian Hunter				# Translate * and ? into SQL LIKE pattern characters % and _
604ebd70c7dSAdrian Hunter				trans = string.maketrans("*?", "%_")
605ebd70c7dSAdrian Hunter				match = " LIKE '" + str(s).translate(trans) + "'"
606ebd70c7dSAdrian Hunter			else:
607ebd70c7dSAdrian Hunter				match = " GLOB '" + str(value) + "'"
608ebd70c7dSAdrian Hunter		else:
609ebd70c7dSAdrian Hunter			match = " = '" + str(value) + "'"
610254c0d82SAdrian Hunter		self.DoFindSelect(query, match)
611ebd70c7dSAdrian Hunter
612ebd70c7dSAdrian Hunter	def Found(self, query, found):
613ebd70c7dSAdrian Hunter		if found:
614ebd70c7dSAdrian Hunter			return self.FindPath(query)
615ebd70c7dSAdrian Hunter		return []
616ebd70c7dSAdrian Hunter
617ebd70c7dSAdrian Hunter	def FindValue(self, value, pattern, query, last_value, last_pattern):
618ebd70c7dSAdrian Hunter		if last_value == value and pattern == last_pattern:
619ebd70c7dSAdrian Hunter			found = query.first()
620ebd70c7dSAdrian Hunter		else:
621ebd70c7dSAdrian Hunter			self.FindSelect(value, pattern, query)
622ebd70c7dSAdrian Hunter			found = query.next()
623ebd70c7dSAdrian Hunter		return self.Found(query, found)
624ebd70c7dSAdrian Hunter
625ebd70c7dSAdrian Hunter	def FindNext(self, query):
626ebd70c7dSAdrian Hunter		found = query.next()
627ebd70c7dSAdrian Hunter		if not found:
628ebd70c7dSAdrian Hunter			found = query.first()
629ebd70c7dSAdrian Hunter		return self.Found(query, found)
630ebd70c7dSAdrian Hunter
631ebd70c7dSAdrian Hunter	def FindPrev(self, query):
632ebd70c7dSAdrian Hunter		found = query.previous()
633ebd70c7dSAdrian Hunter		if not found:
634ebd70c7dSAdrian Hunter			found = query.last()
635ebd70c7dSAdrian Hunter		return self.Found(query, found)
636ebd70c7dSAdrian Hunter
637ebd70c7dSAdrian Hunter	def FindThread(self, c):
638ebd70c7dSAdrian Hunter		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
639ebd70c7dSAdrian Hunter			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
640ebd70c7dSAdrian Hunter		elif c.direction > 0:
641ebd70c7dSAdrian Hunter			ids = self.FindNext(c.query)
642ebd70c7dSAdrian Hunter		else:
643ebd70c7dSAdrian Hunter			ids = self.FindPrev(c.query)
644ebd70c7dSAdrian Hunter		return (True, ids)
645ebd70c7dSAdrian Hunter
646ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
647ebd70c7dSAdrian Hunter		class Context():
648ebd70c7dSAdrian Hunter			def __init__(self, *x):
649ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
650ebd70c7dSAdrian Hunter			def Update(self, *x):
651ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
652ebd70c7dSAdrian Hunter		if len(context):
653ebd70c7dSAdrian Hunter			context[0].Update(value, direction, pattern)
654ebd70c7dSAdrian Hunter		else:
655ebd70c7dSAdrian Hunter			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
656ebd70c7dSAdrian Hunter		# Use a thread so the UI is not blocked during the SELECT
657ebd70c7dSAdrian Hunter		thread = Thread(self.FindThread, context[0])
658ebd70c7dSAdrian Hunter		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
659ebd70c7dSAdrian Hunter		thread.start()
660ebd70c7dSAdrian Hunter
661ebd70c7dSAdrian Hunter	def FindDone(self, thread, callback, ids):
662ebd70c7dSAdrian Hunter		callback(ids)
663ebd70c7dSAdrian Hunter
664254c0d82SAdrian Hunter# Context-sensitive call graph data model
665254c0d82SAdrian Hunter
666254c0d82SAdrian Hunterclass CallGraphModel(CallGraphModelBase):
667254c0d82SAdrian Hunter
668254c0d82SAdrian Hunter	def __init__(self, glb, parent=None):
669254c0d82SAdrian Hunter		super(CallGraphModel, self).__init__(glb, parent)
670254c0d82SAdrian Hunter
671254c0d82SAdrian Hunter	def GetRoot(self):
672254c0d82SAdrian Hunter		return CallGraphRootItem(self.glb)
673254c0d82SAdrian Hunter
674254c0d82SAdrian Hunter	def columnCount(self, parent=None):
675254c0d82SAdrian Hunter		return 7
676254c0d82SAdrian Hunter
677254c0d82SAdrian Hunter	def columnHeader(self, column):
678254c0d82SAdrian Hunter		headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
679254c0d82SAdrian Hunter		return headers[column]
680254c0d82SAdrian Hunter
681254c0d82SAdrian Hunter	def columnAlignment(self, column):
682254c0d82SAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
683254c0d82SAdrian Hunter		return alignment[column]
684254c0d82SAdrian Hunter
685254c0d82SAdrian Hunter	def DoFindSelect(self, query, match):
686254c0d82SAdrian Hunter		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
687254c0d82SAdrian Hunter						" FROM calls"
688254c0d82SAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
689254c0d82SAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
690254c0d82SAdrian Hunter						" WHERE symbols.name" + match +
691254c0d82SAdrian Hunter						" GROUP BY comm_id, thread_id, call_path_id"
692254c0d82SAdrian Hunter						" ORDER BY comm_id, thread_id, call_path_id")
693254c0d82SAdrian Hunter
694254c0d82SAdrian Hunter	def FindPath(self, query):
695254c0d82SAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
696254c0d82SAdrian Hunter		# to open the tree at the right place.
697254c0d82SAdrian Hunter		ids = []
698254c0d82SAdrian Hunter		parent_id = query.value(0)
699254c0d82SAdrian Hunter		while parent_id:
700254c0d82SAdrian Hunter			ids.insert(0, parent_id)
701254c0d82SAdrian Hunter			q2 = QSqlQuery(self.glb.db)
702254c0d82SAdrian Hunter			QueryExec(q2, "SELECT parent_id"
703254c0d82SAdrian Hunter					" FROM call_paths"
704254c0d82SAdrian Hunter					" WHERE id = " + str(parent_id))
705254c0d82SAdrian Hunter			if not q2.next():
706254c0d82SAdrian Hunter				break
707254c0d82SAdrian Hunter			parent_id = q2.value(0)
708254c0d82SAdrian Hunter		# The call path root is not used
709254c0d82SAdrian Hunter		if ids[0] == 1:
710254c0d82SAdrian Hunter			del ids[0]
711254c0d82SAdrian Hunter		ids.insert(0, query.value(2))
712254c0d82SAdrian Hunter		ids.insert(0, query.value(1))
713254c0d82SAdrian Hunter		return ids
714254c0d82SAdrian Hunter
715ae8b887cSAdrian Hunter# Call tree data model level 2+ item base
716ae8b887cSAdrian Hunter
717ae8b887cSAdrian Hunterclass CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
718ae8b887cSAdrian Hunter
719ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
720ae8b887cSAdrian Hunter		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
721ae8b887cSAdrian Hunter		self.comm_id = comm_id
722ae8b887cSAdrian Hunter		self.thread_id = thread_id
723ae8b887cSAdrian Hunter		self.calls_id = calls_id
724ae8b887cSAdrian Hunter		self.branch_count = branch_count
725ae8b887cSAdrian Hunter		self.time = time
726ae8b887cSAdrian Hunter
727ae8b887cSAdrian Hunter	def Select(self):
728ae8b887cSAdrian Hunter		self.query_done = True;
729ae8b887cSAdrian Hunter		if self.calls_id == 0:
730ae8b887cSAdrian Hunter			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
731ae8b887cSAdrian Hunter		else:
732ae8b887cSAdrian Hunter			comm_thread = ""
733ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
734ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
735ae8b887cSAdrian Hunter					" FROM calls"
736ae8b887cSAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
737ae8b887cSAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
738ae8b887cSAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
739ae8b887cSAdrian Hunter					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
740ae8b887cSAdrian Hunter					" ORDER BY call_time, calls.id")
741ae8b887cSAdrian Hunter		while query.next():
742ae8b887cSAdrian 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)
743ae8b887cSAdrian Hunter			self.child_items.append(child_item)
744ae8b887cSAdrian Hunter			self.child_count += 1
745ae8b887cSAdrian Hunter
746ae8b887cSAdrian Hunter# Call tree data model level three item
747ae8b887cSAdrian Hunter
748ae8b887cSAdrian Hunterclass CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
749ae8b887cSAdrian Hunter
750ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
751ae8b887cSAdrian Hunter		super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
752ae8b887cSAdrian Hunter		dso = dsoname(dso)
753ae8b887cSAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
754ae8b887cSAdrian Hunter		self.dbid = calls_id
755ae8b887cSAdrian Hunter
756ae8b887cSAdrian Hunter# Call tree data model level two item
757ae8b887cSAdrian Hunter
758ae8b887cSAdrian Hunterclass CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
759ae8b887cSAdrian Hunter
760ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
761ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item)
762ae8b887cSAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
763ae8b887cSAdrian Hunter		self.dbid = thread_id
764ae8b887cSAdrian Hunter
765ae8b887cSAdrian Hunter	def Select(self):
766ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).Select()
767ae8b887cSAdrian Hunter		for child_item in self.child_items:
768ae8b887cSAdrian Hunter			self.time += child_item.time
769ae8b887cSAdrian Hunter			self.branch_count += child_item.branch_count
770ae8b887cSAdrian Hunter		for child_item in self.child_items:
771ae8b887cSAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
772ae8b887cSAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
773ae8b887cSAdrian Hunter
774ae8b887cSAdrian Hunter# Call tree data model level one item
775ae8b887cSAdrian Hunter
776ae8b887cSAdrian Hunterclass CallTreeLevelOneItem(CallGraphLevelItemBase):
777ae8b887cSAdrian Hunter
778ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, comm, parent_item):
779ae8b887cSAdrian Hunter		super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item)
780ae8b887cSAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
781ae8b887cSAdrian Hunter		self.dbid = comm_id
782ae8b887cSAdrian Hunter
783ae8b887cSAdrian Hunter	def Select(self):
784ae8b887cSAdrian Hunter		self.query_done = True;
785ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
786ae8b887cSAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
787ae8b887cSAdrian Hunter					" FROM comm_threads"
788ae8b887cSAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
789ae8b887cSAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
790ae8b887cSAdrian Hunter		while query.next():
791ae8b887cSAdrian Hunter			child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
792ae8b887cSAdrian Hunter			self.child_items.append(child_item)
793ae8b887cSAdrian Hunter			self.child_count += 1
794ae8b887cSAdrian Hunter
795ae8b887cSAdrian Hunter# Call tree data model root item
796ae8b887cSAdrian Hunter
797ae8b887cSAdrian Hunterclass CallTreeRootItem(CallGraphLevelItemBase):
798ae8b887cSAdrian Hunter
799ae8b887cSAdrian Hunter	def __init__(self, glb):
800ae8b887cSAdrian Hunter		super(CallTreeRootItem, self).__init__(glb, 0, None)
801ae8b887cSAdrian Hunter		self.dbid = 0
802ae8b887cSAdrian Hunter		self.query_done = True;
803ae8b887cSAdrian Hunter		query = QSqlQuery(glb.db)
804ae8b887cSAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
805ae8b887cSAdrian Hunter		while query.next():
806ae8b887cSAdrian Hunter			if not query.value(0):
807ae8b887cSAdrian Hunter				continue
808ae8b887cSAdrian Hunter			child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
809ae8b887cSAdrian Hunter			self.child_items.append(child_item)
810ae8b887cSAdrian Hunter			self.child_count += 1
811ae8b887cSAdrian Hunter
812ae8b887cSAdrian Hunter# Call Tree data model
813ae8b887cSAdrian Hunter
814ae8b887cSAdrian Hunterclass CallTreeModel(CallGraphModelBase):
815ae8b887cSAdrian Hunter
816ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
817ae8b887cSAdrian Hunter		super(CallTreeModel, self).__init__(glb, parent)
818ae8b887cSAdrian Hunter
819ae8b887cSAdrian Hunter	def GetRoot(self):
820ae8b887cSAdrian Hunter		return CallTreeRootItem(self.glb)
821ae8b887cSAdrian Hunter
822ae8b887cSAdrian Hunter	def columnCount(self, parent=None):
823ae8b887cSAdrian Hunter		return 7
824ae8b887cSAdrian Hunter
825ae8b887cSAdrian Hunter	def columnHeader(self, column):
826ae8b887cSAdrian Hunter		headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
827ae8b887cSAdrian Hunter		return headers[column]
828ae8b887cSAdrian Hunter
829ae8b887cSAdrian Hunter	def columnAlignment(self, column):
830ae8b887cSAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
831ae8b887cSAdrian Hunter		return alignment[column]
832ae8b887cSAdrian Hunter
833ae8b887cSAdrian Hunter	def DoFindSelect(self, query, match):
834ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
835ae8b887cSAdrian Hunter						" FROM calls"
836ae8b887cSAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
837ae8b887cSAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
838ae8b887cSAdrian Hunter						" WHERE symbols.name" + match +
839ae8b887cSAdrian Hunter						" ORDER BY comm_id, thread_id, call_time, calls.id")
840ae8b887cSAdrian Hunter
841ae8b887cSAdrian Hunter	def FindPath(self, query):
842ae8b887cSAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
843ae8b887cSAdrian Hunter		# to open the tree at the right place.
844ae8b887cSAdrian Hunter		ids = []
845ae8b887cSAdrian Hunter		parent_id = query.value(0)
846ae8b887cSAdrian Hunter		while parent_id:
847ae8b887cSAdrian Hunter			ids.insert(0, parent_id)
848ae8b887cSAdrian Hunter			q2 = QSqlQuery(self.glb.db)
849ae8b887cSAdrian Hunter			QueryExec(q2, "SELECT parent_id"
850ae8b887cSAdrian Hunter					" FROM calls"
851ae8b887cSAdrian Hunter					" WHERE id = " + str(parent_id))
852ae8b887cSAdrian Hunter			if not q2.next():
853ae8b887cSAdrian Hunter				break
854ae8b887cSAdrian Hunter			parent_id = q2.value(0)
855ae8b887cSAdrian Hunter		ids.insert(0, query.value(2))
856ae8b887cSAdrian Hunter		ids.insert(0, query.value(1))
857ae8b887cSAdrian Hunter		return ids
858ae8b887cSAdrian Hunter
859ebd70c7dSAdrian Hunter# Vertical widget layout
860ebd70c7dSAdrian Hunter
861ebd70c7dSAdrian Hunterclass VBox():
862ebd70c7dSAdrian Hunter
863ebd70c7dSAdrian Hunter	def __init__(self, w1, w2, w3=None):
864ebd70c7dSAdrian Hunter		self.vbox = QWidget()
865ebd70c7dSAdrian Hunter		self.vbox.setLayout(QVBoxLayout());
866ebd70c7dSAdrian Hunter
867ebd70c7dSAdrian Hunter		self.vbox.layout().setContentsMargins(0, 0, 0, 0)
868ebd70c7dSAdrian Hunter
869ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w1)
870ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w2)
871ebd70c7dSAdrian Hunter		if w3:
872ebd70c7dSAdrian Hunter			self.vbox.layout().addWidget(w3)
873ebd70c7dSAdrian Hunter
874ebd70c7dSAdrian Hunter	def Widget(self):
875ebd70c7dSAdrian Hunter		return self.vbox
876ebd70c7dSAdrian Hunter
877a731cc4cSAdrian Hunter# Tree window base
8781beb5c7bSAdrian Hunter
879a731cc4cSAdrian Hunterclass TreeWindowBase(QMdiSubWindow):
8801beb5c7bSAdrian Hunter
881a731cc4cSAdrian Hunter	def __init__(self, parent=None):
882a731cc4cSAdrian Hunter		super(TreeWindowBase, self).__init__(parent)
8831beb5c7bSAdrian Hunter
884a731cc4cSAdrian Hunter		self.model = None
885a731cc4cSAdrian Hunter		self.find_bar = None
8861beb5c7bSAdrian Hunter
887be6e7471SAdrian Hunter		self.view = QTreeView()
88896c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
88996c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
890be6e7471SAdrian Hunter
8919bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
8929bc4e4bfSAdrian Hunter
893ebd70c7dSAdrian Hunter	def DisplayFound(self, ids):
894ebd70c7dSAdrian Hunter		if not len(ids):
895ebd70c7dSAdrian Hunter			return False
896ebd70c7dSAdrian Hunter		parent = QModelIndex()
897ebd70c7dSAdrian Hunter		for dbid in ids:
898ebd70c7dSAdrian Hunter			found = False
899ebd70c7dSAdrian Hunter			n = self.model.rowCount(parent)
900ebd70c7dSAdrian Hunter			for row in xrange(n):
901ebd70c7dSAdrian Hunter				child = self.model.index(row, 0, parent)
902ebd70c7dSAdrian Hunter				if child.internalPointer().dbid == dbid:
903ebd70c7dSAdrian Hunter					found = True
904ebd70c7dSAdrian Hunter					self.view.setCurrentIndex(child)
905ebd70c7dSAdrian Hunter					parent = child
906ebd70c7dSAdrian Hunter					break
907ebd70c7dSAdrian Hunter			if not found:
908ebd70c7dSAdrian Hunter				break
909ebd70c7dSAdrian Hunter		return found
910ebd70c7dSAdrian Hunter
911ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context):
912ebd70c7dSAdrian Hunter		self.view.setFocus()
913ebd70c7dSAdrian Hunter		self.find_bar.Busy()
914ebd70c7dSAdrian Hunter		self.model.Find(value, direction, pattern, context, self.FindDone)
915ebd70c7dSAdrian Hunter
916ebd70c7dSAdrian Hunter	def FindDone(self, ids):
917ebd70c7dSAdrian Hunter		found = True
918ebd70c7dSAdrian Hunter		if not self.DisplayFound(ids):
919ebd70c7dSAdrian Hunter			found = False
920ebd70c7dSAdrian Hunter		self.find_bar.Idle()
921ebd70c7dSAdrian Hunter		if not found:
922ebd70c7dSAdrian Hunter			self.find_bar.NotFound()
923ebd70c7dSAdrian Hunter
924a731cc4cSAdrian Hunter
925a731cc4cSAdrian Hunter# Context-sensitive call graph window
926a731cc4cSAdrian Hunter
927a731cc4cSAdrian Hunterclass CallGraphWindow(TreeWindowBase):
928a731cc4cSAdrian Hunter
929a731cc4cSAdrian Hunter	def __init__(self, glb, parent=None):
930a731cc4cSAdrian Hunter		super(CallGraphWindow, self).__init__(parent)
931a731cc4cSAdrian Hunter
932a731cc4cSAdrian Hunter		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
933a731cc4cSAdrian Hunter
934a731cc4cSAdrian Hunter		self.view.setModel(self.model)
935a731cc4cSAdrian Hunter
936a731cc4cSAdrian Hunter		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
937a731cc4cSAdrian Hunter			self.view.setColumnWidth(c, w)
938a731cc4cSAdrian Hunter
939a731cc4cSAdrian Hunter		self.find_bar = FindBar(self, self)
940a731cc4cSAdrian Hunter
941a731cc4cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
942a731cc4cSAdrian Hunter
943a731cc4cSAdrian Hunter		self.setWidget(self.vbox.Widget())
944a731cc4cSAdrian Hunter
945a731cc4cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
946a731cc4cSAdrian Hunter
947ae8b887cSAdrian Hunter# Call tree window
948ae8b887cSAdrian Hunter
949ae8b887cSAdrian Hunterclass CallTreeWindow(TreeWindowBase):
950ae8b887cSAdrian Hunter
951ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
952ae8b887cSAdrian Hunter		super(CallTreeWindow, self).__init__(parent)
953ae8b887cSAdrian Hunter
954ae8b887cSAdrian Hunter		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
955ae8b887cSAdrian Hunter
956ae8b887cSAdrian Hunter		self.view.setModel(self.model)
957ae8b887cSAdrian Hunter
958ae8b887cSAdrian Hunter		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
959ae8b887cSAdrian Hunter			self.view.setColumnWidth(c, w)
960ae8b887cSAdrian Hunter
961ae8b887cSAdrian Hunter		self.find_bar = FindBar(self, self)
962ae8b887cSAdrian Hunter
963ae8b887cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
964ae8b887cSAdrian Hunter
965ae8b887cSAdrian Hunter		self.setWidget(self.vbox.Widget())
966ae8b887cSAdrian Hunter
967ae8b887cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
968ae8b887cSAdrian Hunter
9698392b74bSAdrian Hunter# Child data item  finder
9708392b74bSAdrian Hunter
9718392b74bSAdrian Hunterclass ChildDataItemFinder():
9728392b74bSAdrian Hunter
9738392b74bSAdrian Hunter	def __init__(self, root):
9748392b74bSAdrian Hunter		self.root = root
9758392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
9768392b74bSAdrian Hunter		self.rows = []
9778392b74bSAdrian Hunter		self.pos = 0
9788392b74bSAdrian Hunter
9798392b74bSAdrian Hunter	def FindSelect(self):
9808392b74bSAdrian Hunter		self.rows = []
9818392b74bSAdrian Hunter		if self.pattern:
9828392b74bSAdrian Hunter			pattern = re.compile(self.value)
9838392b74bSAdrian Hunter			for child in self.root.child_items:
9848392b74bSAdrian Hunter				for column_data in child.data:
9858392b74bSAdrian Hunter					if re.search(pattern, str(column_data)) is not None:
9868392b74bSAdrian Hunter						self.rows.append(child.row)
9878392b74bSAdrian Hunter						break
9888392b74bSAdrian Hunter		else:
9898392b74bSAdrian Hunter			for child in self.root.child_items:
9908392b74bSAdrian Hunter				for column_data in child.data:
9918392b74bSAdrian Hunter					if self.value in str(column_data):
9928392b74bSAdrian Hunter						self.rows.append(child.row)
9938392b74bSAdrian Hunter						break
9948392b74bSAdrian Hunter
9958392b74bSAdrian Hunter	def FindValue(self):
9968392b74bSAdrian Hunter		self.pos = 0
9978392b74bSAdrian Hunter		if self.last_value != self.value or self.pattern != self.last_pattern:
9988392b74bSAdrian Hunter			self.FindSelect()
9998392b74bSAdrian Hunter		if not len(self.rows):
10008392b74bSAdrian Hunter			return -1
10018392b74bSAdrian Hunter		return self.rows[self.pos]
10028392b74bSAdrian Hunter
10038392b74bSAdrian Hunter	def FindThread(self):
10048392b74bSAdrian Hunter		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
10058392b74bSAdrian Hunter			row = self.FindValue()
10068392b74bSAdrian Hunter		elif len(self.rows):
10078392b74bSAdrian Hunter			if self.direction > 0:
10088392b74bSAdrian Hunter				self.pos += 1
10098392b74bSAdrian Hunter				if self.pos >= len(self.rows):
10108392b74bSAdrian Hunter					self.pos = 0
10118392b74bSAdrian Hunter			else:
10128392b74bSAdrian Hunter				self.pos -= 1
10138392b74bSAdrian Hunter				if self.pos < 0:
10148392b74bSAdrian Hunter					self.pos = len(self.rows) - 1
10158392b74bSAdrian Hunter			row = self.rows[self.pos]
10168392b74bSAdrian Hunter		else:
10178392b74bSAdrian Hunter			row = -1
10188392b74bSAdrian Hunter		return (True, row)
10198392b74bSAdrian Hunter
10208392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
10218392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
10228392b74bSAdrian Hunter		# Use a thread so the UI is not blocked
10238392b74bSAdrian Hunter		thread = Thread(self.FindThread)
10248392b74bSAdrian Hunter		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
10258392b74bSAdrian Hunter		thread.start()
10268392b74bSAdrian Hunter
10278392b74bSAdrian Hunter	def FindDone(self, thread, callback, row):
10288392b74bSAdrian Hunter		callback(row)
10298392b74bSAdrian Hunter
10308392b74bSAdrian Hunter# Number of database records to fetch in one go
10318392b74bSAdrian Hunter
10328392b74bSAdrian Hunterglb_chunk_sz = 10000
10338392b74bSAdrian Hunter
10348392b74bSAdrian Hunter# Background process for SQL data fetcher
10358392b74bSAdrian Hunter
10368392b74bSAdrian Hunterclass SQLFetcherProcess():
10378392b74bSAdrian Hunter
10388392b74bSAdrian Hunter	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
10398392b74bSAdrian Hunter		# Need a unique connection name
10408392b74bSAdrian Hunter		conn_name = "SQLFetcher" + str(os.getpid())
10418392b74bSAdrian Hunter		self.db, dbname = dbref.Open(conn_name)
10428392b74bSAdrian Hunter		self.sql = sql
10438392b74bSAdrian Hunter		self.buffer = buffer
10448392b74bSAdrian Hunter		self.head = head
10458392b74bSAdrian Hunter		self.tail = tail
10468392b74bSAdrian Hunter		self.fetch_count = fetch_count
10478392b74bSAdrian Hunter		self.fetching_done = fetching_done
10488392b74bSAdrian Hunter		self.process_target = process_target
10498392b74bSAdrian Hunter		self.wait_event = wait_event
10508392b74bSAdrian Hunter		self.fetched_event = fetched_event
10518392b74bSAdrian Hunter		self.prep = prep
10528392b74bSAdrian Hunter		self.query = QSqlQuery(self.db)
10538392b74bSAdrian Hunter		self.query_limit = 0 if "$$last_id$$" in sql else 2
10548392b74bSAdrian Hunter		self.last_id = -1
10558392b74bSAdrian Hunter		self.fetched = 0
10568392b74bSAdrian Hunter		self.more = True
10578392b74bSAdrian Hunter		self.local_head = self.head.value
10588392b74bSAdrian Hunter		self.local_tail = self.tail.value
10598392b74bSAdrian Hunter
10608392b74bSAdrian Hunter	def Select(self):
10618392b74bSAdrian Hunter		if self.query_limit:
10628392b74bSAdrian Hunter			if self.query_limit == 1:
10638392b74bSAdrian Hunter				return
10648392b74bSAdrian Hunter			self.query_limit -= 1
10658392b74bSAdrian Hunter		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
10668392b74bSAdrian Hunter		QueryExec(self.query, stmt)
10678392b74bSAdrian Hunter
10688392b74bSAdrian Hunter	def Next(self):
10698392b74bSAdrian Hunter		if not self.query.next():
10708392b74bSAdrian Hunter			self.Select()
10718392b74bSAdrian Hunter			if not self.query.next():
10728392b74bSAdrian Hunter				return None
10738392b74bSAdrian Hunter		self.last_id = self.query.value(0)
10748392b74bSAdrian Hunter		return self.prep(self.query)
10758392b74bSAdrian Hunter
10768392b74bSAdrian Hunter	def WaitForTarget(self):
10778392b74bSAdrian Hunter		while True:
10788392b74bSAdrian Hunter			self.wait_event.clear()
10798392b74bSAdrian Hunter			target = self.process_target.value
10808392b74bSAdrian Hunter			if target > self.fetched or target < 0:
10818392b74bSAdrian Hunter				break
10828392b74bSAdrian Hunter			self.wait_event.wait()
10838392b74bSAdrian Hunter		return target
10848392b74bSAdrian Hunter
10858392b74bSAdrian Hunter	def HasSpace(self, sz):
10868392b74bSAdrian Hunter		if self.local_tail <= self.local_head:
10878392b74bSAdrian Hunter			space = len(self.buffer) - self.local_head
10888392b74bSAdrian Hunter			if space > sz:
10898392b74bSAdrian Hunter				return True
10908392b74bSAdrian Hunter			if space >= glb_nsz:
10918392b74bSAdrian Hunter				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1092beda0e72STony Jones				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
10938392b74bSAdrian Hunter				self.buffer[self.local_head : self.local_head + len(nd)] = nd
10948392b74bSAdrian Hunter			self.local_head = 0
10958392b74bSAdrian Hunter		if self.local_tail - self.local_head > sz:
10968392b74bSAdrian Hunter			return True
10978392b74bSAdrian Hunter		return False
10988392b74bSAdrian Hunter
10998392b74bSAdrian Hunter	def WaitForSpace(self, sz):
11008392b74bSAdrian Hunter		if self.HasSpace(sz):
11018392b74bSAdrian Hunter			return
11028392b74bSAdrian Hunter		while True:
11038392b74bSAdrian Hunter			self.wait_event.clear()
11048392b74bSAdrian Hunter			self.local_tail = self.tail.value
11058392b74bSAdrian Hunter			if self.HasSpace(sz):
11068392b74bSAdrian Hunter				return
11078392b74bSAdrian Hunter			self.wait_event.wait()
11088392b74bSAdrian Hunter
11098392b74bSAdrian Hunter	def AddToBuffer(self, obj):
1110beda0e72STony Jones		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
11118392b74bSAdrian Hunter		n = len(d)
1112beda0e72STony Jones		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
11138392b74bSAdrian Hunter		sz = n + glb_nsz
11148392b74bSAdrian Hunter		self.WaitForSpace(sz)
11158392b74bSAdrian Hunter		pos = self.local_head
11168392b74bSAdrian Hunter		self.buffer[pos : pos + len(nd)] = nd
11178392b74bSAdrian Hunter		self.buffer[pos + glb_nsz : pos + sz] = d
11188392b74bSAdrian Hunter		self.local_head += sz
11198392b74bSAdrian Hunter
11208392b74bSAdrian Hunter	def FetchBatch(self, batch_size):
11218392b74bSAdrian Hunter		fetched = 0
11228392b74bSAdrian Hunter		while batch_size > fetched:
11238392b74bSAdrian Hunter			obj = self.Next()
11248392b74bSAdrian Hunter			if obj is None:
11258392b74bSAdrian Hunter				self.more = False
11268392b74bSAdrian Hunter				break
11278392b74bSAdrian Hunter			self.AddToBuffer(obj)
11288392b74bSAdrian Hunter			fetched += 1
11298392b74bSAdrian Hunter		if fetched:
11308392b74bSAdrian Hunter			self.fetched += fetched
11318392b74bSAdrian Hunter			with self.fetch_count.get_lock():
11328392b74bSAdrian Hunter				self.fetch_count.value += fetched
11338392b74bSAdrian Hunter			self.head.value = self.local_head
11348392b74bSAdrian Hunter			self.fetched_event.set()
11358392b74bSAdrian Hunter
11368392b74bSAdrian Hunter	def Run(self):
11378392b74bSAdrian Hunter		while self.more:
11388392b74bSAdrian Hunter			target = self.WaitForTarget()
11398392b74bSAdrian Hunter			if target < 0:
11408392b74bSAdrian Hunter				break
11418392b74bSAdrian Hunter			batch_size = min(glb_chunk_sz, target - self.fetched)
11428392b74bSAdrian Hunter			self.FetchBatch(batch_size)
11438392b74bSAdrian Hunter		self.fetching_done.value = True
11448392b74bSAdrian Hunter		self.fetched_event.set()
11458392b74bSAdrian Hunter
11468392b74bSAdrian Hunterdef SQLFetcherFn(*x):
11478392b74bSAdrian Hunter	process = SQLFetcherProcess(*x)
11488392b74bSAdrian Hunter	process.Run()
11498392b74bSAdrian Hunter
11508392b74bSAdrian Hunter# SQL data fetcher
11518392b74bSAdrian Hunter
11528392b74bSAdrian Hunterclass SQLFetcher(QObject):
11538392b74bSAdrian Hunter
11548392b74bSAdrian Hunter	done = Signal(object)
11558392b74bSAdrian Hunter
11568392b74bSAdrian Hunter	def __init__(self, glb, sql, prep, process_data, parent=None):
11578392b74bSAdrian Hunter		super(SQLFetcher, self).__init__(parent)
11588392b74bSAdrian Hunter		self.process_data = process_data
11598392b74bSAdrian Hunter		self.more = True
11608392b74bSAdrian Hunter		self.target = 0
11618392b74bSAdrian Hunter		self.last_target = 0
11628392b74bSAdrian Hunter		self.fetched = 0
11638392b74bSAdrian Hunter		self.buffer_size = 16 * 1024 * 1024
11648392b74bSAdrian Hunter		self.buffer = Array(c_char, self.buffer_size, lock=False)
11658392b74bSAdrian Hunter		self.head = Value(c_longlong)
11668392b74bSAdrian Hunter		self.tail = Value(c_longlong)
11678392b74bSAdrian Hunter		self.local_tail = 0
11688392b74bSAdrian Hunter		self.fetch_count = Value(c_longlong)
11698392b74bSAdrian Hunter		self.fetching_done = Value(c_bool)
11708392b74bSAdrian Hunter		self.last_count = 0
11718392b74bSAdrian Hunter		self.process_target = Value(c_longlong)
11728392b74bSAdrian Hunter		self.wait_event = Event()
11738392b74bSAdrian Hunter		self.fetched_event = Event()
11748392b74bSAdrian Hunter		glb.AddInstanceToShutdownOnExit(self)
11758392b74bSAdrian 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))
11768392b74bSAdrian Hunter		self.process.start()
11778392b74bSAdrian Hunter		self.thread = Thread(self.Thread)
11788392b74bSAdrian Hunter		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
11798392b74bSAdrian Hunter		self.thread.start()
11808392b74bSAdrian Hunter
11818392b74bSAdrian Hunter	def Shutdown(self):
11828392b74bSAdrian Hunter		# Tell the thread and process to exit
11838392b74bSAdrian Hunter		self.process_target.value = -1
11848392b74bSAdrian Hunter		self.wait_event.set()
11858392b74bSAdrian Hunter		self.more = False
11868392b74bSAdrian Hunter		self.fetching_done.value = True
11878392b74bSAdrian Hunter		self.fetched_event.set()
11888392b74bSAdrian Hunter
11898392b74bSAdrian Hunter	def Thread(self):
11908392b74bSAdrian Hunter		if not self.more:
11918392b74bSAdrian Hunter			return True, 0
11928392b74bSAdrian Hunter		while True:
11938392b74bSAdrian Hunter			self.fetched_event.clear()
11948392b74bSAdrian Hunter			fetch_count = self.fetch_count.value
11958392b74bSAdrian Hunter			if fetch_count != self.last_count:
11968392b74bSAdrian Hunter				break
11978392b74bSAdrian Hunter			if self.fetching_done.value:
11988392b74bSAdrian Hunter				self.more = False
11998392b74bSAdrian Hunter				return True, 0
12008392b74bSAdrian Hunter			self.fetched_event.wait()
12018392b74bSAdrian Hunter		count = fetch_count - self.last_count
12028392b74bSAdrian Hunter		self.last_count = fetch_count
12038392b74bSAdrian Hunter		self.fetched += count
12048392b74bSAdrian Hunter		return False, count
12058392b74bSAdrian Hunter
12068392b74bSAdrian Hunter	def Fetch(self, nr):
12078392b74bSAdrian Hunter		if not self.more:
12088392b74bSAdrian Hunter			# -1 inidcates there are no more
12098392b74bSAdrian Hunter			return -1
12108392b74bSAdrian Hunter		result = self.fetched
12118392b74bSAdrian Hunter		extra = result + nr - self.target
12128392b74bSAdrian Hunter		if extra > 0:
12138392b74bSAdrian Hunter			self.target += extra
12148392b74bSAdrian Hunter			# process_target < 0 indicates shutting down
12158392b74bSAdrian Hunter			if self.process_target.value >= 0:
12168392b74bSAdrian Hunter				self.process_target.value = self.target
12178392b74bSAdrian Hunter			self.wait_event.set()
12188392b74bSAdrian Hunter		return result
12198392b74bSAdrian Hunter
12208392b74bSAdrian Hunter	def RemoveFromBuffer(self):
12218392b74bSAdrian Hunter		pos = self.local_tail
12228392b74bSAdrian Hunter		if len(self.buffer) - pos < glb_nsz:
12238392b74bSAdrian Hunter			pos = 0
1224beda0e72STony Jones		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
12258392b74bSAdrian Hunter		if n == 0:
12268392b74bSAdrian Hunter			pos = 0
1227beda0e72STony Jones			n = pickle.loads(self.buffer[0 : glb_nsz])
12288392b74bSAdrian Hunter		pos += glb_nsz
1229beda0e72STony Jones		obj = pickle.loads(self.buffer[pos : pos + n])
12308392b74bSAdrian Hunter		self.local_tail = pos + n
12318392b74bSAdrian Hunter		return obj
12328392b74bSAdrian Hunter
12338392b74bSAdrian Hunter	def ProcessData(self, count):
12348392b74bSAdrian Hunter		for i in xrange(count):
12358392b74bSAdrian Hunter			obj = self.RemoveFromBuffer()
12368392b74bSAdrian Hunter			self.process_data(obj)
12378392b74bSAdrian Hunter		self.tail.value = self.local_tail
12388392b74bSAdrian Hunter		self.wait_event.set()
12398392b74bSAdrian Hunter		self.done.emit(count)
12408392b74bSAdrian Hunter
12418392b74bSAdrian Hunter# Fetch more records bar
12428392b74bSAdrian Hunter
12438392b74bSAdrian Hunterclass FetchMoreRecordsBar():
12448392b74bSAdrian Hunter
12458392b74bSAdrian Hunter	def __init__(self, model, parent):
12468392b74bSAdrian Hunter		self.model = model
12478392b74bSAdrian Hunter
12488392b74bSAdrian Hunter		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
12498392b74bSAdrian Hunter		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12508392b74bSAdrian Hunter
12518392b74bSAdrian Hunter		self.fetch_count = QSpinBox()
12528392b74bSAdrian Hunter		self.fetch_count.setRange(1, 1000000)
12538392b74bSAdrian Hunter		self.fetch_count.setValue(10)
12548392b74bSAdrian Hunter		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12558392b74bSAdrian Hunter
12568392b74bSAdrian Hunter		self.fetch = QPushButton("Go!")
12578392b74bSAdrian Hunter		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12588392b74bSAdrian Hunter		self.fetch.released.connect(self.FetchMoreRecords)
12598392b74bSAdrian Hunter
12608392b74bSAdrian Hunter		self.progress = QProgressBar()
12618392b74bSAdrian Hunter		self.progress.setRange(0, 100)
12628392b74bSAdrian Hunter		self.progress.hide()
12638392b74bSAdrian Hunter
12648392b74bSAdrian Hunter		self.done_label = QLabel("All records fetched")
12658392b74bSAdrian Hunter		self.done_label.hide()
12668392b74bSAdrian Hunter
12678392b74bSAdrian Hunter		self.spacer = QLabel("")
12688392b74bSAdrian Hunter
12698392b74bSAdrian Hunter		self.close_button = QToolButton()
12708392b74bSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
12718392b74bSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
12728392b74bSAdrian Hunter
12738392b74bSAdrian Hunter		self.hbox = QHBoxLayout()
12748392b74bSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
12758392b74bSAdrian Hunter
12768392b74bSAdrian Hunter		self.hbox.addWidget(self.label)
12778392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch_count)
12788392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch)
12798392b74bSAdrian Hunter		self.hbox.addWidget(self.spacer)
12808392b74bSAdrian Hunter		self.hbox.addWidget(self.progress)
12818392b74bSAdrian Hunter		self.hbox.addWidget(self.done_label)
12828392b74bSAdrian Hunter		self.hbox.addWidget(self.close_button)
12838392b74bSAdrian Hunter
12848392b74bSAdrian Hunter		self.bar = QWidget()
12858392b74bSAdrian Hunter		self.bar.setLayout(self.hbox);
12868392b74bSAdrian Hunter		self.bar.show()
12878392b74bSAdrian Hunter
12888392b74bSAdrian Hunter		self.in_progress = False
12898392b74bSAdrian Hunter		self.model.progress.connect(self.Progress)
12908392b74bSAdrian Hunter
12918392b74bSAdrian Hunter		self.done = False
12928392b74bSAdrian Hunter
12938392b74bSAdrian Hunter		if not model.HasMoreRecords():
12948392b74bSAdrian Hunter			self.Done()
12958392b74bSAdrian Hunter
12968392b74bSAdrian Hunter	def Widget(self):
12978392b74bSAdrian Hunter		return self.bar
12988392b74bSAdrian Hunter
12998392b74bSAdrian Hunter	def Activate(self):
13008392b74bSAdrian Hunter		self.bar.show()
13018392b74bSAdrian Hunter		self.fetch.setFocus()
13028392b74bSAdrian Hunter
13038392b74bSAdrian Hunter	def Deactivate(self):
13048392b74bSAdrian Hunter		self.bar.hide()
13058392b74bSAdrian Hunter
13068392b74bSAdrian Hunter	def Enable(self, enable):
13078392b74bSAdrian Hunter		self.fetch.setEnabled(enable)
13088392b74bSAdrian Hunter		self.fetch_count.setEnabled(enable)
13098392b74bSAdrian Hunter
13108392b74bSAdrian Hunter	def Busy(self):
13118392b74bSAdrian Hunter		self.Enable(False)
13128392b74bSAdrian Hunter		self.fetch.hide()
13138392b74bSAdrian Hunter		self.spacer.hide()
13148392b74bSAdrian Hunter		self.progress.show()
13158392b74bSAdrian Hunter
13168392b74bSAdrian Hunter	def Idle(self):
13178392b74bSAdrian Hunter		self.in_progress = False
13188392b74bSAdrian Hunter		self.Enable(True)
13198392b74bSAdrian Hunter		self.progress.hide()
13208392b74bSAdrian Hunter		self.fetch.show()
13218392b74bSAdrian Hunter		self.spacer.show()
13228392b74bSAdrian Hunter
13238392b74bSAdrian Hunter	def Target(self):
13248392b74bSAdrian Hunter		return self.fetch_count.value() * glb_chunk_sz
13258392b74bSAdrian Hunter
13268392b74bSAdrian Hunter	def Done(self):
13278392b74bSAdrian Hunter		self.done = True
13288392b74bSAdrian Hunter		self.Idle()
13298392b74bSAdrian Hunter		self.label.hide()
13308392b74bSAdrian Hunter		self.fetch_count.hide()
13318392b74bSAdrian Hunter		self.fetch.hide()
13328392b74bSAdrian Hunter		self.spacer.hide()
13338392b74bSAdrian Hunter		self.done_label.show()
13348392b74bSAdrian Hunter
13358392b74bSAdrian Hunter	def Progress(self, count):
13368392b74bSAdrian Hunter		if self.in_progress:
13378392b74bSAdrian Hunter			if count:
13388392b74bSAdrian Hunter				percent = ((count - self.start) * 100) / self.Target()
13398392b74bSAdrian Hunter				if percent >= 100:
13408392b74bSAdrian Hunter					self.Idle()
13418392b74bSAdrian Hunter				else:
13428392b74bSAdrian Hunter					self.progress.setValue(percent)
13438392b74bSAdrian Hunter		if not count:
13448392b74bSAdrian Hunter			# Count value of zero means no more records
13458392b74bSAdrian Hunter			self.Done()
13468392b74bSAdrian Hunter
13478392b74bSAdrian Hunter	def FetchMoreRecords(self):
13488392b74bSAdrian Hunter		if self.done:
13498392b74bSAdrian Hunter			return
13508392b74bSAdrian Hunter		self.progress.setValue(0)
13518392b74bSAdrian Hunter		self.Busy()
13528392b74bSAdrian Hunter		self.in_progress = True
13538392b74bSAdrian Hunter		self.start = self.model.FetchMoreRecords(self.Target())
13548392b74bSAdrian Hunter
135576099f98SAdrian Hunter# Brance data model level two item
135676099f98SAdrian Hunter
135776099f98SAdrian Hunterclass BranchLevelTwoItem():
135876099f98SAdrian Hunter
135976099f98SAdrian Hunter	def __init__(self, row, text, parent_item):
136076099f98SAdrian Hunter		self.row = row
136176099f98SAdrian Hunter		self.parent_item = parent_item
136276099f98SAdrian Hunter		self.data = [""] * 8
136376099f98SAdrian Hunter		self.data[7] = text
136476099f98SAdrian Hunter		self.level = 2
136576099f98SAdrian Hunter
136676099f98SAdrian Hunter	def getParentItem(self):
136776099f98SAdrian Hunter		return self.parent_item
136876099f98SAdrian Hunter
136976099f98SAdrian Hunter	def getRow(self):
137076099f98SAdrian Hunter		return self.row
137176099f98SAdrian Hunter
137276099f98SAdrian Hunter	def childCount(self):
137376099f98SAdrian Hunter		return 0
137476099f98SAdrian Hunter
137576099f98SAdrian Hunter	def hasChildren(self):
137676099f98SAdrian Hunter		return False
137776099f98SAdrian Hunter
137876099f98SAdrian Hunter	def getData(self, column):
137976099f98SAdrian Hunter		return self.data[column]
138076099f98SAdrian Hunter
138176099f98SAdrian Hunter# Brance data model level one item
138276099f98SAdrian Hunter
138376099f98SAdrian Hunterclass BranchLevelOneItem():
138476099f98SAdrian Hunter
138576099f98SAdrian Hunter	def __init__(self, glb, row, data, parent_item):
138676099f98SAdrian Hunter		self.glb = glb
138776099f98SAdrian Hunter		self.row = row
138876099f98SAdrian Hunter		self.parent_item = parent_item
138976099f98SAdrian Hunter		self.child_count = 0
139076099f98SAdrian Hunter		self.child_items = []
139176099f98SAdrian Hunter		self.data = data[1:]
139276099f98SAdrian Hunter		self.dbid = data[0]
139376099f98SAdrian Hunter		self.level = 1
139476099f98SAdrian Hunter		self.query_done = False
139576099f98SAdrian Hunter
139676099f98SAdrian Hunter	def getChildItem(self, row):
139776099f98SAdrian Hunter		return self.child_items[row]
139876099f98SAdrian Hunter
139976099f98SAdrian Hunter	def getParentItem(self):
140076099f98SAdrian Hunter		return self.parent_item
140176099f98SAdrian Hunter
140276099f98SAdrian Hunter	def getRow(self):
140376099f98SAdrian Hunter		return self.row
140476099f98SAdrian Hunter
140576099f98SAdrian Hunter	def Select(self):
140676099f98SAdrian Hunter		self.query_done = True
140776099f98SAdrian Hunter
140876099f98SAdrian Hunter		if not self.glb.have_disassembler:
140976099f98SAdrian Hunter			return
141076099f98SAdrian Hunter
141176099f98SAdrian Hunter		query = QSqlQuery(self.glb.db)
141276099f98SAdrian Hunter
141376099f98SAdrian Hunter		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
141476099f98SAdrian Hunter				  " FROM samples"
141576099f98SAdrian Hunter				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
141676099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
141776099f98SAdrian Hunter				  " WHERE samples.id = " + str(self.dbid))
141876099f98SAdrian Hunter		if not query.next():
141976099f98SAdrian Hunter			return
142076099f98SAdrian Hunter		cpu = query.value(0)
142176099f98SAdrian Hunter		dso = query.value(1)
142276099f98SAdrian Hunter		sym = query.value(2)
142376099f98SAdrian Hunter		if dso == 0 or sym == 0:
142476099f98SAdrian Hunter			return
142576099f98SAdrian Hunter		off = query.value(3)
142676099f98SAdrian Hunter		short_name = query.value(4)
142776099f98SAdrian Hunter		long_name = query.value(5)
142876099f98SAdrian Hunter		build_id = query.value(6)
142976099f98SAdrian Hunter		sym_start = query.value(7)
143076099f98SAdrian Hunter		ip = query.value(8)
143176099f98SAdrian Hunter
143276099f98SAdrian Hunter		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
143376099f98SAdrian Hunter				  " FROM samples"
143476099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
143576099f98SAdrian Hunter				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
143676099f98SAdrian Hunter				  " ORDER BY samples.id"
143776099f98SAdrian Hunter				  " LIMIT 1")
143876099f98SAdrian Hunter		if not query.next():
143976099f98SAdrian Hunter			return
144076099f98SAdrian Hunter		if query.value(0) != dso:
144176099f98SAdrian Hunter			# Cannot disassemble from one dso to another
144276099f98SAdrian Hunter			return
144376099f98SAdrian Hunter		bsym = query.value(1)
144476099f98SAdrian Hunter		boff = query.value(2)
144576099f98SAdrian Hunter		bsym_start = query.value(3)
144676099f98SAdrian Hunter		if bsym == 0:
144776099f98SAdrian Hunter			return
144876099f98SAdrian Hunter		tot = bsym_start + boff + 1 - sym_start - off
144976099f98SAdrian Hunter		if tot <= 0 or tot > 16384:
145076099f98SAdrian Hunter			return
145176099f98SAdrian Hunter
145276099f98SAdrian Hunter		inst = self.glb.disassembler.Instruction()
145376099f98SAdrian Hunter		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
145476099f98SAdrian Hunter		if not f:
145576099f98SAdrian Hunter			return
145676099f98SAdrian Hunter		mode = 0 if Is64Bit(f) else 1
145776099f98SAdrian Hunter		self.glb.disassembler.SetMode(inst, mode)
145876099f98SAdrian Hunter
145976099f98SAdrian Hunter		buf_sz = tot + 16
146076099f98SAdrian Hunter		buf = create_string_buffer(tot + 16)
146176099f98SAdrian Hunter		f.seek(sym_start + off)
146276099f98SAdrian Hunter		buf.value = f.read(buf_sz)
146376099f98SAdrian Hunter		buf_ptr = addressof(buf)
146476099f98SAdrian Hunter		i = 0
146576099f98SAdrian Hunter		while tot > 0:
146676099f98SAdrian Hunter			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
146776099f98SAdrian Hunter			if cnt:
146876099f98SAdrian Hunter				byte_str = tohex(ip).rjust(16)
146976099f98SAdrian Hunter				for k in xrange(cnt):
147076099f98SAdrian Hunter					byte_str += " %02x" % ord(buf[i])
147176099f98SAdrian Hunter					i += 1
147276099f98SAdrian Hunter				while k < 15:
147376099f98SAdrian Hunter					byte_str += "   "
147476099f98SAdrian Hunter					k += 1
147576099f98SAdrian Hunter				self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
147676099f98SAdrian Hunter				self.child_count += 1
147776099f98SAdrian Hunter			else:
147876099f98SAdrian Hunter				return
147976099f98SAdrian Hunter			buf_ptr += cnt
148076099f98SAdrian Hunter			tot -= cnt
148176099f98SAdrian Hunter			buf_sz -= cnt
148276099f98SAdrian Hunter			ip += cnt
148376099f98SAdrian Hunter
148476099f98SAdrian Hunter	def childCount(self):
148576099f98SAdrian Hunter		if not self.query_done:
148676099f98SAdrian Hunter			self.Select()
148776099f98SAdrian Hunter			if not self.child_count:
148876099f98SAdrian Hunter				return -1
148976099f98SAdrian Hunter		return self.child_count
149076099f98SAdrian Hunter
149176099f98SAdrian Hunter	def hasChildren(self):
149276099f98SAdrian Hunter		if not self.query_done:
149376099f98SAdrian Hunter			return True
149476099f98SAdrian Hunter		return self.child_count > 0
149576099f98SAdrian Hunter
149676099f98SAdrian Hunter	def getData(self, column):
149776099f98SAdrian Hunter		return self.data[column]
149876099f98SAdrian Hunter
149976099f98SAdrian Hunter# Brance data model root item
150076099f98SAdrian Hunter
150176099f98SAdrian Hunterclass BranchRootItem():
150276099f98SAdrian Hunter
150376099f98SAdrian Hunter	def __init__(self):
150476099f98SAdrian Hunter		self.child_count = 0
150576099f98SAdrian Hunter		self.child_items = []
150676099f98SAdrian Hunter		self.level = 0
150776099f98SAdrian Hunter
150876099f98SAdrian Hunter	def getChildItem(self, row):
150976099f98SAdrian Hunter		return self.child_items[row]
151076099f98SAdrian Hunter
151176099f98SAdrian Hunter	def getParentItem(self):
151276099f98SAdrian Hunter		return None
151376099f98SAdrian Hunter
151476099f98SAdrian Hunter	def getRow(self):
151576099f98SAdrian Hunter		return 0
151676099f98SAdrian Hunter
151776099f98SAdrian Hunter	def childCount(self):
151876099f98SAdrian Hunter		return self.child_count
151976099f98SAdrian Hunter
152076099f98SAdrian Hunter	def hasChildren(self):
152176099f98SAdrian Hunter		return self.child_count > 0
152276099f98SAdrian Hunter
152376099f98SAdrian Hunter	def getData(self, column):
152476099f98SAdrian Hunter		return ""
152576099f98SAdrian Hunter
152676099f98SAdrian Hunter# Branch data preparation
152776099f98SAdrian Hunter
152876099f98SAdrian Hunterdef BranchDataPrep(query):
152976099f98SAdrian Hunter	data = []
153076099f98SAdrian Hunter	for i in xrange(0, 8):
153176099f98SAdrian Hunter		data.append(query.value(i))
153276099f98SAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
153376099f98SAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
153476099f98SAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
153576099f98SAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
153676099f98SAdrian Hunter	return data
153776099f98SAdrian Hunter
15388453c936SAdrian Hunterdef BranchDataPrepWA(query):
15398453c936SAdrian Hunter	data = []
15408453c936SAdrian Hunter	data.append(query.value(0))
15418453c936SAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
15428453c936SAdrian Hunter	data.append("{:>19}".format(query.value(1)))
15438453c936SAdrian Hunter	for i in xrange(2, 8):
15448453c936SAdrian Hunter		data.append(query.value(i))
15458453c936SAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
15468453c936SAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
15478453c936SAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
15488453c936SAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
15498453c936SAdrian Hunter	return data
15508453c936SAdrian Hunter
155176099f98SAdrian Hunter# Branch data model
155276099f98SAdrian Hunter
155376099f98SAdrian Hunterclass BranchModel(TreeModel):
155476099f98SAdrian Hunter
155576099f98SAdrian Hunter	progress = Signal(object)
155676099f98SAdrian Hunter
155776099f98SAdrian Hunter	def __init__(self, glb, event_id, where_clause, parent=None):
1558a448ba23SAdrian Hunter		super(BranchModel, self).__init__(glb, parent)
155976099f98SAdrian Hunter		self.event_id = event_id
156076099f98SAdrian Hunter		self.more = True
156176099f98SAdrian Hunter		self.populated = 0
156276099f98SAdrian Hunter		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
156376099f98SAdrian Hunter			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
156476099f98SAdrian Hunter			" ip, symbols.name, sym_offset, dsos.short_name,"
156576099f98SAdrian Hunter			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
156676099f98SAdrian Hunter			" FROM samples"
156776099f98SAdrian Hunter			" INNER JOIN comms ON comm_id = comms.id"
156876099f98SAdrian Hunter			" INNER JOIN threads ON thread_id = threads.id"
156976099f98SAdrian Hunter			" INNER JOIN branch_types ON branch_type = branch_types.id"
157076099f98SAdrian Hunter			" INNER JOIN symbols ON symbol_id = symbols.id"
157176099f98SAdrian Hunter			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
157276099f98SAdrian Hunter			" INNER JOIN dsos ON samples.dso_id = dsos.id"
157376099f98SAdrian Hunter			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
157476099f98SAdrian Hunter			" WHERE samples.id > $$last_id$$" + where_clause +
157576099f98SAdrian Hunter			" AND evsel_id = " + str(self.event_id) +
157676099f98SAdrian Hunter			" ORDER BY samples.id"
157776099f98SAdrian Hunter			" LIMIT " + str(glb_chunk_sz))
15788453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
15798453c936SAdrian Hunter			prep = BranchDataPrepWA
15808453c936SAdrian Hunter		else:
15818453c936SAdrian Hunter			prep = BranchDataPrep
15828453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
158376099f98SAdrian Hunter		self.fetcher.done.connect(self.Update)
158476099f98SAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
158576099f98SAdrian Hunter
1586a448ba23SAdrian Hunter	def GetRoot(self):
1587a448ba23SAdrian Hunter		return BranchRootItem()
1588a448ba23SAdrian Hunter
158976099f98SAdrian Hunter	def columnCount(self, parent=None):
159076099f98SAdrian Hunter		return 8
159176099f98SAdrian Hunter
159276099f98SAdrian Hunter	def columnHeader(self, column):
159376099f98SAdrian Hunter		return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
159476099f98SAdrian Hunter
159576099f98SAdrian Hunter	def columnFont(self, column):
159676099f98SAdrian Hunter		if column != 7:
159776099f98SAdrian Hunter			return None
159876099f98SAdrian Hunter		return QFont("Monospace")
159976099f98SAdrian Hunter
160076099f98SAdrian Hunter	def DisplayData(self, item, index):
160176099f98SAdrian Hunter		if item.level == 1:
160276099f98SAdrian Hunter			self.FetchIfNeeded(item.row)
160376099f98SAdrian Hunter		return item.getData(index.column())
160476099f98SAdrian Hunter
160576099f98SAdrian Hunter	def AddSample(self, data):
160676099f98SAdrian Hunter		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
160776099f98SAdrian Hunter		self.root.child_items.append(child)
160876099f98SAdrian Hunter		self.populated += 1
160976099f98SAdrian Hunter
161076099f98SAdrian Hunter	def Update(self, fetched):
161176099f98SAdrian Hunter		if not fetched:
161276099f98SAdrian Hunter			self.more = False
161376099f98SAdrian Hunter			self.progress.emit(0)
161476099f98SAdrian Hunter		child_count = self.root.child_count
161576099f98SAdrian Hunter		count = self.populated - child_count
161676099f98SAdrian Hunter		if count > 0:
161776099f98SAdrian Hunter			parent = QModelIndex()
161876099f98SAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
161976099f98SAdrian Hunter			self.insertRows(child_count, count, parent)
162076099f98SAdrian Hunter			self.root.child_count += count
162176099f98SAdrian Hunter			self.endInsertRows()
162276099f98SAdrian Hunter			self.progress.emit(self.root.child_count)
162376099f98SAdrian Hunter
162476099f98SAdrian Hunter	def FetchMoreRecords(self, count):
162576099f98SAdrian Hunter		current = self.root.child_count
162676099f98SAdrian Hunter		if self.more:
162776099f98SAdrian Hunter			self.fetcher.Fetch(count)
162876099f98SAdrian Hunter		else:
162976099f98SAdrian Hunter			self.progress.emit(0)
163076099f98SAdrian Hunter		return current
163176099f98SAdrian Hunter
163276099f98SAdrian Hunter	def HasMoreRecords(self):
163376099f98SAdrian Hunter		return self.more
163476099f98SAdrian Hunter
16350bf0947aSAdrian Hunter# Report Variables
16360bf0947aSAdrian Hunter
16370bf0947aSAdrian Hunterclass ReportVars():
16380bf0947aSAdrian Hunter
1639cd358012SAdrian Hunter	def __init__(self, name = "", where_clause = "", limit = ""):
1640947cc38dSAdrian Hunter		self.name = name
16410bf0947aSAdrian Hunter		self.where_clause = where_clause
1642cd358012SAdrian Hunter		self.limit = limit
16430bf0947aSAdrian Hunter
16440bf0947aSAdrian Hunter	def UniqueId(self):
1645cd358012SAdrian Hunter		return str(self.where_clause + ";" + self.limit)
16460bf0947aSAdrian Hunter
164776099f98SAdrian Hunter# Branch window
164876099f98SAdrian Hunter
164976099f98SAdrian Hunterclass BranchWindow(QMdiSubWindow):
165076099f98SAdrian Hunter
1651947cc38dSAdrian Hunter	def __init__(self, glb, event_id, report_vars, parent=None):
165276099f98SAdrian Hunter		super(BranchWindow, self).__init__(parent)
165376099f98SAdrian Hunter
16540bf0947aSAdrian Hunter		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
165576099f98SAdrian Hunter
16560bf0947aSAdrian Hunter		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
165776099f98SAdrian Hunter
165876099f98SAdrian Hunter		self.view = QTreeView()
165976099f98SAdrian Hunter		self.view.setUniformRowHeights(True)
166096c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
166196c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
166276099f98SAdrian Hunter		self.view.setModel(self.model)
166376099f98SAdrian Hunter
166476099f98SAdrian Hunter		self.ResizeColumnsToContents()
166576099f98SAdrian Hunter
16669bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
16679bc4e4bfSAdrian Hunter
166876099f98SAdrian Hunter		self.find_bar = FindBar(self, self, True)
166976099f98SAdrian Hunter
167076099f98SAdrian Hunter		self.finder = ChildDataItemFinder(self.model.root)
167176099f98SAdrian Hunter
167276099f98SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
167376099f98SAdrian Hunter
167476099f98SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
167576099f98SAdrian Hunter
167676099f98SAdrian Hunter		self.setWidget(self.vbox.Widget())
167776099f98SAdrian Hunter
1678947cc38dSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
167976099f98SAdrian Hunter
168076099f98SAdrian Hunter	def ResizeColumnToContents(self, column, n):
168176099f98SAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
168276099f98SAdrian Hunter		# so implement a crude alternative
168376099f98SAdrian Hunter		mm = "MM" if column else "MMMM"
168476099f98SAdrian Hunter		font = self.view.font()
168576099f98SAdrian Hunter		metrics = QFontMetrics(font)
168676099f98SAdrian Hunter		max = 0
168776099f98SAdrian Hunter		for row in xrange(n):
168876099f98SAdrian Hunter			val = self.model.root.child_items[row].data[column]
168976099f98SAdrian Hunter			len = metrics.width(str(val) + mm)
169076099f98SAdrian Hunter			max = len if len > max else max
169176099f98SAdrian Hunter		val = self.model.columnHeader(column)
169276099f98SAdrian Hunter		len = metrics.width(str(val) + mm)
169376099f98SAdrian Hunter		max = len if len > max else max
169476099f98SAdrian Hunter		self.view.setColumnWidth(column, max)
169576099f98SAdrian Hunter
169676099f98SAdrian Hunter	def ResizeColumnsToContents(self):
169776099f98SAdrian Hunter		n = min(self.model.root.child_count, 100)
169876099f98SAdrian Hunter		if n < 1:
169976099f98SAdrian Hunter			# No data yet, so connect a signal to notify when there is
170076099f98SAdrian Hunter			self.model.rowsInserted.connect(self.UpdateColumnWidths)
170176099f98SAdrian Hunter			return
170276099f98SAdrian Hunter		columns = self.model.columnCount()
170376099f98SAdrian Hunter		for i in xrange(columns):
170476099f98SAdrian Hunter			self.ResizeColumnToContents(i, n)
170576099f98SAdrian Hunter
170676099f98SAdrian Hunter	def UpdateColumnWidths(self, *x):
170776099f98SAdrian Hunter		# This only needs to be done once, so disconnect the signal now
170876099f98SAdrian Hunter		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
170976099f98SAdrian Hunter		self.ResizeColumnsToContents()
171076099f98SAdrian Hunter
171176099f98SAdrian Hunter	def Find(self, value, direction, pattern, context):
171276099f98SAdrian Hunter		self.view.setFocus()
171376099f98SAdrian Hunter		self.find_bar.Busy()
171476099f98SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
171576099f98SAdrian Hunter
171676099f98SAdrian Hunter	def FindDone(self, row):
171776099f98SAdrian Hunter		self.find_bar.Idle()
171876099f98SAdrian Hunter		if row >= 0:
171976099f98SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
172076099f98SAdrian Hunter		else:
172176099f98SAdrian Hunter			self.find_bar.NotFound()
172276099f98SAdrian Hunter
17231c3ca1b3SAdrian Hunter# Line edit data item
17241c3ca1b3SAdrian Hunter
17251c3ca1b3SAdrian Hunterclass LineEditDataItem(object):
17261c3ca1b3SAdrian Hunter
1727cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
17281c3ca1b3SAdrian Hunter		self.glb = glb
17291c3ca1b3SAdrian Hunter		self.label = label
17301c3ca1b3SAdrian Hunter		self.placeholder_text = placeholder_text
17311c3ca1b3SAdrian Hunter		self.parent = parent
17321c3ca1b3SAdrian Hunter		self.id = id
17331c3ca1b3SAdrian Hunter
1734cd358012SAdrian Hunter		self.value = default
17351c3ca1b3SAdrian Hunter
1736cd358012SAdrian Hunter		self.widget = QLineEdit(default)
17371c3ca1b3SAdrian Hunter		self.widget.editingFinished.connect(self.Validate)
17381c3ca1b3SAdrian Hunter		self.widget.textChanged.connect(self.Invalidate)
17391c3ca1b3SAdrian Hunter		self.red = False
17401c3ca1b3SAdrian Hunter		self.error = ""
17411c3ca1b3SAdrian Hunter		self.validated = True
17421c3ca1b3SAdrian Hunter
17431c3ca1b3SAdrian Hunter		if placeholder_text:
17441c3ca1b3SAdrian Hunter			self.widget.setPlaceholderText(placeholder_text)
17451c3ca1b3SAdrian Hunter
17461c3ca1b3SAdrian Hunter	def TurnTextRed(self):
17471c3ca1b3SAdrian Hunter		if not self.red:
17481c3ca1b3SAdrian Hunter			palette = QPalette()
17491c3ca1b3SAdrian Hunter			palette.setColor(QPalette.Text,Qt.red)
17501c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
17511c3ca1b3SAdrian Hunter			self.red = True
17521c3ca1b3SAdrian Hunter
17531c3ca1b3SAdrian Hunter	def TurnTextNormal(self):
17541c3ca1b3SAdrian Hunter		if self.red:
17551c3ca1b3SAdrian Hunter			palette = QPalette()
17561c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
17571c3ca1b3SAdrian Hunter			self.red = False
17581c3ca1b3SAdrian Hunter
17591c3ca1b3SAdrian Hunter	def InvalidValue(self, value):
17601c3ca1b3SAdrian Hunter		self.value = ""
17611c3ca1b3SAdrian Hunter		self.TurnTextRed()
17621c3ca1b3SAdrian Hunter		self.error = self.label + " invalid value '" + value + "'"
17631c3ca1b3SAdrian Hunter		self.parent.ShowMessage(self.error)
17641c3ca1b3SAdrian Hunter
17651c3ca1b3SAdrian Hunter	def Invalidate(self):
17661c3ca1b3SAdrian Hunter		self.validated = False
17671c3ca1b3SAdrian Hunter
17681c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
17691c3ca1b3SAdrian Hunter		self.value = input_string.strip()
17701c3ca1b3SAdrian Hunter
17711c3ca1b3SAdrian Hunter	def Validate(self):
17721c3ca1b3SAdrian Hunter		self.validated = True
17731c3ca1b3SAdrian Hunter		self.error = ""
17741c3ca1b3SAdrian Hunter		self.TurnTextNormal()
17751c3ca1b3SAdrian Hunter		self.parent.ClearMessage()
17761c3ca1b3SAdrian Hunter		input_string = self.widget.text()
17771c3ca1b3SAdrian Hunter		if not len(input_string.strip()):
17781c3ca1b3SAdrian Hunter			self.value = ""
17791c3ca1b3SAdrian Hunter			return
17801c3ca1b3SAdrian Hunter		self.DoValidate(input_string)
17811c3ca1b3SAdrian Hunter
17821c3ca1b3SAdrian Hunter	def IsValid(self):
17831c3ca1b3SAdrian Hunter		if not self.validated:
17841c3ca1b3SAdrian Hunter			self.Validate()
17851c3ca1b3SAdrian Hunter		if len(self.error):
17861c3ca1b3SAdrian Hunter			self.parent.ShowMessage(self.error)
17871c3ca1b3SAdrian Hunter			return False
17881c3ca1b3SAdrian Hunter		return True
17891c3ca1b3SAdrian Hunter
17901c3ca1b3SAdrian Hunter	def IsNumber(self, value):
17911c3ca1b3SAdrian Hunter		try:
17921c3ca1b3SAdrian Hunter			x = int(value)
17931c3ca1b3SAdrian Hunter		except:
17941c3ca1b3SAdrian Hunter			x = 0
17951c3ca1b3SAdrian Hunter		return str(x) == value
17961c3ca1b3SAdrian Hunter
17971c3ca1b3SAdrian Hunter# Non-negative integer ranges dialog data item
17981c3ca1b3SAdrian Hunter
17991c3ca1b3SAdrian Hunterclass NonNegativeIntegerRangesDataItem(LineEditDataItem):
18001c3ca1b3SAdrian Hunter
18011c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
18021c3ca1b3SAdrian Hunter		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
18031c3ca1b3SAdrian Hunter
18041c3ca1b3SAdrian Hunter		self.column_name = column_name
18051c3ca1b3SAdrian Hunter
18061c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
18071c3ca1b3SAdrian Hunter		singles = []
18081c3ca1b3SAdrian Hunter		ranges = []
18091c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
18101c3ca1b3SAdrian Hunter			if "-" in value:
18111c3ca1b3SAdrian Hunter				vrange = value.split("-")
18121c3ca1b3SAdrian Hunter				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
18131c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
18141c3ca1b3SAdrian Hunter				ranges.append(vrange)
18151c3ca1b3SAdrian Hunter			else:
18161c3ca1b3SAdrian Hunter				if not self.IsNumber(value):
18171c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
18181c3ca1b3SAdrian Hunter				singles.append(value)
18191c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
18201c3ca1b3SAdrian Hunter		if len(singles):
18211c3ca1b3SAdrian Hunter			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
18221c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
18231c3ca1b3SAdrian Hunter
1824cd358012SAdrian Hunter# Positive integer dialog data item
1825cd358012SAdrian Hunter
1826cd358012SAdrian Hunterclass PositiveIntegerDataItem(LineEditDataItem):
1827cd358012SAdrian Hunter
1828cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1829cd358012SAdrian Hunter		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1830cd358012SAdrian Hunter
1831cd358012SAdrian Hunter	def DoValidate(self, input_string):
1832cd358012SAdrian Hunter		if not self.IsNumber(input_string.strip()):
1833cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1834cd358012SAdrian Hunter		value = int(input_string.strip())
1835cd358012SAdrian Hunter		if value <= 0:
1836cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1837cd358012SAdrian Hunter		self.value = str(value)
1838cd358012SAdrian Hunter
18391c3ca1b3SAdrian Hunter# Dialog data item converted and validated using a SQL table
18401c3ca1b3SAdrian Hunter
18411c3ca1b3SAdrian Hunterclass SQLTableDataItem(LineEditDataItem):
18421c3ca1b3SAdrian Hunter
18431c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
18441c3ca1b3SAdrian Hunter		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
18451c3ca1b3SAdrian Hunter
18461c3ca1b3SAdrian Hunter		self.table_name = table_name
18471c3ca1b3SAdrian Hunter		self.match_column = match_column
18481c3ca1b3SAdrian Hunter		self.column_name1 = column_name1
18491c3ca1b3SAdrian Hunter		self.column_name2 = column_name2
18501c3ca1b3SAdrian Hunter
18511c3ca1b3SAdrian Hunter	def ValueToIds(self, value):
18521c3ca1b3SAdrian Hunter		ids = []
18531c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
18541c3ca1b3SAdrian Hunter		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
18551c3ca1b3SAdrian Hunter		ret = query.exec_(stmt)
18561c3ca1b3SAdrian Hunter		if ret:
18571c3ca1b3SAdrian Hunter			while query.next():
18581c3ca1b3SAdrian Hunter				ids.append(str(query.value(0)))
18591c3ca1b3SAdrian Hunter		return ids
18601c3ca1b3SAdrian Hunter
18611c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
18621c3ca1b3SAdrian Hunter		all_ids = []
18631c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
18641c3ca1b3SAdrian Hunter			ids = self.ValueToIds(value)
18651c3ca1b3SAdrian Hunter			if len(ids):
18661c3ca1b3SAdrian Hunter				all_ids.extend(ids)
18671c3ca1b3SAdrian Hunter			else:
18681c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
18691c3ca1b3SAdrian Hunter		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
18701c3ca1b3SAdrian Hunter		if self.column_name2:
18711c3ca1b3SAdrian Hunter			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
18721c3ca1b3SAdrian Hunter
18731c3ca1b3SAdrian Hunter# Sample time ranges dialog data item converted and validated using 'samples' SQL table
18741c3ca1b3SAdrian Hunter
18751c3ca1b3SAdrian Hunterclass SampleTimeRangesDataItem(LineEditDataItem):
18761c3ca1b3SAdrian Hunter
18771c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
18781c3ca1b3SAdrian Hunter		self.column_name = column_name
18791c3ca1b3SAdrian Hunter
18801c3ca1b3SAdrian Hunter		self.last_id = 0
18811c3ca1b3SAdrian Hunter		self.first_time = 0
18821c3ca1b3SAdrian Hunter		self.last_time = 2 ** 64
18831c3ca1b3SAdrian Hunter
18841c3ca1b3SAdrian Hunter		query = QSqlQuery(glb.db)
18851c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
18861c3ca1b3SAdrian Hunter		if query.next():
18871c3ca1b3SAdrian Hunter			self.last_id = int(query.value(0))
18881c3ca1b3SAdrian Hunter			self.last_time = int(query.value(1))
18891c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
18901c3ca1b3SAdrian Hunter		if query.next():
18911c3ca1b3SAdrian Hunter			self.first_time = int(query.value(0))
18921c3ca1b3SAdrian Hunter		if placeholder_text:
18931c3ca1b3SAdrian Hunter			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
18941c3ca1b3SAdrian Hunter
18951c3ca1b3SAdrian Hunter		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
18961c3ca1b3SAdrian Hunter
18971c3ca1b3SAdrian Hunter	def IdBetween(self, query, lower_id, higher_id, order):
18981c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
18991c3ca1b3SAdrian Hunter		if query.next():
19001c3ca1b3SAdrian Hunter			return True, int(query.value(0))
19011c3ca1b3SAdrian Hunter		else:
19021c3ca1b3SAdrian Hunter			return False, 0
19031c3ca1b3SAdrian Hunter
19041c3ca1b3SAdrian Hunter	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
19051c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
19061c3ca1b3SAdrian Hunter		while True:
19071c3ca1b3SAdrian Hunter			next_id = int((lower_id + higher_id) / 2)
19081c3ca1b3SAdrian Hunter			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
19091c3ca1b3SAdrian Hunter			if not query.next():
19101c3ca1b3SAdrian Hunter				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
19111c3ca1b3SAdrian Hunter				if not ok:
19121c3ca1b3SAdrian Hunter					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
19131c3ca1b3SAdrian Hunter					if not ok:
19141c3ca1b3SAdrian Hunter						return str(higher_id)
19151c3ca1b3SAdrian Hunter				next_id = dbid
19161c3ca1b3SAdrian Hunter				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
19171c3ca1b3SAdrian Hunter			next_time = int(query.value(0))
19181c3ca1b3SAdrian Hunter			if get_floor:
19191c3ca1b3SAdrian Hunter				if target_time > next_time:
19201c3ca1b3SAdrian Hunter					lower_id = next_id
19211c3ca1b3SAdrian Hunter				else:
19221c3ca1b3SAdrian Hunter					higher_id = next_id
19231c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
19241c3ca1b3SAdrian Hunter					return str(higher_id)
19251c3ca1b3SAdrian Hunter			else:
19261c3ca1b3SAdrian Hunter				if target_time >= next_time:
19271c3ca1b3SAdrian Hunter					lower_id = next_id
19281c3ca1b3SAdrian Hunter				else:
19291c3ca1b3SAdrian Hunter					higher_id = next_id
19301c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
19311c3ca1b3SAdrian Hunter					return str(lower_id)
19321c3ca1b3SAdrian Hunter
19331c3ca1b3SAdrian Hunter	def ConvertRelativeTime(self, val):
19341c3ca1b3SAdrian Hunter		mult = 1
19351c3ca1b3SAdrian Hunter		suffix = val[-2:]
19361c3ca1b3SAdrian Hunter		if suffix == "ms":
19371c3ca1b3SAdrian Hunter			mult = 1000000
19381c3ca1b3SAdrian Hunter		elif suffix == "us":
19391c3ca1b3SAdrian Hunter			mult = 1000
19401c3ca1b3SAdrian Hunter		elif suffix == "ns":
19411c3ca1b3SAdrian Hunter			mult = 1
19421c3ca1b3SAdrian Hunter		else:
19431c3ca1b3SAdrian Hunter			return val
19441c3ca1b3SAdrian Hunter		val = val[:-2].strip()
19451c3ca1b3SAdrian Hunter		if not self.IsNumber(val):
19461c3ca1b3SAdrian Hunter			return val
19471c3ca1b3SAdrian Hunter		val = int(val) * mult
19481c3ca1b3SAdrian Hunter		if val >= 0:
19491c3ca1b3SAdrian Hunter			val += self.first_time
19501c3ca1b3SAdrian Hunter		else:
19511c3ca1b3SAdrian Hunter			val += self.last_time
19521c3ca1b3SAdrian Hunter		return str(val)
19531c3ca1b3SAdrian Hunter
19541c3ca1b3SAdrian Hunter	def ConvertTimeRange(self, vrange):
19551c3ca1b3SAdrian Hunter		if vrange[0] == "":
19561c3ca1b3SAdrian Hunter			vrange[0] = str(self.first_time)
19571c3ca1b3SAdrian Hunter		if vrange[1] == "":
19581c3ca1b3SAdrian Hunter			vrange[1] = str(self.last_time)
19591c3ca1b3SAdrian Hunter		vrange[0] = self.ConvertRelativeTime(vrange[0])
19601c3ca1b3SAdrian Hunter		vrange[1] = self.ConvertRelativeTime(vrange[1])
19611c3ca1b3SAdrian Hunter		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
19621c3ca1b3SAdrian Hunter			return False
19631c3ca1b3SAdrian Hunter		beg_range = max(int(vrange[0]), self.first_time)
19641c3ca1b3SAdrian Hunter		end_range = min(int(vrange[1]), self.last_time)
19651c3ca1b3SAdrian Hunter		if beg_range > self.last_time or end_range < self.first_time:
19661c3ca1b3SAdrian Hunter			return False
19671c3ca1b3SAdrian Hunter		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
19681c3ca1b3SAdrian Hunter		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
19691c3ca1b3SAdrian Hunter		return True
19701c3ca1b3SAdrian Hunter
19711c3ca1b3SAdrian Hunter	def AddTimeRange(self, value, ranges):
19721c3ca1b3SAdrian Hunter		n = value.count("-")
19731c3ca1b3SAdrian Hunter		if n == 1:
19741c3ca1b3SAdrian Hunter			pass
19751c3ca1b3SAdrian Hunter		elif n == 2:
19761c3ca1b3SAdrian Hunter			if value.split("-")[1].strip() == "":
19771c3ca1b3SAdrian Hunter				n = 1
19781c3ca1b3SAdrian Hunter		elif n == 3:
19791c3ca1b3SAdrian Hunter			n = 2
19801c3ca1b3SAdrian Hunter		else:
19811c3ca1b3SAdrian Hunter			return False
19821c3ca1b3SAdrian Hunter		pos = findnth(value, "-", n)
19831c3ca1b3SAdrian Hunter		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
19841c3ca1b3SAdrian Hunter		if self.ConvertTimeRange(vrange):
19851c3ca1b3SAdrian Hunter			ranges.append(vrange)
19861c3ca1b3SAdrian Hunter			return True
19871c3ca1b3SAdrian Hunter		return False
19881c3ca1b3SAdrian Hunter
19891c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
19901c3ca1b3SAdrian Hunter		ranges = []
19911c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
19921c3ca1b3SAdrian Hunter			if not self.AddTimeRange(value, ranges):
19931c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
19941c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
19951c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
19961c3ca1b3SAdrian Hunter
19970924cd68SAdrian Hunter# Report Dialog Base
1998210cf1f9SAdrian Hunter
19990924cd68SAdrian Hunterclass ReportDialogBase(QDialog):
2000210cf1f9SAdrian Hunter
20010924cd68SAdrian Hunter	def __init__(self, glb, title, items, partial, parent=None):
20020924cd68SAdrian Hunter		super(ReportDialogBase, self).__init__(parent)
2003210cf1f9SAdrian Hunter
2004210cf1f9SAdrian Hunter		self.glb = glb
2005210cf1f9SAdrian Hunter
20060bf0947aSAdrian Hunter		self.report_vars = ReportVars()
2007210cf1f9SAdrian Hunter
20080924cd68SAdrian Hunter		self.setWindowTitle(title)
2009210cf1f9SAdrian Hunter		self.setMinimumWidth(600)
2010210cf1f9SAdrian Hunter
20111c3ca1b3SAdrian Hunter		self.data_items = [x(glb, self) for x in items]
2012210cf1f9SAdrian Hunter
20130924cd68SAdrian Hunter		self.partial = partial
20140924cd68SAdrian Hunter
2015210cf1f9SAdrian Hunter		self.grid = QGridLayout()
2016210cf1f9SAdrian Hunter
2017210cf1f9SAdrian Hunter		for row in xrange(len(self.data_items)):
2018210cf1f9SAdrian Hunter			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2019210cf1f9SAdrian Hunter			self.grid.addWidget(self.data_items[row].widget, row, 1)
2020210cf1f9SAdrian Hunter
2021210cf1f9SAdrian Hunter		self.status = QLabel()
2022210cf1f9SAdrian Hunter
2023210cf1f9SAdrian Hunter		self.ok_button = QPushButton("Ok", self)
2024210cf1f9SAdrian Hunter		self.ok_button.setDefault(True)
2025210cf1f9SAdrian Hunter		self.ok_button.released.connect(self.Ok)
2026210cf1f9SAdrian Hunter		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2027210cf1f9SAdrian Hunter
2028210cf1f9SAdrian Hunter		self.cancel_button = QPushButton("Cancel", self)
2029210cf1f9SAdrian Hunter		self.cancel_button.released.connect(self.reject)
2030210cf1f9SAdrian Hunter		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2031210cf1f9SAdrian Hunter
2032210cf1f9SAdrian Hunter		self.hbox = QHBoxLayout()
2033210cf1f9SAdrian Hunter		#self.hbox.addStretch()
2034210cf1f9SAdrian Hunter		self.hbox.addWidget(self.status)
2035210cf1f9SAdrian Hunter		self.hbox.addWidget(self.ok_button)
2036210cf1f9SAdrian Hunter		self.hbox.addWidget(self.cancel_button)
2037210cf1f9SAdrian Hunter
2038210cf1f9SAdrian Hunter		self.vbox = QVBoxLayout()
2039210cf1f9SAdrian Hunter		self.vbox.addLayout(self.grid)
2040210cf1f9SAdrian Hunter		self.vbox.addLayout(self.hbox)
2041210cf1f9SAdrian Hunter
2042210cf1f9SAdrian Hunter		self.setLayout(self.vbox);
2043210cf1f9SAdrian Hunter
2044210cf1f9SAdrian Hunter	def Ok(self):
20450bf0947aSAdrian Hunter		vars = self.report_vars
20461c3ca1b3SAdrian Hunter		for d in self.data_items:
20471c3ca1b3SAdrian Hunter			if d.id == "REPORTNAME":
20481c3ca1b3SAdrian Hunter				vars.name = d.value
2049947cc38dSAdrian Hunter		if not vars.name:
2050210cf1f9SAdrian Hunter			self.ShowMessage("Report name is required")
2051210cf1f9SAdrian Hunter			return
2052210cf1f9SAdrian Hunter		for d in self.data_items:
2053210cf1f9SAdrian Hunter			if not d.IsValid():
2054210cf1f9SAdrian Hunter				return
2055210cf1f9SAdrian Hunter		for d in self.data_items[1:]:
2056cd358012SAdrian Hunter			if d.id == "LIMIT":
2057cd358012SAdrian Hunter				vars.limit = d.value
2058cd358012SAdrian Hunter			elif len(d.value):
20590bf0947aSAdrian Hunter				if len(vars.where_clause):
20600bf0947aSAdrian Hunter					vars.where_clause += " AND "
20610bf0947aSAdrian Hunter				vars.where_clause += d.value
20620bf0947aSAdrian Hunter		if len(vars.where_clause):
20630924cd68SAdrian Hunter			if self.partial:
20640bf0947aSAdrian Hunter				vars.where_clause = " AND ( " + vars.where_clause + " ) "
2065210cf1f9SAdrian Hunter			else:
20660bf0947aSAdrian Hunter				vars.where_clause = " WHERE " + vars.where_clause + " "
2067210cf1f9SAdrian Hunter		self.accept()
2068210cf1f9SAdrian Hunter
2069210cf1f9SAdrian Hunter	def ShowMessage(self, msg):
2070210cf1f9SAdrian Hunter		self.status.setText("<font color=#FF0000>" + msg)
2071210cf1f9SAdrian Hunter
2072210cf1f9SAdrian Hunter	def ClearMessage(self):
2073210cf1f9SAdrian Hunter		self.status.setText("")
2074210cf1f9SAdrian Hunter
20750924cd68SAdrian Hunter# Selected branch report creation dialog
20760924cd68SAdrian Hunter
20770924cd68SAdrian Hunterclass SelectedBranchDialog(ReportDialogBase):
20780924cd68SAdrian Hunter
20790924cd68SAdrian Hunter	def __init__(self, glb, parent=None):
20800924cd68SAdrian Hunter		title = "Selected Branches"
20811c3ca1b3SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
20821c3ca1b3SAdrian Hunter			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
20831c3ca1b3SAdrian Hunter			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
20841c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
20851c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
20861c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
20871c3ca1b3SAdrian 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),
20881c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
20891c3ca1b3SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
20900924cd68SAdrian Hunter		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
20910924cd68SAdrian Hunter
209276099f98SAdrian Hunter# Event list
209376099f98SAdrian Hunter
209476099f98SAdrian Hunterdef GetEventList(db):
209576099f98SAdrian Hunter	events = []
209676099f98SAdrian Hunter	query = QSqlQuery(db)
209776099f98SAdrian Hunter	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
209876099f98SAdrian Hunter	while query.next():
209976099f98SAdrian Hunter		events.append(query.value(0))
210076099f98SAdrian Hunter	return events
210176099f98SAdrian Hunter
2102655cb952SAdrian Hunter# Is a table selectable
2103655cb952SAdrian Hunter
2104ae8b887cSAdrian Hunterdef IsSelectable(db, table, sql = ""):
2105655cb952SAdrian Hunter	query = QSqlQuery(db)
2106655cb952SAdrian Hunter	try:
2107ae8b887cSAdrian Hunter		QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1")
2108655cb952SAdrian Hunter	except:
2109655cb952SAdrian Hunter		return False
2110655cb952SAdrian Hunter	return True
2111655cb952SAdrian Hunter
21128392b74bSAdrian Hunter# SQL table data model item
21138392b74bSAdrian Hunter
21148392b74bSAdrian Hunterclass SQLTableItem():
21158392b74bSAdrian Hunter
21168392b74bSAdrian Hunter	def __init__(self, row, data):
21178392b74bSAdrian Hunter		self.row = row
21188392b74bSAdrian Hunter		self.data = data
21198392b74bSAdrian Hunter
21208392b74bSAdrian Hunter	def getData(self, column):
21218392b74bSAdrian Hunter		return self.data[column]
21228392b74bSAdrian Hunter
21238392b74bSAdrian Hunter# SQL table data model
21248392b74bSAdrian Hunter
21258392b74bSAdrian Hunterclass SQLTableModel(TableModel):
21268392b74bSAdrian Hunter
21278392b74bSAdrian Hunter	progress = Signal(object)
21288392b74bSAdrian Hunter
21298c90fef9SAdrian Hunter	def __init__(self, glb, sql, column_headers, parent=None):
21308392b74bSAdrian Hunter		super(SQLTableModel, self).__init__(parent)
21318392b74bSAdrian Hunter		self.glb = glb
21328392b74bSAdrian Hunter		self.more = True
21338392b74bSAdrian Hunter		self.populated = 0
21348c90fef9SAdrian Hunter		self.column_headers = column_headers
21358453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
21368392b74bSAdrian Hunter		self.fetcher.done.connect(self.Update)
21378392b74bSAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
21388392b74bSAdrian Hunter
21398392b74bSAdrian Hunter	def DisplayData(self, item, index):
21408392b74bSAdrian Hunter		self.FetchIfNeeded(item.row)
21418392b74bSAdrian Hunter		return item.getData(index.column())
21428392b74bSAdrian Hunter
21438392b74bSAdrian Hunter	def AddSample(self, data):
21448392b74bSAdrian Hunter		child = SQLTableItem(self.populated, data)
21458392b74bSAdrian Hunter		self.child_items.append(child)
21468392b74bSAdrian Hunter		self.populated += 1
21478392b74bSAdrian Hunter
21488392b74bSAdrian Hunter	def Update(self, fetched):
21498392b74bSAdrian Hunter		if not fetched:
21508392b74bSAdrian Hunter			self.more = False
21518392b74bSAdrian Hunter			self.progress.emit(0)
21528392b74bSAdrian Hunter		child_count = self.child_count
21538392b74bSAdrian Hunter		count = self.populated - child_count
21548392b74bSAdrian Hunter		if count > 0:
21558392b74bSAdrian Hunter			parent = QModelIndex()
21568392b74bSAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
21578392b74bSAdrian Hunter			self.insertRows(child_count, count, parent)
21588392b74bSAdrian Hunter			self.child_count += count
21598392b74bSAdrian Hunter			self.endInsertRows()
21608392b74bSAdrian Hunter			self.progress.emit(self.child_count)
21618392b74bSAdrian Hunter
21628392b74bSAdrian Hunter	def FetchMoreRecords(self, count):
21638392b74bSAdrian Hunter		current = self.child_count
21648392b74bSAdrian Hunter		if self.more:
21658392b74bSAdrian Hunter			self.fetcher.Fetch(count)
21668392b74bSAdrian Hunter		else:
21678392b74bSAdrian Hunter			self.progress.emit(0)
21688392b74bSAdrian Hunter		return current
21698392b74bSAdrian Hunter
21708392b74bSAdrian Hunter	def HasMoreRecords(self):
21718392b74bSAdrian Hunter		return self.more
21728392b74bSAdrian Hunter
21738c90fef9SAdrian Hunter	def columnCount(self, parent=None):
21748c90fef9SAdrian Hunter		return len(self.column_headers)
21758c90fef9SAdrian Hunter
21768c90fef9SAdrian Hunter	def columnHeader(self, column):
21778c90fef9SAdrian Hunter		return self.column_headers[column]
21788c90fef9SAdrian Hunter
21798453c936SAdrian Hunter	def SQLTableDataPrep(self, query, count):
21808453c936SAdrian Hunter		data = []
21818453c936SAdrian Hunter		for i in xrange(count):
21828453c936SAdrian Hunter			data.append(query.value(i))
21838453c936SAdrian Hunter		return data
21848453c936SAdrian Hunter
21858392b74bSAdrian Hunter# SQL automatic table data model
21868392b74bSAdrian Hunter
21878392b74bSAdrian Hunterclass SQLAutoTableModel(SQLTableModel):
21888392b74bSAdrian Hunter
21898392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
21908392b74bSAdrian Hunter		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
21918392b74bSAdrian Hunter		if table_name == "comm_threads_view":
21928392b74bSAdrian Hunter			# For now, comm_threads_view has no id column
21938392b74bSAdrian Hunter			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
21948c90fef9SAdrian Hunter		column_headers = []
21958392b74bSAdrian Hunter		query = QSqlQuery(glb.db)
21968392b74bSAdrian Hunter		if glb.dbref.is_sqlite3:
21978392b74bSAdrian Hunter			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
21988392b74bSAdrian Hunter			while query.next():
21998c90fef9SAdrian Hunter				column_headers.append(query.value(1))
22008392b74bSAdrian Hunter			if table_name == "sqlite_master":
22018392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
22028392b74bSAdrian Hunter		else:
22038392b74bSAdrian Hunter			if table_name[:19] == "information_schema.":
22048392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
22058392b74bSAdrian Hunter				select_table_name = table_name[19:]
22068392b74bSAdrian Hunter				schema = "information_schema"
22078392b74bSAdrian Hunter			else:
22088392b74bSAdrian Hunter				select_table_name = table_name
22098392b74bSAdrian Hunter				schema = "public"
22108392b74bSAdrian Hunter			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
22118392b74bSAdrian Hunter			while query.next():
22128c90fef9SAdrian Hunter				column_headers.append(query.value(0))
22138453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
22148453c936SAdrian Hunter			if table_name == "samples_view":
22158453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_view_DataPrep
22168453c936SAdrian Hunter			if table_name == "samples":
22178453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_DataPrep
22188c90fef9SAdrian Hunter		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
22198392b74bSAdrian Hunter
22208453c936SAdrian Hunter	def samples_view_DataPrep(self, query, count):
22218453c936SAdrian Hunter		data = []
22228453c936SAdrian Hunter		data.append(query.value(0))
22238453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
22248453c936SAdrian Hunter		data.append("{:>19}".format(query.value(1)))
22258453c936SAdrian Hunter		for i in xrange(2, count):
22268453c936SAdrian Hunter			data.append(query.value(i))
22278453c936SAdrian Hunter		return data
22288453c936SAdrian Hunter
22298453c936SAdrian Hunter	def samples_DataPrep(self, query, count):
22308453c936SAdrian Hunter		data = []
22318453c936SAdrian Hunter		for i in xrange(9):
22328453c936SAdrian Hunter			data.append(query.value(i))
22338453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
22348453c936SAdrian Hunter		data.append("{:>19}".format(query.value(9)))
22358453c936SAdrian Hunter		for i in xrange(10, count):
22368453c936SAdrian Hunter			data.append(query.value(i))
22378453c936SAdrian Hunter		return data
22388453c936SAdrian Hunter
22398392b74bSAdrian Hunter# Base class for custom ResizeColumnsToContents
22408392b74bSAdrian Hunter
22418392b74bSAdrian Hunterclass ResizeColumnsToContentsBase(QObject):
22428392b74bSAdrian Hunter
22438392b74bSAdrian Hunter	def __init__(self, parent=None):
22448392b74bSAdrian Hunter		super(ResizeColumnsToContentsBase, self).__init__(parent)
22458392b74bSAdrian Hunter
22468392b74bSAdrian Hunter	def ResizeColumnToContents(self, column, n):
22478392b74bSAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
22488392b74bSAdrian Hunter		# so implement a crude alternative
22498392b74bSAdrian Hunter		font = self.view.font()
22508392b74bSAdrian Hunter		metrics = QFontMetrics(font)
22518392b74bSAdrian Hunter		max = 0
22528392b74bSAdrian Hunter		for row in xrange(n):
22538392b74bSAdrian Hunter			val = self.data_model.child_items[row].data[column]
22548392b74bSAdrian Hunter			len = metrics.width(str(val) + "MM")
22558392b74bSAdrian Hunter			max = len if len > max else max
22568392b74bSAdrian Hunter		val = self.data_model.columnHeader(column)
22578392b74bSAdrian Hunter		len = metrics.width(str(val) + "MM")
22588392b74bSAdrian Hunter		max = len if len > max else max
22598392b74bSAdrian Hunter		self.view.setColumnWidth(column, max)
22608392b74bSAdrian Hunter
22618392b74bSAdrian Hunter	def ResizeColumnsToContents(self):
22628392b74bSAdrian Hunter		n = min(self.data_model.child_count, 100)
22638392b74bSAdrian Hunter		if n < 1:
22648392b74bSAdrian Hunter			# No data yet, so connect a signal to notify when there is
22658392b74bSAdrian Hunter			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
22668392b74bSAdrian Hunter			return
22678392b74bSAdrian Hunter		columns = self.data_model.columnCount()
22688392b74bSAdrian Hunter		for i in xrange(columns):
22698392b74bSAdrian Hunter			self.ResizeColumnToContents(i, n)
22708392b74bSAdrian Hunter
22718392b74bSAdrian Hunter	def UpdateColumnWidths(self, *x):
22728392b74bSAdrian Hunter		# This only needs to be done once, so disconnect the signal now
22738392b74bSAdrian Hunter		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
22748392b74bSAdrian Hunter		self.ResizeColumnsToContents()
22758392b74bSAdrian Hunter
227696c43b9aSAdrian Hunter# Convert value to CSV
227796c43b9aSAdrian Hunter
227896c43b9aSAdrian Hunterdef ToCSValue(val):
227996c43b9aSAdrian Hunter	if '"' in val:
228096c43b9aSAdrian Hunter		val = val.replace('"', '""')
228196c43b9aSAdrian Hunter	if "," in val or '"' in val:
228296c43b9aSAdrian Hunter		val = '"' + val + '"'
228396c43b9aSAdrian Hunter	return val
228496c43b9aSAdrian Hunter
228596c43b9aSAdrian Hunter# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
228696c43b9aSAdrian Hunter
228796c43b9aSAdrian Hunterglb_max_cols = 1000
228896c43b9aSAdrian Hunter
228996c43b9aSAdrian Hunterdef RowColumnKey(a):
229096c43b9aSAdrian Hunter	return a.row() * glb_max_cols + a.column()
229196c43b9aSAdrian Hunter
229296c43b9aSAdrian Hunter# Copy selected table cells to clipboard
229396c43b9aSAdrian Hunter
229496c43b9aSAdrian Hunterdef CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
229596c43b9aSAdrian Hunter	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
229696c43b9aSAdrian Hunter	idx_cnt = len(indexes)
229796c43b9aSAdrian Hunter	if not idx_cnt:
229896c43b9aSAdrian Hunter		return
229996c43b9aSAdrian Hunter	if idx_cnt == 1:
230096c43b9aSAdrian Hunter		with_hdr=False
230196c43b9aSAdrian Hunter	min_row = indexes[0].row()
230296c43b9aSAdrian Hunter	max_row = indexes[0].row()
230396c43b9aSAdrian Hunter	min_col = indexes[0].column()
230496c43b9aSAdrian Hunter	max_col = indexes[0].column()
230596c43b9aSAdrian Hunter	for i in indexes:
230696c43b9aSAdrian Hunter		min_row = min(min_row, i.row())
230796c43b9aSAdrian Hunter		max_row = max(max_row, i.row())
230896c43b9aSAdrian Hunter		min_col = min(min_col, i.column())
230996c43b9aSAdrian Hunter		max_col = max(max_col, i.column())
231096c43b9aSAdrian Hunter	if max_col > glb_max_cols:
231196c43b9aSAdrian Hunter		raise RuntimeError("glb_max_cols is too low")
231296c43b9aSAdrian Hunter	max_width = [0] * (1 + max_col - min_col)
231396c43b9aSAdrian Hunter	for i in indexes:
231496c43b9aSAdrian Hunter		c = i.column() - min_col
231596c43b9aSAdrian Hunter		max_width[c] = max(max_width[c], len(str(i.data())))
231696c43b9aSAdrian Hunter	text = ""
231796c43b9aSAdrian Hunter	pad = ""
231896c43b9aSAdrian Hunter	sep = ""
231996c43b9aSAdrian Hunter	if with_hdr:
232096c43b9aSAdrian Hunter		model = indexes[0].model()
232196c43b9aSAdrian Hunter		for col in range(min_col, max_col + 1):
232296c43b9aSAdrian Hunter			val = model.headerData(col, Qt.Horizontal)
232396c43b9aSAdrian Hunter			if as_csv:
232496c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
232596c43b9aSAdrian Hunter				sep = ","
232696c43b9aSAdrian Hunter			else:
232796c43b9aSAdrian Hunter				c = col - min_col
232896c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
232996c43b9aSAdrian Hunter				width = max_width[c]
233096c43b9aSAdrian Hunter				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
233196c43b9aSAdrian Hunter				if align & Qt.AlignRight:
233296c43b9aSAdrian Hunter					val = val.rjust(width)
233396c43b9aSAdrian Hunter				text += pad + sep + val
233496c43b9aSAdrian Hunter				pad = " " * (width - len(val))
233596c43b9aSAdrian Hunter				sep = "  "
233696c43b9aSAdrian Hunter		text += "\n"
233796c43b9aSAdrian Hunter		pad = ""
233896c43b9aSAdrian Hunter		sep = ""
233996c43b9aSAdrian Hunter	last_row = min_row
234096c43b9aSAdrian Hunter	for i in indexes:
234196c43b9aSAdrian Hunter		if i.row() > last_row:
234296c43b9aSAdrian Hunter			last_row = i.row()
234396c43b9aSAdrian Hunter			text += "\n"
234496c43b9aSAdrian Hunter			pad = ""
234596c43b9aSAdrian Hunter			sep = ""
234696c43b9aSAdrian Hunter		if as_csv:
234796c43b9aSAdrian Hunter			text += sep + ToCSValue(str(i.data()))
234896c43b9aSAdrian Hunter			sep = ","
234996c43b9aSAdrian Hunter		else:
235096c43b9aSAdrian Hunter			width = max_width[i.column() - min_col]
235196c43b9aSAdrian Hunter			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
235296c43b9aSAdrian Hunter				val = str(i.data()).rjust(width)
235396c43b9aSAdrian Hunter			else:
235496c43b9aSAdrian Hunter				val = str(i.data())
235596c43b9aSAdrian Hunter			text += pad + sep + val
235696c43b9aSAdrian Hunter			pad = " " * (width - len(val))
235796c43b9aSAdrian Hunter			sep = "  "
235896c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
235996c43b9aSAdrian Hunter
236096c43b9aSAdrian Hunterdef CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
236196c43b9aSAdrian Hunter	indexes = view.selectedIndexes()
236296c43b9aSAdrian Hunter	if not len(indexes):
236396c43b9aSAdrian Hunter		return
236496c43b9aSAdrian Hunter
236596c43b9aSAdrian Hunter	selection = view.selectionModel()
236696c43b9aSAdrian Hunter
236796c43b9aSAdrian Hunter	first = None
236896c43b9aSAdrian Hunter	for i in indexes:
236996c43b9aSAdrian Hunter		above = view.indexAbove(i)
237096c43b9aSAdrian Hunter		if not selection.isSelected(above):
237196c43b9aSAdrian Hunter			first = i
237296c43b9aSAdrian Hunter			break
237396c43b9aSAdrian Hunter
237496c43b9aSAdrian Hunter	if first is None:
237596c43b9aSAdrian Hunter		raise RuntimeError("CopyTreeCellsToClipboard internal error")
237696c43b9aSAdrian Hunter
237796c43b9aSAdrian Hunter	model = first.model()
237896c43b9aSAdrian Hunter	row_cnt = 0
237996c43b9aSAdrian Hunter	col_cnt = model.columnCount(first)
238096c43b9aSAdrian Hunter	max_width = [0] * col_cnt
238196c43b9aSAdrian Hunter
238296c43b9aSAdrian Hunter	indent_sz = 2
238396c43b9aSAdrian Hunter	indent_str = " " * indent_sz
238496c43b9aSAdrian Hunter
238596c43b9aSAdrian Hunter	expanded_mark_sz = 2
238696c43b9aSAdrian Hunter	if sys.version_info[0] == 3:
238796c43b9aSAdrian Hunter		expanded_mark = "\u25BC "
238896c43b9aSAdrian Hunter		not_expanded_mark = "\u25B6 "
238996c43b9aSAdrian Hunter	else:
239096c43b9aSAdrian Hunter		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
239196c43b9aSAdrian Hunter		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
239296c43b9aSAdrian Hunter	leaf_mark = "  "
239396c43b9aSAdrian Hunter
239496c43b9aSAdrian Hunter	if not as_csv:
239596c43b9aSAdrian Hunter		pos = first
239696c43b9aSAdrian Hunter		while True:
239796c43b9aSAdrian Hunter			row_cnt += 1
239896c43b9aSAdrian Hunter			row = pos.row()
239996c43b9aSAdrian Hunter			for c in range(col_cnt):
240096c43b9aSAdrian Hunter				i = pos.sibling(row, c)
240196c43b9aSAdrian Hunter				if c:
240296c43b9aSAdrian Hunter					n = len(str(i.data()))
240396c43b9aSAdrian Hunter				else:
240496c43b9aSAdrian Hunter					n = len(str(i.data()).strip())
240596c43b9aSAdrian Hunter					n += (i.internalPointer().level - 1) * indent_sz
240696c43b9aSAdrian Hunter					n += expanded_mark_sz
240796c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], n)
240896c43b9aSAdrian Hunter			pos = view.indexBelow(pos)
240996c43b9aSAdrian Hunter			if not selection.isSelected(pos):
241096c43b9aSAdrian Hunter				break
241196c43b9aSAdrian Hunter
241296c43b9aSAdrian Hunter	text = ""
241396c43b9aSAdrian Hunter	pad = ""
241496c43b9aSAdrian Hunter	sep = ""
241596c43b9aSAdrian Hunter	if with_hdr:
241696c43b9aSAdrian Hunter		for c in range(col_cnt):
241796c43b9aSAdrian Hunter			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
241896c43b9aSAdrian Hunter			if as_csv:
241996c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
242096c43b9aSAdrian Hunter				sep = ","
242196c43b9aSAdrian Hunter			else:
242296c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
242396c43b9aSAdrian Hunter				width = max_width[c]
242496c43b9aSAdrian Hunter				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
242596c43b9aSAdrian Hunter				if align & Qt.AlignRight:
242696c43b9aSAdrian Hunter					val = val.rjust(width)
242796c43b9aSAdrian Hunter				text += pad + sep + val
242896c43b9aSAdrian Hunter				pad = " " * (width - len(val))
242996c43b9aSAdrian Hunter				sep = "   "
243096c43b9aSAdrian Hunter		text += "\n"
243196c43b9aSAdrian Hunter		pad = ""
243296c43b9aSAdrian Hunter		sep = ""
243396c43b9aSAdrian Hunter
243496c43b9aSAdrian Hunter	pos = first
243596c43b9aSAdrian Hunter	while True:
243696c43b9aSAdrian Hunter		row = pos.row()
243796c43b9aSAdrian Hunter		for c in range(col_cnt):
243896c43b9aSAdrian Hunter			i = pos.sibling(row, c)
243996c43b9aSAdrian Hunter			val = str(i.data())
244096c43b9aSAdrian Hunter			if not c:
244196c43b9aSAdrian Hunter				if model.hasChildren(i):
244296c43b9aSAdrian Hunter					if view.isExpanded(i):
244396c43b9aSAdrian Hunter						mark = expanded_mark
244496c43b9aSAdrian Hunter					else:
244596c43b9aSAdrian Hunter						mark = not_expanded_mark
244696c43b9aSAdrian Hunter				else:
244796c43b9aSAdrian Hunter					mark = leaf_mark
244896c43b9aSAdrian Hunter				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
244996c43b9aSAdrian Hunter			if as_csv:
245096c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
245196c43b9aSAdrian Hunter				sep = ","
245296c43b9aSAdrian Hunter			else:
245396c43b9aSAdrian Hunter				width = max_width[c]
245496c43b9aSAdrian Hunter				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
245596c43b9aSAdrian Hunter					val = val.rjust(width)
245696c43b9aSAdrian Hunter				text += pad + sep + val
245796c43b9aSAdrian Hunter				pad = " " * (width - len(val))
245896c43b9aSAdrian Hunter				sep = "   "
245996c43b9aSAdrian Hunter		pos = view.indexBelow(pos)
246096c43b9aSAdrian Hunter		if not selection.isSelected(pos):
246196c43b9aSAdrian Hunter			break
246296c43b9aSAdrian Hunter		text = text.rstrip() + "\n"
246396c43b9aSAdrian Hunter		pad = ""
246496c43b9aSAdrian Hunter		sep = ""
246596c43b9aSAdrian Hunter
246696c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
246796c43b9aSAdrian Hunter
246896c43b9aSAdrian Hunterdef CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
246996c43b9aSAdrian Hunter	view.CopyCellsToClipboard(view, as_csv, with_hdr)
247096c43b9aSAdrian Hunter
247196c43b9aSAdrian Hunterdef CopyCellsToClipboardHdr(view):
247296c43b9aSAdrian Hunter	CopyCellsToClipboard(view, False, True)
247396c43b9aSAdrian Hunter
247496c43b9aSAdrian Hunterdef CopyCellsToClipboardCSV(view):
247596c43b9aSAdrian Hunter	CopyCellsToClipboard(view, True, True)
247696c43b9aSAdrian Hunter
24779bc4e4bfSAdrian Hunter# Context menu
24789bc4e4bfSAdrian Hunter
24799bc4e4bfSAdrian Hunterclass ContextMenu(object):
24809bc4e4bfSAdrian Hunter
24819bc4e4bfSAdrian Hunter	def __init__(self, view):
24829bc4e4bfSAdrian Hunter		self.view = view
24839bc4e4bfSAdrian Hunter		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
24849bc4e4bfSAdrian Hunter		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
24859bc4e4bfSAdrian Hunter
24869bc4e4bfSAdrian Hunter	def ShowContextMenu(self, pos):
24879bc4e4bfSAdrian Hunter		menu = QMenu(self.view)
24889bc4e4bfSAdrian Hunter		self.AddActions(menu)
24899bc4e4bfSAdrian Hunter		menu.exec_(self.view.mapToGlobal(pos))
24909bc4e4bfSAdrian Hunter
24919bc4e4bfSAdrian Hunter	def AddCopy(self, menu):
24929bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
24939bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
24949bc4e4bfSAdrian Hunter
24959bc4e4bfSAdrian Hunter	def AddActions(self, menu):
24969bc4e4bfSAdrian Hunter		self.AddCopy(menu)
24979bc4e4bfSAdrian Hunter
24989bc4e4bfSAdrian Hunterclass TreeContextMenu(ContextMenu):
24999bc4e4bfSAdrian Hunter
25009bc4e4bfSAdrian Hunter	def __init__(self, view):
25019bc4e4bfSAdrian Hunter		super(TreeContextMenu, self).__init__(view)
25029bc4e4bfSAdrian Hunter
25039bc4e4bfSAdrian Hunter	def AddActions(self, menu):
25049bc4e4bfSAdrian Hunter		i = self.view.currentIndex()
25059bc4e4bfSAdrian Hunter		text = str(i.data()).strip()
25069bc4e4bfSAdrian Hunter		if len(text):
25079bc4e4bfSAdrian Hunter			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
25089bc4e4bfSAdrian Hunter		self.AddCopy(menu)
25099bc4e4bfSAdrian Hunter
25108392b74bSAdrian Hunter# Table window
25118392b74bSAdrian Hunter
25128392b74bSAdrian Hunterclass TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
25138392b74bSAdrian Hunter
25148392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
25158392b74bSAdrian Hunter		super(TableWindow, self).__init__(parent)
25168392b74bSAdrian Hunter
25178392b74bSAdrian Hunter		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
25188392b74bSAdrian Hunter
25198392b74bSAdrian Hunter		self.model = QSortFilterProxyModel()
25208392b74bSAdrian Hunter		self.model.setSourceModel(self.data_model)
25218392b74bSAdrian Hunter
25228392b74bSAdrian Hunter		self.view = QTableView()
25238392b74bSAdrian Hunter		self.view.setModel(self.model)
25248392b74bSAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
25258392b74bSAdrian Hunter		self.view.verticalHeader().setVisible(False)
25268392b74bSAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
25278392b74bSAdrian Hunter		self.view.setSortingEnabled(True)
252896c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
252996c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
25308392b74bSAdrian Hunter
25318392b74bSAdrian Hunter		self.ResizeColumnsToContents()
25328392b74bSAdrian Hunter
25339bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
25349bc4e4bfSAdrian Hunter
25358392b74bSAdrian Hunter		self.find_bar = FindBar(self, self, True)
25368392b74bSAdrian Hunter
25378392b74bSAdrian Hunter		self.finder = ChildDataItemFinder(self.data_model)
25388392b74bSAdrian Hunter
25398392b74bSAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
25408392b74bSAdrian Hunter
25418392b74bSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
25428392b74bSAdrian Hunter
25438392b74bSAdrian Hunter		self.setWidget(self.vbox.Widget())
25448392b74bSAdrian Hunter
25458392b74bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
25468392b74bSAdrian Hunter
25478392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context):
25488392b74bSAdrian Hunter		self.view.setFocus()
25498392b74bSAdrian Hunter		self.find_bar.Busy()
25508392b74bSAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
25518392b74bSAdrian Hunter
25528392b74bSAdrian Hunter	def FindDone(self, row):
25538392b74bSAdrian Hunter		self.find_bar.Idle()
25548392b74bSAdrian Hunter		if row >= 0:
255535fa1ceeSAdrian Hunter			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
25568392b74bSAdrian Hunter		else:
25578392b74bSAdrian Hunter			self.find_bar.NotFound()
25588392b74bSAdrian Hunter
25598392b74bSAdrian Hunter# Table list
25608392b74bSAdrian Hunter
25618392b74bSAdrian Hunterdef GetTableList(glb):
25628392b74bSAdrian Hunter	tables = []
25638392b74bSAdrian Hunter	query = QSqlQuery(glb.db)
25648392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
25658392b74bSAdrian Hunter		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
25668392b74bSAdrian Hunter	else:
25678392b74bSAdrian 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")
25688392b74bSAdrian Hunter	while query.next():
25698392b74bSAdrian Hunter		tables.append(query.value(0))
25708392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
25718392b74bSAdrian Hunter		tables.append("sqlite_master")
25728392b74bSAdrian Hunter	else:
25738392b74bSAdrian Hunter		tables.append("information_schema.tables")
25748392b74bSAdrian Hunter		tables.append("information_schema.views")
25758392b74bSAdrian Hunter		tables.append("information_schema.columns")
25768392b74bSAdrian Hunter	return tables
25778392b74bSAdrian Hunter
2578cd358012SAdrian Hunter# Top Calls data model
2579cd358012SAdrian Hunter
2580cd358012SAdrian Hunterclass TopCallsModel(SQLTableModel):
2581cd358012SAdrian Hunter
2582cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2583cd358012SAdrian Hunter		text = ""
2584cd358012SAdrian Hunter		if not glb.dbref.is_sqlite3:
2585cd358012SAdrian Hunter			text = "::text"
2586cd358012SAdrian Hunter		limit = ""
2587cd358012SAdrian Hunter		if len(report_vars.limit):
2588cd358012SAdrian Hunter			limit = " LIMIT " + report_vars.limit
2589cd358012SAdrian Hunter		sql = ("SELECT comm, pid, tid, name,"
2590cd358012SAdrian Hunter			" CASE"
2591cd358012SAdrian Hunter			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2592cd358012SAdrian Hunter			" ELSE short_name"
2593cd358012SAdrian Hunter			" END AS dso,"
2594cd358012SAdrian Hunter			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2595cd358012SAdrian Hunter			" CASE"
2596cd358012SAdrian Hunter			" WHEN (calls.flags = 1) THEN 'no call'" + text +
2597cd358012SAdrian Hunter			" WHEN (calls.flags = 2) THEN 'no return'" + text +
2598cd358012SAdrian Hunter			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2599cd358012SAdrian Hunter			" ELSE ''" + text +
2600cd358012SAdrian Hunter			" END AS flags"
2601cd358012SAdrian Hunter			" FROM calls"
2602cd358012SAdrian Hunter			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2603cd358012SAdrian Hunter			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2604cd358012SAdrian Hunter			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
2605cd358012SAdrian Hunter			" INNER JOIN comms ON calls.comm_id = comms.id"
2606cd358012SAdrian Hunter			" INNER JOIN threads ON calls.thread_id = threads.id" +
2607cd358012SAdrian Hunter			report_vars.where_clause +
2608cd358012SAdrian Hunter			" ORDER BY elapsed_time DESC" +
2609cd358012SAdrian Hunter			limit
2610cd358012SAdrian Hunter			)
2611cd358012SAdrian Hunter		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2612cd358012SAdrian Hunter		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2613cd358012SAdrian Hunter		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2614cd358012SAdrian Hunter
2615cd358012SAdrian Hunter	def columnAlignment(self, column):
2616cd358012SAdrian Hunter		return self.alignment[column]
2617cd358012SAdrian Hunter
2618cd358012SAdrian Hunter# Top Calls report creation dialog
2619cd358012SAdrian Hunter
2620cd358012SAdrian Hunterclass TopCallsDialog(ReportDialogBase):
2621cd358012SAdrian Hunter
2622cd358012SAdrian Hunter	def __init__(self, glb, parent=None):
2623cd358012SAdrian Hunter		title = "Top Calls by Elapsed Time"
2624cd358012SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2625cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2626cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2627cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2628cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2629cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2630cd358012SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2631cd358012SAdrian Hunter			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2632cd358012SAdrian Hunter		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2633cd358012SAdrian Hunter
2634cd358012SAdrian Hunter# Top Calls window
2635cd358012SAdrian Hunter
2636cd358012SAdrian Hunterclass TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2637cd358012SAdrian Hunter
2638cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2639cd358012SAdrian Hunter		super(TopCallsWindow, self).__init__(parent)
2640cd358012SAdrian Hunter
2641cd358012SAdrian Hunter		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2642cd358012SAdrian Hunter		self.model = self.data_model
2643cd358012SAdrian Hunter
2644cd358012SAdrian Hunter		self.view = QTableView()
2645cd358012SAdrian Hunter		self.view.setModel(self.model)
2646cd358012SAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2647cd358012SAdrian Hunter		self.view.verticalHeader().setVisible(False)
264896c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
264996c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2650cd358012SAdrian Hunter
26519bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
26529bc4e4bfSAdrian Hunter
2653cd358012SAdrian Hunter		self.ResizeColumnsToContents()
2654cd358012SAdrian Hunter
2655cd358012SAdrian Hunter		self.find_bar = FindBar(self, self, True)
2656cd358012SAdrian Hunter
2657cd358012SAdrian Hunter		self.finder = ChildDataItemFinder(self.model)
2658cd358012SAdrian Hunter
2659cd358012SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2660cd358012SAdrian Hunter
2661cd358012SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2662cd358012SAdrian Hunter
2663cd358012SAdrian Hunter		self.setWidget(self.vbox.Widget())
2664cd358012SAdrian Hunter
2665cd358012SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2666cd358012SAdrian Hunter
2667cd358012SAdrian Hunter	def Find(self, value, direction, pattern, context):
2668cd358012SAdrian Hunter		self.view.setFocus()
2669cd358012SAdrian Hunter		self.find_bar.Busy()
2670cd358012SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
2671cd358012SAdrian Hunter
2672cd358012SAdrian Hunter	def FindDone(self, row):
2673cd358012SAdrian Hunter		self.find_bar.Idle()
2674cd358012SAdrian Hunter		if row >= 0:
2675cd358012SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2676cd358012SAdrian Hunter		else:
2677cd358012SAdrian Hunter			self.find_bar.NotFound()
2678cd358012SAdrian Hunter
26791beb5c7bSAdrian Hunter# Action Definition
26801beb5c7bSAdrian Hunter
26811beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None):
26821beb5c7bSAdrian Hunter	action = QAction(label, parent)
26831beb5c7bSAdrian Hunter	if shortcut != None:
26841beb5c7bSAdrian Hunter		action.setShortcuts(shortcut)
26851beb5c7bSAdrian Hunter	action.setStatusTip(tip)
26861beb5c7bSAdrian Hunter	action.triggered.connect(callback)
26871beb5c7bSAdrian Hunter	return action
26881beb5c7bSAdrian Hunter
26891beb5c7bSAdrian Hunter# Typical application actions
26901beb5c7bSAdrian Hunter
26911beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None):
26921beb5c7bSAdrian Hunter	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
26931beb5c7bSAdrian Hunter
26941beb5c7bSAdrian Hunter# Typical MDI actions
26951beb5c7bSAdrian Hunter
26961beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area):
26971beb5c7bSAdrian Hunter	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
26981beb5c7bSAdrian Hunter
26991beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area):
27001beb5c7bSAdrian Hunter	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
27011beb5c7bSAdrian Hunter
27021beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area):
27031beb5c7bSAdrian Hunter	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
27041beb5c7bSAdrian Hunter
27051beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area):
27061beb5c7bSAdrian Hunter	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
27071beb5c7bSAdrian Hunter
27081beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area):
27091beb5c7bSAdrian Hunter	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
27101beb5c7bSAdrian Hunter
27111beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area):
27121beb5c7bSAdrian Hunter	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
27131beb5c7bSAdrian Hunter
27141beb5c7bSAdrian Hunter# Typical MDI window menu
27151beb5c7bSAdrian Hunter
27161beb5c7bSAdrian Hunterclass WindowMenu():
27171beb5c7bSAdrian Hunter
27181beb5c7bSAdrian Hunter	def __init__(self, mdi_area, menu):
27191beb5c7bSAdrian Hunter		self.mdi_area = mdi_area
27201beb5c7bSAdrian Hunter		self.window_menu = menu.addMenu("&Windows")
27211beb5c7bSAdrian Hunter		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
27221beb5c7bSAdrian Hunter		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
27231beb5c7bSAdrian Hunter		self.tile_windows = CreateTileWindowsAction(mdi_area)
27241beb5c7bSAdrian Hunter		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
27251beb5c7bSAdrian Hunter		self.next_window = CreateNextWindowAction(mdi_area)
27261beb5c7bSAdrian Hunter		self.previous_window = CreatePreviousWindowAction(mdi_area)
27271beb5c7bSAdrian Hunter		self.window_menu.aboutToShow.connect(self.Update)
27281beb5c7bSAdrian Hunter
27291beb5c7bSAdrian Hunter	def Update(self):
27301beb5c7bSAdrian Hunter		self.window_menu.clear()
27311beb5c7bSAdrian Hunter		sub_window_count = len(self.mdi_area.subWindowList())
27321beb5c7bSAdrian Hunter		have_sub_windows = sub_window_count != 0
27331beb5c7bSAdrian Hunter		self.close_active_window.setEnabled(have_sub_windows)
27341beb5c7bSAdrian Hunter		self.close_all_windows.setEnabled(have_sub_windows)
27351beb5c7bSAdrian Hunter		self.tile_windows.setEnabled(have_sub_windows)
27361beb5c7bSAdrian Hunter		self.cascade_windows.setEnabled(have_sub_windows)
27371beb5c7bSAdrian Hunter		self.next_window.setEnabled(have_sub_windows)
27381beb5c7bSAdrian Hunter		self.previous_window.setEnabled(have_sub_windows)
27391beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_active_window)
27401beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_all_windows)
27411beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
27421beb5c7bSAdrian Hunter		self.window_menu.addAction(self.tile_windows)
27431beb5c7bSAdrian Hunter		self.window_menu.addAction(self.cascade_windows)
27441beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
27451beb5c7bSAdrian Hunter		self.window_menu.addAction(self.next_window)
27461beb5c7bSAdrian Hunter		self.window_menu.addAction(self.previous_window)
27471beb5c7bSAdrian Hunter		if sub_window_count == 0:
27481beb5c7bSAdrian Hunter			return
27491beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
27501beb5c7bSAdrian Hunter		nr = 1
27511beb5c7bSAdrian Hunter		for sub_window in self.mdi_area.subWindowList():
27521beb5c7bSAdrian Hunter			label = str(nr) + " " + sub_window.name
27531beb5c7bSAdrian Hunter			if nr < 10:
27541beb5c7bSAdrian Hunter				label = "&" + label
27551beb5c7bSAdrian Hunter			action = self.window_menu.addAction(label)
27561beb5c7bSAdrian Hunter			action.setCheckable(True)
27571beb5c7bSAdrian Hunter			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
27581beb5c7bSAdrian Hunter			action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
27591beb5c7bSAdrian Hunter			self.window_menu.addAction(action)
27601beb5c7bSAdrian Hunter			nr += 1
27611beb5c7bSAdrian Hunter
27621beb5c7bSAdrian Hunter	def setActiveSubWindow(self, nr):
27631beb5c7bSAdrian Hunter		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
27641beb5c7bSAdrian Hunter
276565b24292SAdrian Hunter# Help text
276665b24292SAdrian Hunter
276765b24292SAdrian Hunterglb_help_text = """
276865b24292SAdrian Hunter<h1>Contents</h1>
276965b24292SAdrian Hunter<style>
277065b24292SAdrian Hunterp.c1 {
277165b24292SAdrian Hunter    text-indent: 40px;
277265b24292SAdrian Hunter}
277365b24292SAdrian Hunterp.c2 {
277465b24292SAdrian Hunter    text-indent: 80px;
277565b24292SAdrian Hunter}
277665b24292SAdrian Hunter}
277765b24292SAdrian Hunter</style>
277865b24292SAdrian Hunter<p class=c1><a href=#reports>1. Reports</a></p>
277965b24292SAdrian Hunter<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2780ae8b887cSAdrian Hunter<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2781ae8b887cSAdrian Hunter<p class=c2><a href=#allbranches>1.3 All branches</a></p>
2782ae8b887cSAdrian Hunter<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2783ae8b887cSAdrian Hunter<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
278465b24292SAdrian Hunter<p class=c1><a href=#tables>2. Tables</a></p>
278565b24292SAdrian Hunter<h1 id=reports>1. Reports</h1>
278665b24292SAdrian Hunter<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
278765b24292SAdrian HunterThe result is a GUI window with a tree representing a context-sensitive
278865b24292SAdrian Huntercall-graph. Expanding a couple of levels of the tree and adjusting column
278965b24292SAdrian Hunterwidths to suit will display something like:
279065b24292SAdrian Hunter<pre>
279165b24292SAdrian Hunter                                         Call Graph: pt_example
279265b24292SAdrian HunterCall Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
279365b24292SAdrian Hunterv- ls
279465b24292SAdrian Hunter    v- 2638:2638
279565b24292SAdrian Hunter        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
279665b24292SAdrian Hunter          |- unknown               unknown       1        13198     0.1              1              0.0
279765b24292SAdrian Hunter          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
279865b24292SAdrian Hunter          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
279965b24292SAdrian Hunter          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
280065b24292SAdrian Hunter             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
280165b24292SAdrian Hunter             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
280265b24292SAdrian Hunter             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
280365b24292SAdrian Hunter             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
280465b24292SAdrian Hunter             v- main               ls            1      8182043    99.6         180254             99.9
280565b24292SAdrian Hunter</pre>
280665b24292SAdrian Hunter<h3>Points to note:</h3>
280765b24292SAdrian Hunter<ul>
280865b24292SAdrian Hunter<li>The top level is a command name (comm)</li>
280965b24292SAdrian Hunter<li>The next level is a thread (pid:tid)</li>
281065b24292SAdrian Hunter<li>Subsequent levels are functions</li>
281165b24292SAdrian Hunter<li>'Count' is the number of calls</li>
281265b24292SAdrian Hunter<li>'Time' is the elapsed time until the function returns</li>
281365b24292SAdrian Hunter<li>Percentages are relative to the level above</li>
281465b24292SAdrian Hunter<li>'Branch Count' is the total number of branches for that function and all functions that it calls
281565b24292SAdrian Hunter</ul>
281665b24292SAdrian Hunter<h3>Find</h3>
281765b24292SAdrian HunterCtrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
281865b24292SAdrian HunterThe pattern matching symbols are ? for any character and * for zero or more characters.
2819ae8b887cSAdrian Hunter<h2 id=calltree>1.2 Call Tree</h2>
2820ae8b887cSAdrian HunterThe Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2821ae8b887cSAdrian HunterAlso the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2822ae8b887cSAdrian Hunter<h2 id=allbranches>1.3 All branches</h2>
282365b24292SAdrian HunterThe All branches report displays all branches in chronological order.
282465b24292SAdrian HunterNot all data is fetched immediately. More records can be fetched using the Fetch bar provided.
282565b24292SAdrian Hunter<h3>Disassembly</h3>
282665b24292SAdrian HunterOpen a branch to display disassembly. This only works if:
282765b24292SAdrian Hunter<ol>
282865b24292SAdrian Hunter<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
282965b24292SAdrian Hunter<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
283065b24292SAdrian HunterThe default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
283165b24292SAdrian HunterOne exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
283265b24292SAdrian Hunteror alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
283365b24292SAdrian Hunter</ol>
283465b24292SAdrian Hunter<h4 id=xed>Intel XED Setup</h4>
283565b24292SAdrian HunterTo use Intel XED, libxed.so must be present.  To build and install libxed.so:
283665b24292SAdrian Hunter<pre>
283765b24292SAdrian Huntergit clone https://github.com/intelxed/mbuild.git mbuild
283865b24292SAdrian Huntergit clone https://github.com/intelxed/xed
283965b24292SAdrian Huntercd xed
284065b24292SAdrian Hunter./mfile.py --share
284165b24292SAdrian Huntersudo ./mfile.py --prefix=/usr/local install
284265b24292SAdrian Huntersudo ldconfig
284365b24292SAdrian Hunter</pre>
284465b24292SAdrian Hunter<h3>Find</h3>
284565b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
284665b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
284765b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
2848ae8b887cSAdrian Hunter<h2 id=selectedbranches>1.4 Selected branches</h2>
284965b24292SAdrian HunterThis is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
285065b24292SAdrian Hunterby various selection criteria. A dialog box displays available criteria which are AND'ed together.
2851ae8b887cSAdrian Hunter<h3>1.4.1 Time ranges</h3>
285265b24292SAdrian HunterThe time ranges hint text shows the total time range. Relative time ranges can also be entered in
285365b24292SAdrian Hunterms, us or ns. Also, negative values are relative to the end of trace.  Examples:
285465b24292SAdrian Hunter<pre>
285565b24292SAdrian Hunter	81073085947329-81073085958238	From 81073085947329 to 81073085958238
285665b24292SAdrian Hunter	100us-200us		From 100us to 200us
285765b24292SAdrian Hunter	10ms-			From 10ms to the end
285865b24292SAdrian Hunter	-100ns			The first 100ns
285965b24292SAdrian Hunter	-10ms-			The last 10ms
286065b24292SAdrian Hunter</pre>
286165b24292SAdrian HunterN.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2862ae8b887cSAdrian Hunter<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2863cd358012SAdrian 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.
2864cd358012SAdrian HunterThe data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2865cd358012SAdrian HunterIf not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
286665b24292SAdrian Hunter<h1 id=tables>2. Tables</h1>
286765b24292SAdrian HunterThe Tables menu shows all tables and views in the database. Most tables have an associated view
286865b24292SAdrian Hunterwhich displays the information in a more friendly way. Not all data for large tables is fetched
286965b24292SAdrian Hunterimmediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
287065b24292SAdrian Hunterbut that can be slow for large tables.
287165b24292SAdrian Hunter<p>There are also tables of database meta-information.
287265b24292SAdrian HunterFor SQLite3 databases, the sqlite_master table is included.
287365b24292SAdrian HunterFor PostgreSQL databases, information_schema.tables/views/columns are included.
287465b24292SAdrian Hunter<h3>Find</h3>
287565b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
287665b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
287765b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
287835fa1ceeSAdrian Hunter<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
287935fa1ceeSAdrian Hunterwill go to the next/previous result in id order, instead of display order.
288065b24292SAdrian Hunter"""
288165b24292SAdrian Hunter
288265b24292SAdrian Hunter# Help window
288365b24292SAdrian Hunter
288465b24292SAdrian Hunterclass HelpWindow(QMdiSubWindow):
288565b24292SAdrian Hunter
288665b24292SAdrian Hunter	def __init__(self, glb, parent=None):
288765b24292SAdrian Hunter		super(HelpWindow, self).__init__(parent)
288865b24292SAdrian Hunter
288965b24292SAdrian Hunter		self.text = QTextBrowser()
289065b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
289165b24292SAdrian Hunter		self.text.setReadOnly(True)
289265b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
289365b24292SAdrian Hunter
289465b24292SAdrian Hunter		self.setWidget(self.text)
289565b24292SAdrian Hunter
289665b24292SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
289765b24292SAdrian Hunter
289865b24292SAdrian Hunter# Main window that only displays the help text
289965b24292SAdrian Hunter
290065b24292SAdrian Hunterclass HelpOnlyWindow(QMainWindow):
290165b24292SAdrian Hunter
290265b24292SAdrian Hunter	def __init__(self, parent=None):
290365b24292SAdrian Hunter		super(HelpOnlyWindow, self).__init__(parent)
290465b24292SAdrian Hunter
290565b24292SAdrian Hunter		self.setMinimumSize(200, 100)
290665b24292SAdrian Hunter		self.resize(800, 600)
290765b24292SAdrian Hunter		self.setWindowTitle("Exported SQL Viewer Help")
290865b24292SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
290965b24292SAdrian Hunter
291065b24292SAdrian Hunter		self.text = QTextBrowser()
291165b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
291265b24292SAdrian Hunter		self.text.setReadOnly(True)
291365b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
291465b24292SAdrian Hunter
291565b24292SAdrian Hunter		self.setCentralWidget(self.text)
291665b24292SAdrian Hunter
2917b62d18abSAdrian Hunter# PostqreSQL server version
2918b62d18abSAdrian Hunter
2919b62d18abSAdrian Hunterdef PostqreSQLServerVersion(db):
2920b62d18abSAdrian Hunter	query = QSqlQuery(db)
2921b62d18abSAdrian Hunter	QueryExec(query, "SELECT VERSION()")
2922b62d18abSAdrian Hunter	if query.next():
2923b62d18abSAdrian Hunter		v_str = query.value(0)
2924b62d18abSAdrian Hunter		v_list = v_str.strip().split(" ")
2925b62d18abSAdrian Hunter		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
2926b62d18abSAdrian Hunter			return v_list[1]
2927b62d18abSAdrian Hunter		return v_str
2928b62d18abSAdrian Hunter	return "Unknown"
2929b62d18abSAdrian Hunter
2930b62d18abSAdrian Hunter# SQLite version
2931b62d18abSAdrian Hunter
2932b62d18abSAdrian Hunterdef SQLiteVersion(db):
2933b62d18abSAdrian Hunter	query = QSqlQuery(db)
2934b62d18abSAdrian Hunter	QueryExec(query, "SELECT sqlite_version()")
2935b62d18abSAdrian Hunter	if query.next():
2936b62d18abSAdrian Hunter		return query.value(0)
2937b62d18abSAdrian Hunter	return "Unknown"
2938b62d18abSAdrian Hunter
2939b62d18abSAdrian Hunter# About dialog
2940b62d18abSAdrian Hunter
2941b62d18abSAdrian Hunterclass AboutDialog(QDialog):
2942b62d18abSAdrian Hunter
2943b62d18abSAdrian Hunter	def __init__(self, glb, parent=None):
2944b62d18abSAdrian Hunter		super(AboutDialog, self).__init__(parent)
2945b62d18abSAdrian Hunter
2946b62d18abSAdrian Hunter		self.setWindowTitle("About Exported SQL Viewer")
2947b62d18abSAdrian Hunter		self.setMinimumWidth(300)
2948b62d18abSAdrian Hunter
2949b62d18abSAdrian Hunter		pyside_version = "1" if pyside_version_1 else "2"
2950b62d18abSAdrian Hunter
2951b62d18abSAdrian Hunter		text = "<pre>"
2952b62d18abSAdrian Hunter		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
2953b62d18abSAdrian Hunter		text += "PySide version:     " + pyside_version + "\n"
2954b62d18abSAdrian Hunter		text += "Qt version:         " + qVersion() + "\n"
2955b62d18abSAdrian Hunter		if glb.dbref.is_sqlite3:
2956b62d18abSAdrian Hunter			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
2957b62d18abSAdrian Hunter		else:
2958b62d18abSAdrian Hunter			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
2959b62d18abSAdrian Hunter		text += "</pre>"
2960b62d18abSAdrian Hunter
2961b62d18abSAdrian Hunter		self.text = QTextBrowser()
2962b62d18abSAdrian Hunter		self.text.setHtml(text)
2963b62d18abSAdrian Hunter		self.text.setReadOnly(True)
2964b62d18abSAdrian Hunter		self.text.setOpenExternalLinks(True)
2965b62d18abSAdrian Hunter
2966b62d18abSAdrian Hunter		self.vbox = QVBoxLayout()
2967b62d18abSAdrian Hunter		self.vbox.addWidget(self.text)
2968b62d18abSAdrian Hunter
2969b62d18abSAdrian Hunter		self.setLayout(self.vbox);
2970b62d18abSAdrian Hunter
297182f68e28SAdrian Hunter# Font resize
297282f68e28SAdrian Hunter
297382f68e28SAdrian Hunterdef ResizeFont(widget, diff):
297482f68e28SAdrian Hunter	font = widget.font()
297582f68e28SAdrian Hunter	sz = font.pointSize()
297682f68e28SAdrian Hunter	font.setPointSize(sz + diff)
297782f68e28SAdrian Hunter	widget.setFont(font)
297882f68e28SAdrian Hunter
297982f68e28SAdrian Hunterdef ShrinkFont(widget):
298082f68e28SAdrian Hunter	ResizeFont(widget, -1)
298182f68e28SAdrian Hunter
298282f68e28SAdrian Hunterdef EnlargeFont(widget):
298382f68e28SAdrian Hunter	ResizeFont(widget, 1)
298482f68e28SAdrian Hunter
29851beb5c7bSAdrian Hunter# Unique name for sub-windows
29861beb5c7bSAdrian Hunter
29871beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr):
29881beb5c7bSAdrian Hunter	if nr > 1:
29891beb5c7bSAdrian Hunter		name += " <" + str(nr) + ">"
29901beb5c7bSAdrian Hunter	return name
29911beb5c7bSAdrian Hunter
29921beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name):
29931beb5c7bSAdrian Hunter	nr = 1
29941beb5c7bSAdrian Hunter	while True:
29951beb5c7bSAdrian Hunter		unique_name = NumberedWindowName(name, nr)
29961beb5c7bSAdrian Hunter		ok = True
29971beb5c7bSAdrian Hunter		for sub_window in mdi_area.subWindowList():
29981beb5c7bSAdrian Hunter			if sub_window.name == unique_name:
29991beb5c7bSAdrian Hunter				ok = False
30001beb5c7bSAdrian Hunter				break
30011beb5c7bSAdrian Hunter		if ok:
30021beb5c7bSAdrian Hunter			return unique_name
30031beb5c7bSAdrian Hunter		nr += 1
30041beb5c7bSAdrian Hunter
30051beb5c7bSAdrian Hunter# Add a sub-window
30061beb5c7bSAdrian Hunter
30071beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name):
30081beb5c7bSAdrian Hunter	unique_name = UniqueSubWindowName(mdi_area, name)
30091beb5c7bSAdrian Hunter	sub_window.setMinimumSize(200, 100)
30101beb5c7bSAdrian Hunter	sub_window.resize(800, 600)
30111beb5c7bSAdrian Hunter	sub_window.setWindowTitle(unique_name)
30121beb5c7bSAdrian Hunter	sub_window.setAttribute(Qt.WA_DeleteOnClose)
30131beb5c7bSAdrian Hunter	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
30141beb5c7bSAdrian Hunter	sub_window.name = unique_name
30151beb5c7bSAdrian Hunter	mdi_area.addSubWindow(sub_window)
30161beb5c7bSAdrian Hunter	sub_window.show()
30171beb5c7bSAdrian Hunter
3018031c2a00SAdrian Hunter# Main window
3019031c2a00SAdrian Hunter
3020031c2a00SAdrian Hunterclass MainWindow(QMainWindow):
3021031c2a00SAdrian Hunter
3022031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
3023031c2a00SAdrian Hunter		super(MainWindow, self).__init__(parent)
3024031c2a00SAdrian Hunter
3025031c2a00SAdrian Hunter		self.glb = glb
3026031c2a00SAdrian Hunter
30271beb5c7bSAdrian Hunter		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3028031c2a00SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3029031c2a00SAdrian Hunter		self.setMinimumSize(200, 100)
3030031c2a00SAdrian Hunter
30311beb5c7bSAdrian Hunter		self.mdi_area = QMdiArea()
30321beb5c7bSAdrian Hunter		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
30331beb5c7bSAdrian Hunter		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3034031c2a00SAdrian Hunter
30351beb5c7bSAdrian Hunter		self.setCentralWidget(self.mdi_area)
3036031c2a00SAdrian Hunter
30371beb5c7bSAdrian Hunter		menu = self.menuBar()
3038031c2a00SAdrian Hunter
30391beb5c7bSAdrian Hunter		file_menu = menu.addMenu("&File")
30401beb5c7bSAdrian Hunter		file_menu.addAction(CreateExitAction(glb.app, self))
30411beb5c7bSAdrian Hunter
3042ebd70c7dSAdrian Hunter		edit_menu = menu.addMenu("&Edit")
304396c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
304496c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3045ebd70c7dSAdrian Hunter		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
30468392b74bSAdrian Hunter		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
304782f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
304882f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3049ebd70c7dSAdrian Hunter
30501beb5c7bSAdrian Hunter		reports_menu = menu.addMenu("&Reports")
3051655cb952SAdrian Hunter		if IsSelectable(glb.db, "calls"):
30521beb5c7bSAdrian Hunter			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
30531beb5c7bSAdrian Hunter
3054ae8b887cSAdrian Hunter		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3055ae8b887cSAdrian Hunter			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3056ae8b887cSAdrian Hunter
305776099f98SAdrian Hunter		self.EventMenu(GetEventList(glb.db), reports_menu)
305876099f98SAdrian Hunter
3059cd358012SAdrian Hunter		if IsSelectable(glb.db, "calls"):
3060cd358012SAdrian Hunter			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3061cd358012SAdrian Hunter
30628392b74bSAdrian Hunter		self.TableMenu(GetTableList(glb), menu)
30638392b74bSAdrian Hunter
30641beb5c7bSAdrian Hunter		self.window_menu = WindowMenu(self.mdi_area, menu)
30651beb5c7bSAdrian Hunter
306665b24292SAdrian Hunter		help_menu = menu.addMenu("&Help")
306765b24292SAdrian Hunter		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3068b62d18abSAdrian Hunter		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
306965b24292SAdrian Hunter
30704b208453SAdrian Hunter	def Try(self, fn):
30714b208453SAdrian Hunter		win = self.mdi_area.activeSubWindow()
30724b208453SAdrian Hunter		if win:
30734b208453SAdrian Hunter			try:
30744b208453SAdrian Hunter				fn(win.view)
30754b208453SAdrian Hunter			except:
30764b208453SAdrian Hunter				pass
30774b208453SAdrian Hunter
307896c43b9aSAdrian Hunter	def CopyToClipboard(self):
307996c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardHdr)
308096c43b9aSAdrian Hunter
308196c43b9aSAdrian Hunter	def CopyToClipboardCSV(self):
308296c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardCSV)
308396c43b9aSAdrian Hunter
3084ebd70c7dSAdrian Hunter	def Find(self):
3085ebd70c7dSAdrian Hunter		win = self.mdi_area.activeSubWindow()
3086ebd70c7dSAdrian Hunter		if win:
3087ebd70c7dSAdrian Hunter			try:
3088ebd70c7dSAdrian Hunter				win.find_bar.Activate()
3089ebd70c7dSAdrian Hunter			except:
3090ebd70c7dSAdrian Hunter				pass
3091ebd70c7dSAdrian Hunter
30928392b74bSAdrian Hunter	def FetchMoreRecords(self):
30938392b74bSAdrian Hunter		win = self.mdi_area.activeSubWindow()
30948392b74bSAdrian Hunter		if win:
30958392b74bSAdrian Hunter			try:
30968392b74bSAdrian Hunter				win.fetch_bar.Activate()
30978392b74bSAdrian Hunter			except:
30988392b74bSAdrian Hunter				pass
30998392b74bSAdrian Hunter
310082f68e28SAdrian Hunter	def ShrinkFont(self):
31014b208453SAdrian Hunter		self.Try(ShrinkFont)
310282f68e28SAdrian Hunter
310382f68e28SAdrian Hunter	def EnlargeFont(self):
31044b208453SAdrian Hunter		self.Try(EnlargeFont)
310582f68e28SAdrian Hunter
310676099f98SAdrian Hunter	def EventMenu(self, events, reports_menu):
310776099f98SAdrian Hunter		branches_events = 0
310876099f98SAdrian Hunter		for event in events:
310976099f98SAdrian Hunter			event = event.split(":")[0]
311076099f98SAdrian Hunter			if event == "branches":
311176099f98SAdrian Hunter				branches_events += 1
311276099f98SAdrian Hunter		dbid = 0
311376099f98SAdrian Hunter		for event in events:
311476099f98SAdrian Hunter			dbid += 1
311576099f98SAdrian Hunter			event = event.split(":")[0]
311676099f98SAdrian Hunter			if event == "branches":
311776099f98SAdrian Hunter				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
311876099f98SAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
3119210cf1f9SAdrian Hunter				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3120210cf1f9SAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
312176099f98SAdrian Hunter
31228392b74bSAdrian Hunter	def TableMenu(self, tables, menu):
31238392b74bSAdrian Hunter		table_menu = menu.addMenu("&Tables")
31248392b74bSAdrian Hunter		for table in tables:
31258392b74bSAdrian Hunter			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
31268392b74bSAdrian Hunter
31271beb5c7bSAdrian Hunter	def NewCallGraph(self):
31281beb5c7bSAdrian Hunter		CallGraphWindow(self.glb, self)
3129031c2a00SAdrian Hunter
3130ae8b887cSAdrian Hunter	def NewCallTree(self):
3131ae8b887cSAdrian Hunter		CallTreeWindow(self.glb, self)
3132ae8b887cSAdrian Hunter
3133cd358012SAdrian Hunter	def NewTopCalls(self):
3134cd358012SAdrian Hunter		dialog = TopCallsDialog(self.glb, self)
3135cd358012SAdrian Hunter		ret = dialog.exec_()
3136cd358012SAdrian Hunter		if ret:
3137cd358012SAdrian Hunter			TopCallsWindow(self.glb, dialog.report_vars, self)
3138cd358012SAdrian Hunter
313976099f98SAdrian Hunter	def NewBranchView(self, event_id):
3140947cc38dSAdrian Hunter		BranchWindow(self.glb, event_id, ReportVars(), self)
314176099f98SAdrian Hunter
3142210cf1f9SAdrian Hunter	def NewSelectedBranchView(self, event_id):
3143210cf1f9SAdrian Hunter		dialog = SelectedBranchDialog(self.glb, self)
3144210cf1f9SAdrian Hunter		ret = dialog.exec_()
3145210cf1f9SAdrian Hunter		if ret:
3146947cc38dSAdrian Hunter			BranchWindow(self.glb, event_id, dialog.report_vars, self)
3147210cf1f9SAdrian Hunter
31488392b74bSAdrian Hunter	def NewTableView(self, table_name):
31498392b74bSAdrian Hunter		TableWindow(self.glb, table_name, self)
31508392b74bSAdrian Hunter
315165b24292SAdrian Hunter	def Help(self):
315265b24292SAdrian Hunter		HelpWindow(self.glb, self)
315365b24292SAdrian Hunter
3154b62d18abSAdrian Hunter	def About(self):
3155b62d18abSAdrian Hunter		dialog = AboutDialog(self.glb, self)
3156b62d18abSAdrian Hunter		dialog.exec_()
3157b62d18abSAdrian Hunter
315876099f98SAdrian Hunter# XED Disassembler
315976099f98SAdrian Hunter
316076099f98SAdrian Hunterclass xed_state_t(Structure):
316176099f98SAdrian Hunter
316276099f98SAdrian Hunter	_fields_ = [
316376099f98SAdrian Hunter		("mode", c_int),
316476099f98SAdrian Hunter		("width", c_int)
316576099f98SAdrian Hunter	]
316676099f98SAdrian Hunter
316776099f98SAdrian Hunterclass XEDInstruction():
316876099f98SAdrian Hunter
316976099f98SAdrian Hunter	def __init__(self, libxed):
317076099f98SAdrian Hunter		# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
317176099f98SAdrian Hunter		xedd_t = c_byte * 512
317276099f98SAdrian Hunter		self.xedd = xedd_t()
317376099f98SAdrian Hunter		self.xedp = addressof(self.xedd)
317476099f98SAdrian Hunter		libxed.xed_decoded_inst_zero(self.xedp)
317576099f98SAdrian Hunter		self.state = xed_state_t()
317676099f98SAdrian Hunter		self.statep = addressof(self.state)
317776099f98SAdrian Hunter		# Buffer for disassembled instruction text
317876099f98SAdrian Hunter		self.buffer = create_string_buffer(256)
317976099f98SAdrian Hunter		self.bufferp = addressof(self.buffer)
318076099f98SAdrian Hunter
318176099f98SAdrian Hunterclass LibXED():
318276099f98SAdrian Hunter
318376099f98SAdrian Hunter	def __init__(self):
31845ed4419dSAdrian Hunter		try:
318576099f98SAdrian Hunter			self.libxed = CDLL("libxed.so")
31865ed4419dSAdrian Hunter		except:
31875ed4419dSAdrian Hunter			self.libxed = None
31885ed4419dSAdrian Hunter		if not self.libxed:
31895ed4419dSAdrian Hunter			self.libxed = CDLL("/usr/local/lib/libxed.so")
319076099f98SAdrian Hunter
319176099f98SAdrian Hunter		self.xed_tables_init = self.libxed.xed_tables_init
319276099f98SAdrian Hunter		self.xed_tables_init.restype = None
319376099f98SAdrian Hunter		self.xed_tables_init.argtypes = []
319476099f98SAdrian Hunter
319576099f98SAdrian Hunter		self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
319676099f98SAdrian Hunter		self.xed_decoded_inst_zero.restype = None
319776099f98SAdrian Hunter		self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
319876099f98SAdrian Hunter
319976099f98SAdrian Hunter		self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
320076099f98SAdrian Hunter		self.xed_operand_values_set_mode.restype = None
320176099f98SAdrian Hunter		self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
320276099f98SAdrian Hunter
320376099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
320476099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.restype = None
320576099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
320676099f98SAdrian Hunter
320776099f98SAdrian Hunter		self.xed_decode = self.libxed.xed_decode
320876099f98SAdrian Hunter		self.xed_decode.restype = c_int
320976099f98SAdrian Hunter		self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
321076099f98SAdrian Hunter
321176099f98SAdrian Hunter		self.xed_format_context = self.libxed.xed_format_context
321276099f98SAdrian Hunter		self.xed_format_context.restype = c_uint
321376099f98SAdrian Hunter		self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
321476099f98SAdrian Hunter
321576099f98SAdrian Hunter		self.xed_tables_init()
321676099f98SAdrian Hunter
321776099f98SAdrian Hunter	def Instruction(self):
321876099f98SAdrian Hunter		return XEDInstruction(self)
321976099f98SAdrian Hunter
322076099f98SAdrian Hunter	def SetMode(self, inst, mode):
322176099f98SAdrian Hunter		if mode:
322276099f98SAdrian Hunter			inst.state.mode = 4 # 32-bit
322376099f98SAdrian Hunter			inst.state.width = 4 # 4 bytes
322476099f98SAdrian Hunter		else:
322576099f98SAdrian Hunter			inst.state.mode = 1 # 64-bit
322676099f98SAdrian Hunter			inst.state.width = 8 # 8 bytes
322776099f98SAdrian Hunter		self.xed_operand_values_set_mode(inst.xedp, inst.statep)
322876099f98SAdrian Hunter
322976099f98SAdrian Hunter	def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
323076099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode(inst.xedp)
323176099f98SAdrian Hunter		err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
323276099f98SAdrian Hunter		if err:
323376099f98SAdrian Hunter			return 0, ""
323476099f98SAdrian Hunter		# Use AT&T mode (2), alternative is Intel (3)
323576099f98SAdrian Hunter		ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
323676099f98SAdrian Hunter		if not ok:
323776099f98SAdrian Hunter			return 0, ""
3238606bd60aSAdrian Hunter		if sys.version_info[0] == 2:
3239606bd60aSAdrian Hunter			result = inst.buffer.value
3240606bd60aSAdrian Hunter		else:
3241606bd60aSAdrian Hunter			result = inst.buffer.value.decode()
324276099f98SAdrian Hunter		# Return instruction length and the disassembled instruction text
324376099f98SAdrian Hunter		# For now, assume the length is in byte 166
3244606bd60aSAdrian Hunter		return inst.xedd[166], result
324576099f98SAdrian Hunter
324676099f98SAdrian Hunterdef TryOpen(file_name):
324776099f98SAdrian Hunter	try:
324876099f98SAdrian Hunter		return open(file_name, "rb")
324976099f98SAdrian Hunter	except:
325076099f98SAdrian Hunter		return None
325176099f98SAdrian Hunter
325276099f98SAdrian Hunterdef Is64Bit(f):
325376099f98SAdrian Hunter	result = sizeof(c_void_p)
325476099f98SAdrian Hunter	# ELF support only
325576099f98SAdrian Hunter	pos = f.tell()
325676099f98SAdrian Hunter	f.seek(0)
325776099f98SAdrian Hunter	header = f.read(7)
325876099f98SAdrian Hunter	f.seek(pos)
325976099f98SAdrian Hunter	magic = header[0:4]
3260606bd60aSAdrian Hunter	if sys.version_info[0] == 2:
326176099f98SAdrian Hunter		eclass = ord(header[4])
326276099f98SAdrian Hunter		encoding = ord(header[5])
326376099f98SAdrian Hunter		version = ord(header[6])
3264606bd60aSAdrian Hunter	else:
3265606bd60aSAdrian Hunter		eclass = header[4]
3266606bd60aSAdrian Hunter		encoding = header[5]
3267606bd60aSAdrian Hunter		version = header[6]
326876099f98SAdrian Hunter	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
326976099f98SAdrian Hunter		result = True if eclass == 2 else False
327076099f98SAdrian Hunter	return result
327176099f98SAdrian Hunter
3272031c2a00SAdrian Hunter# Global data
3273031c2a00SAdrian Hunter
3274031c2a00SAdrian Hunterclass Glb():
3275031c2a00SAdrian Hunter
3276031c2a00SAdrian Hunter	def __init__(self, dbref, db, dbname):
3277031c2a00SAdrian Hunter		self.dbref = dbref
3278031c2a00SAdrian Hunter		self.db = db
3279031c2a00SAdrian Hunter		self.dbname = dbname
328076099f98SAdrian Hunter		self.home_dir = os.path.expanduser("~")
328176099f98SAdrian Hunter		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
328276099f98SAdrian Hunter		if self.buildid_dir:
328376099f98SAdrian Hunter			self.buildid_dir += "/.build-id/"
328476099f98SAdrian Hunter		else:
328576099f98SAdrian Hunter			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3286031c2a00SAdrian Hunter		self.app = None
3287031c2a00SAdrian Hunter		self.mainwindow = None
32888392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit = weakref.WeakSet()
328976099f98SAdrian Hunter		try:
329076099f98SAdrian Hunter			self.disassembler = LibXED()
329176099f98SAdrian Hunter			self.have_disassembler = True
329276099f98SAdrian Hunter		except:
329376099f98SAdrian Hunter			self.have_disassembler = False
329476099f98SAdrian Hunter
329576099f98SAdrian Hunter	def FileFromBuildId(self, build_id):
329676099f98SAdrian Hunter		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
329776099f98SAdrian Hunter		return TryOpen(file_name)
329876099f98SAdrian Hunter
329976099f98SAdrian Hunter	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
330076099f98SAdrian Hunter		# Assume current machine i.e. no support for virtualization
330176099f98SAdrian Hunter		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
330276099f98SAdrian Hunter			file_name = os.getenv("PERF_KCORE")
330376099f98SAdrian Hunter			f = TryOpen(file_name) if file_name else None
330476099f98SAdrian Hunter			if f:
330576099f98SAdrian Hunter				return f
330676099f98SAdrian Hunter			# For now, no special handling if long_name is /proc/kcore
330776099f98SAdrian Hunter			f = TryOpen(long_name)
330876099f98SAdrian Hunter			if f:
330976099f98SAdrian Hunter				return f
331076099f98SAdrian Hunter		f = self.FileFromBuildId(build_id)
331176099f98SAdrian Hunter		if f:
331276099f98SAdrian Hunter			return f
331376099f98SAdrian Hunter		return None
33148392b74bSAdrian Hunter
33158392b74bSAdrian Hunter	def AddInstanceToShutdownOnExit(self, instance):
33168392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit.add(instance)
33178392b74bSAdrian Hunter
33188392b74bSAdrian Hunter	# Shutdown any background processes or threads
33198392b74bSAdrian Hunter	def ShutdownInstances(self):
33208392b74bSAdrian Hunter		for x in self.instances_to_shutdown_on_exit:
33218392b74bSAdrian Hunter			try:
33228392b74bSAdrian Hunter				x.Shutdown()
33238392b74bSAdrian Hunter			except:
33248392b74bSAdrian Hunter				pass
3325031c2a00SAdrian Hunter
3326031c2a00SAdrian Hunter# Database reference
3327031c2a00SAdrian Hunter
3328031c2a00SAdrian Hunterclass DBRef():
3329031c2a00SAdrian Hunter
3330031c2a00SAdrian Hunter	def __init__(self, is_sqlite3, dbname):
3331031c2a00SAdrian Hunter		self.is_sqlite3 = is_sqlite3
3332031c2a00SAdrian Hunter		self.dbname = dbname
3333031c2a00SAdrian Hunter
3334031c2a00SAdrian Hunter	def Open(self, connection_name):
3335031c2a00SAdrian Hunter		dbname = self.dbname
3336031c2a00SAdrian Hunter		if self.is_sqlite3:
3337031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3338031c2a00SAdrian Hunter		else:
3339031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3340031c2a00SAdrian Hunter			opts = dbname.split()
3341031c2a00SAdrian Hunter			for opt in opts:
3342031c2a00SAdrian Hunter				if "=" in opt:
3343031c2a00SAdrian Hunter					opt = opt.split("=")
3344031c2a00SAdrian Hunter					if opt[0] == "hostname":
3345031c2a00SAdrian Hunter						db.setHostName(opt[1])
3346031c2a00SAdrian Hunter					elif opt[0] == "port":
3347031c2a00SAdrian Hunter						db.setPort(int(opt[1]))
3348031c2a00SAdrian Hunter					elif opt[0] == "username":
3349031c2a00SAdrian Hunter						db.setUserName(opt[1])
3350031c2a00SAdrian Hunter					elif opt[0] == "password":
3351031c2a00SAdrian Hunter						db.setPassword(opt[1])
3352031c2a00SAdrian Hunter					elif opt[0] == "dbname":
3353031c2a00SAdrian Hunter						dbname = opt[1]
3354031c2a00SAdrian Hunter				else:
3355031c2a00SAdrian Hunter					dbname = opt
3356031c2a00SAdrian Hunter
3357031c2a00SAdrian Hunter		db.setDatabaseName(dbname)
3358031c2a00SAdrian Hunter		if not db.open():
3359031c2a00SAdrian Hunter			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3360031c2a00SAdrian Hunter		return db, dbname
3361031c2a00SAdrian Hunter
3362031c2a00SAdrian Hunter# Main
3363031c2a00SAdrian Hunter
3364031c2a00SAdrian Hunterdef Main():
3365*1ed7f47fSAdrian Hunter	usage_str =	"exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
3366*1ed7f47fSAdrian Hunter			"   or: exported-sql-viewer.py --help-only"
3367*1ed7f47fSAdrian Hunter	ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
3368*1ed7f47fSAdrian Hunter	ap.add_argument("dbname", nargs="?")
3369*1ed7f47fSAdrian Hunter	ap.add_argument("--help-only", action='store_true')
3370*1ed7f47fSAdrian Hunter	args = ap.parse_args()
3371031c2a00SAdrian Hunter
3372*1ed7f47fSAdrian Hunter	if args.help_only:
337365b24292SAdrian Hunter		app = QApplication(sys.argv)
337465b24292SAdrian Hunter		mainwindow = HelpOnlyWindow()
337565b24292SAdrian Hunter		mainwindow.show()
337665b24292SAdrian Hunter		err = app.exec_()
337765b24292SAdrian Hunter		sys.exit(err)
3378031c2a00SAdrian Hunter
3379*1ed7f47fSAdrian Hunter	dbname = args.dbname
3380*1ed7f47fSAdrian Hunter	if dbname is None:
3381*1ed7f47fSAdrian Hunter		ap.print_usage()
3382*1ed7f47fSAdrian Hunter		print("Too few arguments")
3383*1ed7f47fSAdrian Hunter		sys.exit(1)
3384*1ed7f47fSAdrian Hunter
3385031c2a00SAdrian Hunter	is_sqlite3 = False
3386031c2a00SAdrian Hunter	try:
3387beda0e72STony Jones		f = open(dbname, "rb")
3388beda0e72STony Jones		if f.read(15) == b'SQLite format 3':
3389031c2a00SAdrian Hunter			is_sqlite3 = True
3390031c2a00SAdrian Hunter		f.close()
3391031c2a00SAdrian Hunter	except:
3392031c2a00SAdrian Hunter		pass
3393031c2a00SAdrian Hunter
3394031c2a00SAdrian Hunter	dbref = DBRef(is_sqlite3, dbname)
3395031c2a00SAdrian Hunter	db, dbname = dbref.Open("main")
3396031c2a00SAdrian Hunter	glb = Glb(dbref, db, dbname)
3397031c2a00SAdrian Hunter	app = QApplication(sys.argv)
3398031c2a00SAdrian Hunter	glb.app = app
3399031c2a00SAdrian Hunter	mainwindow = MainWindow(glb)
3400031c2a00SAdrian Hunter	glb.mainwindow = mainwindow
3401031c2a00SAdrian Hunter	mainwindow.show()
3402031c2a00SAdrian Hunter	err = app.exec_()
34038392b74bSAdrian Hunter	glb.ShutdownInstances()
3404031c2a00SAdrian Hunter	db.close()
3405031c2a00SAdrian Hunter	sys.exit(err)
3406031c2a00SAdrian Hunter
3407031c2a00SAdrian Hunterif __name__ == "__main__":
3408031c2a00SAdrian Hunter	Main()
3409