1031c2a00SAdrian Hunter#!/usr/bin/python2
2031c2a00SAdrian Hunter# SPDX-License-Identifier: GPL-2.0
3031c2a00SAdrian Hunter# exported-sql-viewer.py: view data from sql database
4031c2a00SAdrian Hunter# Copyright (c) 2014-2018, Intel Corporation.
5031c2a00SAdrian Hunter
6031c2a00SAdrian Hunter# To use this script you will need to have exported data using either the
7031c2a00SAdrian Hunter# export-to-sqlite.py or the export-to-postgresql.py script.  Refer to those
8031c2a00SAdrian Hunter# scripts for details.
9031c2a00SAdrian Hunter#
10031c2a00SAdrian Hunter# Following on from the example in the export scripts, a
11031c2a00SAdrian Hunter# call-graph can be displayed for the pt_example database like this:
12031c2a00SAdrian Hunter#
13031c2a00SAdrian Hunter#	python tools/perf/scripts/python/exported-sql-viewer.py pt_example
14031c2a00SAdrian Hunter#
15031c2a00SAdrian Hunter# Note that for PostgreSQL, this script supports connecting to remote databases
16031c2a00SAdrian Hunter# by setting hostname, port, username, password, and dbname e.g.
17031c2a00SAdrian Hunter#
18031c2a00SAdrian Hunter#	python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
19031c2a00SAdrian Hunter#
20031c2a00SAdrian Hunter# The result is a GUI window with a tree representing a context-sensitive
21031c2a00SAdrian Hunter# call-graph.  Expanding a couple of levels of the tree and adjusting column
22031c2a00SAdrian Hunter# widths to suit will display something like:
23031c2a00SAdrian Hunter#
24031c2a00SAdrian Hunter#                                         Call Graph: pt_example
25031c2a00SAdrian Hunter# Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
26031c2a00SAdrian Hunter# v- ls
27031c2a00SAdrian Hunter#     v- 2638:2638
28031c2a00SAdrian Hunter#         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
29031c2a00SAdrian Hunter#           |- unknown               unknown       1        13198     0.1              1              0.0
30031c2a00SAdrian Hunter#           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
31031c2a00SAdrian Hunter#           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
32031c2a00SAdrian Hunter#           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
33031c2a00SAdrian Hunter#              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
34031c2a00SAdrian Hunter#              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
35031c2a00SAdrian Hunter#              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
36031c2a00SAdrian Hunter#              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
37031c2a00SAdrian Hunter#              v- main               ls            1      8182043    99.6         180254             99.9
38031c2a00SAdrian Hunter#
39031c2a00SAdrian Hunter# Points to note:
40031c2a00SAdrian Hunter#	The top level is a command name (comm)
41031c2a00SAdrian Hunter#	The next level is a thread (pid:tid)
42031c2a00SAdrian Hunter#	Subsequent levels are functions
43031c2a00SAdrian Hunter#	'Count' is the number of calls
44031c2a00SAdrian Hunter#	'Time' is the elapsed time until the function returns
45031c2a00SAdrian Hunter#	Percentages are relative to the level above
46031c2a00SAdrian Hunter#	'Branch Count' is the total number of branches for that function and all
47031c2a00SAdrian Hunter#       functions that it calls
48031c2a00SAdrian Hunter
4976099f98SAdrian Hunter# There is also a "All branches" report, which displays branches and
5076099f98SAdrian Hunter# possibly disassembly.  However, presently, the only supported disassembler is
5176099f98SAdrian Hunter# Intel XED, and additionally the object code must be present in perf build ID
5276099f98SAdrian Hunter# cache. To use Intel XED, libxed.so must be present. To build and install
5376099f98SAdrian Hunter# libxed.so:
5476099f98SAdrian Hunter#            git clone https://github.com/intelxed/mbuild.git mbuild
5576099f98SAdrian Hunter#            git clone https://github.com/intelxed/xed
5676099f98SAdrian Hunter#            cd xed
5776099f98SAdrian Hunter#            ./mfile.py --share
5876099f98SAdrian Hunter#            sudo ./mfile.py --prefix=/usr/local install
5976099f98SAdrian Hunter#            sudo ldconfig
6076099f98SAdrian Hunter#
6176099f98SAdrian Hunter# Example report:
6276099f98SAdrian Hunter#
6376099f98SAdrian Hunter# Time           CPU  Command  PID    TID    Branch Type            In Tx  Branch
6476099f98SAdrian Hunter# 8107675239590  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
6576099f98SAdrian Hunter#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
6676099f98SAdrian Hunter# 8107675239899  2    ls       22011  22011  hardware interrupt     No         7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
6776099f98SAdrian Hunter# 8107675241900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
6876099f98SAdrian Hunter#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
6976099f98SAdrian Hunter#                                                                              7fab593ea263 e8 c8 06 00 00                                  callq  0x7fab593ea930
7076099f98SAdrian Hunter# 8107675241900  2    ls       22011  22011  call                   No         7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
7176099f98SAdrian Hunter#                                                                              7fab593ea930 55                                              pushq  %rbp
7276099f98SAdrian Hunter#                                                                              7fab593ea931 48 89 e5                                        mov %rsp, %rbp
7376099f98SAdrian Hunter#                                                                              7fab593ea934 41 57                                           pushq  %r15
7476099f98SAdrian Hunter#                                                                              7fab593ea936 41 56                                           pushq  %r14
7576099f98SAdrian Hunter#                                                                              7fab593ea938 41 55                                           pushq  %r13
7676099f98SAdrian Hunter#                                                                              7fab593ea93a 41 54                                           pushq  %r12
7776099f98SAdrian Hunter#                                                                              7fab593ea93c 53                                              pushq  %rbx
7876099f98SAdrian Hunter#                                                                              7fab593ea93d 48 89 fb                                        mov %rdi, %rbx
7976099f98SAdrian Hunter#                                                                              7fab593ea940 48 83 ec 68                                     sub $0x68, %rsp
8076099f98SAdrian Hunter#                                                                              7fab593ea944 0f 31                                           rdtsc
8176099f98SAdrian Hunter#                                                                              7fab593ea946 48 c1 e2 20                                     shl $0x20, %rdx
8276099f98SAdrian Hunter#                                                                              7fab593ea94a 89 c0                                           mov %eax, %eax
8376099f98SAdrian Hunter#                                                                              7fab593ea94c 48 09 c2                                        or %rax, %rdx
8476099f98SAdrian Hunter#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
8576099f98SAdrian Hunter# 8107675242232  2    ls       22011  22011  hardware interrupt     No         7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
8676099f98SAdrian Hunter# 8107675242900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
8776099f98SAdrian Hunter#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
8876099f98SAdrian Hunter#                                                                              7fab593ea956 48 89 15 3b 13 22 00                            movq  %rdx, 0x22133b(%rip)
8976099f98SAdrian Hunter# 8107675243232  2    ls       22011  22011  hardware interrupt     No         7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
9076099f98SAdrian Hunter
91031c2a00SAdrian Hunterimport sys
921beb5c7bSAdrian Hunterimport weakref
931beb5c7bSAdrian Hunterimport threading
94ebd70c7dSAdrian Hunterimport string
958392b74bSAdrian Hunterimport cPickle
968392b74bSAdrian Hunterimport re
978392b74bSAdrian Hunterimport os
98031c2a00SAdrian Hunterfrom PySide.QtCore import *
99031c2a00SAdrian Hunterfrom PySide.QtGui import *
100031c2a00SAdrian Hunterfrom PySide.QtSql import *
101031c2a00SAdrian Hunterfrom decimal import *
1028392b74bSAdrian Hunterfrom ctypes import *
1038392b74bSAdrian Hunterfrom multiprocessing import Process, Array, Value, Event
104031c2a00SAdrian Hunter
105031c2a00SAdrian Hunter# Data formatting helpers
106031c2a00SAdrian Hunter
10776099f98SAdrian Hunterdef tohex(ip):
10876099f98SAdrian Hunter	if ip < 0:
10976099f98SAdrian Hunter		ip += 1 << 64
11076099f98SAdrian Hunter	return "%x" % ip
11176099f98SAdrian Hunter
11276099f98SAdrian Hunterdef offstr(offset):
11376099f98SAdrian Hunter	if offset:
11476099f98SAdrian Hunter		return "+0x%x" % offset
11576099f98SAdrian Hunter	return ""
11676099f98SAdrian Hunter
117031c2a00SAdrian Hunterdef dsoname(name):
118031c2a00SAdrian Hunter	if name == "[kernel.kallsyms]":
119031c2a00SAdrian Hunter		return "[kernel]"
120031c2a00SAdrian Hunter	return name
121031c2a00SAdrian Hunter
122031c2a00SAdrian Hunter# Percent to one decimal place
123031c2a00SAdrian Hunter
124031c2a00SAdrian Hunterdef PercentToOneDP(n, d):
125031c2a00SAdrian Hunter	if not d:
126031c2a00SAdrian Hunter		return "0.0"
127031c2a00SAdrian Hunter	x = (n * Decimal(100)) / d
128031c2a00SAdrian Hunter	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
129031c2a00SAdrian Hunter
130031c2a00SAdrian Hunter# Helper for queries that must not fail
131031c2a00SAdrian Hunter
132031c2a00SAdrian Hunterdef QueryExec(query, stmt):
133031c2a00SAdrian Hunter	ret = query.exec_(stmt)
134031c2a00SAdrian Hunter	if not ret:
135031c2a00SAdrian Hunter		raise Exception("Query failed: " + query.lastError().text())
136031c2a00SAdrian Hunter
137ebd70c7dSAdrian Hunter# Background thread
138ebd70c7dSAdrian Hunter
139ebd70c7dSAdrian Hunterclass Thread(QThread):
140ebd70c7dSAdrian Hunter
141ebd70c7dSAdrian Hunter	done = Signal(object)
142ebd70c7dSAdrian Hunter
143ebd70c7dSAdrian Hunter	def __init__(self, task, param=None, parent=None):
144ebd70c7dSAdrian Hunter		super(Thread, self).__init__(parent)
145ebd70c7dSAdrian Hunter		self.task = task
146ebd70c7dSAdrian Hunter		self.param = param
147ebd70c7dSAdrian Hunter
148ebd70c7dSAdrian Hunter	def run(self):
149ebd70c7dSAdrian Hunter		while True:
150ebd70c7dSAdrian Hunter			if self.param is None:
151ebd70c7dSAdrian Hunter				done, result = self.task()
152ebd70c7dSAdrian Hunter			else:
153ebd70c7dSAdrian Hunter				done, result = self.task(self.param)
154ebd70c7dSAdrian Hunter			self.done.emit(result)
155ebd70c7dSAdrian Hunter			if done:
156ebd70c7dSAdrian Hunter				break
157ebd70c7dSAdrian Hunter
158031c2a00SAdrian Hunter# Tree data model
159031c2a00SAdrian Hunter
160031c2a00SAdrian Hunterclass TreeModel(QAbstractItemModel):
161031c2a00SAdrian Hunter
162031c2a00SAdrian Hunter	def __init__(self, root, parent=None):
163031c2a00SAdrian Hunter		super(TreeModel, self).__init__(parent)
164031c2a00SAdrian Hunter		self.root = root
165031c2a00SAdrian Hunter		self.last_row_read = 0
166031c2a00SAdrian Hunter
167031c2a00SAdrian Hunter	def Item(self, parent):
168031c2a00SAdrian Hunter		if parent.isValid():
169031c2a00SAdrian Hunter			return parent.internalPointer()
170031c2a00SAdrian Hunter		else:
171031c2a00SAdrian Hunter			return self.root
172031c2a00SAdrian Hunter
173031c2a00SAdrian Hunter	def rowCount(self, parent):
174031c2a00SAdrian Hunter		result = self.Item(parent).childCount()
175031c2a00SAdrian Hunter		if result < 0:
176031c2a00SAdrian Hunter			result = 0
177031c2a00SAdrian Hunter			self.dataChanged.emit(parent, parent)
178031c2a00SAdrian Hunter		return result
179031c2a00SAdrian Hunter
180031c2a00SAdrian Hunter	def hasChildren(self, parent):
181031c2a00SAdrian Hunter		return self.Item(parent).hasChildren()
182031c2a00SAdrian Hunter
183031c2a00SAdrian Hunter	def headerData(self, section, orientation, role):
184031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
185031c2a00SAdrian Hunter			return self.columnAlignment(section)
186031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
187031c2a00SAdrian Hunter			return None
188031c2a00SAdrian Hunter		if orientation != Qt.Horizontal:
189031c2a00SAdrian Hunter			return None
190031c2a00SAdrian Hunter		return self.columnHeader(section)
191031c2a00SAdrian Hunter
192031c2a00SAdrian Hunter	def parent(self, child):
193031c2a00SAdrian Hunter		child_item = child.internalPointer()
194031c2a00SAdrian Hunter		if child_item is self.root:
195031c2a00SAdrian Hunter			return QModelIndex()
196031c2a00SAdrian Hunter		parent_item = child_item.getParentItem()
197031c2a00SAdrian Hunter		return self.createIndex(parent_item.getRow(), 0, parent_item)
198031c2a00SAdrian Hunter
199031c2a00SAdrian Hunter	def index(self, row, column, parent):
200031c2a00SAdrian Hunter		child_item = self.Item(parent).getChildItem(row)
201031c2a00SAdrian Hunter		return self.createIndex(row, column, child_item)
202031c2a00SAdrian Hunter
203031c2a00SAdrian Hunter	def DisplayData(self, item, index):
204031c2a00SAdrian Hunter		return item.getData(index.column())
205031c2a00SAdrian Hunter
2068392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2078392b74bSAdrian Hunter		if row > self.last_row_read:
2088392b74bSAdrian Hunter			self.last_row_read = row
2098392b74bSAdrian Hunter			if row + 10 >= self.root.child_count:
2108392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2118392b74bSAdrian Hunter
2128392b74bSAdrian Hunter	def columnAlignment(self, column):
2138392b74bSAdrian Hunter		return Qt.AlignLeft
2148392b74bSAdrian Hunter
2158392b74bSAdrian Hunter	def columnFont(self, column):
2168392b74bSAdrian Hunter		return None
2178392b74bSAdrian Hunter
2188392b74bSAdrian Hunter	def data(self, index, role):
2198392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2208392b74bSAdrian Hunter			return self.columnAlignment(index.column())
2218392b74bSAdrian Hunter		if role == Qt.FontRole:
2228392b74bSAdrian Hunter			return self.columnFont(index.column())
2238392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2248392b74bSAdrian Hunter			return None
2258392b74bSAdrian Hunter		item = index.internalPointer()
2268392b74bSAdrian Hunter		return self.DisplayData(item, index)
2278392b74bSAdrian Hunter
2288392b74bSAdrian Hunter# Table data model
2298392b74bSAdrian Hunter
2308392b74bSAdrian Hunterclass TableModel(QAbstractTableModel):
2318392b74bSAdrian Hunter
2328392b74bSAdrian Hunter	def __init__(self, parent=None):
2338392b74bSAdrian Hunter		super(TableModel, self).__init__(parent)
2348392b74bSAdrian Hunter		self.child_count = 0
2358392b74bSAdrian Hunter		self.child_items = []
2368392b74bSAdrian Hunter		self.last_row_read = 0
2378392b74bSAdrian Hunter
2388392b74bSAdrian Hunter	def Item(self, parent):
2398392b74bSAdrian Hunter		if parent.isValid():
2408392b74bSAdrian Hunter			return parent.internalPointer()
2418392b74bSAdrian Hunter		else:
2428392b74bSAdrian Hunter			return self
2438392b74bSAdrian Hunter
2448392b74bSAdrian Hunter	def rowCount(self, parent):
2458392b74bSAdrian Hunter		return self.child_count
2468392b74bSAdrian Hunter
2478392b74bSAdrian Hunter	def headerData(self, section, orientation, role):
2488392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2498392b74bSAdrian Hunter			return self.columnAlignment(section)
2508392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2518392b74bSAdrian Hunter			return None
2528392b74bSAdrian Hunter		if orientation != Qt.Horizontal:
2538392b74bSAdrian Hunter			return None
2548392b74bSAdrian Hunter		return self.columnHeader(section)
2558392b74bSAdrian Hunter
2568392b74bSAdrian Hunter	def index(self, row, column, parent):
2578392b74bSAdrian Hunter		return self.createIndex(row, column, self.child_items[row])
2588392b74bSAdrian Hunter
2598392b74bSAdrian Hunter	def DisplayData(self, item, index):
2608392b74bSAdrian Hunter		return item.getData(index.column())
2618392b74bSAdrian Hunter
2628392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2638392b74bSAdrian Hunter		if row > self.last_row_read:
2648392b74bSAdrian Hunter			self.last_row_read = row
2658392b74bSAdrian Hunter			if row + 10 >= self.child_count:
2668392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2678392b74bSAdrian Hunter
268031c2a00SAdrian Hunter	def columnAlignment(self, column):
269031c2a00SAdrian Hunter		return Qt.AlignLeft
270031c2a00SAdrian Hunter
271031c2a00SAdrian Hunter	def columnFont(self, column):
272031c2a00SAdrian Hunter		return None
273031c2a00SAdrian Hunter
274031c2a00SAdrian Hunter	def data(self, index, role):
275031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
276031c2a00SAdrian Hunter			return self.columnAlignment(index.column())
277031c2a00SAdrian Hunter		if role == Qt.FontRole:
278031c2a00SAdrian Hunter			return self.columnFont(index.column())
279031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
280031c2a00SAdrian Hunter			return None
281031c2a00SAdrian Hunter		item = index.internalPointer()
282031c2a00SAdrian Hunter		return self.DisplayData(item, index)
283031c2a00SAdrian Hunter
2841beb5c7bSAdrian Hunter# Model cache
2851beb5c7bSAdrian Hunter
2861beb5c7bSAdrian Huntermodel_cache = weakref.WeakValueDictionary()
2871beb5c7bSAdrian Huntermodel_cache_lock = threading.Lock()
2881beb5c7bSAdrian Hunter
2891beb5c7bSAdrian Hunterdef LookupCreateModel(model_name, create_fn):
2901beb5c7bSAdrian Hunter	model_cache_lock.acquire()
2911beb5c7bSAdrian Hunter	try:
2921beb5c7bSAdrian Hunter		model = model_cache[model_name]
2931beb5c7bSAdrian Hunter	except:
2941beb5c7bSAdrian Hunter		model = None
2951beb5c7bSAdrian Hunter	if model is None:
2961beb5c7bSAdrian Hunter		model = create_fn()
2971beb5c7bSAdrian Hunter		model_cache[model_name] = model
2981beb5c7bSAdrian Hunter	model_cache_lock.release()
2991beb5c7bSAdrian Hunter	return model
3001beb5c7bSAdrian Hunter
301ebd70c7dSAdrian Hunter# Find bar
302ebd70c7dSAdrian Hunter
303ebd70c7dSAdrian Hunterclass FindBar():
304ebd70c7dSAdrian Hunter
305ebd70c7dSAdrian Hunter	def __init__(self, parent, finder, is_reg_expr=False):
306ebd70c7dSAdrian Hunter		self.finder = finder
307ebd70c7dSAdrian Hunter		self.context = []
308ebd70c7dSAdrian Hunter		self.last_value = None
309ebd70c7dSAdrian Hunter		self.last_pattern = None
310ebd70c7dSAdrian Hunter
311ebd70c7dSAdrian Hunter		label = QLabel("Find:")
312ebd70c7dSAdrian Hunter		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
313ebd70c7dSAdrian Hunter
314ebd70c7dSAdrian Hunter		self.textbox = QComboBox()
315ebd70c7dSAdrian Hunter		self.textbox.setEditable(True)
316ebd70c7dSAdrian Hunter		self.textbox.currentIndexChanged.connect(self.ValueChanged)
317ebd70c7dSAdrian Hunter
318ebd70c7dSAdrian Hunter		self.progress = QProgressBar()
319ebd70c7dSAdrian Hunter		self.progress.setRange(0, 0)
320ebd70c7dSAdrian Hunter		self.progress.hide()
321ebd70c7dSAdrian Hunter
322ebd70c7dSAdrian Hunter		if is_reg_expr:
323ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Regular Expression")
324ebd70c7dSAdrian Hunter		else:
325ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Pattern")
326ebd70c7dSAdrian Hunter		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
327ebd70c7dSAdrian Hunter
328ebd70c7dSAdrian Hunter		self.next_button = QToolButton()
329ebd70c7dSAdrian Hunter		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
330ebd70c7dSAdrian Hunter		self.next_button.released.connect(lambda: self.NextPrev(1))
331ebd70c7dSAdrian Hunter
332ebd70c7dSAdrian Hunter		self.prev_button = QToolButton()
333ebd70c7dSAdrian Hunter		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
334ebd70c7dSAdrian Hunter		self.prev_button.released.connect(lambda: self.NextPrev(-1))
335ebd70c7dSAdrian Hunter
336ebd70c7dSAdrian Hunter		self.close_button = QToolButton()
337ebd70c7dSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
338ebd70c7dSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
339ebd70c7dSAdrian Hunter
340ebd70c7dSAdrian Hunter		self.hbox = QHBoxLayout()
341ebd70c7dSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
342ebd70c7dSAdrian Hunter
343ebd70c7dSAdrian Hunter		self.hbox.addWidget(label)
344ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.textbox)
345ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.progress)
346ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.pattern)
347ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.next_button)
348ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.prev_button)
349ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.close_button)
350ebd70c7dSAdrian Hunter
351ebd70c7dSAdrian Hunter		self.bar = QWidget()
352ebd70c7dSAdrian Hunter		self.bar.setLayout(self.hbox);
353ebd70c7dSAdrian Hunter		self.bar.hide()
354ebd70c7dSAdrian Hunter
355ebd70c7dSAdrian Hunter	def Widget(self):
356ebd70c7dSAdrian Hunter		return self.bar
357ebd70c7dSAdrian Hunter
358ebd70c7dSAdrian Hunter	def Activate(self):
359ebd70c7dSAdrian Hunter		self.bar.show()
360ebd70c7dSAdrian Hunter		self.textbox.setFocus()
361ebd70c7dSAdrian Hunter
362ebd70c7dSAdrian Hunter	def Deactivate(self):
363ebd70c7dSAdrian Hunter		self.bar.hide()
364ebd70c7dSAdrian Hunter
365ebd70c7dSAdrian Hunter	def Busy(self):
366ebd70c7dSAdrian Hunter		self.textbox.setEnabled(False)
367ebd70c7dSAdrian Hunter		self.pattern.hide()
368ebd70c7dSAdrian Hunter		self.next_button.hide()
369ebd70c7dSAdrian Hunter		self.prev_button.hide()
370ebd70c7dSAdrian Hunter		self.progress.show()
371ebd70c7dSAdrian Hunter
372ebd70c7dSAdrian Hunter	def Idle(self):
373ebd70c7dSAdrian Hunter		self.textbox.setEnabled(True)
374ebd70c7dSAdrian Hunter		self.progress.hide()
375ebd70c7dSAdrian Hunter		self.pattern.show()
376ebd70c7dSAdrian Hunter		self.next_button.show()
377ebd70c7dSAdrian Hunter		self.prev_button.show()
378ebd70c7dSAdrian Hunter
379ebd70c7dSAdrian Hunter	def Find(self, direction):
380ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
381ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
382ebd70c7dSAdrian Hunter		self.last_value = value
383ebd70c7dSAdrian Hunter		self.last_pattern = pattern
384ebd70c7dSAdrian Hunter		self.finder.Find(value, direction, pattern, self.context)
385ebd70c7dSAdrian Hunter
386ebd70c7dSAdrian Hunter	def ValueChanged(self):
387ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
388ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
389ebd70c7dSAdrian Hunter		index = self.textbox.currentIndex()
390ebd70c7dSAdrian Hunter		data = self.textbox.itemData(index)
391ebd70c7dSAdrian Hunter		# Store the pattern in the combo box to keep it with the text value
392ebd70c7dSAdrian Hunter		if data == None:
393ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
394ebd70c7dSAdrian Hunter		else:
395ebd70c7dSAdrian Hunter			self.pattern.setChecked(data)
396ebd70c7dSAdrian Hunter		self.Find(0)
397ebd70c7dSAdrian Hunter
398ebd70c7dSAdrian Hunter	def NextPrev(self, direction):
399ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
400ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
401ebd70c7dSAdrian Hunter		if value != self.last_value:
402ebd70c7dSAdrian Hunter			index = self.textbox.findText(value)
403ebd70c7dSAdrian Hunter			# Allow for a button press before the value has been added to the combo box
404ebd70c7dSAdrian Hunter			if index < 0:
405ebd70c7dSAdrian Hunter				index = self.textbox.count()
406ebd70c7dSAdrian Hunter				self.textbox.addItem(value, pattern)
407ebd70c7dSAdrian Hunter				self.textbox.setCurrentIndex(index)
408ebd70c7dSAdrian Hunter				return
409ebd70c7dSAdrian Hunter			else:
410ebd70c7dSAdrian Hunter				self.textbox.setItemData(index, pattern)
411ebd70c7dSAdrian Hunter		elif pattern != self.last_pattern:
412ebd70c7dSAdrian Hunter			# Keep the pattern recorded in the combo box up to date
413ebd70c7dSAdrian Hunter			index = self.textbox.currentIndex()
414ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
415ebd70c7dSAdrian Hunter		self.Find(direction)
416ebd70c7dSAdrian Hunter
417ebd70c7dSAdrian Hunter	def NotFound(self):
418ebd70c7dSAdrian Hunter		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
419ebd70c7dSAdrian Hunter
420031c2a00SAdrian Hunter# Context-sensitive call graph data model item base
421031c2a00SAdrian Hunter
422031c2a00SAdrian Hunterclass CallGraphLevelItemBase(object):
423031c2a00SAdrian Hunter
424031c2a00SAdrian Hunter	def __init__(self, glb, row, parent_item):
425031c2a00SAdrian Hunter		self.glb = glb
426031c2a00SAdrian Hunter		self.row = row
427031c2a00SAdrian Hunter		self.parent_item = parent_item
428031c2a00SAdrian Hunter		self.query_done = False;
429031c2a00SAdrian Hunter		self.child_count = 0
430031c2a00SAdrian Hunter		self.child_items = []
431031c2a00SAdrian Hunter
432031c2a00SAdrian Hunter	def getChildItem(self, row):
433031c2a00SAdrian Hunter		return self.child_items[row]
434031c2a00SAdrian Hunter
435031c2a00SAdrian Hunter	def getParentItem(self):
436031c2a00SAdrian Hunter		return self.parent_item
437031c2a00SAdrian Hunter
438031c2a00SAdrian Hunter	def getRow(self):
439031c2a00SAdrian Hunter		return self.row
440031c2a00SAdrian Hunter
441031c2a00SAdrian Hunter	def childCount(self):
442031c2a00SAdrian Hunter		if not self.query_done:
443031c2a00SAdrian Hunter			self.Select()
444031c2a00SAdrian Hunter			if not self.child_count:
445031c2a00SAdrian Hunter				return -1
446031c2a00SAdrian Hunter		return self.child_count
447031c2a00SAdrian Hunter
448031c2a00SAdrian Hunter	def hasChildren(self):
449031c2a00SAdrian Hunter		if not self.query_done:
450031c2a00SAdrian Hunter			return True
451031c2a00SAdrian Hunter		return self.child_count > 0
452031c2a00SAdrian Hunter
453031c2a00SAdrian Hunter	def getData(self, column):
454031c2a00SAdrian Hunter		return self.data[column]
455031c2a00SAdrian Hunter
456031c2a00SAdrian Hunter# Context-sensitive call graph data model level 2+ item base
457031c2a00SAdrian Hunter
458031c2a00SAdrian Hunterclass CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
459031c2a00SAdrian Hunter
460031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
461031c2a00SAdrian Hunter		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
462031c2a00SAdrian Hunter		self.comm_id = comm_id
463031c2a00SAdrian Hunter		self.thread_id = thread_id
464031c2a00SAdrian Hunter		self.call_path_id = call_path_id
465031c2a00SAdrian Hunter		self.branch_count = branch_count
466031c2a00SAdrian Hunter		self.time = time
467031c2a00SAdrian Hunter
468031c2a00SAdrian Hunter	def Select(self):
469031c2a00SAdrian Hunter		self.query_done = True;
470031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
471031c2a00SAdrian Hunter		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
472031c2a00SAdrian Hunter					" FROM calls"
473031c2a00SAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
474031c2a00SAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
475031c2a00SAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
476031c2a00SAdrian Hunter					" WHERE parent_call_path_id = " + str(self.call_path_id) +
477031c2a00SAdrian Hunter					" AND comm_id = " + str(self.comm_id) +
478031c2a00SAdrian Hunter					" AND thread_id = " + str(self.thread_id) +
479031c2a00SAdrian Hunter					" GROUP BY call_path_id, name, short_name"
480031c2a00SAdrian Hunter					" ORDER BY call_path_id")
481031c2a00SAdrian Hunter		while query.next():
482031c2a00SAdrian 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)
483031c2a00SAdrian Hunter			self.child_items.append(child_item)
484031c2a00SAdrian Hunter			self.child_count += 1
485031c2a00SAdrian Hunter
486031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item
487031c2a00SAdrian Hunter
488031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
489031c2a00SAdrian Hunter
490031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
491031c2a00SAdrian Hunter		super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
492031c2a00SAdrian Hunter		dso = dsoname(dso)
493031c2a00SAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
494031c2a00SAdrian Hunter		self.dbid = call_path_id
495031c2a00SAdrian Hunter
496031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item
497031c2a00SAdrian Hunter
498031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
499031c2a00SAdrian Hunter
500031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
501031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
502031c2a00SAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
503031c2a00SAdrian Hunter		self.dbid = thread_id
504031c2a00SAdrian Hunter
505031c2a00SAdrian Hunter	def Select(self):
506031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).Select()
507031c2a00SAdrian Hunter		for child_item in self.child_items:
508031c2a00SAdrian Hunter			self.time += child_item.time
509031c2a00SAdrian Hunter			self.branch_count += child_item.branch_count
510031c2a00SAdrian Hunter		for child_item in self.child_items:
511031c2a00SAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
512031c2a00SAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
513031c2a00SAdrian Hunter
514031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item
515031c2a00SAdrian Hunter
516031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase):
517031c2a00SAdrian Hunter
518031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, comm, parent_item):
519031c2a00SAdrian Hunter		super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
520031c2a00SAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
521031c2a00SAdrian Hunter		self.dbid = comm_id
522031c2a00SAdrian Hunter
523031c2a00SAdrian Hunter	def Select(self):
524031c2a00SAdrian Hunter		self.query_done = True;
525031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
526031c2a00SAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
527031c2a00SAdrian Hunter					" FROM comm_threads"
528031c2a00SAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
529031c2a00SAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
530031c2a00SAdrian Hunter		while query.next():
531031c2a00SAdrian Hunter			child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
532031c2a00SAdrian Hunter			self.child_items.append(child_item)
533031c2a00SAdrian Hunter			self.child_count += 1
534031c2a00SAdrian Hunter
535031c2a00SAdrian Hunter# Context-sensitive call graph data model root item
536031c2a00SAdrian Hunter
537031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase):
538031c2a00SAdrian Hunter
539031c2a00SAdrian Hunter	def __init__(self, glb):
540031c2a00SAdrian Hunter		super(CallGraphRootItem, self).__init__(glb, 0, None)
541031c2a00SAdrian Hunter		self.dbid = 0
542031c2a00SAdrian Hunter		self.query_done = True;
543031c2a00SAdrian Hunter		query = QSqlQuery(glb.db)
544031c2a00SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
545031c2a00SAdrian Hunter		while query.next():
546031c2a00SAdrian Hunter			if not query.value(0):
547031c2a00SAdrian Hunter				continue
548031c2a00SAdrian Hunter			child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
549031c2a00SAdrian Hunter			self.child_items.append(child_item)
550031c2a00SAdrian Hunter			self.child_count += 1
551031c2a00SAdrian Hunter
552031c2a00SAdrian Hunter# Context-sensitive call graph data model
553031c2a00SAdrian Hunter
554031c2a00SAdrian Hunterclass CallGraphModel(TreeModel):
555031c2a00SAdrian Hunter
556031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
557031c2a00SAdrian Hunter		super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent)
558031c2a00SAdrian Hunter		self.glb = glb
559031c2a00SAdrian Hunter
560031c2a00SAdrian Hunter	def columnCount(self, parent=None):
561031c2a00SAdrian Hunter		return 7
562031c2a00SAdrian Hunter
563031c2a00SAdrian Hunter	def columnHeader(self, column):
564031c2a00SAdrian Hunter		headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
565031c2a00SAdrian Hunter		return headers[column]
566031c2a00SAdrian Hunter
567031c2a00SAdrian Hunter	def columnAlignment(self, column):
568031c2a00SAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
569031c2a00SAdrian Hunter		return alignment[column]
570031c2a00SAdrian Hunter
571ebd70c7dSAdrian Hunter	def FindSelect(self, value, pattern, query):
572ebd70c7dSAdrian Hunter		if pattern:
573ebd70c7dSAdrian Hunter			# postgresql and sqlite pattern patching differences:
574ebd70c7dSAdrian Hunter			#   postgresql LIKE is case sensitive but sqlite LIKE is not
575ebd70c7dSAdrian Hunter			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
576ebd70c7dSAdrian Hunter			#   postgresql supports ILIKE which is case insensitive
577ebd70c7dSAdrian Hunter			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
578ebd70c7dSAdrian Hunter			if not self.glb.dbref.is_sqlite3:
579ebd70c7dSAdrian Hunter				# Escape % and _
580ebd70c7dSAdrian Hunter				s = value.replace("%", "\%")
581ebd70c7dSAdrian Hunter				s = s.replace("_", "\_")
582ebd70c7dSAdrian Hunter				# Translate * and ? into SQL LIKE pattern characters % and _
583ebd70c7dSAdrian Hunter				trans = string.maketrans("*?", "%_")
584ebd70c7dSAdrian Hunter				match = " LIKE '" + str(s).translate(trans) + "'"
585ebd70c7dSAdrian Hunter			else:
586ebd70c7dSAdrian Hunter				match = " GLOB '" + str(value) + "'"
587ebd70c7dSAdrian Hunter		else:
588ebd70c7dSAdrian Hunter			match = " = '" + str(value) + "'"
589ebd70c7dSAdrian Hunter		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
590ebd70c7dSAdrian Hunter						" FROM calls"
591ebd70c7dSAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
592ebd70c7dSAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
593ebd70c7dSAdrian Hunter						" WHERE symbols.name" + match +
594ebd70c7dSAdrian Hunter						" GROUP BY comm_id, thread_id, call_path_id"
595ebd70c7dSAdrian Hunter						" ORDER BY comm_id, thread_id, call_path_id")
596ebd70c7dSAdrian Hunter
597ebd70c7dSAdrian Hunter	def FindPath(self, query):
598ebd70c7dSAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
599ebd70c7dSAdrian Hunter		# to open the tree at the right place.
600ebd70c7dSAdrian Hunter		ids = []
601ebd70c7dSAdrian Hunter		parent_id = query.value(0)
602ebd70c7dSAdrian Hunter		while parent_id:
603ebd70c7dSAdrian Hunter			ids.insert(0, parent_id)
604ebd70c7dSAdrian Hunter			q2 = QSqlQuery(self.glb.db)
605ebd70c7dSAdrian Hunter			QueryExec(q2, "SELECT parent_id"
606ebd70c7dSAdrian Hunter					" FROM call_paths"
607ebd70c7dSAdrian Hunter					" WHERE id = " + str(parent_id))
608ebd70c7dSAdrian Hunter			if not q2.next():
609ebd70c7dSAdrian Hunter				break
610ebd70c7dSAdrian Hunter			parent_id = q2.value(0)
611ebd70c7dSAdrian Hunter		# The call path root is not used
612ebd70c7dSAdrian Hunter		if ids[0] == 1:
613ebd70c7dSAdrian Hunter			del ids[0]
614ebd70c7dSAdrian Hunter		ids.insert(0, query.value(2))
615ebd70c7dSAdrian Hunter		ids.insert(0, query.value(1))
616ebd70c7dSAdrian Hunter		return ids
617ebd70c7dSAdrian Hunter
618ebd70c7dSAdrian Hunter	def Found(self, query, found):
619ebd70c7dSAdrian Hunter		if found:
620ebd70c7dSAdrian Hunter			return self.FindPath(query)
621ebd70c7dSAdrian Hunter		return []
622ebd70c7dSAdrian Hunter
623ebd70c7dSAdrian Hunter	def FindValue(self, value, pattern, query, last_value, last_pattern):
624ebd70c7dSAdrian Hunter		if last_value == value and pattern == last_pattern:
625ebd70c7dSAdrian Hunter			found = query.first()
626ebd70c7dSAdrian Hunter		else:
627ebd70c7dSAdrian Hunter			self.FindSelect(value, pattern, query)
628ebd70c7dSAdrian Hunter			found = query.next()
629ebd70c7dSAdrian Hunter		return self.Found(query, found)
630ebd70c7dSAdrian Hunter
631ebd70c7dSAdrian Hunter	def FindNext(self, query):
632ebd70c7dSAdrian Hunter		found = query.next()
633ebd70c7dSAdrian Hunter		if not found:
634ebd70c7dSAdrian Hunter			found = query.first()
635ebd70c7dSAdrian Hunter		return self.Found(query, found)
636ebd70c7dSAdrian Hunter
637ebd70c7dSAdrian Hunter	def FindPrev(self, query):
638ebd70c7dSAdrian Hunter		found = query.previous()
639ebd70c7dSAdrian Hunter		if not found:
640ebd70c7dSAdrian Hunter			found = query.last()
641ebd70c7dSAdrian Hunter		return self.Found(query, found)
642ebd70c7dSAdrian Hunter
643ebd70c7dSAdrian Hunter	def FindThread(self, c):
644ebd70c7dSAdrian Hunter		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
645ebd70c7dSAdrian Hunter			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
646ebd70c7dSAdrian Hunter		elif c.direction > 0:
647ebd70c7dSAdrian Hunter			ids = self.FindNext(c.query)
648ebd70c7dSAdrian Hunter		else:
649ebd70c7dSAdrian Hunter			ids = self.FindPrev(c.query)
650ebd70c7dSAdrian Hunter		return (True, ids)
651ebd70c7dSAdrian Hunter
652ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
653ebd70c7dSAdrian Hunter		class Context():
654ebd70c7dSAdrian Hunter			def __init__(self, *x):
655ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
656ebd70c7dSAdrian Hunter			def Update(self, *x):
657ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
658ebd70c7dSAdrian Hunter		if len(context):
659ebd70c7dSAdrian Hunter			context[0].Update(value, direction, pattern)
660ebd70c7dSAdrian Hunter		else:
661ebd70c7dSAdrian Hunter			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
662ebd70c7dSAdrian Hunter		# Use a thread so the UI is not blocked during the SELECT
663ebd70c7dSAdrian Hunter		thread = Thread(self.FindThread, context[0])
664ebd70c7dSAdrian Hunter		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
665ebd70c7dSAdrian Hunter		thread.start()
666ebd70c7dSAdrian Hunter
667ebd70c7dSAdrian Hunter	def FindDone(self, thread, callback, ids):
668ebd70c7dSAdrian Hunter		callback(ids)
669ebd70c7dSAdrian Hunter
670ebd70c7dSAdrian Hunter# Vertical widget layout
671ebd70c7dSAdrian Hunter
672ebd70c7dSAdrian Hunterclass VBox():
673ebd70c7dSAdrian Hunter
674ebd70c7dSAdrian Hunter	def __init__(self, w1, w2, w3=None):
675ebd70c7dSAdrian Hunter		self.vbox = QWidget()
676ebd70c7dSAdrian Hunter		self.vbox.setLayout(QVBoxLayout());
677ebd70c7dSAdrian Hunter
678ebd70c7dSAdrian Hunter		self.vbox.layout().setContentsMargins(0, 0, 0, 0)
679ebd70c7dSAdrian Hunter
680ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w1)
681ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w2)
682ebd70c7dSAdrian Hunter		if w3:
683ebd70c7dSAdrian Hunter			self.vbox.layout().addWidget(w3)
684ebd70c7dSAdrian Hunter
685ebd70c7dSAdrian Hunter	def Widget(self):
686ebd70c7dSAdrian Hunter		return self.vbox
687ebd70c7dSAdrian Hunter
6881beb5c7bSAdrian Hunter# Context-sensitive call graph window
6891beb5c7bSAdrian Hunter
6901beb5c7bSAdrian Hunterclass CallGraphWindow(QMdiSubWindow):
6911beb5c7bSAdrian Hunter
6921beb5c7bSAdrian Hunter	def __init__(self, glb, parent=None):
6931beb5c7bSAdrian Hunter		super(CallGraphWindow, self).__init__(parent)
6941beb5c7bSAdrian Hunter
6951beb5c7bSAdrian Hunter		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
6961beb5c7bSAdrian Hunter
6971beb5c7bSAdrian Hunter		self.view = QTreeView()
6981beb5c7bSAdrian Hunter		self.view.setModel(self.model)
6991beb5c7bSAdrian Hunter
7001beb5c7bSAdrian Hunter		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
7011beb5c7bSAdrian Hunter			self.view.setColumnWidth(c, w)
7021beb5c7bSAdrian Hunter
703ebd70c7dSAdrian Hunter		self.find_bar = FindBar(self, self)
704ebd70c7dSAdrian Hunter
705ebd70c7dSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
706ebd70c7dSAdrian Hunter
707ebd70c7dSAdrian Hunter		self.setWidget(self.vbox.Widget())
7081beb5c7bSAdrian Hunter
7091beb5c7bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
7101beb5c7bSAdrian Hunter
711ebd70c7dSAdrian Hunter	def DisplayFound(self, ids):
712ebd70c7dSAdrian Hunter		if not len(ids):
713ebd70c7dSAdrian Hunter			return False
714ebd70c7dSAdrian Hunter		parent = QModelIndex()
715ebd70c7dSAdrian Hunter		for dbid in ids:
716ebd70c7dSAdrian Hunter			found = False
717ebd70c7dSAdrian Hunter			n = self.model.rowCount(parent)
718ebd70c7dSAdrian Hunter			for row in xrange(n):
719ebd70c7dSAdrian Hunter				child = self.model.index(row, 0, parent)
720ebd70c7dSAdrian Hunter				if child.internalPointer().dbid == dbid:
721ebd70c7dSAdrian Hunter					found = True
722ebd70c7dSAdrian Hunter					self.view.setCurrentIndex(child)
723ebd70c7dSAdrian Hunter					parent = child
724ebd70c7dSAdrian Hunter					break
725ebd70c7dSAdrian Hunter			if not found:
726ebd70c7dSAdrian Hunter				break
727ebd70c7dSAdrian Hunter		return found
728ebd70c7dSAdrian Hunter
729ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context):
730ebd70c7dSAdrian Hunter		self.view.setFocus()
731ebd70c7dSAdrian Hunter		self.find_bar.Busy()
732ebd70c7dSAdrian Hunter		self.model.Find(value, direction, pattern, context, self.FindDone)
733ebd70c7dSAdrian Hunter
734ebd70c7dSAdrian Hunter	def FindDone(self, ids):
735ebd70c7dSAdrian Hunter		found = True
736ebd70c7dSAdrian Hunter		if not self.DisplayFound(ids):
737ebd70c7dSAdrian Hunter			found = False
738ebd70c7dSAdrian Hunter		self.find_bar.Idle()
739ebd70c7dSAdrian Hunter		if not found:
740ebd70c7dSAdrian Hunter			self.find_bar.NotFound()
741ebd70c7dSAdrian Hunter
7428392b74bSAdrian Hunter# Child data item  finder
7438392b74bSAdrian Hunter
7448392b74bSAdrian Hunterclass ChildDataItemFinder():
7458392b74bSAdrian Hunter
7468392b74bSAdrian Hunter	def __init__(self, root):
7478392b74bSAdrian Hunter		self.root = root
7488392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
7498392b74bSAdrian Hunter		self.rows = []
7508392b74bSAdrian Hunter		self.pos = 0
7518392b74bSAdrian Hunter
7528392b74bSAdrian Hunter	def FindSelect(self):
7538392b74bSAdrian Hunter		self.rows = []
7548392b74bSAdrian Hunter		if self.pattern:
7558392b74bSAdrian Hunter			pattern = re.compile(self.value)
7568392b74bSAdrian Hunter			for child in self.root.child_items:
7578392b74bSAdrian Hunter				for column_data in child.data:
7588392b74bSAdrian Hunter					if re.search(pattern, str(column_data)) is not None:
7598392b74bSAdrian Hunter						self.rows.append(child.row)
7608392b74bSAdrian Hunter						break
7618392b74bSAdrian Hunter		else:
7628392b74bSAdrian Hunter			for child in self.root.child_items:
7638392b74bSAdrian Hunter				for column_data in child.data:
7648392b74bSAdrian Hunter					if self.value in str(column_data):
7658392b74bSAdrian Hunter						self.rows.append(child.row)
7668392b74bSAdrian Hunter						break
7678392b74bSAdrian Hunter
7688392b74bSAdrian Hunter	def FindValue(self):
7698392b74bSAdrian Hunter		self.pos = 0
7708392b74bSAdrian Hunter		if self.last_value != self.value or self.pattern != self.last_pattern:
7718392b74bSAdrian Hunter			self.FindSelect()
7728392b74bSAdrian Hunter		if not len(self.rows):
7738392b74bSAdrian Hunter			return -1
7748392b74bSAdrian Hunter		return self.rows[self.pos]
7758392b74bSAdrian Hunter
7768392b74bSAdrian Hunter	def FindThread(self):
7778392b74bSAdrian Hunter		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
7788392b74bSAdrian Hunter			row = self.FindValue()
7798392b74bSAdrian Hunter		elif len(self.rows):
7808392b74bSAdrian Hunter			if self.direction > 0:
7818392b74bSAdrian Hunter				self.pos += 1
7828392b74bSAdrian Hunter				if self.pos >= len(self.rows):
7838392b74bSAdrian Hunter					self.pos = 0
7848392b74bSAdrian Hunter			else:
7858392b74bSAdrian Hunter				self.pos -= 1
7868392b74bSAdrian Hunter				if self.pos < 0:
7878392b74bSAdrian Hunter					self.pos = len(self.rows) - 1
7888392b74bSAdrian Hunter			row = self.rows[self.pos]
7898392b74bSAdrian Hunter		else:
7908392b74bSAdrian Hunter			row = -1
7918392b74bSAdrian Hunter		return (True, row)
7928392b74bSAdrian Hunter
7938392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
7948392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
7958392b74bSAdrian Hunter		# Use a thread so the UI is not blocked
7968392b74bSAdrian Hunter		thread = Thread(self.FindThread)
7978392b74bSAdrian Hunter		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
7988392b74bSAdrian Hunter		thread.start()
7998392b74bSAdrian Hunter
8008392b74bSAdrian Hunter	def FindDone(self, thread, callback, row):
8018392b74bSAdrian Hunter		callback(row)
8028392b74bSAdrian Hunter
8038392b74bSAdrian Hunter# Number of database records to fetch in one go
8048392b74bSAdrian Hunter
8058392b74bSAdrian Hunterglb_chunk_sz = 10000
8068392b74bSAdrian Hunter
8078392b74bSAdrian Hunter# size of pickled integer big enough for record size
8088392b74bSAdrian Hunter
8098392b74bSAdrian Hunterglb_nsz = 8
8108392b74bSAdrian Hunter
8118392b74bSAdrian Hunter# Background process for SQL data fetcher
8128392b74bSAdrian Hunter
8138392b74bSAdrian Hunterclass SQLFetcherProcess():
8148392b74bSAdrian Hunter
8158392b74bSAdrian Hunter	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
8168392b74bSAdrian Hunter		# Need a unique connection name
8178392b74bSAdrian Hunter		conn_name = "SQLFetcher" + str(os.getpid())
8188392b74bSAdrian Hunter		self.db, dbname = dbref.Open(conn_name)
8198392b74bSAdrian Hunter		self.sql = sql
8208392b74bSAdrian Hunter		self.buffer = buffer
8218392b74bSAdrian Hunter		self.head = head
8228392b74bSAdrian Hunter		self.tail = tail
8238392b74bSAdrian Hunter		self.fetch_count = fetch_count
8248392b74bSAdrian Hunter		self.fetching_done = fetching_done
8258392b74bSAdrian Hunter		self.process_target = process_target
8268392b74bSAdrian Hunter		self.wait_event = wait_event
8278392b74bSAdrian Hunter		self.fetched_event = fetched_event
8288392b74bSAdrian Hunter		self.prep = prep
8298392b74bSAdrian Hunter		self.query = QSqlQuery(self.db)
8308392b74bSAdrian Hunter		self.query_limit = 0 if "$$last_id$$" in sql else 2
8318392b74bSAdrian Hunter		self.last_id = -1
8328392b74bSAdrian Hunter		self.fetched = 0
8338392b74bSAdrian Hunter		self.more = True
8348392b74bSAdrian Hunter		self.local_head = self.head.value
8358392b74bSAdrian Hunter		self.local_tail = self.tail.value
8368392b74bSAdrian Hunter
8378392b74bSAdrian Hunter	def Select(self):
8388392b74bSAdrian Hunter		if self.query_limit:
8398392b74bSAdrian Hunter			if self.query_limit == 1:
8408392b74bSAdrian Hunter				return
8418392b74bSAdrian Hunter			self.query_limit -= 1
8428392b74bSAdrian Hunter		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
8438392b74bSAdrian Hunter		QueryExec(self.query, stmt)
8448392b74bSAdrian Hunter
8458392b74bSAdrian Hunter	def Next(self):
8468392b74bSAdrian Hunter		if not self.query.next():
8478392b74bSAdrian Hunter			self.Select()
8488392b74bSAdrian Hunter			if not self.query.next():
8498392b74bSAdrian Hunter				return None
8508392b74bSAdrian Hunter		self.last_id = self.query.value(0)
8518392b74bSAdrian Hunter		return self.prep(self.query)
8528392b74bSAdrian Hunter
8538392b74bSAdrian Hunter	def WaitForTarget(self):
8548392b74bSAdrian Hunter		while True:
8558392b74bSAdrian Hunter			self.wait_event.clear()
8568392b74bSAdrian Hunter			target = self.process_target.value
8578392b74bSAdrian Hunter			if target > self.fetched or target < 0:
8588392b74bSAdrian Hunter				break
8598392b74bSAdrian Hunter			self.wait_event.wait()
8608392b74bSAdrian Hunter		return target
8618392b74bSAdrian Hunter
8628392b74bSAdrian Hunter	def HasSpace(self, sz):
8638392b74bSAdrian Hunter		if self.local_tail <= self.local_head:
8648392b74bSAdrian Hunter			space = len(self.buffer) - self.local_head
8658392b74bSAdrian Hunter			if space > sz:
8668392b74bSAdrian Hunter				return True
8678392b74bSAdrian Hunter			if space >= glb_nsz:
8688392b74bSAdrian Hunter				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
8698392b74bSAdrian Hunter				nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL)
8708392b74bSAdrian Hunter				self.buffer[self.local_head : self.local_head + len(nd)] = nd
8718392b74bSAdrian Hunter			self.local_head = 0
8728392b74bSAdrian Hunter		if self.local_tail - self.local_head > sz:
8738392b74bSAdrian Hunter			return True
8748392b74bSAdrian Hunter		return False
8758392b74bSAdrian Hunter
8768392b74bSAdrian Hunter	def WaitForSpace(self, sz):
8778392b74bSAdrian Hunter		if self.HasSpace(sz):
8788392b74bSAdrian Hunter			return
8798392b74bSAdrian Hunter		while True:
8808392b74bSAdrian Hunter			self.wait_event.clear()
8818392b74bSAdrian Hunter			self.local_tail = self.tail.value
8828392b74bSAdrian Hunter			if self.HasSpace(sz):
8838392b74bSAdrian Hunter				return
8848392b74bSAdrian Hunter			self.wait_event.wait()
8858392b74bSAdrian Hunter
8868392b74bSAdrian Hunter	def AddToBuffer(self, obj):
8878392b74bSAdrian Hunter		d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL)
8888392b74bSAdrian Hunter		n = len(d)
8898392b74bSAdrian Hunter		nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL)
8908392b74bSAdrian Hunter		sz = n + glb_nsz
8918392b74bSAdrian Hunter		self.WaitForSpace(sz)
8928392b74bSAdrian Hunter		pos = self.local_head
8938392b74bSAdrian Hunter		self.buffer[pos : pos + len(nd)] = nd
8948392b74bSAdrian Hunter		self.buffer[pos + glb_nsz : pos + sz] = d
8958392b74bSAdrian Hunter		self.local_head += sz
8968392b74bSAdrian Hunter
8978392b74bSAdrian Hunter	def FetchBatch(self, batch_size):
8988392b74bSAdrian Hunter		fetched = 0
8998392b74bSAdrian Hunter		while batch_size > fetched:
9008392b74bSAdrian Hunter			obj = self.Next()
9018392b74bSAdrian Hunter			if obj is None:
9028392b74bSAdrian Hunter				self.more = False
9038392b74bSAdrian Hunter				break
9048392b74bSAdrian Hunter			self.AddToBuffer(obj)
9058392b74bSAdrian Hunter			fetched += 1
9068392b74bSAdrian Hunter		if fetched:
9078392b74bSAdrian Hunter			self.fetched += fetched
9088392b74bSAdrian Hunter			with self.fetch_count.get_lock():
9098392b74bSAdrian Hunter				self.fetch_count.value += fetched
9108392b74bSAdrian Hunter			self.head.value = self.local_head
9118392b74bSAdrian Hunter			self.fetched_event.set()
9128392b74bSAdrian Hunter
9138392b74bSAdrian Hunter	def Run(self):
9148392b74bSAdrian Hunter		while self.more:
9158392b74bSAdrian Hunter			target = self.WaitForTarget()
9168392b74bSAdrian Hunter			if target < 0:
9178392b74bSAdrian Hunter				break
9188392b74bSAdrian Hunter			batch_size = min(glb_chunk_sz, target - self.fetched)
9198392b74bSAdrian Hunter			self.FetchBatch(batch_size)
9208392b74bSAdrian Hunter		self.fetching_done.value = True
9218392b74bSAdrian Hunter		self.fetched_event.set()
9228392b74bSAdrian Hunter
9238392b74bSAdrian Hunterdef SQLFetcherFn(*x):
9248392b74bSAdrian Hunter	process = SQLFetcherProcess(*x)
9258392b74bSAdrian Hunter	process.Run()
9268392b74bSAdrian Hunter
9278392b74bSAdrian Hunter# SQL data fetcher
9288392b74bSAdrian Hunter
9298392b74bSAdrian Hunterclass SQLFetcher(QObject):
9308392b74bSAdrian Hunter
9318392b74bSAdrian Hunter	done = Signal(object)
9328392b74bSAdrian Hunter
9338392b74bSAdrian Hunter	def __init__(self, glb, sql, prep, process_data, parent=None):
9348392b74bSAdrian Hunter		super(SQLFetcher, self).__init__(parent)
9358392b74bSAdrian Hunter		self.process_data = process_data
9368392b74bSAdrian Hunter		self.more = True
9378392b74bSAdrian Hunter		self.target = 0
9388392b74bSAdrian Hunter		self.last_target = 0
9398392b74bSAdrian Hunter		self.fetched = 0
9408392b74bSAdrian Hunter		self.buffer_size = 16 * 1024 * 1024
9418392b74bSAdrian Hunter		self.buffer = Array(c_char, self.buffer_size, lock=False)
9428392b74bSAdrian Hunter		self.head = Value(c_longlong)
9438392b74bSAdrian Hunter		self.tail = Value(c_longlong)
9448392b74bSAdrian Hunter		self.local_tail = 0
9458392b74bSAdrian Hunter		self.fetch_count = Value(c_longlong)
9468392b74bSAdrian Hunter		self.fetching_done = Value(c_bool)
9478392b74bSAdrian Hunter		self.last_count = 0
9488392b74bSAdrian Hunter		self.process_target = Value(c_longlong)
9498392b74bSAdrian Hunter		self.wait_event = Event()
9508392b74bSAdrian Hunter		self.fetched_event = Event()
9518392b74bSAdrian Hunter		glb.AddInstanceToShutdownOnExit(self)
9528392b74bSAdrian 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))
9538392b74bSAdrian Hunter		self.process.start()
9548392b74bSAdrian Hunter		self.thread = Thread(self.Thread)
9558392b74bSAdrian Hunter		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
9568392b74bSAdrian Hunter		self.thread.start()
9578392b74bSAdrian Hunter
9588392b74bSAdrian Hunter	def Shutdown(self):
9598392b74bSAdrian Hunter		# Tell the thread and process to exit
9608392b74bSAdrian Hunter		self.process_target.value = -1
9618392b74bSAdrian Hunter		self.wait_event.set()
9628392b74bSAdrian Hunter		self.more = False
9638392b74bSAdrian Hunter		self.fetching_done.value = True
9648392b74bSAdrian Hunter		self.fetched_event.set()
9658392b74bSAdrian Hunter
9668392b74bSAdrian Hunter	def Thread(self):
9678392b74bSAdrian Hunter		if not self.more:
9688392b74bSAdrian Hunter			return True, 0
9698392b74bSAdrian Hunter		while True:
9708392b74bSAdrian Hunter			self.fetched_event.clear()
9718392b74bSAdrian Hunter			fetch_count = self.fetch_count.value
9728392b74bSAdrian Hunter			if fetch_count != self.last_count:
9738392b74bSAdrian Hunter				break
9748392b74bSAdrian Hunter			if self.fetching_done.value:
9758392b74bSAdrian Hunter				self.more = False
9768392b74bSAdrian Hunter				return True, 0
9778392b74bSAdrian Hunter			self.fetched_event.wait()
9788392b74bSAdrian Hunter		count = fetch_count - self.last_count
9798392b74bSAdrian Hunter		self.last_count = fetch_count
9808392b74bSAdrian Hunter		self.fetched += count
9818392b74bSAdrian Hunter		return False, count
9828392b74bSAdrian Hunter
9838392b74bSAdrian Hunter	def Fetch(self, nr):
9848392b74bSAdrian Hunter		if not self.more:
9858392b74bSAdrian Hunter			# -1 inidcates there are no more
9868392b74bSAdrian Hunter			return -1
9878392b74bSAdrian Hunter		result = self.fetched
9888392b74bSAdrian Hunter		extra = result + nr - self.target
9898392b74bSAdrian Hunter		if extra > 0:
9908392b74bSAdrian Hunter			self.target += extra
9918392b74bSAdrian Hunter			# process_target < 0 indicates shutting down
9928392b74bSAdrian Hunter			if self.process_target.value >= 0:
9938392b74bSAdrian Hunter				self.process_target.value = self.target
9948392b74bSAdrian Hunter			self.wait_event.set()
9958392b74bSAdrian Hunter		return result
9968392b74bSAdrian Hunter
9978392b74bSAdrian Hunter	def RemoveFromBuffer(self):
9988392b74bSAdrian Hunter		pos = self.local_tail
9998392b74bSAdrian Hunter		if len(self.buffer) - pos < glb_nsz:
10008392b74bSAdrian Hunter			pos = 0
10018392b74bSAdrian Hunter		n = cPickle.loads(self.buffer[pos : pos + glb_nsz])
10028392b74bSAdrian Hunter		if n == 0:
10038392b74bSAdrian Hunter			pos = 0
10048392b74bSAdrian Hunter			n = cPickle.loads(self.buffer[0 : glb_nsz])
10058392b74bSAdrian Hunter		pos += glb_nsz
10068392b74bSAdrian Hunter		obj = cPickle.loads(self.buffer[pos : pos + n])
10078392b74bSAdrian Hunter		self.local_tail = pos + n
10088392b74bSAdrian Hunter		return obj
10098392b74bSAdrian Hunter
10108392b74bSAdrian Hunter	def ProcessData(self, count):
10118392b74bSAdrian Hunter		for i in xrange(count):
10128392b74bSAdrian Hunter			obj = self.RemoveFromBuffer()
10138392b74bSAdrian Hunter			self.process_data(obj)
10148392b74bSAdrian Hunter		self.tail.value = self.local_tail
10158392b74bSAdrian Hunter		self.wait_event.set()
10168392b74bSAdrian Hunter		self.done.emit(count)
10178392b74bSAdrian Hunter
10188392b74bSAdrian Hunter# Fetch more records bar
10198392b74bSAdrian Hunter
10208392b74bSAdrian Hunterclass FetchMoreRecordsBar():
10218392b74bSAdrian Hunter
10228392b74bSAdrian Hunter	def __init__(self, model, parent):
10238392b74bSAdrian Hunter		self.model = model
10248392b74bSAdrian Hunter
10258392b74bSAdrian Hunter		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
10268392b74bSAdrian Hunter		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
10278392b74bSAdrian Hunter
10288392b74bSAdrian Hunter		self.fetch_count = QSpinBox()
10298392b74bSAdrian Hunter		self.fetch_count.setRange(1, 1000000)
10308392b74bSAdrian Hunter		self.fetch_count.setValue(10)
10318392b74bSAdrian Hunter		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
10328392b74bSAdrian Hunter
10338392b74bSAdrian Hunter		self.fetch = QPushButton("Go!")
10348392b74bSAdrian Hunter		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
10358392b74bSAdrian Hunter		self.fetch.released.connect(self.FetchMoreRecords)
10368392b74bSAdrian Hunter
10378392b74bSAdrian Hunter		self.progress = QProgressBar()
10388392b74bSAdrian Hunter		self.progress.setRange(0, 100)
10398392b74bSAdrian Hunter		self.progress.hide()
10408392b74bSAdrian Hunter
10418392b74bSAdrian Hunter		self.done_label = QLabel("All records fetched")
10428392b74bSAdrian Hunter		self.done_label.hide()
10438392b74bSAdrian Hunter
10448392b74bSAdrian Hunter		self.spacer = QLabel("")
10458392b74bSAdrian Hunter
10468392b74bSAdrian Hunter		self.close_button = QToolButton()
10478392b74bSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
10488392b74bSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
10498392b74bSAdrian Hunter
10508392b74bSAdrian Hunter		self.hbox = QHBoxLayout()
10518392b74bSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
10528392b74bSAdrian Hunter
10538392b74bSAdrian Hunter		self.hbox.addWidget(self.label)
10548392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch_count)
10558392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch)
10568392b74bSAdrian Hunter		self.hbox.addWidget(self.spacer)
10578392b74bSAdrian Hunter		self.hbox.addWidget(self.progress)
10588392b74bSAdrian Hunter		self.hbox.addWidget(self.done_label)
10598392b74bSAdrian Hunter		self.hbox.addWidget(self.close_button)
10608392b74bSAdrian Hunter
10618392b74bSAdrian Hunter		self.bar = QWidget()
10628392b74bSAdrian Hunter		self.bar.setLayout(self.hbox);
10638392b74bSAdrian Hunter		self.bar.show()
10648392b74bSAdrian Hunter
10658392b74bSAdrian Hunter		self.in_progress = False
10668392b74bSAdrian Hunter		self.model.progress.connect(self.Progress)
10678392b74bSAdrian Hunter
10688392b74bSAdrian Hunter		self.done = False
10698392b74bSAdrian Hunter
10708392b74bSAdrian Hunter		if not model.HasMoreRecords():
10718392b74bSAdrian Hunter			self.Done()
10728392b74bSAdrian Hunter
10738392b74bSAdrian Hunter	def Widget(self):
10748392b74bSAdrian Hunter		return self.bar
10758392b74bSAdrian Hunter
10768392b74bSAdrian Hunter	def Activate(self):
10778392b74bSAdrian Hunter		self.bar.show()
10788392b74bSAdrian Hunter		self.fetch.setFocus()
10798392b74bSAdrian Hunter
10808392b74bSAdrian Hunter	def Deactivate(self):
10818392b74bSAdrian Hunter		self.bar.hide()
10828392b74bSAdrian Hunter
10838392b74bSAdrian Hunter	def Enable(self, enable):
10848392b74bSAdrian Hunter		self.fetch.setEnabled(enable)
10858392b74bSAdrian Hunter		self.fetch_count.setEnabled(enable)
10868392b74bSAdrian Hunter
10878392b74bSAdrian Hunter	def Busy(self):
10888392b74bSAdrian Hunter		self.Enable(False)
10898392b74bSAdrian Hunter		self.fetch.hide()
10908392b74bSAdrian Hunter		self.spacer.hide()
10918392b74bSAdrian Hunter		self.progress.show()
10928392b74bSAdrian Hunter
10938392b74bSAdrian Hunter	def Idle(self):
10948392b74bSAdrian Hunter		self.in_progress = False
10958392b74bSAdrian Hunter		self.Enable(True)
10968392b74bSAdrian Hunter		self.progress.hide()
10978392b74bSAdrian Hunter		self.fetch.show()
10988392b74bSAdrian Hunter		self.spacer.show()
10998392b74bSAdrian Hunter
11008392b74bSAdrian Hunter	def Target(self):
11018392b74bSAdrian Hunter		return self.fetch_count.value() * glb_chunk_sz
11028392b74bSAdrian Hunter
11038392b74bSAdrian Hunter	def Done(self):
11048392b74bSAdrian Hunter		self.done = True
11058392b74bSAdrian Hunter		self.Idle()
11068392b74bSAdrian Hunter		self.label.hide()
11078392b74bSAdrian Hunter		self.fetch_count.hide()
11088392b74bSAdrian Hunter		self.fetch.hide()
11098392b74bSAdrian Hunter		self.spacer.hide()
11108392b74bSAdrian Hunter		self.done_label.show()
11118392b74bSAdrian Hunter
11128392b74bSAdrian Hunter	def Progress(self, count):
11138392b74bSAdrian Hunter		if self.in_progress:
11148392b74bSAdrian Hunter			if count:
11158392b74bSAdrian Hunter				percent = ((count - self.start) * 100) / self.Target()
11168392b74bSAdrian Hunter				if percent >= 100:
11178392b74bSAdrian Hunter					self.Idle()
11188392b74bSAdrian Hunter				else:
11198392b74bSAdrian Hunter					self.progress.setValue(percent)
11208392b74bSAdrian Hunter		if not count:
11218392b74bSAdrian Hunter			# Count value of zero means no more records
11228392b74bSAdrian Hunter			self.Done()
11238392b74bSAdrian Hunter
11248392b74bSAdrian Hunter	def FetchMoreRecords(self):
11258392b74bSAdrian Hunter		if self.done:
11268392b74bSAdrian Hunter			return
11278392b74bSAdrian Hunter		self.progress.setValue(0)
11288392b74bSAdrian Hunter		self.Busy()
11298392b74bSAdrian Hunter		self.in_progress = True
11308392b74bSAdrian Hunter		self.start = self.model.FetchMoreRecords(self.Target())
11318392b74bSAdrian Hunter
113276099f98SAdrian Hunter# Brance data model level two item
113376099f98SAdrian Hunter
113476099f98SAdrian Hunterclass BranchLevelTwoItem():
113576099f98SAdrian Hunter
113676099f98SAdrian Hunter	def __init__(self, row, text, parent_item):
113776099f98SAdrian Hunter		self.row = row
113876099f98SAdrian Hunter		self.parent_item = parent_item
113976099f98SAdrian Hunter		self.data = [""] * 8
114076099f98SAdrian Hunter		self.data[7] = text
114176099f98SAdrian Hunter		self.level = 2
114276099f98SAdrian Hunter
114376099f98SAdrian Hunter	def getParentItem(self):
114476099f98SAdrian Hunter		return self.parent_item
114576099f98SAdrian Hunter
114676099f98SAdrian Hunter	def getRow(self):
114776099f98SAdrian Hunter		return self.row
114876099f98SAdrian Hunter
114976099f98SAdrian Hunter	def childCount(self):
115076099f98SAdrian Hunter		return 0
115176099f98SAdrian Hunter
115276099f98SAdrian Hunter	def hasChildren(self):
115376099f98SAdrian Hunter		return False
115476099f98SAdrian Hunter
115576099f98SAdrian Hunter	def getData(self, column):
115676099f98SAdrian Hunter		return self.data[column]
115776099f98SAdrian Hunter
115876099f98SAdrian Hunter# Brance data model level one item
115976099f98SAdrian Hunter
116076099f98SAdrian Hunterclass BranchLevelOneItem():
116176099f98SAdrian Hunter
116276099f98SAdrian Hunter	def __init__(self, glb, row, data, parent_item):
116376099f98SAdrian Hunter		self.glb = glb
116476099f98SAdrian Hunter		self.row = row
116576099f98SAdrian Hunter		self.parent_item = parent_item
116676099f98SAdrian Hunter		self.child_count = 0
116776099f98SAdrian Hunter		self.child_items = []
116876099f98SAdrian Hunter		self.data = data[1:]
116976099f98SAdrian Hunter		self.dbid = data[0]
117076099f98SAdrian Hunter		self.level = 1
117176099f98SAdrian Hunter		self.query_done = False
117276099f98SAdrian Hunter
117376099f98SAdrian Hunter	def getChildItem(self, row):
117476099f98SAdrian Hunter		return self.child_items[row]
117576099f98SAdrian Hunter
117676099f98SAdrian Hunter	def getParentItem(self):
117776099f98SAdrian Hunter		return self.parent_item
117876099f98SAdrian Hunter
117976099f98SAdrian Hunter	def getRow(self):
118076099f98SAdrian Hunter		return self.row
118176099f98SAdrian Hunter
118276099f98SAdrian Hunter	def Select(self):
118376099f98SAdrian Hunter		self.query_done = True
118476099f98SAdrian Hunter
118576099f98SAdrian Hunter		if not self.glb.have_disassembler:
118676099f98SAdrian Hunter			return
118776099f98SAdrian Hunter
118876099f98SAdrian Hunter		query = QSqlQuery(self.glb.db)
118976099f98SAdrian Hunter
119076099f98SAdrian Hunter		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
119176099f98SAdrian Hunter				  " FROM samples"
119276099f98SAdrian Hunter				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
119376099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
119476099f98SAdrian Hunter				  " WHERE samples.id = " + str(self.dbid))
119576099f98SAdrian Hunter		if not query.next():
119676099f98SAdrian Hunter			return
119776099f98SAdrian Hunter		cpu = query.value(0)
119876099f98SAdrian Hunter		dso = query.value(1)
119976099f98SAdrian Hunter		sym = query.value(2)
120076099f98SAdrian Hunter		if dso == 0 or sym == 0:
120176099f98SAdrian Hunter			return
120276099f98SAdrian Hunter		off = query.value(3)
120376099f98SAdrian Hunter		short_name = query.value(4)
120476099f98SAdrian Hunter		long_name = query.value(5)
120576099f98SAdrian Hunter		build_id = query.value(6)
120676099f98SAdrian Hunter		sym_start = query.value(7)
120776099f98SAdrian Hunter		ip = query.value(8)
120876099f98SAdrian Hunter
120976099f98SAdrian Hunter		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
121076099f98SAdrian Hunter				  " FROM samples"
121176099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
121276099f98SAdrian Hunter				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
121376099f98SAdrian Hunter				  " ORDER BY samples.id"
121476099f98SAdrian Hunter				  " LIMIT 1")
121576099f98SAdrian Hunter		if not query.next():
121676099f98SAdrian Hunter			return
121776099f98SAdrian Hunter		if query.value(0) != dso:
121876099f98SAdrian Hunter			# Cannot disassemble from one dso to another
121976099f98SAdrian Hunter			return
122076099f98SAdrian Hunter		bsym = query.value(1)
122176099f98SAdrian Hunter		boff = query.value(2)
122276099f98SAdrian Hunter		bsym_start = query.value(3)
122376099f98SAdrian Hunter		if bsym == 0:
122476099f98SAdrian Hunter			return
122576099f98SAdrian Hunter		tot = bsym_start + boff + 1 - sym_start - off
122676099f98SAdrian Hunter		if tot <= 0 or tot > 16384:
122776099f98SAdrian Hunter			return
122876099f98SAdrian Hunter
122976099f98SAdrian Hunter		inst = self.glb.disassembler.Instruction()
123076099f98SAdrian Hunter		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
123176099f98SAdrian Hunter		if not f:
123276099f98SAdrian Hunter			return
123376099f98SAdrian Hunter		mode = 0 if Is64Bit(f) else 1
123476099f98SAdrian Hunter		self.glb.disassembler.SetMode(inst, mode)
123576099f98SAdrian Hunter
123676099f98SAdrian Hunter		buf_sz = tot + 16
123776099f98SAdrian Hunter		buf = create_string_buffer(tot + 16)
123876099f98SAdrian Hunter		f.seek(sym_start + off)
123976099f98SAdrian Hunter		buf.value = f.read(buf_sz)
124076099f98SAdrian Hunter		buf_ptr = addressof(buf)
124176099f98SAdrian Hunter		i = 0
124276099f98SAdrian Hunter		while tot > 0:
124376099f98SAdrian Hunter			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
124476099f98SAdrian Hunter			if cnt:
124576099f98SAdrian Hunter				byte_str = tohex(ip).rjust(16)
124676099f98SAdrian Hunter				for k in xrange(cnt):
124776099f98SAdrian Hunter					byte_str += " %02x" % ord(buf[i])
124876099f98SAdrian Hunter					i += 1
124976099f98SAdrian Hunter				while k < 15:
125076099f98SAdrian Hunter					byte_str += "   "
125176099f98SAdrian Hunter					k += 1
125276099f98SAdrian Hunter				self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
125376099f98SAdrian Hunter				self.child_count += 1
125476099f98SAdrian Hunter			else:
125576099f98SAdrian Hunter				return
125676099f98SAdrian Hunter			buf_ptr += cnt
125776099f98SAdrian Hunter			tot -= cnt
125876099f98SAdrian Hunter			buf_sz -= cnt
125976099f98SAdrian Hunter			ip += cnt
126076099f98SAdrian Hunter
126176099f98SAdrian Hunter	def childCount(self):
126276099f98SAdrian Hunter		if not self.query_done:
126376099f98SAdrian Hunter			self.Select()
126476099f98SAdrian Hunter			if not self.child_count:
126576099f98SAdrian Hunter				return -1
126676099f98SAdrian Hunter		return self.child_count
126776099f98SAdrian Hunter
126876099f98SAdrian Hunter	def hasChildren(self):
126976099f98SAdrian Hunter		if not self.query_done:
127076099f98SAdrian Hunter			return True
127176099f98SAdrian Hunter		return self.child_count > 0
127276099f98SAdrian Hunter
127376099f98SAdrian Hunter	def getData(self, column):
127476099f98SAdrian Hunter		return self.data[column]
127576099f98SAdrian Hunter
127676099f98SAdrian Hunter# Brance data model root item
127776099f98SAdrian Hunter
127876099f98SAdrian Hunterclass BranchRootItem():
127976099f98SAdrian Hunter
128076099f98SAdrian Hunter	def __init__(self):
128176099f98SAdrian Hunter		self.child_count = 0
128276099f98SAdrian Hunter		self.child_items = []
128376099f98SAdrian Hunter		self.level = 0
128476099f98SAdrian Hunter
128576099f98SAdrian Hunter	def getChildItem(self, row):
128676099f98SAdrian Hunter		return self.child_items[row]
128776099f98SAdrian Hunter
128876099f98SAdrian Hunter	def getParentItem(self):
128976099f98SAdrian Hunter		return None
129076099f98SAdrian Hunter
129176099f98SAdrian Hunter	def getRow(self):
129276099f98SAdrian Hunter		return 0
129376099f98SAdrian Hunter
129476099f98SAdrian Hunter	def childCount(self):
129576099f98SAdrian Hunter		return self.child_count
129676099f98SAdrian Hunter
129776099f98SAdrian Hunter	def hasChildren(self):
129876099f98SAdrian Hunter		return self.child_count > 0
129976099f98SAdrian Hunter
130076099f98SAdrian Hunter	def getData(self, column):
130176099f98SAdrian Hunter		return ""
130276099f98SAdrian Hunter
130376099f98SAdrian Hunter# Branch data preparation
130476099f98SAdrian Hunter
130576099f98SAdrian Hunterdef BranchDataPrep(query):
130676099f98SAdrian Hunter	data = []
130776099f98SAdrian Hunter	for i in xrange(0, 8):
130876099f98SAdrian Hunter		data.append(query.value(i))
130976099f98SAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
131076099f98SAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
131176099f98SAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
131276099f98SAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
131376099f98SAdrian Hunter	return data
131476099f98SAdrian Hunter
131576099f98SAdrian Hunter# Branch data model
131676099f98SAdrian Hunter
131776099f98SAdrian Hunterclass BranchModel(TreeModel):
131876099f98SAdrian Hunter
131976099f98SAdrian Hunter	progress = Signal(object)
132076099f98SAdrian Hunter
132176099f98SAdrian Hunter	def __init__(self, glb, event_id, where_clause, parent=None):
132276099f98SAdrian Hunter		super(BranchModel, self).__init__(BranchRootItem(), parent)
132376099f98SAdrian Hunter		self.glb = glb
132476099f98SAdrian Hunter		self.event_id = event_id
132576099f98SAdrian Hunter		self.more = True
132676099f98SAdrian Hunter		self.populated = 0
132776099f98SAdrian Hunter		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
132876099f98SAdrian Hunter			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
132976099f98SAdrian Hunter			" ip, symbols.name, sym_offset, dsos.short_name,"
133076099f98SAdrian Hunter			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
133176099f98SAdrian Hunter			" FROM samples"
133276099f98SAdrian Hunter			" INNER JOIN comms ON comm_id = comms.id"
133376099f98SAdrian Hunter			" INNER JOIN threads ON thread_id = threads.id"
133476099f98SAdrian Hunter			" INNER JOIN branch_types ON branch_type = branch_types.id"
133576099f98SAdrian Hunter			" INNER JOIN symbols ON symbol_id = symbols.id"
133676099f98SAdrian Hunter			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
133776099f98SAdrian Hunter			" INNER JOIN dsos ON samples.dso_id = dsos.id"
133876099f98SAdrian Hunter			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
133976099f98SAdrian Hunter			" WHERE samples.id > $$last_id$$" + where_clause +
134076099f98SAdrian Hunter			" AND evsel_id = " + str(self.event_id) +
134176099f98SAdrian Hunter			" ORDER BY samples.id"
134276099f98SAdrian Hunter			" LIMIT " + str(glb_chunk_sz))
134376099f98SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, BranchDataPrep, self.AddSample)
134476099f98SAdrian Hunter		self.fetcher.done.connect(self.Update)
134576099f98SAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
134676099f98SAdrian Hunter
134776099f98SAdrian Hunter	def columnCount(self, parent=None):
134876099f98SAdrian Hunter		return 8
134976099f98SAdrian Hunter
135076099f98SAdrian Hunter	def columnHeader(self, column):
135176099f98SAdrian Hunter		return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
135276099f98SAdrian Hunter
135376099f98SAdrian Hunter	def columnFont(self, column):
135476099f98SAdrian Hunter		if column != 7:
135576099f98SAdrian Hunter			return None
135676099f98SAdrian Hunter		return QFont("Monospace")
135776099f98SAdrian Hunter
135876099f98SAdrian Hunter	def DisplayData(self, item, index):
135976099f98SAdrian Hunter		if item.level == 1:
136076099f98SAdrian Hunter			self.FetchIfNeeded(item.row)
136176099f98SAdrian Hunter		return item.getData(index.column())
136276099f98SAdrian Hunter
136376099f98SAdrian Hunter	def AddSample(self, data):
136476099f98SAdrian Hunter		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
136576099f98SAdrian Hunter		self.root.child_items.append(child)
136676099f98SAdrian Hunter		self.populated += 1
136776099f98SAdrian Hunter
136876099f98SAdrian Hunter	def Update(self, fetched):
136976099f98SAdrian Hunter		if not fetched:
137076099f98SAdrian Hunter			self.more = False
137176099f98SAdrian Hunter			self.progress.emit(0)
137276099f98SAdrian Hunter		child_count = self.root.child_count
137376099f98SAdrian Hunter		count = self.populated - child_count
137476099f98SAdrian Hunter		if count > 0:
137576099f98SAdrian Hunter			parent = QModelIndex()
137676099f98SAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
137776099f98SAdrian Hunter			self.insertRows(child_count, count, parent)
137876099f98SAdrian Hunter			self.root.child_count += count
137976099f98SAdrian Hunter			self.endInsertRows()
138076099f98SAdrian Hunter			self.progress.emit(self.root.child_count)
138176099f98SAdrian Hunter
138276099f98SAdrian Hunter	def FetchMoreRecords(self, count):
138376099f98SAdrian Hunter		current = self.root.child_count
138476099f98SAdrian Hunter		if self.more:
138576099f98SAdrian Hunter			self.fetcher.Fetch(count)
138676099f98SAdrian Hunter		else:
138776099f98SAdrian Hunter			self.progress.emit(0)
138876099f98SAdrian Hunter		return current
138976099f98SAdrian Hunter
139076099f98SAdrian Hunter	def HasMoreRecords(self):
139176099f98SAdrian Hunter		return self.more
139276099f98SAdrian Hunter
139376099f98SAdrian Hunter# Branch window
139476099f98SAdrian Hunter
139576099f98SAdrian Hunterclass BranchWindow(QMdiSubWindow):
139676099f98SAdrian Hunter
139776099f98SAdrian Hunter	def __init__(self, glb, event_id, name, where_clause, parent=None):
139876099f98SAdrian Hunter		super(BranchWindow, self).__init__(parent)
139976099f98SAdrian Hunter
140076099f98SAdrian Hunter		model_name = "Branch Events " + str(event_id)
140176099f98SAdrian Hunter		if len(where_clause):
140276099f98SAdrian Hunter			model_name = where_clause + " " + model_name
140376099f98SAdrian Hunter
140476099f98SAdrian Hunter		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, where_clause))
140576099f98SAdrian Hunter
140676099f98SAdrian Hunter		self.view = QTreeView()
140776099f98SAdrian Hunter		self.view.setUniformRowHeights(True)
140876099f98SAdrian Hunter		self.view.setModel(self.model)
140976099f98SAdrian Hunter
141076099f98SAdrian Hunter		self.ResizeColumnsToContents()
141176099f98SAdrian Hunter
141276099f98SAdrian Hunter		self.find_bar = FindBar(self, self, True)
141376099f98SAdrian Hunter
141476099f98SAdrian Hunter		self.finder = ChildDataItemFinder(self.model.root)
141576099f98SAdrian Hunter
141676099f98SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
141776099f98SAdrian Hunter
141876099f98SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
141976099f98SAdrian Hunter
142076099f98SAdrian Hunter		self.setWidget(self.vbox.Widget())
142176099f98SAdrian Hunter
142276099f98SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, name + " Branch Events")
142376099f98SAdrian Hunter
142476099f98SAdrian Hunter	def ResizeColumnToContents(self, column, n):
142576099f98SAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
142676099f98SAdrian Hunter		# so implement a crude alternative
142776099f98SAdrian Hunter		mm = "MM" if column else "MMMM"
142876099f98SAdrian Hunter		font = self.view.font()
142976099f98SAdrian Hunter		metrics = QFontMetrics(font)
143076099f98SAdrian Hunter		max = 0
143176099f98SAdrian Hunter		for row in xrange(n):
143276099f98SAdrian Hunter			val = self.model.root.child_items[row].data[column]
143376099f98SAdrian Hunter			len = metrics.width(str(val) + mm)
143476099f98SAdrian Hunter			max = len if len > max else max
143576099f98SAdrian Hunter		val = self.model.columnHeader(column)
143676099f98SAdrian Hunter		len = metrics.width(str(val) + mm)
143776099f98SAdrian Hunter		max = len if len > max else max
143876099f98SAdrian Hunter		self.view.setColumnWidth(column, max)
143976099f98SAdrian Hunter
144076099f98SAdrian Hunter	def ResizeColumnsToContents(self):
144176099f98SAdrian Hunter		n = min(self.model.root.child_count, 100)
144276099f98SAdrian Hunter		if n < 1:
144376099f98SAdrian Hunter			# No data yet, so connect a signal to notify when there is
144476099f98SAdrian Hunter			self.model.rowsInserted.connect(self.UpdateColumnWidths)
144576099f98SAdrian Hunter			return
144676099f98SAdrian Hunter		columns = self.model.columnCount()
144776099f98SAdrian Hunter		for i in xrange(columns):
144876099f98SAdrian Hunter			self.ResizeColumnToContents(i, n)
144976099f98SAdrian Hunter
145076099f98SAdrian Hunter	def UpdateColumnWidths(self, *x):
145176099f98SAdrian Hunter		# This only needs to be done once, so disconnect the signal now
145276099f98SAdrian Hunter		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
145376099f98SAdrian Hunter		self.ResizeColumnsToContents()
145476099f98SAdrian Hunter
145576099f98SAdrian Hunter	def Find(self, value, direction, pattern, context):
145676099f98SAdrian Hunter		self.view.setFocus()
145776099f98SAdrian Hunter		self.find_bar.Busy()
145876099f98SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
145976099f98SAdrian Hunter
146076099f98SAdrian Hunter	def FindDone(self, row):
146176099f98SAdrian Hunter		self.find_bar.Idle()
146276099f98SAdrian Hunter		if row >= 0:
146376099f98SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
146476099f98SAdrian Hunter		else:
146576099f98SAdrian Hunter			self.find_bar.NotFound()
146676099f98SAdrian Hunter
146776099f98SAdrian Hunter# Event list
146876099f98SAdrian Hunter
146976099f98SAdrian Hunterdef GetEventList(db):
147076099f98SAdrian Hunter	events = []
147176099f98SAdrian Hunter	query = QSqlQuery(db)
147276099f98SAdrian Hunter	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
147376099f98SAdrian Hunter	while query.next():
147476099f98SAdrian Hunter		events.append(query.value(0))
147576099f98SAdrian Hunter	return events
147676099f98SAdrian Hunter
14778392b74bSAdrian Hunter# SQL data preparation
14788392b74bSAdrian Hunter
14798392b74bSAdrian Hunterdef SQLTableDataPrep(query, count):
14808392b74bSAdrian Hunter	data = []
14818392b74bSAdrian Hunter	for i in xrange(count):
14828392b74bSAdrian Hunter		data.append(query.value(i))
14838392b74bSAdrian Hunter	return data
14848392b74bSAdrian Hunter
14858392b74bSAdrian Hunter# SQL table data model item
14868392b74bSAdrian Hunter
14878392b74bSAdrian Hunterclass SQLTableItem():
14888392b74bSAdrian Hunter
14898392b74bSAdrian Hunter	def __init__(self, row, data):
14908392b74bSAdrian Hunter		self.row = row
14918392b74bSAdrian Hunter		self.data = data
14928392b74bSAdrian Hunter
14938392b74bSAdrian Hunter	def getData(self, column):
14948392b74bSAdrian Hunter		return self.data[column]
14958392b74bSAdrian Hunter
14968392b74bSAdrian Hunter# SQL table data model
14978392b74bSAdrian Hunter
14988392b74bSAdrian Hunterclass SQLTableModel(TableModel):
14998392b74bSAdrian Hunter
15008392b74bSAdrian Hunter	progress = Signal(object)
15018392b74bSAdrian Hunter
15028392b74bSAdrian Hunter	def __init__(self, glb, sql, column_count, parent=None):
15038392b74bSAdrian Hunter		super(SQLTableModel, self).__init__(parent)
15048392b74bSAdrian Hunter		self.glb = glb
15058392b74bSAdrian Hunter		self.more = True
15068392b74bSAdrian Hunter		self.populated = 0
15078392b74bSAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample)
15088392b74bSAdrian Hunter		self.fetcher.done.connect(self.Update)
15098392b74bSAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
15108392b74bSAdrian Hunter
15118392b74bSAdrian Hunter	def DisplayData(self, item, index):
15128392b74bSAdrian Hunter		self.FetchIfNeeded(item.row)
15138392b74bSAdrian Hunter		return item.getData(index.column())
15148392b74bSAdrian Hunter
15158392b74bSAdrian Hunter	def AddSample(self, data):
15168392b74bSAdrian Hunter		child = SQLTableItem(self.populated, data)
15178392b74bSAdrian Hunter		self.child_items.append(child)
15188392b74bSAdrian Hunter		self.populated += 1
15198392b74bSAdrian Hunter
15208392b74bSAdrian Hunter	def Update(self, fetched):
15218392b74bSAdrian Hunter		if not fetched:
15228392b74bSAdrian Hunter			self.more = False
15238392b74bSAdrian Hunter			self.progress.emit(0)
15248392b74bSAdrian Hunter		child_count = self.child_count
15258392b74bSAdrian Hunter		count = self.populated - child_count
15268392b74bSAdrian Hunter		if count > 0:
15278392b74bSAdrian Hunter			parent = QModelIndex()
15288392b74bSAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
15298392b74bSAdrian Hunter			self.insertRows(child_count, count, parent)
15308392b74bSAdrian Hunter			self.child_count += count
15318392b74bSAdrian Hunter			self.endInsertRows()
15328392b74bSAdrian Hunter			self.progress.emit(self.child_count)
15338392b74bSAdrian Hunter
15348392b74bSAdrian Hunter	def FetchMoreRecords(self, count):
15358392b74bSAdrian Hunter		current = self.child_count
15368392b74bSAdrian Hunter		if self.more:
15378392b74bSAdrian Hunter			self.fetcher.Fetch(count)
15388392b74bSAdrian Hunter		else:
15398392b74bSAdrian Hunter			self.progress.emit(0)
15408392b74bSAdrian Hunter		return current
15418392b74bSAdrian Hunter
15428392b74bSAdrian Hunter	def HasMoreRecords(self):
15438392b74bSAdrian Hunter		return self.more
15448392b74bSAdrian Hunter
15458392b74bSAdrian Hunter# SQL automatic table data model
15468392b74bSAdrian Hunter
15478392b74bSAdrian Hunterclass SQLAutoTableModel(SQLTableModel):
15488392b74bSAdrian Hunter
15498392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
15508392b74bSAdrian Hunter		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
15518392b74bSAdrian Hunter		if table_name == "comm_threads_view":
15528392b74bSAdrian Hunter			# For now, comm_threads_view has no id column
15538392b74bSAdrian Hunter			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
15548392b74bSAdrian Hunter		self.column_headers = []
15558392b74bSAdrian Hunter		query = QSqlQuery(glb.db)
15568392b74bSAdrian Hunter		if glb.dbref.is_sqlite3:
15578392b74bSAdrian Hunter			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
15588392b74bSAdrian Hunter			while query.next():
15598392b74bSAdrian Hunter				self.column_headers.append(query.value(1))
15608392b74bSAdrian Hunter			if table_name == "sqlite_master":
15618392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
15628392b74bSAdrian Hunter		else:
15638392b74bSAdrian Hunter			if table_name[:19] == "information_schema.":
15648392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
15658392b74bSAdrian Hunter				select_table_name = table_name[19:]
15668392b74bSAdrian Hunter				schema = "information_schema"
15678392b74bSAdrian Hunter			else:
15688392b74bSAdrian Hunter				select_table_name = table_name
15698392b74bSAdrian Hunter				schema = "public"
15708392b74bSAdrian Hunter			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
15718392b74bSAdrian Hunter			while query.next():
15728392b74bSAdrian Hunter				self.column_headers.append(query.value(0))
15738392b74bSAdrian Hunter		super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent)
15748392b74bSAdrian Hunter
15758392b74bSAdrian Hunter	def columnCount(self, parent=None):
15768392b74bSAdrian Hunter		return len(self.column_headers)
15778392b74bSAdrian Hunter
15788392b74bSAdrian Hunter	def columnHeader(self, column):
15798392b74bSAdrian Hunter		return self.column_headers[column]
15808392b74bSAdrian Hunter
15818392b74bSAdrian Hunter# Base class for custom ResizeColumnsToContents
15828392b74bSAdrian Hunter
15838392b74bSAdrian Hunterclass ResizeColumnsToContentsBase(QObject):
15848392b74bSAdrian Hunter
15858392b74bSAdrian Hunter	def __init__(self, parent=None):
15868392b74bSAdrian Hunter		super(ResizeColumnsToContentsBase, self).__init__(parent)
15878392b74bSAdrian Hunter
15888392b74bSAdrian Hunter	def ResizeColumnToContents(self, column, n):
15898392b74bSAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
15908392b74bSAdrian Hunter		# so implement a crude alternative
15918392b74bSAdrian Hunter		font = self.view.font()
15928392b74bSAdrian Hunter		metrics = QFontMetrics(font)
15938392b74bSAdrian Hunter		max = 0
15948392b74bSAdrian Hunter		for row in xrange(n):
15958392b74bSAdrian Hunter			val = self.data_model.child_items[row].data[column]
15968392b74bSAdrian Hunter			len = metrics.width(str(val) + "MM")
15978392b74bSAdrian Hunter			max = len if len > max else max
15988392b74bSAdrian Hunter		val = self.data_model.columnHeader(column)
15998392b74bSAdrian Hunter		len = metrics.width(str(val) + "MM")
16008392b74bSAdrian Hunter		max = len if len > max else max
16018392b74bSAdrian Hunter		self.view.setColumnWidth(column, max)
16028392b74bSAdrian Hunter
16038392b74bSAdrian Hunter	def ResizeColumnsToContents(self):
16048392b74bSAdrian Hunter		n = min(self.data_model.child_count, 100)
16058392b74bSAdrian Hunter		if n < 1:
16068392b74bSAdrian Hunter			# No data yet, so connect a signal to notify when there is
16078392b74bSAdrian Hunter			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
16088392b74bSAdrian Hunter			return
16098392b74bSAdrian Hunter		columns = self.data_model.columnCount()
16108392b74bSAdrian Hunter		for i in xrange(columns):
16118392b74bSAdrian Hunter			self.ResizeColumnToContents(i, n)
16128392b74bSAdrian Hunter
16138392b74bSAdrian Hunter	def UpdateColumnWidths(self, *x):
16148392b74bSAdrian Hunter		# This only needs to be done once, so disconnect the signal now
16158392b74bSAdrian Hunter		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
16168392b74bSAdrian Hunter		self.ResizeColumnsToContents()
16178392b74bSAdrian Hunter
16188392b74bSAdrian Hunter# Table window
16198392b74bSAdrian Hunter
16208392b74bSAdrian Hunterclass TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
16218392b74bSAdrian Hunter
16228392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
16238392b74bSAdrian Hunter		super(TableWindow, self).__init__(parent)
16248392b74bSAdrian Hunter
16258392b74bSAdrian Hunter		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
16268392b74bSAdrian Hunter
16278392b74bSAdrian Hunter		self.model = QSortFilterProxyModel()
16288392b74bSAdrian Hunter		self.model.setSourceModel(self.data_model)
16298392b74bSAdrian Hunter
16308392b74bSAdrian Hunter		self.view = QTableView()
16318392b74bSAdrian Hunter		self.view.setModel(self.model)
16328392b74bSAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
16338392b74bSAdrian Hunter		self.view.verticalHeader().setVisible(False)
16348392b74bSAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
16358392b74bSAdrian Hunter		self.view.setSortingEnabled(True)
16368392b74bSAdrian Hunter
16378392b74bSAdrian Hunter		self.ResizeColumnsToContents()
16388392b74bSAdrian Hunter
16398392b74bSAdrian Hunter		self.find_bar = FindBar(self, self, True)
16408392b74bSAdrian Hunter
16418392b74bSAdrian Hunter		self.finder = ChildDataItemFinder(self.data_model)
16428392b74bSAdrian Hunter
16438392b74bSAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
16448392b74bSAdrian Hunter
16458392b74bSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
16468392b74bSAdrian Hunter
16478392b74bSAdrian Hunter		self.setWidget(self.vbox.Widget())
16488392b74bSAdrian Hunter
16498392b74bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
16508392b74bSAdrian Hunter
16518392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context):
16528392b74bSAdrian Hunter		self.view.setFocus()
16538392b74bSAdrian Hunter		self.find_bar.Busy()
16548392b74bSAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
16558392b74bSAdrian Hunter
16568392b74bSAdrian Hunter	def FindDone(self, row):
16578392b74bSAdrian Hunter		self.find_bar.Idle()
16588392b74bSAdrian Hunter		if row >= 0:
16598392b74bSAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
16608392b74bSAdrian Hunter		else:
16618392b74bSAdrian Hunter			self.find_bar.NotFound()
16628392b74bSAdrian Hunter
16638392b74bSAdrian Hunter# Table list
16648392b74bSAdrian Hunter
16658392b74bSAdrian Hunterdef GetTableList(glb):
16668392b74bSAdrian Hunter	tables = []
16678392b74bSAdrian Hunter	query = QSqlQuery(glb.db)
16688392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
16698392b74bSAdrian Hunter		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
16708392b74bSAdrian Hunter	else:
16718392b74bSAdrian 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")
16728392b74bSAdrian Hunter	while query.next():
16738392b74bSAdrian Hunter		tables.append(query.value(0))
16748392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
16758392b74bSAdrian Hunter		tables.append("sqlite_master")
16768392b74bSAdrian Hunter	else:
16778392b74bSAdrian Hunter		tables.append("information_schema.tables")
16788392b74bSAdrian Hunter		tables.append("information_schema.views")
16798392b74bSAdrian Hunter		tables.append("information_schema.columns")
16808392b74bSAdrian Hunter	return tables
16818392b74bSAdrian Hunter
16821beb5c7bSAdrian Hunter# Action Definition
16831beb5c7bSAdrian Hunter
16841beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None):
16851beb5c7bSAdrian Hunter	action = QAction(label, parent)
16861beb5c7bSAdrian Hunter	if shortcut != None:
16871beb5c7bSAdrian Hunter		action.setShortcuts(shortcut)
16881beb5c7bSAdrian Hunter	action.setStatusTip(tip)
16891beb5c7bSAdrian Hunter	action.triggered.connect(callback)
16901beb5c7bSAdrian Hunter	return action
16911beb5c7bSAdrian Hunter
16921beb5c7bSAdrian Hunter# Typical application actions
16931beb5c7bSAdrian Hunter
16941beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None):
16951beb5c7bSAdrian Hunter	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
16961beb5c7bSAdrian Hunter
16971beb5c7bSAdrian Hunter# Typical MDI actions
16981beb5c7bSAdrian Hunter
16991beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area):
17001beb5c7bSAdrian Hunter	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
17011beb5c7bSAdrian Hunter
17021beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area):
17031beb5c7bSAdrian Hunter	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
17041beb5c7bSAdrian Hunter
17051beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area):
17061beb5c7bSAdrian Hunter	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
17071beb5c7bSAdrian Hunter
17081beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area):
17091beb5c7bSAdrian Hunter	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
17101beb5c7bSAdrian Hunter
17111beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area):
17121beb5c7bSAdrian Hunter	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
17131beb5c7bSAdrian Hunter
17141beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area):
17151beb5c7bSAdrian Hunter	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
17161beb5c7bSAdrian Hunter
17171beb5c7bSAdrian Hunter# Typical MDI window menu
17181beb5c7bSAdrian Hunter
17191beb5c7bSAdrian Hunterclass WindowMenu():
17201beb5c7bSAdrian Hunter
17211beb5c7bSAdrian Hunter	def __init__(self, mdi_area, menu):
17221beb5c7bSAdrian Hunter		self.mdi_area = mdi_area
17231beb5c7bSAdrian Hunter		self.window_menu = menu.addMenu("&Windows")
17241beb5c7bSAdrian Hunter		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
17251beb5c7bSAdrian Hunter		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
17261beb5c7bSAdrian Hunter		self.tile_windows = CreateTileWindowsAction(mdi_area)
17271beb5c7bSAdrian Hunter		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
17281beb5c7bSAdrian Hunter		self.next_window = CreateNextWindowAction(mdi_area)
17291beb5c7bSAdrian Hunter		self.previous_window = CreatePreviousWindowAction(mdi_area)
17301beb5c7bSAdrian Hunter		self.window_menu.aboutToShow.connect(self.Update)
17311beb5c7bSAdrian Hunter
17321beb5c7bSAdrian Hunter	def Update(self):
17331beb5c7bSAdrian Hunter		self.window_menu.clear()
17341beb5c7bSAdrian Hunter		sub_window_count = len(self.mdi_area.subWindowList())
17351beb5c7bSAdrian Hunter		have_sub_windows = sub_window_count != 0
17361beb5c7bSAdrian Hunter		self.close_active_window.setEnabled(have_sub_windows)
17371beb5c7bSAdrian Hunter		self.close_all_windows.setEnabled(have_sub_windows)
17381beb5c7bSAdrian Hunter		self.tile_windows.setEnabled(have_sub_windows)
17391beb5c7bSAdrian Hunter		self.cascade_windows.setEnabled(have_sub_windows)
17401beb5c7bSAdrian Hunter		self.next_window.setEnabled(have_sub_windows)
17411beb5c7bSAdrian Hunter		self.previous_window.setEnabled(have_sub_windows)
17421beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_active_window)
17431beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_all_windows)
17441beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
17451beb5c7bSAdrian Hunter		self.window_menu.addAction(self.tile_windows)
17461beb5c7bSAdrian Hunter		self.window_menu.addAction(self.cascade_windows)
17471beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
17481beb5c7bSAdrian Hunter		self.window_menu.addAction(self.next_window)
17491beb5c7bSAdrian Hunter		self.window_menu.addAction(self.previous_window)
17501beb5c7bSAdrian Hunter		if sub_window_count == 0:
17511beb5c7bSAdrian Hunter			return
17521beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
17531beb5c7bSAdrian Hunter		nr = 1
17541beb5c7bSAdrian Hunter		for sub_window in self.mdi_area.subWindowList():
17551beb5c7bSAdrian Hunter			label = str(nr) + " " + sub_window.name
17561beb5c7bSAdrian Hunter			if nr < 10:
17571beb5c7bSAdrian Hunter				label = "&" + label
17581beb5c7bSAdrian Hunter			action = self.window_menu.addAction(label)
17591beb5c7bSAdrian Hunter			action.setCheckable(True)
17601beb5c7bSAdrian Hunter			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
17611beb5c7bSAdrian Hunter			action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
17621beb5c7bSAdrian Hunter			self.window_menu.addAction(action)
17631beb5c7bSAdrian Hunter			nr += 1
17641beb5c7bSAdrian Hunter
17651beb5c7bSAdrian Hunter	def setActiveSubWindow(self, nr):
17661beb5c7bSAdrian Hunter		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
17671beb5c7bSAdrian Hunter
176882f68e28SAdrian Hunter# Font resize
176982f68e28SAdrian Hunter
177082f68e28SAdrian Hunterdef ResizeFont(widget, diff):
177182f68e28SAdrian Hunter	font = widget.font()
177282f68e28SAdrian Hunter	sz = font.pointSize()
177382f68e28SAdrian Hunter	font.setPointSize(sz + diff)
177482f68e28SAdrian Hunter	widget.setFont(font)
177582f68e28SAdrian Hunter
177682f68e28SAdrian Hunterdef ShrinkFont(widget):
177782f68e28SAdrian Hunter	ResizeFont(widget, -1)
177882f68e28SAdrian Hunter
177982f68e28SAdrian Hunterdef EnlargeFont(widget):
178082f68e28SAdrian Hunter	ResizeFont(widget, 1)
178182f68e28SAdrian Hunter
17821beb5c7bSAdrian Hunter# Unique name for sub-windows
17831beb5c7bSAdrian Hunter
17841beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr):
17851beb5c7bSAdrian Hunter	if nr > 1:
17861beb5c7bSAdrian Hunter		name += " <" + str(nr) + ">"
17871beb5c7bSAdrian Hunter	return name
17881beb5c7bSAdrian Hunter
17891beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name):
17901beb5c7bSAdrian Hunter	nr = 1
17911beb5c7bSAdrian Hunter	while True:
17921beb5c7bSAdrian Hunter		unique_name = NumberedWindowName(name, nr)
17931beb5c7bSAdrian Hunter		ok = True
17941beb5c7bSAdrian Hunter		for sub_window in mdi_area.subWindowList():
17951beb5c7bSAdrian Hunter			if sub_window.name == unique_name:
17961beb5c7bSAdrian Hunter				ok = False
17971beb5c7bSAdrian Hunter				break
17981beb5c7bSAdrian Hunter		if ok:
17991beb5c7bSAdrian Hunter			return unique_name
18001beb5c7bSAdrian Hunter		nr += 1
18011beb5c7bSAdrian Hunter
18021beb5c7bSAdrian Hunter# Add a sub-window
18031beb5c7bSAdrian Hunter
18041beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name):
18051beb5c7bSAdrian Hunter	unique_name = UniqueSubWindowName(mdi_area, name)
18061beb5c7bSAdrian Hunter	sub_window.setMinimumSize(200, 100)
18071beb5c7bSAdrian Hunter	sub_window.resize(800, 600)
18081beb5c7bSAdrian Hunter	sub_window.setWindowTitle(unique_name)
18091beb5c7bSAdrian Hunter	sub_window.setAttribute(Qt.WA_DeleteOnClose)
18101beb5c7bSAdrian Hunter	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
18111beb5c7bSAdrian Hunter	sub_window.name = unique_name
18121beb5c7bSAdrian Hunter	mdi_area.addSubWindow(sub_window)
18131beb5c7bSAdrian Hunter	sub_window.show()
18141beb5c7bSAdrian Hunter
1815031c2a00SAdrian Hunter# Main window
1816031c2a00SAdrian Hunter
1817031c2a00SAdrian Hunterclass MainWindow(QMainWindow):
1818031c2a00SAdrian Hunter
1819031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
1820031c2a00SAdrian Hunter		super(MainWindow, self).__init__(parent)
1821031c2a00SAdrian Hunter
1822031c2a00SAdrian Hunter		self.glb = glb
1823031c2a00SAdrian Hunter
18241beb5c7bSAdrian Hunter		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
1825031c2a00SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
1826031c2a00SAdrian Hunter		self.setMinimumSize(200, 100)
1827031c2a00SAdrian Hunter
18281beb5c7bSAdrian Hunter		self.mdi_area = QMdiArea()
18291beb5c7bSAdrian Hunter		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
18301beb5c7bSAdrian Hunter		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
1831031c2a00SAdrian Hunter
18321beb5c7bSAdrian Hunter		self.setCentralWidget(self.mdi_area)
1833031c2a00SAdrian Hunter
18341beb5c7bSAdrian Hunter		menu = self.menuBar()
1835031c2a00SAdrian Hunter
18361beb5c7bSAdrian Hunter		file_menu = menu.addMenu("&File")
18371beb5c7bSAdrian Hunter		file_menu.addAction(CreateExitAction(glb.app, self))
18381beb5c7bSAdrian Hunter
1839ebd70c7dSAdrian Hunter		edit_menu = menu.addMenu("&Edit")
1840ebd70c7dSAdrian Hunter		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
18418392b74bSAdrian Hunter		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
184282f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
184382f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
1844ebd70c7dSAdrian Hunter
18451beb5c7bSAdrian Hunter		reports_menu = menu.addMenu("&Reports")
18461beb5c7bSAdrian Hunter		reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
18471beb5c7bSAdrian Hunter
184876099f98SAdrian Hunter		self.EventMenu(GetEventList(glb.db), reports_menu)
184976099f98SAdrian Hunter
18508392b74bSAdrian Hunter		self.TableMenu(GetTableList(glb), menu)
18518392b74bSAdrian Hunter
18521beb5c7bSAdrian Hunter		self.window_menu = WindowMenu(self.mdi_area, menu)
18531beb5c7bSAdrian Hunter
1854ebd70c7dSAdrian Hunter	def Find(self):
1855ebd70c7dSAdrian Hunter		win = self.mdi_area.activeSubWindow()
1856ebd70c7dSAdrian Hunter		if win:
1857ebd70c7dSAdrian Hunter			try:
1858ebd70c7dSAdrian Hunter				win.find_bar.Activate()
1859ebd70c7dSAdrian Hunter			except:
1860ebd70c7dSAdrian Hunter				pass
1861ebd70c7dSAdrian Hunter
18628392b74bSAdrian Hunter	def FetchMoreRecords(self):
18638392b74bSAdrian Hunter		win = self.mdi_area.activeSubWindow()
18648392b74bSAdrian Hunter		if win:
18658392b74bSAdrian Hunter			try:
18668392b74bSAdrian Hunter				win.fetch_bar.Activate()
18678392b74bSAdrian Hunter			except:
18688392b74bSAdrian Hunter				pass
18698392b74bSAdrian Hunter
187082f68e28SAdrian Hunter	def ShrinkFont(self):
187182f68e28SAdrian Hunter		win = self.mdi_area.activeSubWindow()
187282f68e28SAdrian Hunter		ShrinkFont(win.view)
187382f68e28SAdrian Hunter
187482f68e28SAdrian Hunter	def EnlargeFont(self):
187582f68e28SAdrian Hunter		win = self.mdi_area.activeSubWindow()
187682f68e28SAdrian Hunter		EnlargeFont(win.view)
187782f68e28SAdrian Hunter
187876099f98SAdrian Hunter	def EventMenu(self, events, reports_menu):
187976099f98SAdrian Hunter		branches_events = 0
188076099f98SAdrian Hunter		for event in events:
188176099f98SAdrian Hunter			event = event.split(":")[0]
188276099f98SAdrian Hunter			if event == "branches":
188376099f98SAdrian Hunter				branches_events += 1
188476099f98SAdrian Hunter		dbid = 0
188576099f98SAdrian Hunter		for event in events:
188676099f98SAdrian Hunter			dbid += 1
188776099f98SAdrian Hunter			event = event.split(":")[0]
188876099f98SAdrian Hunter			if event == "branches":
188976099f98SAdrian Hunter				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
189076099f98SAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
189176099f98SAdrian Hunter
18928392b74bSAdrian Hunter	def TableMenu(self, tables, menu):
18938392b74bSAdrian Hunter		table_menu = menu.addMenu("&Tables")
18948392b74bSAdrian Hunter		for table in tables:
18958392b74bSAdrian Hunter			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
18968392b74bSAdrian Hunter
18971beb5c7bSAdrian Hunter	def NewCallGraph(self):
18981beb5c7bSAdrian Hunter		CallGraphWindow(self.glb, self)
1899031c2a00SAdrian Hunter
190076099f98SAdrian Hunter	def NewBranchView(self, event_id):
190176099f98SAdrian Hunter		BranchWindow(self.glb, event_id, "", "", self)
190276099f98SAdrian Hunter
19038392b74bSAdrian Hunter	def NewTableView(self, table_name):
19048392b74bSAdrian Hunter		TableWindow(self.glb, table_name, self)
19058392b74bSAdrian Hunter
190676099f98SAdrian Hunter# XED Disassembler
190776099f98SAdrian Hunter
190876099f98SAdrian Hunterclass xed_state_t(Structure):
190976099f98SAdrian Hunter
191076099f98SAdrian Hunter	_fields_ = [
191176099f98SAdrian Hunter		("mode", c_int),
191276099f98SAdrian Hunter		("width", c_int)
191376099f98SAdrian Hunter	]
191476099f98SAdrian Hunter
191576099f98SAdrian Hunterclass XEDInstruction():
191676099f98SAdrian Hunter
191776099f98SAdrian Hunter	def __init__(self, libxed):
191876099f98SAdrian Hunter		# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
191976099f98SAdrian Hunter		xedd_t = c_byte * 512
192076099f98SAdrian Hunter		self.xedd = xedd_t()
192176099f98SAdrian Hunter		self.xedp = addressof(self.xedd)
192276099f98SAdrian Hunter		libxed.xed_decoded_inst_zero(self.xedp)
192376099f98SAdrian Hunter		self.state = xed_state_t()
192476099f98SAdrian Hunter		self.statep = addressof(self.state)
192576099f98SAdrian Hunter		# Buffer for disassembled instruction text
192676099f98SAdrian Hunter		self.buffer = create_string_buffer(256)
192776099f98SAdrian Hunter		self.bufferp = addressof(self.buffer)
192876099f98SAdrian Hunter
192976099f98SAdrian Hunterclass LibXED():
193076099f98SAdrian Hunter
193176099f98SAdrian Hunter	def __init__(self):
1932*5ed4419dSAdrian Hunter		try:
193376099f98SAdrian Hunter			self.libxed = CDLL("libxed.so")
1934*5ed4419dSAdrian Hunter		except:
1935*5ed4419dSAdrian Hunter			self.libxed = None
1936*5ed4419dSAdrian Hunter		if not self.libxed:
1937*5ed4419dSAdrian Hunter			self.libxed = CDLL("/usr/local/lib/libxed.so")
193876099f98SAdrian Hunter
193976099f98SAdrian Hunter		self.xed_tables_init = self.libxed.xed_tables_init
194076099f98SAdrian Hunter		self.xed_tables_init.restype = None
194176099f98SAdrian Hunter		self.xed_tables_init.argtypes = []
194276099f98SAdrian Hunter
194376099f98SAdrian Hunter		self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
194476099f98SAdrian Hunter		self.xed_decoded_inst_zero.restype = None
194576099f98SAdrian Hunter		self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
194676099f98SAdrian Hunter
194776099f98SAdrian Hunter		self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
194876099f98SAdrian Hunter		self.xed_operand_values_set_mode.restype = None
194976099f98SAdrian Hunter		self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
195076099f98SAdrian Hunter
195176099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
195276099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.restype = None
195376099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
195476099f98SAdrian Hunter
195576099f98SAdrian Hunter		self.xed_decode = self.libxed.xed_decode
195676099f98SAdrian Hunter		self.xed_decode.restype = c_int
195776099f98SAdrian Hunter		self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
195876099f98SAdrian Hunter
195976099f98SAdrian Hunter		self.xed_format_context = self.libxed.xed_format_context
196076099f98SAdrian Hunter		self.xed_format_context.restype = c_uint
196176099f98SAdrian Hunter		self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
196276099f98SAdrian Hunter
196376099f98SAdrian Hunter		self.xed_tables_init()
196476099f98SAdrian Hunter
196576099f98SAdrian Hunter	def Instruction(self):
196676099f98SAdrian Hunter		return XEDInstruction(self)
196776099f98SAdrian Hunter
196876099f98SAdrian Hunter	def SetMode(self, inst, mode):
196976099f98SAdrian Hunter		if mode:
197076099f98SAdrian Hunter			inst.state.mode = 4 # 32-bit
197176099f98SAdrian Hunter			inst.state.width = 4 # 4 bytes
197276099f98SAdrian Hunter		else:
197376099f98SAdrian Hunter			inst.state.mode = 1 # 64-bit
197476099f98SAdrian Hunter			inst.state.width = 8 # 8 bytes
197576099f98SAdrian Hunter		self.xed_operand_values_set_mode(inst.xedp, inst.statep)
197676099f98SAdrian Hunter
197776099f98SAdrian Hunter	def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
197876099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode(inst.xedp)
197976099f98SAdrian Hunter		err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
198076099f98SAdrian Hunter		if err:
198176099f98SAdrian Hunter			return 0, ""
198276099f98SAdrian Hunter		# Use AT&T mode (2), alternative is Intel (3)
198376099f98SAdrian Hunter		ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
198476099f98SAdrian Hunter		if not ok:
198576099f98SAdrian Hunter			return 0, ""
198676099f98SAdrian Hunter		# Return instruction length and the disassembled instruction text
198776099f98SAdrian Hunter		# For now, assume the length is in byte 166
198876099f98SAdrian Hunter		return inst.xedd[166], inst.buffer.value
198976099f98SAdrian Hunter
199076099f98SAdrian Hunterdef TryOpen(file_name):
199176099f98SAdrian Hunter	try:
199276099f98SAdrian Hunter		return open(file_name, "rb")
199376099f98SAdrian Hunter	except:
199476099f98SAdrian Hunter		return None
199576099f98SAdrian Hunter
199676099f98SAdrian Hunterdef Is64Bit(f):
199776099f98SAdrian Hunter	result = sizeof(c_void_p)
199876099f98SAdrian Hunter	# ELF support only
199976099f98SAdrian Hunter	pos = f.tell()
200076099f98SAdrian Hunter	f.seek(0)
200176099f98SAdrian Hunter	header = f.read(7)
200276099f98SAdrian Hunter	f.seek(pos)
200376099f98SAdrian Hunter	magic = header[0:4]
200476099f98SAdrian Hunter	eclass = ord(header[4])
200576099f98SAdrian Hunter	encoding = ord(header[5])
200676099f98SAdrian Hunter	version = ord(header[6])
200776099f98SAdrian Hunter	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
200876099f98SAdrian Hunter		result = True if eclass == 2 else False
200976099f98SAdrian Hunter	return result
201076099f98SAdrian Hunter
2011031c2a00SAdrian Hunter# Global data
2012031c2a00SAdrian Hunter
2013031c2a00SAdrian Hunterclass Glb():
2014031c2a00SAdrian Hunter
2015031c2a00SAdrian Hunter	def __init__(self, dbref, db, dbname):
2016031c2a00SAdrian Hunter		self.dbref = dbref
2017031c2a00SAdrian Hunter		self.db = db
2018031c2a00SAdrian Hunter		self.dbname = dbname
201976099f98SAdrian Hunter		self.home_dir = os.path.expanduser("~")
202076099f98SAdrian Hunter		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
202176099f98SAdrian Hunter		if self.buildid_dir:
202276099f98SAdrian Hunter			self.buildid_dir += "/.build-id/"
202376099f98SAdrian Hunter		else:
202476099f98SAdrian Hunter			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
2025031c2a00SAdrian Hunter		self.app = None
2026031c2a00SAdrian Hunter		self.mainwindow = None
20278392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit = weakref.WeakSet()
202876099f98SAdrian Hunter		try:
202976099f98SAdrian Hunter			self.disassembler = LibXED()
203076099f98SAdrian Hunter			self.have_disassembler = True
203176099f98SAdrian Hunter		except:
203276099f98SAdrian Hunter			self.have_disassembler = False
203376099f98SAdrian Hunter
203476099f98SAdrian Hunter	def FileFromBuildId(self, build_id):
203576099f98SAdrian Hunter		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
203676099f98SAdrian Hunter		return TryOpen(file_name)
203776099f98SAdrian Hunter
203876099f98SAdrian Hunter	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
203976099f98SAdrian Hunter		# Assume current machine i.e. no support for virtualization
204076099f98SAdrian Hunter		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
204176099f98SAdrian Hunter			file_name = os.getenv("PERF_KCORE")
204276099f98SAdrian Hunter			f = TryOpen(file_name) if file_name else None
204376099f98SAdrian Hunter			if f:
204476099f98SAdrian Hunter				return f
204576099f98SAdrian Hunter			# For now, no special handling if long_name is /proc/kcore
204676099f98SAdrian Hunter			f = TryOpen(long_name)
204776099f98SAdrian Hunter			if f:
204876099f98SAdrian Hunter				return f
204976099f98SAdrian Hunter		f = self.FileFromBuildId(build_id)
205076099f98SAdrian Hunter		if f:
205176099f98SAdrian Hunter			return f
205276099f98SAdrian Hunter		return None
20538392b74bSAdrian Hunter
20548392b74bSAdrian Hunter	def AddInstanceToShutdownOnExit(self, instance):
20558392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit.add(instance)
20568392b74bSAdrian Hunter
20578392b74bSAdrian Hunter	# Shutdown any background processes or threads
20588392b74bSAdrian Hunter	def ShutdownInstances(self):
20598392b74bSAdrian Hunter		for x in self.instances_to_shutdown_on_exit:
20608392b74bSAdrian Hunter			try:
20618392b74bSAdrian Hunter				x.Shutdown()
20628392b74bSAdrian Hunter			except:
20638392b74bSAdrian Hunter				pass
2064031c2a00SAdrian Hunter
2065031c2a00SAdrian Hunter# Database reference
2066031c2a00SAdrian Hunter
2067031c2a00SAdrian Hunterclass DBRef():
2068031c2a00SAdrian Hunter
2069031c2a00SAdrian Hunter	def __init__(self, is_sqlite3, dbname):
2070031c2a00SAdrian Hunter		self.is_sqlite3 = is_sqlite3
2071031c2a00SAdrian Hunter		self.dbname = dbname
2072031c2a00SAdrian Hunter
2073031c2a00SAdrian Hunter	def Open(self, connection_name):
2074031c2a00SAdrian Hunter		dbname = self.dbname
2075031c2a00SAdrian Hunter		if self.is_sqlite3:
2076031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
2077031c2a00SAdrian Hunter		else:
2078031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
2079031c2a00SAdrian Hunter			opts = dbname.split()
2080031c2a00SAdrian Hunter			for opt in opts:
2081031c2a00SAdrian Hunter				if "=" in opt:
2082031c2a00SAdrian Hunter					opt = opt.split("=")
2083031c2a00SAdrian Hunter					if opt[0] == "hostname":
2084031c2a00SAdrian Hunter						db.setHostName(opt[1])
2085031c2a00SAdrian Hunter					elif opt[0] == "port":
2086031c2a00SAdrian Hunter						db.setPort(int(opt[1]))
2087031c2a00SAdrian Hunter					elif opt[0] == "username":
2088031c2a00SAdrian Hunter						db.setUserName(opt[1])
2089031c2a00SAdrian Hunter					elif opt[0] == "password":
2090031c2a00SAdrian Hunter						db.setPassword(opt[1])
2091031c2a00SAdrian Hunter					elif opt[0] == "dbname":
2092031c2a00SAdrian Hunter						dbname = opt[1]
2093031c2a00SAdrian Hunter				else:
2094031c2a00SAdrian Hunter					dbname = opt
2095031c2a00SAdrian Hunter
2096031c2a00SAdrian Hunter		db.setDatabaseName(dbname)
2097031c2a00SAdrian Hunter		if not db.open():
2098031c2a00SAdrian Hunter			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
2099031c2a00SAdrian Hunter		return db, dbname
2100031c2a00SAdrian Hunter
2101031c2a00SAdrian Hunter# Main
2102031c2a00SAdrian Hunter
2103031c2a00SAdrian Hunterdef Main():
2104031c2a00SAdrian Hunter	if (len(sys.argv) < 2):
2105031c2a00SAdrian Hunter		print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>"
2106031c2a00SAdrian Hunter		raise Exception("Too few arguments")
2107031c2a00SAdrian Hunter
2108031c2a00SAdrian Hunter	dbname = sys.argv[1]
2109031c2a00SAdrian Hunter
2110031c2a00SAdrian Hunter	is_sqlite3 = False
2111031c2a00SAdrian Hunter	try:
2112031c2a00SAdrian Hunter		f = open(dbname)
2113031c2a00SAdrian Hunter		if f.read(15) == "SQLite format 3":
2114031c2a00SAdrian Hunter			is_sqlite3 = True
2115031c2a00SAdrian Hunter		f.close()
2116031c2a00SAdrian Hunter	except:
2117031c2a00SAdrian Hunter		pass
2118031c2a00SAdrian Hunter
2119031c2a00SAdrian Hunter	dbref = DBRef(is_sqlite3, dbname)
2120031c2a00SAdrian Hunter	db, dbname = dbref.Open("main")
2121031c2a00SAdrian Hunter	glb = Glb(dbref, db, dbname)
2122031c2a00SAdrian Hunter	app = QApplication(sys.argv)
2123031c2a00SAdrian Hunter	glb.app = app
2124031c2a00SAdrian Hunter	mainwindow = MainWindow(glb)
2125031c2a00SAdrian Hunter	glb.mainwindow = mainwindow
2126031c2a00SAdrian Hunter	mainwindow.show()
2127031c2a00SAdrian Hunter	err = app.exec_()
21288392b74bSAdrian Hunter	glb.ShutdownInstances()
2129031c2a00SAdrian Hunter	db.close()
2130031c2a00SAdrian Hunter	sys.exit(err)
2131031c2a00SAdrian Hunter
2132031c2a00SAdrian Hunterif __name__ == "__main__":
2133031c2a00SAdrian Hunter	Main()
2134