1c6aba1bfSAdrian Hunter#!/usr/bin/env python
2031c2a00SAdrian Hunter# SPDX-License-Identifier: GPL-2.0
3031c2a00SAdrian Hunter# exported-sql-viewer.py: view data from sql database
4031c2a00SAdrian Hunter# Copyright (c) 2014-2018, Intel Corporation.
5031c2a00SAdrian Hunter
6031c2a00SAdrian Hunter# To use this script you will need to have exported data using either the
7031c2a00SAdrian Hunter# export-to-sqlite.py or the export-to-postgresql.py script.  Refer to those
8031c2a00SAdrian Hunter# scripts for details.
9031c2a00SAdrian Hunter#
10031c2a00SAdrian Hunter# Following on from the example in the export scripts, a
11031c2a00SAdrian Hunter# call-graph can be displayed for the pt_example database like this:
12031c2a00SAdrian Hunter#
13031c2a00SAdrian Hunter#	python tools/perf/scripts/python/exported-sql-viewer.py pt_example
14031c2a00SAdrian Hunter#
15031c2a00SAdrian Hunter# Note that for PostgreSQL, this script supports connecting to remote databases
16031c2a00SAdrian Hunter# by setting hostname, port, username, password, and dbname e.g.
17031c2a00SAdrian Hunter#
18031c2a00SAdrian Hunter#	python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
19031c2a00SAdrian Hunter#
20031c2a00SAdrian Hunter# The result is a GUI window with a tree representing a context-sensitive
21031c2a00SAdrian Hunter# call-graph.  Expanding a couple of levels of the tree and adjusting column
22031c2a00SAdrian Hunter# widths to suit will display something like:
23031c2a00SAdrian Hunter#
24031c2a00SAdrian Hunter#                                         Call Graph: pt_example
25031c2a00SAdrian Hunter# Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
26031c2a00SAdrian Hunter# v- ls
27031c2a00SAdrian Hunter#     v- 2638:2638
28031c2a00SAdrian Hunter#         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
29031c2a00SAdrian Hunter#           |- unknown               unknown       1        13198     0.1              1              0.0
30031c2a00SAdrian Hunter#           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
31031c2a00SAdrian Hunter#           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
32031c2a00SAdrian Hunter#           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
33031c2a00SAdrian Hunter#              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
34031c2a00SAdrian Hunter#              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
35031c2a00SAdrian Hunter#              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
36031c2a00SAdrian Hunter#              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
37031c2a00SAdrian Hunter#              v- main               ls            1      8182043    99.6         180254             99.9
38031c2a00SAdrian Hunter#
39031c2a00SAdrian Hunter# Points to note:
40031c2a00SAdrian Hunter#	The top level is a command name (comm)
41031c2a00SAdrian Hunter#	The next level is a thread (pid:tid)
42031c2a00SAdrian Hunter#	Subsequent levels are functions
43031c2a00SAdrian Hunter#	'Count' is the number of calls
44031c2a00SAdrian Hunter#	'Time' is the elapsed time until the function returns
45031c2a00SAdrian Hunter#	Percentages are relative to the level above
46031c2a00SAdrian Hunter#	'Branch Count' is the total number of branches for that function and all
47031c2a00SAdrian Hunter#       functions that it calls
48031c2a00SAdrian Hunter
4976099f98SAdrian Hunter# There is also a "All branches" report, which displays branches and
5076099f98SAdrian Hunter# possibly disassembly.  However, presently, the only supported disassembler is
5176099f98SAdrian Hunter# Intel XED, and additionally the object code must be present in perf build ID
5276099f98SAdrian Hunter# cache. To use Intel XED, libxed.so must be present. To build and install
5376099f98SAdrian Hunter# libxed.so:
5476099f98SAdrian Hunter#            git clone https://github.com/intelxed/mbuild.git mbuild
5576099f98SAdrian Hunter#            git clone https://github.com/intelxed/xed
5676099f98SAdrian Hunter#            cd xed
5776099f98SAdrian Hunter#            ./mfile.py --share
5876099f98SAdrian Hunter#            sudo ./mfile.py --prefix=/usr/local install
5976099f98SAdrian Hunter#            sudo ldconfig
6076099f98SAdrian Hunter#
6176099f98SAdrian Hunter# Example report:
6276099f98SAdrian Hunter#
6376099f98SAdrian Hunter# Time           CPU  Command  PID    TID    Branch Type            In Tx  Branch
6476099f98SAdrian Hunter# 8107675239590  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
6576099f98SAdrian Hunter#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
6676099f98SAdrian Hunter# 8107675239899  2    ls       22011  22011  hardware interrupt     No         7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
6776099f98SAdrian Hunter# 8107675241900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
6876099f98SAdrian Hunter#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
6976099f98SAdrian Hunter#                                                                              7fab593ea263 e8 c8 06 00 00                                  callq  0x7fab593ea930
7076099f98SAdrian Hunter# 8107675241900  2    ls       22011  22011  call                   No         7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
7176099f98SAdrian Hunter#                                                                              7fab593ea930 55                                              pushq  %rbp
7276099f98SAdrian Hunter#                                                                              7fab593ea931 48 89 e5                                        mov %rsp, %rbp
7376099f98SAdrian Hunter#                                                                              7fab593ea934 41 57                                           pushq  %r15
7476099f98SAdrian Hunter#                                                                              7fab593ea936 41 56                                           pushq  %r14
7576099f98SAdrian Hunter#                                                                              7fab593ea938 41 55                                           pushq  %r13
7676099f98SAdrian Hunter#                                                                              7fab593ea93a 41 54                                           pushq  %r12
7776099f98SAdrian Hunter#                                                                              7fab593ea93c 53                                              pushq  %rbx
7876099f98SAdrian Hunter#                                                                              7fab593ea93d 48 89 fb                                        mov %rdi, %rbx
7976099f98SAdrian Hunter#                                                                              7fab593ea940 48 83 ec 68                                     sub $0x68, %rsp
8076099f98SAdrian Hunter#                                                                              7fab593ea944 0f 31                                           rdtsc
8176099f98SAdrian Hunter#                                                                              7fab593ea946 48 c1 e2 20                                     shl $0x20, %rdx
8276099f98SAdrian Hunter#                                                                              7fab593ea94a 89 c0                                           mov %eax, %eax
8376099f98SAdrian Hunter#                                                                              7fab593ea94c 48 09 c2                                        or %rax, %rdx
8476099f98SAdrian Hunter#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
8576099f98SAdrian Hunter# 8107675242232  2    ls       22011  22011  hardware interrupt     No         7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
8676099f98SAdrian Hunter# 8107675242900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
8776099f98SAdrian Hunter#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
8876099f98SAdrian Hunter#                                                                              7fab593ea956 48 89 15 3b 13 22 00                            movq  %rdx, 0x22133b(%rip)
8976099f98SAdrian Hunter# 8107675243232  2    ls       22011  22011  hardware interrupt     No         7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
9076099f98SAdrian Hunter
91beda0e72STony Jonesfrom __future__ import print_function
92beda0e72STony Jones
93031c2a00SAdrian Hunterimport sys
941ed7f47fSAdrian Hunterimport argparse
951beb5c7bSAdrian Hunterimport weakref
961beb5c7bSAdrian Hunterimport threading
97ebd70c7dSAdrian Hunterimport string
98beda0e72STony Jonestry:
99beda0e72STony Jones	# Python2
100beda0e72STony Jones	import cPickle as pickle
101beda0e72STony Jones	# size of pickled integer big enough for record size
102beda0e72STony Jones	glb_nsz = 8
103beda0e72STony Jonesexcept ImportError:
104beda0e72STony Jones	import pickle
105beda0e72STony Jones	glb_nsz = 16
1068392b74bSAdrian Hunterimport re
1078392b74bSAdrian Hunterimport os
108df8ea22aSAdrian Hunter
109df8ea22aSAdrian Hunterpyside_version_1 = True
110df8ea22aSAdrian Hunterif not "--pyside-version-1" in sys.argv:
111df8ea22aSAdrian Hunter	try:
112df8ea22aSAdrian Hunter		from PySide2.QtCore import *
113df8ea22aSAdrian Hunter		from PySide2.QtGui import *
114df8ea22aSAdrian Hunter		from PySide2.QtSql import *
115df8ea22aSAdrian Hunter		from PySide2.QtWidgets import *
116df8ea22aSAdrian Hunter		pyside_version_1 = False
117df8ea22aSAdrian Hunter	except:
118df8ea22aSAdrian Hunter		pass
119df8ea22aSAdrian Hunter
120df8ea22aSAdrian Hunterif pyside_version_1:
121031c2a00SAdrian Hunter	from PySide.QtCore import *
122031c2a00SAdrian Hunter	from PySide.QtGui import *
123031c2a00SAdrian Hunter	from PySide.QtSql import *
124df8ea22aSAdrian Hunter
125031c2a00SAdrian Hunterfrom decimal import *
1268392b74bSAdrian Hunterfrom ctypes import *
1278392b74bSAdrian Hunterfrom multiprocessing import Process, Array, Value, Event
128031c2a00SAdrian Hunter
129beda0e72STony Jones# xrange is range in Python3
130beda0e72STony Jonestry:
131beda0e72STony Jones	xrange
132beda0e72STony Jonesexcept NameError:
133beda0e72STony Jones	xrange = range
134beda0e72STony Jones
135beda0e72STony Jonesdef printerr(*args, **keyword_args):
136beda0e72STony Jones	print(*args, file=sys.stderr, **keyword_args)
137beda0e72STony Jones
138031c2a00SAdrian Hunter# Data formatting helpers
139031c2a00SAdrian Hunter
14076099f98SAdrian Hunterdef tohex(ip):
14176099f98SAdrian Hunter	if ip < 0:
14276099f98SAdrian Hunter		ip += 1 << 64
14376099f98SAdrian Hunter	return "%x" % ip
14476099f98SAdrian Hunter
14576099f98SAdrian Hunterdef offstr(offset):
14676099f98SAdrian Hunter	if offset:
14776099f98SAdrian Hunter		return "+0x%x" % offset
14876099f98SAdrian Hunter	return ""
14976099f98SAdrian Hunter
150031c2a00SAdrian Hunterdef dsoname(name):
151031c2a00SAdrian Hunter	if name == "[kernel.kallsyms]":
152031c2a00SAdrian Hunter		return "[kernel]"
153031c2a00SAdrian Hunter	return name
154031c2a00SAdrian Hunter
155210cf1f9SAdrian Hunterdef findnth(s, sub, n, offs=0):
156210cf1f9SAdrian Hunter	pos = s.find(sub)
157210cf1f9SAdrian Hunter	if pos < 0:
158210cf1f9SAdrian Hunter		return pos
159210cf1f9SAdrian Hunter	if n <= 1:
160210cf1f9SAdrian Hunter		return offs + pos
161210cf1f9SAdrian Hunter	return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
162210cf1f9SAdrian Hunter
163031c2a00SAdrian Hunter# Percent to one decimal place
164031c2a00SAdrian Hunter
165031c2a00SAdrian Hunterdef PercentToOneDP(n, d):
166031c2a00SAdrian Hunter	if not d:
167031c2a00SAdrian Hunter		return "0.0"
168031c2a00SAdrian Hunter	x = (n * Decimal(100)) / d
169031c2a00SAdrian Hunter	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
170031c2a00SAdrian Hunter
171031c2a00SAdrian Hunter# Helper for queries that must not fail
172031c2a00SAdrian Hunter
173031c2a00SAdrian Hunterdef QueryExec(query, stmt):
174031c2a00SAdrian Hunter	ret = query.exec_(stmt)
175031c2a00SAdrian Hunter	if not ret:
176031c2a00SAdrian Hunter		raise Exception("Query failed: " + query.lastError().text())
177031c2a00SAdrian Hunter
178ebd70c7dSAdrian Hunter# Background thread
179ebd70c7dSAdrian Hunter
180ebd70c7dSAdrian Hunterclass Thread(QThread):
181ebd70c7dSAdrian Hunter
182ebd70c7dSAdrian Hunter	done = Signal(object)
183ebd70c7dSAdrian Hunter
184ebd70c7dSAdrian Hunter	def __init__(self, task, param=None, parent=None):
185ebd70c7dSAdrian Hunter		super(Thread, self).__init__(parent)
186ebd70c7dSAdrian Hunter		self.task = task
187ebd70c7dSAdrian Hunter		self.param = param
188ebd70c7dSAdrian Hunter
189ebd70c7dSAdrian Hunter	def run(self):
190ebd70c7dSAdrian Hunter		while True:
191ebd70c7dSAdrian Hunter			if self.param is None:
192ebd70c7dSAdrian Hunter				done, result = self.task()
193ebd70c7dSAdrian Hunter			else:
194ebd70c7dSAdrian Hunter				done, result = self.task(self.param)
195ebd70c7dSAdrian Hunter			self.done.emit(result)
196ebd70c7dSAdrian Hunter			if done:
197ebd70c7dSAdrian Hunter				break
198ebd70c7dSAdrian Hunter
199031c2a00SAdrian Hunter# Tree data model
200031c2a00SAdrian Hunter
201031c2a00SAdrian Hunterclass TreeModel(QAbstractItemModel):
202031c2a00SAdrian Hunter
203a448ba23SAdrian Hunter	def __init__(self, glb, parent=None):
204031c2a00SAdrian Hunter		super(TreeModel, self).__init__(parent)
205a448ba23SAdrian Hunter		self.glb = glb
206a448ba23SAdrian Hunter		self.root = self.GetRoot()
207031c2a00SAdrian Hunter		self.last_row_read = 0
208031c2a00SAdrian Hunter
209031c2a00SAdrian Hunter	def Item(self, parent):
210031c2a00SAdrian Hunter		if parent.isValid():
211031c2a00SAdrian Hunter			return parent.internalPointer()
212031c2a00SAdrian Hunter		else:
213031c2a00SAdrian Hunter			return self.root
214031c2a00SAdrian Hunter
215031c2a00SAdrian Hunter	def rowCount(self, parent):
216031c2a00SAdrian Hunter		result = self.Item(parent).childCount()
217031c2a00SAdrian Hunter		if result < 0:
218031c2a00SAdrian Hunter			result = 0
219031c2a00SAdrian Hunter			self.dataChanged.emit(parent, parent)
220031c2a00SAdrian Hunter		return result
221031c2a00SAdrian Hunter
222031c2a00SAdrian Hunter	def hasChildren(self, parent):
223031c2a00SAdrian Hunter		return self.Item(parent).hasChildren()
224031c2a00SAdrian Hunter
225031c2a00SAdrian Hunter	def headerData(self, section, orientation, role):
226031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
227031c2a00SAdrian Hunter			return self.columnAlignment(section)
228031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
229031c2a00SAdrian Hunter			return None
230031c2a00SAdrian Hunter		if orientation != Qt.Horizontal:
231031c2a00SAdrian Hunter			return None
232031c2a00SAdrian Hunter		return self.columnHeader(section)
233031c2a00SAdrian Hunter
234031c2a00SAdrian Hunter	def parent(self, child):
235031c2a00SAdrian Hunter		child_item = child.internalPointer()
236031c2a00SAdrian Hunter		if child_item is self.root:
237031c2a00SAdrian Hunter			return QModelIndex()
238031c2a00SAdrian Hunter		parent_item = child_item.getParentItem()
239031c2a00SAdrian Hunter		return self.createIndex(parent_item.getRow(), 0, parent_item)
240031c2a00SAdrian Hunter
241031c2a00SAdrian Hunter	def index(self, row, column, parent):
242031c2a00SAdrian Hunter		child_item = self.Item(parent).getChildItem(row)
243031c2a00SAdrian Hunter		return self.createIndex(row, column, child_item)
244031c2a00SAdrian Hunter
245031c2a00SAdrian Hunter	def DisplayData(self, item, index):
246031c2a00SAdrian Hunter		return item.getData(index.column())
247031c2a00SAdrian Hunter
2488392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2498392b74bSAdrian Hunter		if row > self.last_row_read:
2508392b74bSAdrian Hunter			self.last_row_read = row
2518392b74bSAdrian Hunter			if row + 10 >= self.root.child_count:
2528392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2538392b74bSAdrian Hunter
2548392b74bSAdrian Hunter	def columnAlignment(self, column):
2558392b74bSAdrian Hunter		return Qt.AlignLeft
2568392b74bSAdrian Hunter
2578392b74bSAdrian Hunter	def columnFont(self, column):
2588392b74bSAdrian Hunter		return None
2598392b74bSAdrian Hunter
2608392b74bSAdrian Hunter	def data(self, index, role):
2618392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2628392b74bSAdrian Hunter			return self.columnAlignment(index.column())
2638392b74bSAdrian Hunter		if role == Qt.FontRole:
2648392b74bSAdrian Hunter			return self.columnFont(index.column())
2658392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2668392b74bSAdrian Hunter			return None
2678392b74bSAdrian Hunter		item = index.internalPointer()
2688392b74bSAdrian Hunter		return self.DisplayData(item, index)
2698392b74bSAdrian Hunter
2708392b74bSAdrian Hunter# Table data model
2718392b74bSAdrian Hunter
2728392b74bSAdrian Hunterclass TableModel(QAbstractTableModel):
2738392b74bSAdrian Hunter
2748392b74bSAdrian Hunter	def __init__(self, parent=None):
2758392b74bSAdrian Hunter		super(TableModel, self).__init__(parent)
2768392b74bSAdrian Hunter		self.child_count = 0
2778392b74bSAdrian Hunter		self.child_items = []
2788392b74bSAdrian Hunter		self.last_row_read = 0
2798392b74bSAdrian Hunter
2808392b74bSAdrian Hunter	def Item(self, parent):
2818392b74bSAdrian Hunter		if parent.isValid():
2828392b74bSAdrian Hunter			return parent.internalPointer()
2838392b74bSAdrian Hunter		else:
2848392b74bSAdrian Hunter			return self
2858392b74bSAdrian Hunter
2868392b74bSAdrian Hunter	def rowCount(self, parent):
2878392b74bSAdrian Hunter		return self.child_count
2888392b74bSAdrian Hunter
2898392b74bSAdrian Hunter	def headerData(self, section, orientation, role):
2908392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2918392b74bSAdrian Hunter			return self.columnAlignment(section)
2928392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2938392b74bSAdrian Hunter			return None
2948392b74bSAdrian Hunter		if orientation != Qt.Horizontal:
2958392b74bSAdrian Hunter			return None
2968392b74bSAdrian Hunter		return self.columnHeader(section)
2978392b74bSAdrian Hunter
2988392b74bSAdrian Hunter	def index(self, row, column, parent):
2998392b74bSAdrian Hunter		return self.createIndex(row, column, self.child_items[row])
3008392b74bSAdrian Hunter
3018392b74bSAdrian Hunter	def DisplayData(self, item, index):
3028392b74bSAdrian Hunter		return item.getData(index.column())
3038392b74bSAdrian Hunter
3048392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
3058392b74bSAdrian Hunter		if row > self.last_row_read:
3068392b74bSAdrian Hunter			self.last_row_read = row
3078392b74bSAdrian Hunter			if row + 10 >= self.child_count:
3088392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
3098392b74bSAdrian Hunter
310031c2a00SAdrian Hunter	def columnAlignment(self, column):
311031c2a00SAdrian Hunter		return Qt.AlignLeft
312031c2a00SAdrian Hunter
313031c2a00SAdrian Hunter	def columnFont(self, column):
314031c2a00SAdrian Hunter		return None
315031c2a00SAdrian Hunter
316031c2a00SAdrian Hunter	def data(self, index, role):
317031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
318031c2a00SAdrian Hunter			return self.columnAlignment(index.column())
319031c2a00SAdrian Hunter		if role == Qt.FontRole:
320031c2a00SAdrian Hunter			return self.columnFont(index.column())
321031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
322031c2a00SAdrian Hunter			return None
323031c2a00SAdrian Hunter		item = index.internalPointer()
324031c2a00SAdrian Hunter		return self.DisplayData(item, index)
325031c2a00SAdrian Hunter
3261beb5c7bSAdrian Hunter# Model cache
3271beb5c7bSAdrian Hunter
3281beb5c7bSAdrian Huntermodel_cache = weakref.WeakValueDictionary()
3291beb5c7bSAdrian Huntermodel_cache_lock = threading.Lock()
3301beb5c7bSAdrian Hunter
3311beb5c7bSAdrian Hunterdef LookupCreateModel(model_name, create_fn):
3321beb5c7bSAdrian Hunter	model_cache_lock.acquire()
3331beb5c7bSAdrian Hunter	try:
3341beb5c7bSAdrian Hunter		model = model_cache[model_name]
3351beb5c7bSAdrian Hunter	except:
3361beb5c7bSAdrian Hunter		model = None
3371beb5c7bSAdrian Hunter	if model is None:
3381beb5c7bSAdrian Hunter		model = create_fn()
3391beb5c7bSAdrian Hunter		model_cache[model_name] = model
3401beb5c7bSAdrian Hunter	model_cache_lock.release()
3411beb5c7bSAdrian Hunter	return model
3421beb5c7bSAdrian Hunter
343ebd70c7dSAdrian Hunter# Find bar
344ebd70c7dSAdrian Hunter
345ebd70c7dSAdrian Hunterclass FindBar():
346ebd70c7dSAdrian Hunter
347ebd70c7dSAdrian Hunter	def __init__(self, parent, finder, is_reg_expr=False):
348ebd70c7dSAdrian Hunter		self.finder = finder
349ebd70c7dSAdrian Hunter		self.context = []
350ebd70c7dSAdrian Hunter		self.last_value = None
351ebd70c7dSAdrian Hunter		self.last_pattern = None
352ebd70c7dSAdrian Hunter
353ebd70c7dSAdrian Hunter		label = QLabel("Find:")
354ebd70c7dSAdrian Hunter		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
355ebd70c7dSAdrian Hunter
356ebd70c7dSAdrian Hunter		self.textbox = QComboBox()
357ebd70c7dSAdrian Hunter		self.textbox.setEditable(True)
358ebd70c7dSAdrian Hunter		self.textbox.currentIndexChanged.connect(self.ValueChanged)
359ebd70c7dSAdrian Hunter
360ebd70c7dSAdrian Hunter		self.progress = QProgressBar()
361ebd70c7dSAdrian Hunter		self.progress.setRange(0, 0)
362ebd70c7dSAdrian Hunter		self.progress.hide()
363ebd70c7dSAdrian Hunter
364ebd70c7dSAdrian Hunter		if is_reg_expr:
365ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Regular Expression")
366ebd70c7dSAdrian Hunter		else:
367ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Pattern")
368ebd70c7dSAdrian Hunter		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
369ebd70c7dSAdrian Hunter
370ebd70c7dSAdrian Hunter		self.next_button = QToolButton()
371ebd70c7dSAdrian Hunter		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
372ebd70c7dSAdrian Hunter		self.next_button.released.connect(lambda: self.NextPrev(1))
373ebd70c7dSAdrian Hunter
374ebd70c7dSAdrian Hunter		self.prev_button = QToolButton()
375ebd70c7dSAdrian Hunter		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
376ebd70c7dSAdrian Hunter		self.prev_button.released.connect(lambda: self.NextPrev(-1))
377ebd70c7dSAdrian Hunter
378ebd70c7dSAdrian Hunter		self.close_button = QToolButton()
379ebd70c7dSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
380ebd70c7dSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
381ebd70c7dSAdrian Hunter
382ebd70c7dSAdrian Hunter		self.hbox = QHBoxLayout()
383ebd70c7dSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
384ebd70c7dSAdrian Hunter
385ebd70c7dSAdrian Hunter		self.hbox.addWidget(label)
386ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.textbox)
387ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.progress)
388ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.pattern)
389ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.next_button)
390ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.prev_button)
391ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.close_button)
392ebd70c7dSAdrian Hunter
393ebd70c7dSAdrian Hunter		self.bar = QWidget()
394ebd70c7dSAdrian Hunter		self.bar.setLayout(self.hbox);
395ebd70c7dSAdrian Hunter		self.bar.hide()
396ebd70c7dSAdrian Hunter
397ebd70c7dSAdrian Hunter	def Widget(self):
398ebd70c7dSAdrian Hunter		return self.bar
399ebd70c7dSAdrian Hunter
400ebd70c7dSAdrian Hunter	def Activate(self):
401ebd70c7dSAdrian Hunter		self.bar.show()
402ebd70c7dSAdrian Hunter		self.textbox.setFocus()
403ebd70c7dSAdrian Hunter
404ebd70c7dSAdrian Hunter	def Deactivate(self):
405ebd70c7dSAdrian Hunter		self.bar.hide()
406ebd70c7dSAdrian Hunter
407ebd70c7dSAdrian Hunter	def Busy(self):
408ebd70c7dSAdrian Hunter		self.textbox.setEnabled(False)
409ebd70c7dSAdrian Hunter		self.pattern.hide()
410ebd70c7dSAdrian Hunter		self.next_button.hide()
411ebd70c7dSAdrian Hunter		self.prev_button.hide()
412ebd70c7dSAdrian Hunter		self.progress.show()
413ebd70c7dSAdrian Hunter
414ebd70c7dSAdrian Hunter	def Idle(self):
415ebd70c7dSAdrian Hunter		self.textbox.setEnabled(True)
416ebd70c7dSAdrian Hunter		self.progress.hide()
417ebd70c7dSAdrian Hunter		self.pattern.show()
418ebd70c7dSAdrian Hunter		self.next_button.show()
419ebd70c7dSAdrian Hunter		self.prev_button.show()
420ebd70c7dSAdrian Hunter
421ebd70c7dSAdrian Hunter	def Find(self, direction):
422ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
423ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
424ebd70c7dSAdrian Hunter		self.last_value = value
425ebd70c7dSAdrian Hunter		self.last_pattern = pattern
426ebd70c7dSAdrian Hunter		self.finder.Find(value, direction, pattern, self.context)
427ebd70c7dSAdrian Hunter
428ebd70c7dSAdrian Hunter	def ValueChanged(self):
429ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
430ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
431ebd70c7dSAdrian Hunter		index = self.textbox.currentIndex()
432ebd70c7dSAdrian Hunter		data = self.textbox.itemData(index)
433ebd70c7dSAdrian Hunter		# Store the pattern in the combo box to keep it with the text value
434ebd70c7dSAdrian Hunter		if data == None:
435ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
436ebd70c7dSAdrian Hunter		else:
437ebd70c7dSAdrian Hunter			self.pattern.setChecked(data)
438ebd70c7dSAdrian Hunter		self.Find(0)
439ebd70c7dSAdrian Hunter
440ebd70c7dSAdrian Hunter	def NextPrev(self, direction):
441ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
442ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
443ebd70c7dSAdrian Hunter		if value != self.last_value:
444ebd70c7dSAdrian Hunter			index = self.textbox.findText(value)
445ebd70c7dSAdrian Hunter			# Allow for a button press before the value has been added to the combo box
446ebd70c7dSAdrian Hunter			if index < 0:
447ebd70c7dSAdrian Hunter				index = self.textbox.count()
448ebd70c7dSAdrian Hunter				self.textbox.addItem(value, pattern)
449ebd70c7dSAdrian Hunter				self.textbox.setCurrentIndex(index)
450ebd70c7dSAdrian Hunter				return
451ebd70c7dSAdrian Hunter			else:
452ebd70c7dSAdrian Hunter				self.textbox.setItemData(index, pattern)
453ebd70c7dSAdrian Hunter		elif pattern != self.last_pattern:
454ebd70c7dSAdrian Hunter			# Keep the pattern recorded in the combo box up to date
455ebd70c7dSAdrian Hunter			index = self.textbox.currentIndex()
456ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
457ebd70c7dSAdrian Hunter		self.Find(direction)
458ebd70c7dSAdrian Hunter
459ebd70c7dSAdrian Hunter	def NotFound(self):
460ebd70c7dSAdrian Hunter		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
461ebd70c7dSAdrian Hunter
462031c2a00SAdrian Hunter# Context-sensitive call graph data model item base
463031c2a00SAdrian Hunter
464031c2a00SAdrian Hunterclass CallGraphLevelItemBase(object):
465031c2a00SAdrian Hunter
466031c2a00SAdrian Hunter	def __init__(self, glb, row, parent_item):
467031c2a00SAdrian Hunter		self.glb = glb
468031c2a00SAdrian Hunter		self.row = row
469031c2a00SAdrian Hunter		self.parent_item = parent_item
470031c2a00SAdrian Hunter		self.query_done = False;
471031c2a00SAdrian Hunter		self.child_count = 0
472031c2a00SAdrian Hunter		self.child_items = []
4733ac641f4SAdrian Hunter		if parent_item:
4743ac641f4SAdrian Hunter			self.level = parent_item.level + 1
4753ac641f4SAdrian Hunter		else:
4763ac641f4SAdrian Hunter			self.level = 0
477031c2a00SAdrian Hunter
478031c2a00SAdrian Hunter	def getChildItem(self, row):
479031c2a00SAdrian Hunter		return self.child_items[row]
480031c2a00SAdrian Hunter
481031c2a00SAdrian Hunter	def getParentItem(self):
482031c2a00SAdrian Hunter		return self.parent_item
483031c2a00SAdrian Hunter
484031c2a00SAdrian Hunter	def getRow(self):
485031c2a00SAdrian Hunter		return self.row
486031c2a00SAdrian Hunter
487031c2a00SAdrian Hunter	def childCount(self):
488031c2a00SAdrian Hunter		if not self.query_done:
489031c2a00SAdrian Hunter			self.Select()
490031c2a00SAdrian Hunter			if not self.child_count:
491031c2a00SAdrian Hunter				return -1
492031c2a00SAdrian Hunter		return self.child_count
493031c2a00SAdrian Hunter
494031c2a00SAdrian Hunter	def hasChildren(self):
495031c2a00SAdrian Hunter		if not self.query_done:
496031c2a00SAdrian Hunter			return True
497031c2a00SAdrian Hunter		return self.child_count > 0
498031c2a00SAdrian Hunter
499031c2a00SAdrian Hunter	def getData(self, column):
500031c2a00SAdrian Hunter		return self.data[column]
501031c2a00SAdrian Hunter
502031c2a00SAdrian Hunter# Context-sensitive call graph data model level 2+ item base
503031c2a00SAdrian Hunter
504031c2a00SAdrian Hunterclass CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
505031c2a00SAdrian Hunter
506031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
507031c2a00SAdrian Hunter		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
508031c2a00SAdrian Hunter		self.comm_id = comm_id
509031c2a00SAdrian Hunter		self.thread_id = thread_id
510031c2a00SAdrian Hunter		self.call_path_id = call_path_id
511031c2a00SAdrian Hunter		self.branch_count = branch_count
512031c2a00SAdrian Hunter		self.time = time
513031c2a00SAdrian Hunter
514031c2a00SAdrian Hunter	def Select(self):
515031c2a00SAdrian Hunter		self.query_done = True;
516031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
517031c2a00SAdrian Hunter		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
518031c2a00SAdrian Hunter					" FROM calls"
519031c2a00SAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
520031c2a00SAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
521031c2a00SAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
522031c2a00SAdrian Hunter					" WHERE parent_call_path_id = " + str(self.call_path_id) +
523031c2a00SAdrian Hunter					" AND comm_id = " + str(self.comm_id) +
524031c2a00SAdrian Hunter					" AND thread_id = " + str(self.thread_id) +
525031c2a00SAdrian Hunter					" GROUP BY call_path_id, name, short_name"
526031c2a00SAdrian Hunter					" ORDER BY call_path_id")
527031c2a00SAdrian Hunter		while query.next():
528031c2a00SAdrian 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)
529031c2a00SAdrian Hunter			self.child_items.append(child_item)
530031c2a00SAdrian Hunter			self.child_count += 1
531031c2a00SAdrian Hunter
532031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item
533031c2a00SAdrian Hunter
534031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
535031c2a00SAdrian Hunter
536031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
537031c2a00SAdrian Hunter		super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
538031c2a00SAdrian Hunter		dso = dsoname(dso)
539031c2a00SAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
540031c2a00SAdrian Hunter		self.dbid = call_path_id
541031c2a00SAdrian Hunter
542031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item
543031c2a00SAdrian Hunter
544031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
545031c2a00SAdrian Hunter
546031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
547031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
548031c2a00SAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
549031c2a00SAdrian Hunter		self.dbid = thread_id
550031c2a00SAdrian Hunter
551031c2a00SAdrian Hunter	def Select(self):
552031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).Select()
553031c2a00SAdrian Hunter		for child_item in self.child_items:
554031c2a00SAdrian Hunter			self.time += child_item.time
555031c2a00SAdrian Hunter			self.branch_count += child_item.branch_count
556031c2a00SAdrian Hunter		for child_item in self.child_items:
557031c2a00SAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
558031c2a00SAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
559031c2a00SAdrian Hunter
560031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item
561031c2a00SAdrian Hunter
562031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase):
563031c2a00SAdrian Hunter
564031c2a00SAdrian Hunter	def __init__(self, glb, row, comm_id, comm, parent_item):
565031c2a00SAdrian Hunter		super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
566031c2a00SAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
567031c2a00SAdrian Hunter		self.dbid = comm_id
568031c2a00SAdrian Hunter
569031c2a00SAdrian Hunter	def Select(self):
570031c2a00SAdrian Hunter		self.query_done = True;
571031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
572031c2a00SAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
573031c2a00SAdrian Hunter					" FROM comm_threads"
574031c2a00SAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
575031c2a00SAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
576031c2a00SAdrian Hunter		while query.next():
577031c2a00SAdrian Hunter			child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
578031c2a00SAdrian Hunter			self.child_items.append(child_item)
579031c2a00SAdrian Hunter			self.child_count += 1
580031c2a00SAdrian Hunter
581031c2a00SAdrian Hunter# Context-sensitive call graph data model root item
582031c2a00SAdrian Hunter
583031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase):
584031c2a00SAdrian Hunter
585031c2a00SAdrian Hunter	def __init__(self, glb):
586031c2a00SAdrian Hunter		super(CallGraphRootItem, self).__init__(glb, 0, None)
587031c2a00SAdrian Hunter		self.dbid = 0
588031c2a00SAdrian Hunter		self.query_done = True;
589031c2a00SAdrian Hunter		query = QSqlQuery(glb.db)
590031c2a00SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
591031c2a00SAdrian Hunter		while query.next():
592031c2a00SAdrian Hunter			if not query.value(0):
593031c2a00SAdrian Hunter				continue
594031c2a00SAdrian Hunter			child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
595031c2a00SAdrian Hunter			self.child_items.append(child_item)
596031c2a00SAdrian Hunter			self.child_count += 1
597031c2a00SAdrian Hunter
598254c0d82SAdrian Hunter# Context-sensitive call graph data model base
599031c2a00SAdrian Hunter
600254c0d82SAdrian Hunterclass CallGraphModelBase(TreeModel):
601031c2a00SAdrian Hunter
602031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
603254c0d82SAdrian Hunter		super(CallGraphModelBase, self).__init__(glb, parent)
604031c2a00SAdrian Hunter
605ebd70c7dSAdrian Hunter	def FindSelect(self, value, pattern, query):
606ebd70c7dSAdrian Hunter		if pattern:
607ebd70c7dSAdrian Hunter			# postgresql and sqlite pattern patching differences:
608ebd70c7dSAdrian Hunter			#   postgresql LIKE is case sensitive but sqlite LIKE is not
609ebd70c7dSAdrian Hunter			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
610ebd70c7dSAdrian Hunter			#   postgresql supports ILIKE which is case insensitive
611ebd70c7dSAdrian Hunter			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
612ebd70c7dSAdrian Hunter			if not self.glb.dbref.is_sqlite3:
613ebd70c7dSAdrian Hunter				# Escape % and _
614ebd70c7dSAdrian Hunter				s = value.replace("%", "\%")
615ebd70c7dSAdrian Hunter				s = s.replace("_", "\_")
616ebd70c7dSAdrian Hunter				# Translate * and ? into SQL LIKE pattern characters % and _
617ebd70c7dSAdrian Hunter				trans = string.maketrans("*?", "%_")
618ebd70c7dSAdrian Hunter				match = " LIKE '" + str(s).translate(trans) + "'"
619ebd70c7dSAdrian Hunter			else:
620ebd70c7dSAdrian Hunter				match = " GLOB '" + str(value) + "'"
621ebd70c7dSAdrian Hunter		else:
622ebd70c7dSAdrian Hunter			match = " = '" + str(value) + "'"
623254c0d82SAdrian Hunter		self.DoFindSelect(query, match)
624ebd70c7dSAdrian Hunter
625ebd70c7dSAdrian Hunter	def Found(self, query, found):
626ebd70c7dSAdrian Hunter		if found:
627ebd70c7dSAdrian Hunter			return self.FindPath(query)
628ebd70c7dSAdrian Hunter		return []
629ebd70c7dSAdrian Hunter
630ebd70c7dSAdrian Hunter	def FindValue(self, value, pattern, query, last_value, last_pattern):
631ebd70c7dSAdrian Hunter		if last_value == value and pattern == last_pattern:
632ebd70c7dSAdrian Hunter			found = query.first()
633ebd70c7dSAdrian Hunter		else:
634ebd70c7dSAdrian Hunter			self.FindSelect(value, pattern, query)
635ebd70c7dSAdrian Hunter			found = query.next()
636ebd70c7dSAdrian Hunter		return self.Found(query, found)
637ebd70c7dSAdrian Hunter
638ebd70c7dSAdrian Hunter	def FindNext(self, query):
639ebd70c7dSAdrian Hunter		found = query.next()
640ebd70c7dSAdrian Hunter		if not found:
641ebd70c7dSAdrian Hunter			found = query.first()
642ebd70c7dSAdrian Hunter		return self.Found(query, found)
643ebd70c7dSAdrian Hunter
644ebd70c7dSAdrian Hunter	def FindPrev(self, query):
645ebd70c7dSAdrian Hunter		found = query.previous()
646ebd70c7dSAdrian Hunter		if not found:
647ebd70c7dSAdrian Hunter			found = query.last()
648ebd70c7dSAdrian Hunter		return self.Found(query, found)
649ebd70c7dSAdrian Hunter
650ebd70c7dSAdrian Hunter	def FindThread(self, c):
651ebd70c7dSAdrian Hunter		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
652ebd70c7dSAdrian Hunter			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
653ebd70c7dSAdrian Hunter		elif c.direction > 0:
654ebd70c7dSAdrian Hunter			ids = self.FindNext(c.query)
655ebd70c7dSAdrian Hunter		else:
656ebd70c7dSAdrian Hunter			ids = self.FindPrev(c.query)
657ebd70c7dSAdrian Hunter		return (True, ids)
658ebd70c7dSAdrian Hunter
659ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
660ebd70c7dSAdrian Hunter		class Context():
661ebd70c7dSAdrian Hunter			def __init__(self, *x):
662ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
663ebd70c7dSAdrian Hunter			def Update(self, *x):
664ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
665ebd70c7dSAdrian Hunter		if len(context):
666ebd70c7dSAdrian Hunter			context[0].Update(value, direction, pattern)
667ebd70c7dSAdrian Hunter		else:
668ebd70c7dSAdrian Hunter			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
669ebd70c7dSAdrian Hunter		# Use a thread so the UI is not blocked during the SELECT
670ebd70c7dSAdrian Hunter		thread = Thread(self.FindThread, context[0])
671ebd70c7dSAdrian Hunter		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
672ebd70c7dSAdrian Hunter		thread.start()
673ebd70c7dSAdrian Hunter
674ebd70c7dSAdrian Hunter	def FindDone(self, thread, callback, ids):
675ebd70c7dSAdrian Hunter		callback(ids)
676ebd70c7dSAdrian Hunter
677254c0d82SAdrian Hunter# Context-sensitive call graph data model
678254c0d82SAdrian Hunter
679254c0d82SAdrian Hunterclass CallGraphModel(CallGraphModelBase):
680254c0d82SAdrian Hunter
681254c0d82SAdrian Hunter	def __init__(self, glb, parent=None):
682254c0d82SAdrian Hunter		super(CallGraphModel, self).__init__(glb, parent)
683254c0d82SAdrian Hunter
684254c0d82SAdrian Hunter	def GetRoot(self):
685254c0d82SAdrian Hunter		return CallGraphRootItem(self.glb)
686254c0d82SAdrian Hunter
687254c0d82SAdrian Hunter	def columnCount(self, parent=None):
688254c0d82SAdrian Hunter		return 7
689254c0d82SAdrian Hunter
690254c0d82SAdrian Hunter	def columnHeader(self, column):
691254c0d82SAdrian Hunter		headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
692254c0d82SAdrian Hunter		return headers[column]
693254c0d82SAdrian Hunter
694254c0d82SAdrian Hunter	def columnAlignment(self, column):
695254c0d82SAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
696254c0d82SAdrian Hunter		return alignment[column]
697254c0d82SAdrian Hunter
698254c0d82SAdrian Hunter	def DoFindSelect(self, query, match):
699254c0d82SAdrian Hunter		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
700254c0d82SAdrian Hunter						" FROM calls"
701254c0d82SAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
702254c0d82SAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
703254c0d82SAdrian Hunter						" WHERE symbols.name" + match +
704254c0d82SAdrian Hunter						" GROUP BY comm_id, thread_id, call_path_id"
705254c0d82SAdrian Hunter						" ORDER BY comm_id, thread_id, call_path_id")
706254c0d82SAdrian Hunter
707254c0d82SAdrian Hunter	def FindPath(self, query):
708254c0d82SAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
709254c0d82SAdrian Hunter		# to open the tree at the right place.
710254c0d82SAdrian Hunter		ids = []
711254c0d82SAdrian Hunter		parent_id = query.value(0)
712254c0d82SAdrian Hunter		while parent_id:
713254c0d82SAdrian Hunter			ids.insert(0, parent_id)
714254c0d82SAdrian Hunter			q2 = QSqlQuery(self.glb.db)
715254c0d82SAdrian Hunter			QueryExec(q2, "SELECT parent_id"
716254c0d82SAdrian Hunter					" FROM call_paths"
717254c0d82SAdrian Hunter					" WHERE id = " + str(parent_id))
718254c0d82SAdrian Hunter			if not q2.next():
719254c0d82SAdrian Hunter				break
720254c0d82SAdrian Hunter			parent_id = q2.value(0)
721254c0d82SAdrian Hunter		# The call path root is not used
722254c0d82SAdrian Hunter		if ids[0] == 1:
723254c0d82SAdrian Hunter			del ids[0]
724254c0d82SAdrian Hunter		ids.insert(0, query.value(2))
725254c0d82SAdrian Hunter		ids.insert(0, query.value(1))
726254c0d82SAdrian Hunter		return ids
727254c0d82SAdrian Hunter
728ae8b887cSAdrian Hunter# Call tree data model level 2+ item base
729ae8b887cSAdrian Hunter
730ae8b887cSAdrian Hunterclass CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
731ae8b887cSAdrian Hunter
732ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
733ae8b887cSAdrian Hunter		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
734ae8b887cSAdrian Hunter		self.comm_id = comm_id
735ae8b887cSAdrian Hunter		self.thread_id = thread_id
736ae8b887cSAdrian Hunter		self.calls_id = calls_id
737ae8b887cSAdrian Hunter		self.branch_count = branch_count
738ae8b887cSAdrian Hunter		self.time = time
739ae8b887cSAdrian Hunter
740ae8b887cSAdrian Hunter	def Select(self):
741ae8b887cSAdrian Hunter		self.query_done = True;
742ae8b887cSAdrian Hunter		if self.calls_id == 0:
743ae8b887cSAdrian Hunter			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
744ae8b887cSAdrian Hunter		else:
745ae8b887cSAdrian Hunter			comm_thread = ""
746ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
747ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
748ae8b887cSAdrian Hunter					" FROM calls"
749ae8b887cSAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
750ae8b887cSAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
751ae8b887cSAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
752ae8b887cSAdrian Hunter					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
753ae8b887cSAdrian Hunter					" ORDER BY call_time, calls.id")
754ae8b887cSAdrian Hunter		while query.next():
755ae8b887cSAdrian Hunter			child_item = CallTreeLevelThreeItem(self.glb, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), int(query.value(5)), self)
756ae8b887cSAdrian Hunter			self.child_items.append(child_item)
757ae8b887cSAdrian Hunter			self.child_count += 1
758ae8b887cSAdrian Hunter
759ae8b887cSAdrian Hunter# Call tree data model level three item
760ae8b887cSAdrian Hunter
761ae8b887cSAdrian Hunterclass CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
762ae8b887cSAdrian Hunter
763ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
764ae8b887cSAdrian Hunter		super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
765ae8b887cSAdrian Hunter		dso = dsoname(dso)
766ae8b887cSAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
767ae8b887cSAdrian Hunter		self.dbid = calls_id
768ae8b887cSAdrian Hunter
769ae8b887cSAdrian Hunter# Call tree data model level two item
770ae8b887cSAdrian Hunter
771ae8b887cSAdrian Hunterclass CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
772ae8b887cSAdrian Hunter
773ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
774ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item)
775ae8b887cSAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
776ae8b887cSAdrian Hunter		self.dbid = thread_id
777ae8b887cSAdrian Hunter
778ae8b887cSAdrian Hunter	def Select(self):
779ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).Select()
780ae8b887cSAdrian Hunter		for child_item in self.child_items:
781ae8b887cSAdrian Hunter			self.time += child_item.time
782ae8b887cSAdrian Hunter			self.branch_count += child_item.branch_count
783ae8b887cSAdrian Hunter		for child_item in self.child_items:
784ae8b887cSAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
785ae8b887cSAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
786ae8b887cSAdrian Hunter
787ae8b887cSAdrian Hunter# Call tree data model level one item
788ae8b887cSAdrian Hunter
789ae8b887cSAdrian Hunterclass CallTreeLevelOneItem(CallGraphLevelItemBase):
790ae8b887cSAdrian Hunter
791ae8b887cSAdrian Hunter	def __init__(self, glb, row, comm_id, comm, parent_item):
792ae8b887cSAdrian Hunter		super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item)
793ae8b887cSAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
794ae8b887cSAdrian Hunter		self.dbid = comm_id
795ae8b887cSAdrian Hunter
796ae8b887cSAdrian Hunter	def Select(self):
797ae8b887cSAdrian Hunter		self.query_done = True;
798ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
799ae8b887cSAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
800ae8b887cSAdrian Hunter					" FROM comm_threads"
801ae8b887cSAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
802ae8b887cSAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
803ae8b887cSAdrian Hunter		while query.next():
804ae8b887cSAdrian Hunter			child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
805ae8b887cSAdrian Hunter			self.child_items.append(child_item)
806ae8b887cSAdrian Hunter			self.child_count += 1
807ae8b887cSAdrian Hunter
808ae8b887cSAdrian Hunter# Call tree data model root item
809ae8b887cSAdrian Hunter
810ae8b887cSAdrian Hunterclass CallTreeRootItem(CallGraphLevelItemBase):
811ae8b887cSAdrian Hunter
812ae8b887cSAdrian Hunter	def __init__(self, glb):
813ae8b887cSAdrian Hunter		super(CallTreeRootItem, self).__init__(glb, 0, None)
814ae8b887cSAdrian Hunter		self.dbid = 0
815ae8b887cSAdrian Hunter		self.query_done = True;
816ae8b887cSAdrian Hunter		query = QSqlQuery(glb.db)
817ae8b887cSAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
818ae8b887cSAdrian Hunter		while query.next():
819ae8b887cSAdrian Hunter			if not query.value(0):
820ae8b887cSAdrian Hunter				continue
821ae8b887cSAdrian Hunter			child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
822ae8b887cSAdrian Hunter			self.child_items.append(child_item)
823ae8b887cSAdrian Hunter			self.child_count += 1
824ae8b887cSAdrian Hunter
825ae8b887cSAdrian Hunter# Call Tree data model
826ae8b887cSAdrian Hunter
827ae8b887cSAdrian Hunterclass CallTreeModel(CallGraphModelBase):
828ae8b887cSAdrian Hunter
829ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
830ae8b887cSAdrian Hunter		super(CallTreeModel, self).__init__(glb, parent)
831ae8b887cSAdrian Hunter
832ae8b887cSAdrian Hunter	def GetRoot(self):
833ae8b887cSAdrian Hunter		return CallTreeRootItem(self.glb)
834ae8b887cSAdrian Hunter
835ae8b887cSAdrian Hunter	def columnCount(self, parent=None):
836ae8b887cSAdrian Hunter		return 7
837ae8b887cSAdrian Hunter
838ae8b887cSAdrian Hunter	def columnHeader(self, column):
839ae8b887cSAdrian Hunter		headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
840ae8b887cSAdrian Hunter		return headers[column]
841ae8b887cSAdrian Hunter
842ae8b887cSAdrian Hunter	def columnAlignment(self, column):
843ae8b887cSAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
844ae8b887cSAdrian Hunter		return alignment[column]
845ae8b887cSAdrian Hunter
846ae8b887cSAdrian Hunter	def DoFindSelect(self, query, match):
847ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
848ae8b887cSAdrian Hunter						" FROM calls"
849ae8b887cSAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
850ae8b887cSAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
851ae8b887cSAdrian Hunter						" WHERE symbols.name" + match +
852ae8b887cSAdrian Hunter						" ORDER BY comm_id, thread_id, call_time, calls.id")
853ae8b887cSAdrian Hunter
854ae8b887cSAdrian Hunter	def FindPath(self, query):
855ae8b887cSAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
856ae8b887cSAdrian Hunter		# to open the tree at the right place.
857ae8b887cSAdrian Hunter		ids = []
858ae8b887cSAdrian Hunter		parent_id = query.value(0)
859ae8b887cSAdrian Hunter		while parent_id:
860ae8b887cSAdrian Hunter			ids.insert(0, parent_id)
861ae8b887cSAdrian Hunter			q2 = QSqlQuery(self.glb.db)
862ae8b887cSAdrian Hunter			QueryExec(q2, "SELECT parent_id"
863ae8b887cSAdrian Hunter					" FROM calls"
864ae8b887cSAdrian Hunter					" WHERE id = " + str(parent_id))
865ae8b887cSAdrian Hunter			if not q2.next():
866ae8b887cSAdrian Hunter				break
867ae8b887cSAdrian Hunter			parent_id = q2.value(0)
868ae8b887cSAdrian Hunter		ids.insert(0, query.value(2))
869ae8b887cSAdrian Hunter		ids.insert(0, query.value(1))
870ae8b887cSAdrian Hunter		return ids
871ae8b887cSAdrian Hunter
872ebd70c7dSAdrian Hunter# Vertical widget layout
873ebd70c7dSAdrian Hunter
874ebd70c7dSAdrian Hunterclass VBox():
875ebd70c7dSAdrian Hunter
876ebd70c7dSAdrian Hunter	def __init__(self, w1, w2, w3=None):
877ebd70c7dSAdrian Hunter		self.vbox = QWidget()
878ebd70c7dSAdrian Hunter		self.vbox.setLayout(QVBoxLayout());
879ebd70c7dSAdrian Hunter
880ebd70c7dSAdrian Hunter		self.vbox.layout().setContentsMargins(0, 0, 0, 0)
881ebd70c7dSAdrian Hunter
882ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w1)
883ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w2)
884ebd70c7dSAdrian Hunter		if w3:
885ebd70c7dSAdrian Hunter			self.vbox.layout().addWidget(w3)
886ebd70c7dSAdrian Hunter
887ebd70c7dSAdrian Hunter	def Widget(self):
888ebd70c7dSAdrian Hunter		return self.vbox
889ebd70c7dSAdrian Hunter
890a731cc4cSAdrian Hunter# Tree window base
8911beb5c7bSAdrian Hunter
892a731cc4cSAdrian Hunterclass TreeWindowBase(QMdiSubWindow):
8931beb5c7bSAdrian Hunter
894a731cc4cSAdrian Hunter	def __init__(self, parent=None):
895a731cc4cSAdrian Hunter		super(TreeWindowBase, self).__init__(parent)
8961beb5c7bSAdrian Hunter
897a731cc4cSAdrian Hunter		self.model = None
898a731cc4cSAdrian Hunter		self.find_bar = None
8991beb5c7bSAdrian Hunter
900be6e7471SAdrian Hunter		self.view = QTreeView()
90196c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
90296c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
903be6e7471SAdrian Hunter
9049bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
9059bc4e4bfSAdrian Hunter
906ebd70c7dSAdrian Hunter	def DisplayFound(self, ids):
907ebd70c7dSAdrian Hunter		if not len(ids):
908ebd70c7dSAdrian Hunter			return False
909ebd70c7dSAdrian Hunter		parent = QModelIndex()
910ebd70c7dSAdrian Hunter		for dbid in ids:
911ebd70c7dSAdrian Hunter			found = False
912ebd70c7dSAdrian Hunter			n = self.model.rowCount(parent)
913ebd70c7dSAdrian Hunter			for row in xrange(n):
914ebd70c7dSAdrian Hunter				child = self.model.index(row, 0, parent)
915ebd70c7dSAdrian Hunter				if child.internalPointer().dbid == dbid:
916ebd70c7dSAdrian Hunter					found = True
917ebd70c7dSAdrian Hunter					self.view.setCurrentIndex(child)
918ebd70c7dSAdrian Hunter					parent = child
919ebd70c7dSAdrian Hunter					break
920ebd70c7dSAdrian Hunter			if not found:
921ebd70c7dSAdrian Hunter				break
922ebd70c7dSAdrian Hunter		return found
923ebd70c7dSAdrian Hunter
924ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context):
925ebd70c7dSAdrian Hunter		self.view.setFocus()
926ebd70c7dSAdrian Hunter		self.find_bar.Busy()
927ebd70c7dSAdrian Hunter		self.model.Find(value, direction, pattern, context, self.FindDone)
928ebd70c7dSAdrian Hunter
929ebd70c7dSAdrian Hunter	def FindDone(self, ids):
930ebd70c7dSAdrian Hunter		found = True
931ebd70c7dSAdrian Hunter		if not self.DisplayFound(ids):
932ebd70c7dSAdrian Hunter			found = False
933ebd70c7dSAdrian Hunter		self.find_bar.Idle()
934ebd70c7dSAdrian Hunter		if not found:
935ebd70c7dSAdrian Hunter			self.find_bar.NotFound()
936ebd70c7dSAdrian Hunter
937a731cc4cSAdrian Hunter
938a731cc4cSAdrian Hunter# Context-sensitive call graph window
939a731cc4cSAdrian Hunter
940a731cc4cSAdrian Hunterclass CallGraphWindow(TreeWindowBase):
941a731cc4cSAdrian Hunter
942a731cc4cSAdrian Hunter	def __init__(self, glb, parent=None):
943a731cc4cSAdrian Hunter		super(CallGraphWindow, self).__init__(parent)
944a731cc4cSAdrian Hunter
945a731cc4cSAdrian Hunter		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
946a731cc4cSAdrian Hunter
947a731cc4cSAdrian Hunter		self.view.setModel(self.model)
948a731cc4cSAdrian Hunter
949a731cc4cSAdrian Hunter		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
950a731cc4cSAdrian Hunter			self.view.setColumnWidth(c, w)
951a731cc4cSAdrian Hunter
952a731cc4cSAdrian Hunter		self.find_bar = FindBar(self, self)
953a731cc4cSAdrian Hunter
954a731cc4cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
955a731cc4cSAdrian Hunter
956a731cc4cSAdrian Hunter		self.setWidget(self.vbox.Widget())
957a731cc4cSAdrian Hunter
958a731cc4cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
959a731cc4cSAdrian Hunter
960ae8b887cSAdrian Hunter# Call tree window
961ae8b887cSAdrian Hunter
962ae8b887cSAdrian Hunterclass CallTreeWindow(TreeWindowBase):
963ae8b887cSAdrian Hunter
964ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
965ae8b887cSAdrian Hunter		super(CallTreeWindow, self).__init__(parent)
966ae8b887cSAdrian Hunter
967ae8b887cSAdrian Hunter		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
968ae8b887cSAdrian Hunter
969ae8b887cSAdrian Hunter		self.view.setModel(self.model)
970ae8b887cSAdrian Hunter
971ae8b887cSAdrian Hunter		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
972ae8b887cSAdrian Hunter			self.view.setColumnWidth(c, w)
973ae8b887cSAdrian Hunter
974ae8b887cSAdrian Hunter		self.find_bar = FindBar(self, self)
975ae8b887cSAdrian Hunter
976ae8b887cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
977ae8b887cSAdrian Hunter
978ae8b887cSAdrian Hunter		self.setWidget(self.vbox.Widget())
979ae8b887cSAdrian Hunter
980ae8b887cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
981ae8b887cSAdrian Hunter
9828392b74bSAdrian Hunter# Child data item  finder
9838392b74bSAdrian Hunter
9848392b74bSAdrian Hunterclass ChildDataItemFinder():
9858392b74bSAdrian Hunter
9868392b74bSAdrian Hunter	def __init__(self, root):
9878392b74bSAdrian Hunter		self.root = root
9888392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
9898392b74bSAdrian Hunter		self.rows = []
9908392b74bSAdrian Hunter		self.pos = 0
9918392b74bSAdrian Hunter
9928392b74bSAdrian Hunter	def FindSelect(self):
9938392b74bSAdrian Hunter		self.rows = []
9948392b74bSAdrian Hunter		if self.pattern:
9958392b74bSAdrian Hunter			pattern = re.compile(self.value)
9968392b74bSAdrian Hunter			for child in self.root.child_items:
9978392b74bSAdrian Hunter				for column_data in child.data:
9988392b74bSAdrian Hunter					if re.search(pattern, str(column_data)) is not None:
9998392b74bSAdrian Hunter						self.rows.append(child.row)
10008392b74bSAdrian Hunter						break
10018392b74bSAdrian Hunter		else:
10028392b74bSAdrian Hunter			for child in self.root.child_items:
10038392b74bSAdrian Hunter				for column_data in child.data:
10048392b74bSAdrian Hunter					if self.value in str(column_data):
10058392b74bSAdrian Hunter						self.rows.append(child.row)
10068392b74bSAdrian Hunter						break
10078392b74bSAdrian Hunter
10088392b74bSAdrian Hunter	def FindValue(self):
10098392b74bSAdrian Hunter		self.pos = 0
10108392b74bSAdrian Hunter		if self.last_value != self.value or self.pattern != self.last_pattern:
10118392b74bSAdrian Hunter			self.FindSelect()
10128392b74bSAdrian Hunter		if not len(self.rows):
10138392b74bSAdrian Hunter			return -1
10148392b74bSAdrian Hunter		return self.rows[self.pos]
10158392b74bSAdrian Hunter
10168392b74bSAdrian Hunter	def FindThread(self):
10178392b74bSAdrian Hunter		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
10188392b74bSAdrian Hunter			row = self.FindValue()
10198392b74bSAdrian Hunter		elif len(self.rows):
10208392b74bSAdrian Hunter			if self.direction > 0:
10218392b74bSAdrian Hunter				self.pos += 1
10228392b74bSAdrian Hunter				if self.pos >= len(self.rows):
10238392b74bSAdrian Hunter					self.pos = 0
10248392b74bSAdrian Hunter			else:
10258392b74bSAdrian Hunter				self.pos -= 1
10268392b74bSAdrian Hunter				if self.pos < 0:
10278392b74bSAdrian Hunter					self.pos = len(self.rows) - 1
10288392b74bSAdrian Hunter			row = self.rows[self.pos]
10298392b74bSAdrian Hunter		else:
10308392b74bSAdrian Hunter			row = -1
10318392b74bSAdrian Hunter		return (True, row)
10328392b74bSAdrian Hunter
10338392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
10348392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
10358392b74bSAdrian Hunter		# Use a thread so the UI is not blocked
10368392b74bSAdrian Hunter		thread = Thread(self.FindThread)
10378392b74bSAdrian Hunter		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
10388392b74bSAdrian Hunter		thread.start()
10398392b74bSAdrian Hunter
10408392b74bSAdrian Hunter	def FindDone(self, thread, callback, row):
10418392b74bSAdrian Hunter		callback(row)
10428392b74bSAdrian Hunter
10438392b74bSAdrian Hunter# Number of database records to fetch in one go
10448392b74bSAdrian Hunter
10458392b74bSAdrian Hunterglb_chunk_sz = 10000
10468392b74bSAdrian Hunter
10478392b74bSAdrian Hunter# Background process for SQL data fetcher
10488392b74bSAdrian Hunter
10498392b74bSAdrian Hunterclass SQLFetcherProcess():
10508392b74bSAdrian Hunter
10518392b74bSAdrian Hunter	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
10528392b74bSAdrian Hunter		# Need a unique connection name
10538392b74bSAdrian Hunter		conn_name = "SQLFetcher" + str(os.getpid())
10548392b74bSAdrian Hunter		self.db, dbname = dbref.Open(conn_name)
10558392b74bSAdrian Hunter		self.sql = sql
10568392b74bSAdrian Hunter		self.buffer = buffer
10578392b74bSAdrian Hunter		self.head = head
10588392b74bSAdrian Hunter		self.tail = tail
10598392b74bSAdrian Hunter		self.fetch_count = fetch_count
10608392b74bSAdrian Hunter		self.fetching_done = fetching_done
10618392b74bSAdrian Hunter		self.process_target = process_target
10628392b74bSAdrian Hunter		self.wait_event = wait_event
10638392b74bSAdrian Hunter		self.fetched_event = fetched_event
10648392b74bSAdrian Hunter		self.prep = prep
10658392b74bSAdrian Hunter		self.query = QSqlQuery(self.db)
10668392b74bSAdrian Hunter		self.query_limit = 0 if "$$last_id$$" in sql else 2
10678392b74bSAdrian Hunter		self.last_id = -1
10688392b74bSAdrian Hunter		self.fetched = 0
10698392b74bSAdrian Hunter		self.more = True
10708392b74bSAdrian Hunter		self.local_head = self.head.value
10718392b74bSAdrian Hunter		self.local_tail = self.tail.value
10728392b74bSAdrian Hunter
10738392b74bSAdrian Hunter	def Select(self):
10748392b74bSAdrian Hunter		if self.query_limit:
10758392b74bSAdrian Hunter			if self.query_limit == 1:
10768392b74bSAdrian Hunter				return
10778392b74bSAdrian Hunter			self.query_limit -= 1
10788392b74bSAdrian Hunter		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
10798392b74bSAdrian Hunter		QueryExec(self.query, stmt)
10808392b74bSAdrian Hunter
10818392b74bSAdrian Hunter	def Next(self):
10828392b74bSAdrian Hunter		if not self.query.next():
10838392b74bSAdrian Hunter			self.Select()
10848392b74bSAdrian Hunter			if not self.query.next():
10858392b74bSAdrian Hunter				return None
10868392b74bSAdrian Hunter		self.last_id = self.query.value(0)
10878392b74bSAdrian Hunter		return self.prep(self.query)
10888392b74bSAdrian Hunter
10898392b74bSAdrian Hunter	def WaitForTarget(self):
10908392b74bSAdrian Hunter		while True:
10918392b74bSAdrian Hunter			self.wait_event.clear()
10928392b74bSAdrian Hunter			target = self.process_target.value
10938392b74bSAdrian Hunter			if target > self.fetched or target < 0:
10948392b74bSAdrian Hunter				break
10958392b74bSAdrian Hunter			self.wait_event.wait()
10968392b74bSAdrian Hunter		return target
10978392b74bSAdrian Hunter
10988392b74bSAdrian Hunter	def HasSpace(self, sz):
10998392b74bSAdrian Hunter		if self.local_tail <= self.local_head:
11008392b74bSAdrian Hunter			space = len(self.buffer) - self.local_head
11018392b74bSAdrian Hunter			if space > sz:
11028392b74bSAdrian Hunter				return True
11038392b74bSAdrian Hunter			if space >= glb_nsz:
11048392b74bSAdrian Hunter				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1105beda0e72STony Jones				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
11068392b74bSAdrian Hunter				self.buffer[self.local_head : self.local_head + len(nd)] = nd
11078392b74bSAdrian Hunter			self.local_head = 0
11088392b74bSAdrian Hunter		if self.local_tail - self.local_head > sz:
11098392b74bSAdrian Hunter			return True
11108392b74bSAdrian Hunter		return False
11118392b74bSAdrian Hunter
11128392b74bSAdrian Hunter	def WaitForSpace(self, sz):
11138392b74bSAdrian Hunter		if self.HasSpace(sz):
11148392b74bSAdrian Hunter			return
11158392b74bSAdrian Hunter		while True:
11168392b74bSAdrian Hunter			self.wait_event.clear()
11178392b74bSAdrian Hunter			self.local_tail = self.tail.value
11188392b74bSAdrian Hunter			if self.HasSpace(sz):
11198392b74bSAdrian Hunter				return
11208392b74bSAdrian Hunter			self.wait_event.wait()
11218392b74bSAdrian Hunter
11228392b74bSAdrian Hunter	def AddToBuffer(self, obj):
1123beda0e72STony Jones		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
11248392b74bSAdrian Hunter		n = len(d)
1125beda0e72STony Jones		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
11268392b74bSAdrian Hunter		sz = n + glb_nsz
11278392b74bSAdrian Hunter		self.WaitForSpace(sz)
11288392b74bSAdrian Hunter		pos = self.local_head
11298392b74bSAdrian Hunter		self.buffer[pos : pos + len(nd)] = nd
11308392b74bSAdrian Hunter		self.buffer[pos + glb_nsz : pos + sz] = d
11318392b74bSAdrian Hunter		self.local_head += sz
11328392b74bSAdrian Hunter
11338392b74bSAdrian Hunter	def FetchBatch(self, batch_size):
11348392b74bSAdrian Hunter		fetched = 0
11358392b74bSAdrian Hunter		while batch_size > fetched:
11368392b74bSAdrian Hunter			obj = self.Next()
11378392b74bSAdrian Hunter			if obj is None:
11388392b74bSAdrian Hunter				self.more = False
11398392b74bSAdrian Hunter				break
11408392b74bSAdrian Hunter			self.AddToBuffer(obj)
11418392b74bSAdrian Hunter			fetched += 1
11428392b74bSAdrian Hunter		if fetched:
11438392b74bSAdrian Hunter			self.fetched += fetched
11448392b74bSAdrian Hunter			with self.fetch_count.get_lock():
11458392b74bSAdrian Hunter				self.fetch_count.value += fetched
11468392b74bSAdrian Hunter			self.head.value = self.local_head
11478392b74bSAdrian Hunter			self.fetched_event.set()
11488392b74bSAdrian Hunter
11498392b74bSAdrian Hunter	def Run(self):
11508392b74bSAdrian Hunter		while self.more:
11518392b74bSAdrian Hunter			target = self.WaitForTarget()
11528392b74bSAdrian Hunter			if target < 0:
11538392b74bSAdrian Hunter				break
11548392b74bSAdrian Hunter			batch_size = min(glb_chunk_sz, target - self.fetched)
11558392b74bSAdrian Hunter			self.FetchBatch(batch_size)
11568392b74bSAdrian Hunter		self.fetching_done.value = True
11578392b74bSAdrian Hunter		self.fetched_event.set()
11588392b74bSAdrian Hunter
11598392b74bSAdrian Hunterdef SQLFetcherFn(*x):
11608392b74bSAdrian Hunter	process = SQLFetcherProcess(*x)
11618392b74bSAdrian Hunter	process.Run()
11628392b74bSAdrian Hunter
11638392b74bSAdrian Hunter# SQL data fetcher
11648392b74bSAdrian Hunter
11658392b74bSAdrian Hunterclass SQLFetcher(QObject):
11668392b74bSAdrian Hunter
11678392b74bSAdrian Hunter	done = Signal(object)
11688392b74bSAdrian Hunter
11698392b74bSAdrian Hunter	def __init__(self, glb, sql, prep, process_data, parent=None):
11708392b74bSAdrian Hunter		super(SQLFetcher, self).__init__(parent)
11718392b74bSAdrian Hunter		self.process_data = process_data
11728392b74bSAdrian Hunter		self.more = True
11738392b74bSAdrian Hunter		self.target = 0
11748392b74bSAdrian Hunter		self.last_target = 0
11758392b74bSAdrian Hunter		self.fetched = 0
11768392b74bSAdrian Hunter		self.buffer_size = 16 * 1024 * 1024
11778392b74bSAdrian Hunter		self.buffer = Array(c_char, self.buffer_size, lock=False)
11788392b74bSAdrian Hunter		self.head = Value(c_longlong)
11798392b74bSAdrian Hunter		self.tail = Value(c_longlong)
11808392b74bSAdrian Hunter		self.local_tail = 0
11818392b74bSAdrian Hunter		self.fetch_count = Value(c_longlong)
11828392b74bSAdrian Hunter		self.fetching_done = Value(c_bool)
11838392b74bSAdrian Hunter		self.last_count = 0
11848392b74bSAdrian Hunter		self.process_target = Value(c_longlong)
11858392b74bSAdrian Hunter		self.wait_event = Event()
11868392b74bSAdrian Hunter		self.fetched_event = Event()
11878392b74bSAdrian Hunter		glb.AddInstanceToShutdownOnExit(self)
11888392b74bSAdrian 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))
11898392b74bSAdrian Hunter		self.process.start()
11908392b74bSAdrian Hunter		self.thread = Thread(self.Thread)
11918392b74bSAdrian Hunter		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
11928392b74bSAdrian Hunter		self.thread.start()
11938392b74bSAdrian Hunter
11948392b74bSAdrian Hunter	def Shutdown(self):
11958392b74bSAdrian Hunter		# Tell the thread and process to exit
11968392b74bSAdrian Hunter		self.process_target.value = -1
11978392b74bSAdrian Hunter		self.wait_event.set()
11988392b74bSAdrian Hunter		self.more = False
11998392b74bSAdrian Hunter		self.fetching_done.value = True
12008392b74bSAdrian Hunter		self.fetched_event.set()
12018392b74bSAdrian Hunter
12028392b74bSAdrian Hunter	def Thread(self):
12038392b74bSAdrian Hunter		if not self.more:
12048392b74bSAdrian Hunter			return True, 0
12058392b74bSAdrian Hunter		while True:
12068392b74bSAdrian Hunter			self.fetched_event.clear()
12078392b74bSAdrian Hunter			fetch_count = self.fetch_count.value
12088392b74bSAdrian Hunter			if fetch_count != self.last_count:
12098392b74bSAdrian Hunter				break
12108392b74bSAdrian Hunter			if self.fetching_done.value:
12118392b74bSAdrian Hunter				self.more = False
12128392b74bSAdrian Hunter				return True, 0
12138392b74bSAdrian Hunter			self.fetched_event.wait()
12148392b74bSAdrian Hunter		count = fetch_count - self.last_count
12158392b74bSAdrian Hunter		self.last_count = fetch_count
12168392b74bSAdrian Hunter		self.fetched += count
12178392b74bSAdrian Hunter		return False, count
12188392b74bSAdrian Hunter
12198392b74bSAdrian Hunter	def Fetch(self, nr):
12208392b74bSAdrian Hunter		if not self.more:
12218392b74bSAdrian Hunter			# -1 inidcates there are no more
12228392b74bSAdrian Hunter			return -1
12238392b74bSAdrian Hunter		result = self.fetched
12248392b74bSAdrian Hunter		extra = result + nr - self.target
12258392b74bSAdrian Hunter		if extra > 0:
12268392b74bSAdrian Hunter			self.target += extra
12278392b74bSAdrian Hunter			# process_target < 0 indicates shutting down
12288392b74bSAdrian Hunter			if self.process_target.value >= 0:
12298392b74bSAdrian Hunter				self.process_target.value = self.target
12308392b74bSAdrian Hunter			self.wait_event.set()
12318392b74bSAdrian Hunter		return result
12328392b74bSAdrian Hunter
12338392b74bSAdrian Hunter	def RemoveFromBuffer(self):
12348392b74bSAdrian Hunter		pos = self.local_tail
12358392b74bSAdrian Hunter		if len(self.buffer) - pos < glb_nsz:
12368392b74bSAdrian Hunter			pos = 0
1237beda0e72STony Jones		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
12388392b74bSAdrian Hunter		if n == 0:
12398392b74bSAdrian Hunter			pos = 0
1240beda0e72STony Jones			n = pickle.loads(self.buffer[0 : glb_nsz])
12418392b74bSAdrian Hunter		pos += glb_nsz
1242beda0e72STony Jones		obj = pickle.loads(self.buffer[pos : pos + n])
12438392b74bSAdrian Hunter		self.local_tail = pos + n
12448392b74bSAdrian Hunter		return obj
12458392b74bSAdrian Hunter
12468392b74bSAdrian Hunter	def ProcessData(self, count):
12478392b74bSAdrian Hunter		for i in xrange(count):
12488392b74bSAdrian Hunter			obj = self.RemoveFromBuffer()
12498392b74bSAdrian Hunter			self.process_data(obj)
12508392b74bSAdrian Hunter		self.tail.value = self.local_tail
12518392b74bSAdrian Hunter		self.wait_event.set()
12528392b74bSAdrian Hunter		self.done.emit(count)
12538392b74bSAdrian Hunter
12548392b74bSAdrian Hunter# Fetch more records bar
12558392b74bSAdrian Hunter
12568392b74bSAdrian Hunterclass FetchMoreRecordsBar():
12578392b74bSAdrian Hunter
12588392b74bSAdrian Hunter	def __init__(self, model, parent):
12598392b74bSAdrian Hunter		self.model = model
12608392b74bSAdrian Hunter
12618392b74bSAdrian Hunter		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
12628392b74bSAdrian Hunter		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12638392b74bSAdrian Hunter
12648392b74bSAdrian Hunter		self.fetch_count = QSpinBox()
12658392b74bSAdrian Hunter		self.fetch_count.setRange(1, 1000000)
12668392b74bSAdrian Hunter		self.fetch_count.setValue(10)
12678392b74bSAdrian Hunter		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12688392b74bSAdrian Hunter
12698392b74bSAdrian Hunter		self.fetch = QPushButton("Go!")
12708392b74bSAdrian Hunter		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12718392b74bSAdrian Hunter		self.fetch.released.connect(self.FetchMoreRecords)
12728392b74bSAdrian Hunter
12738392b74bSAdrian Hunter		self.progress = QProgressBar()
12748392b74bSAdrian Hunter		self.progress.setRange(0, 100)
12758392b74bSAdrian Hunter		self.progress.hide()
12768392b74bSAdrian Hunter
12778392b74bSAdrian Hunter		self.done_label = QLabel("All records fetched")
12788392b74bSAdrian Hunter		self.done_label.hide()
12798392b74bSAdrian Hunter
12808392b74bSAdrian Hunter		self.spacer = QLabel("")
12818392b74bSAdrian Hunter
12828392b74bSAdrian Hunter		self.close_button = QToolButton()
12838392b74bSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
12848392b74bSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
12858392b74bSAdrian Hunter
12868392b74bSAdrian Hunter		self.hbox = QHBoxLayout()
12878392b74bSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
12888392b74bSAdrian Hunter
12898392b74bSAdrian Hunter		self.hbox.addWidget(self.label)
12908392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch_count)
12918392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch)
12928392b74bSAdrian Hunter		self.hbox.addWidget(self.spacer)
12938392b74bSAdrian Hunter		self.hbox.addWidget(self.progress)
12948392b74bSAdrian Hunter		self.hbox.addWidget(self.done_label)
12958392b74bSAdrian Hunter		self.hbox.addWidget(self.close_button)
12968392b74bSAdrian Hunter
12978392b74bSAdrian Hunter		self.bar = QWidget()
12988392b74bSAdrian Hunter		self.bar.setLayout(self.hbox);
12998392b74bSAdrian Hunter		self.bar.show()
13008392b74bSAdrian Hunter
13018392b74bSAdrian Hunter		self.in_progress = False
13028392b74bSAdrian Hunter		self.model.progress.connect(self.Progress)
13038392b74bSAdrian Hunter
13048392b74bSAdrian Hunter		self.done = False
13058392b74bSAdrian Hunter
13068392b74bSAdrian Hunter		if not model.HasMoreRecords():
13078392b74bSAdrian Hunter			self.Done()
13088392b74bSAdrian Hunter
13098392b74bSAdrian Hunter	def Widget(self):
13108392b74bSAdrian Hunter		return self.bar
13118392b74bSAdrian Hunter
13128392b74bSAdrian Hunter	def Activate(self):
13138392b74bSAdrian Hunter		self.bar.show()
13148392b74bSAdrian Hunter		self.fetch.setFocus()
13158392b74bSAdrian Hunter
13168392b74bSAdrian Hunter	def Deactivate(self):
13178392b74bSAdrian Hunter		self.bar.hide()
13188392b74bSAdrian Hunter
13198392b74bSAdrian Hunter	def Enable(self, enable):
13208392b74bSAdrian Hunter		self.fetch.setEnabled(enable)
13218392b74bSAdrian Hunter		self.fetch_count.setEnabled(enable)
13228392b74bSAdrian Hunter
13238392b74bSAdrian Hunter	def Busy(self):
13248392b74bSAdrian Hunter		self.Enable(False)
13258392b74bSAdrian Hunter		self.fetch.hide()
13268392b74bSAdrian Hunter		self.spacer.hide()
13278392b74bSAdrian Hunter		self.progress.show()
13288392b74bSAdrian Hunter
13298392b74bSAdrian Hunter	def Idle(self):
13308392b74bSAdrian Hunter		self.in_progress = False
13318392b74bSAdrian Hunter		self.Enable(True)
13328392b74bSAdrian Hunter		self.progress.hide()
13338392b74bSAdrian Hunter		self.fetch.show()
13348392b74bSAdrian Hunter		self.spacer.show()
13358392b74bSAdrian Hunter
13368392b74bSAdrian Hunter	def Target(self):
13378392b74bSAdrian Hunter		return self.fetch_count.value() * glb_chunk_sz
13388392b74bSAdrian Hunter
13398392b74bSAdrian Hunter	def Done(self):
13408392b74bSAdrian Hunter		self.done = True
13418392b74bSAdrian Hunter		self.Idle()
13428392b74bSAdrian Hunter		self.label.hide()
13438392b74bSAdrian Hunter		self.fetch_count.hide()
13448392b74bSAdrian Hunter		self.fetch.hide()
13458392b74bSAdrian Hunter		self.spacer.hide()
13468392b74bSAdrian Hunter		self.done_label.show()
13478392b74bSAdrian Hunter
13488392b74bSAdrian Hunter	def Progress(self, count):
13498392b74bSAdrian Hunter		if self.in_progress:
13508392b74bSAdrian Hunter			if count:
13518392b74bSAdrian Hunter				percent = ((count - self.start) * 100) / self.Target()
13528392b74bSAdrian Hunter				if percent >= 100:
13538392b74bSAdrian Hunter					self.Idle()
13548392b74bSAdrian Hunter				else:
13558392b74bSAdrian Hunter					self.progress.setValue(percent)
13568392b74bSAdrian Hunter		if not count:
13578392b74bSAdrian Hunter			# Count value of zero means no more records
13588392b74bSAdrian Hunter			self.Done()
13598392b74bSAdrian Hunter
13608392b74bSAdrian Hunter	def FetchMoreRecords(self):
13618392b74bSAdrian Hunter		if self.done:
13628392b74bSAdrian Hunter			return
13638392b74bSAdrian Hunter		self.progress.setValue(0)
13648392b74bSAdrian Hunter		self.Busy()
13658392b74bSAdrian Hunter		self.in_progress = True
13668392b74bSAdrian Hunter		self.start = self.model.FetchMoreRecords(self.Target())
13678392b74bSAdrian Hunter
136876099f98SAdrian Hunter# Brance data model level two item
136976099f98SAdrian Hunter
137076099f98SAdrian Hunterclass BranchLevelTwoItem():
137176099f98SAdrian Hunter
1372*530e22fdSAdrian Hunter	def __init__(self, row, col, text, parent_item):
137376099f98SAdrian Hunter		self.row = row
137476099f98SAdrian Hunter		self.parent_item = parent_item
1375*530e22fdSAdrian Hunter		self.data = [""] * (col + 1)
1376*530e22fdSAdrian Hunter		self.data[col] = text
137776099f98SAdrian Hunter		self.level = 2
137876099f98SAdrian Hunter
137976099f98SAdrian Hunter	def getParentItem(self):
138076099f98SAdrian Hunter		return self.parent_item
138176099f98SAdrian Hunter
138276099f98SAdrian Hunter	def getRow(self):
138376099f98SAdrian Hunter		return self.row
138476099f98SAdrian Hunter
138576099f98SAdrian Hunter	def childCount(self):
138676099f98SAdrian Hunter		return 0
138776099f98SAdrian Hunter
138876099f98SAdrian Hunter	def hasChildren(self):
138976099f98SAdrian Hunter		return False
139076099f98SAdrian Hunter
139176099f98SAdrian Hunter	def getData(self, column):
139276099f98SAdrian Hunter		return self.data[column]
139376099f98SAdrian Hunter
139476099f98SAdrian Hunter# Brance data model level one item
139576099f98SAdrian Hunter
139676099f98SAdrian Hunterclass BranchLevelOneItem():
139776099f98SAdrian Hunter
139876099f98SAdrian Hunter	def __init__(self, glb, row, data, parent_item):
139976099f98SAdrian Hunter		self.glb = glb
140076099f98SAdrian Hunter		self.row = row
140176099f98SAdrian Hunter		self.parent_item = parent_item
140276099f98SAdrian Hunter		self.child_count = 0
140376099f98SAdrian Hunter		self.child_items = []
140476099f98SAdrian Hunter		self.data = data[1:]
140576099f98SAdrian Hunter		self.dbid = data[0]
140676099f98SAdrian Hunter		self.level = 1
140776099f98SAdrian Hunter		self.query_done = False
1408*530e22fdSAdrian Hunter		self.br_col = len(self.data) - 1
140976099f98SAdrian Hunter
141076099f98SAdrian Hunter	def getChildItem(self, row):
141176099f98SAdrian Hunter		return self.child_items[row]
141276099f98SAdrian Hunter
141376099f98SAdrian Hunter	def getParentItem(self):
141476099f98SAdrian Hunter		return self.parent_item
141576099f98SAdrian Hunter
141676099f98SAdrian Hunter	def getRow(self):
141776099f98SAdrian Hunter		return self.row
141876099f98SAdrian Hunter
141976099f98SAdrian Hunter	def Select(self):
142076099f98SAdrian Hunter		self.query_done = True
142176099f98SAdrian Hunter
142276099f98SAdrian Hunter		if not self.glb.have_disassembler:
142376099f98SAdrian Hunter			return
142476099f98SAdrian Hunter
142576099f98SAdrian Hunter		query = QSqlQuery(self.glb.db)
142676099f98SAdrian Hunter
142776099f98SAdrian Hunter		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
142876099f98SAdrian Hunter				  " FROM samples"
142976099f98SAdrian Hunter				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
143076099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
143176099f98SAdrian Hunter				  " WHERE samples.id = " + str(self.dbid))
143276099f98SAdrian Hunter		if not query.next():
143376099f98SAdrian Hunter			return
143476099f98SAdrian Hunter		cpu = query.value(0)
143576099f98SAdrian Hunter		dso = query.value(1)
143676099f98SAdrian Hunter		sym = query.value(2)
143776099f98SAdrian Hunter		if dso == 0 or sym == 0:
143876099f98SAdrian Hunter			return
143976099f98SAdrian Hunter		off = query.value(3)
144076099f98SAdrian Hunter		short_name = query.value(4)
144176099f98SAdrian Hunter		long_name = query.value(5)
144276099f98SAdrian Hunter		build_id = query.value(6)
144376099f98SAdrian Hunter		sym_start = query.value(7)
144476099f98SAdrian Hunter		ip = query.value(8)
144576099f98SAdrian Hunter
144676099f98SAdrian Hunter		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
144776099f98SAdrian Hunter				  " FROM samples"
144876099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
144976099f98SAdrian Hunter				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
145076099f98SAdrian Hunter				  " ORDER BY samples.id"
145176099f98SAdrian Hunter				  " LIMIT 1")
145276099f98SAdrian Hunter		if not query.next():
145376099f98SAdrian Hunter			return
145476099f98SAdrian Hunter		if query.value(0) != dso:
145576099f98SAdrian Hunter			# Cannot disassemble from one dso to another
145676099f98SAdrian Hunter			return
145776099f98SAdrian Hunter		bsym = query.value(1)
145876099f98SAdrian Hunter		boff = query.value(2)
145976099f98SAdrian Hunter		bsym_start = query.value(3)
146076099f98SAdrian Hunter		if bsym == 0:
146176099f98SAdrian Hunter			return
146276099f98SAdrian Hunter		tot = bsym_start + boff + 1 - sym_start - off
146376099f98SAdrian Hunter		if tot <= 0 or tot > 16384:
146476099f98SAdrian Hunter			return
146576099f98SAdrian Hunter
146676099f98SAdrian Hunter		inst = self.glb.disassembler.Instruction()
146776099f98SAdrian Hunter		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
146876099f98SAdrian Hunter		if not f:
146976099f98SAdrian Hunter			return
147076099f98SAdrian Hunter		mode = 0 if Is64Bit(f) else 1
147176099f98SAdrian Hunter		self.glb.disassembler.SetMode(inst, mode)
147276099f98SAdrian Hunter
147376099f98SAdrian Hunter		buf_sz = tot + 16
147476099f98SAdrian Hunter		buf = create_string_buffer(tot + 16)
147576099f98SAdrian Hunter		f.seek(sym_start + off)
147676099f98SAdrian Hunter		buf.value = f.read(buf_sz)
147776099f98SAdrian Hunter		buf_ptr = addressof(buf)
147876099f98SAdrian Hunter		i = 0
147976099f98SAdrian Hunter		while tot > 0:
148076099f98SAdrian Hunter			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
148176099f98SAdrian Hunter			if cnt:
148276099f98SAdrian Hunter				byte_str = tohex(ip).rjust(16)
148376099f98SAdrian Hunter				for k in xrange(cnt):
148476099f98SAdrian Hunter					byte_str += " %02x" % ord(buf[i])
148576099f98SAdrian Hunter					i += 1
148676099f98SAdrian Hunter				while k < 15:
148776099f98SAdrian Hunter					byte_str += "   "
148876099f98SAdrian Hunter					k += 1
1489*530e22fdSAdrian Hunter				self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
149076099f98SAdrian Hunter				self.child_count += 1
149176099f98SAdrian Hunter			else:
149276099f98SAdrian Hunter				return
149376099f98SAdrian Hunter			buf_ptr += cnt
149476099f98SAdrian Hunter			tot -= cnt
149576099f98SAdrian Hunter			buf_sz -= cnt
149676099f98SAdrian Hunter			ip += cnt
149776099f98SAdrian Hunter
149876099f98SAdrian Hunter	def childCount(self):
149976099f98SAdrian Hunter		if not self.query_done:
150076099f98SAdrian Hunter			self.Select()
150176099f98SAdrian Hunter			if not self.child_count:
150276099f98SAdrian Hunter				return -1
150376099f98SAdrian Hunter		return self.child_count
150476099f98SAdrian Hunter
150576099f98SAdrian Hunter	def hasChildren(self):
150676099f98SAdrian Hunter		if not self.query_done:
150776099f98SAdrian Hunter			return True
150876099f98SAdrian Hunter		return self.child_count > 0
150976099f98SAdrian Hunter
151076099f98SAdrian Hunter	def getData(self, column):
151176099f98SAdrian Hunter		return self.data[column]
151276099f98SAdrian Hunter
151376099f98SAdrian Hunter# Brance data model root item
151476099f98SAdrian Hunter
151576099f98SAdrian Hunterclass BranchRootItem():
151676099f98SAdrian Hunter
151776099f98SAdrian Hunter	def __init__(self):
151876099f98SAdrian Hunter		self.child_count = 0
151976099f98SAdrian Hunter		self.child_items = []
152076099f98SAdrian Hunter		self.level = 0
152176099f98SAdrian Hunter
152276099f98SAdrian Hunter	def getChildItem(self, row):
152376099f98SAdrian Hunter		return self.child_items[row]
152476099f98SAdrian Hunter
152576099f98SAdrian Hunter	def getParentItem(self):
152676099f98SAdrian Hunter		return None
152776099f98SAdrian Hunter
152876099f98SAdrian Hunter	def getRow(self):
152976099f98SAdrian Hunter		return 0
153076099f98SAdrian Hunter
153176099f98SAdrian Hunter	def childCount(self):
153276099f98SAdrian Hunter		return self.child_count
153376099f98SAdrian Hunter
153476099f98SAdrian Hunter	def hasChildren(self):
153576099f98SAdrian Hunter		return self.child_count > 0
153676099f98SAdrian Hunter
153776099f98SAdrian Hunter	def getData(self, column):
153876099f98SAdrian Hunter		return ""
153976099f98SAdrian Hunter
1540*530e22fdSAdrian Hunter# Calculate instructions per cycle
1541*530e22fdSAdrian Hunter
1542*530e22fdSAdrian Hunterdef CalcIPC(cyc_cnt, insn_cnt):
1543*530e22fdSAdrian Hunter	if cyc_cnt and insn_cnt:
1544*530e22fdSAdrian Hunter		ipc = Decimal(float(insn_cnt) / cyc_cnt)
1545*530e22fdSAdrian Hunter		ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
1546*530e22fdSAdrian Hunter	else:
1547*530e22fdSAdrian Hunter		ipc = "0"
1548*530e22fdSAdrian Hunter	return ipc
1549*530e22fdSAdrian Hunter
155076099f98SAdrian Hunter# Branch data preparation
155176099f98SAdrian Hunter
1552*530e22fdSAdrian Hunterdef BranchDataPrepBr(query, data):
1553*530e22fdSAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1554*530e22fdSAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
1555*530e22fdSAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1556*530e22fdSAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
1557*530e22fdSAdrian Hunter
1558*530e22fdSAdrian Hunterdef BranchDataPrepIPC(query, data):
1559*530e22fdSAdrian Hunter	insn_cnt = query.value(16)
1560*530e22fdSAdrian Hunter	cyc_cnt = query.value(17)
1561*530e22fdSAdrian Hunter	ipc = CalcIPC(cyc_cnt, insn_cnt)
1562*530e22fdSAdrian Hunter	data.append(insn_cnt)
1563*530e22fdSAdrian Hunter	data.append(cyc_cnt)
1564*530e22fdSAdrian Hunter	data.append(ipc)
1565*530e22fdSAdrian Hunter
156676099f98SAdrian Hunterdef BranchDataPrep(query):
156776099f98SAdrian Hunter	data = []
156876099f98SAdrian Hunter	for i in xrange(0, 8):
156976099f98SAdrian Hunter		data.append(query.value(i))
1570*530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
157176099f98SAdrian Hunter	return data
157276099f98SAdrian Hunter
15738453c936SAdrian Hunterdef BranchDataPrepWA(query):
15748453c936SAdrian Hunter	data = []
15758453c936SAdrian Hunter	data.append(query.value(0))
15768453c936SAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
15778453c936SAdrian Hunter	data.append("{:>19}".format(query.value(1)))
15788453c936SAdrian Hunter	for i in xrange(2, 8):
15798453c936SAdrian Hunter		data.append(query.value(i))
1580*530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
1581*530e22fdSAdrian Hunter	return data
1582*530e22fdSAdrian Hunter
1583*530e22fdSAdrian Hunterdef BranchDataWithIPCPrep(query):
1584*530e22fdSAdrian Hunter	data = []
1585*530e22fdSAdrian Hunter	for i in xrange(0, 8):
1586*530e22fdSAdrian Hunter		data.append(query.value(i))
1587*530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
1588*530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
1589*530e22fdSAdrian Hunter	return data
1590*530e22fdSAdrian Hunter
1591*530e22fdSAdrian Hunterdef BranchDataWithIPCPrepWA(query):
1592*530e22fdSAdrian Hunter	data = []
1593*530e22fdSAdrian Hunter	data.append(query.value(0))
1594*530e22fdSAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1595*530e22fdSAdrian Hunter	data.append("{:>19}".format(query.value(1)))
1596*530e22fdSAdrian Hunter	for i in xrange(2, 8):
1597*530e22fdSAdrian Hunter		data.append(query.value(i))
1598*530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
1599*530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
16008453c936SAdrian Hunter	return data
16018453c936SAdrian Hunter
160276099f98SAdrian Hunter# Branch data model
160376099f98SAdrian Hunter
160476099f98SAdrian Hunterclass BranchModel(TreeModel):
160576099f98SAdrian Hunter
160676099f98SAdrian Hunter	progress = Signal(object)
160776099f98SAdrian Hunter
160876099f98SAdrian Hunter	def __init__(self, glb, event_id, where_clause, parent=None):
1609a448ba23SAdrian Hunter		super(BranchModel, self).__init__(glb, parent)
161076099f98SAdrian Hunter		self.event_id = event_id
161176099f98SAdrian Hunter		self.more = True
161276099f98SAdrian Hunter		self.populated = 0
1613*530e22fdSAdrian Hunter		self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
1614*530e22fdSAdrian Hunter		if self.have_ipc:
1615*530e22fdSAdrian Hunter			select_ipc = ", insn_count, cyc_count"
1616*530e22fdSAdrian Hunter			prep_fn = BranchDataWithIPCPrep
1617*530e22fdSAdrian Hunter			prep_wa_fn = BranchDataWithIPCPrepWA
1618*530e22fdSAdrian Hunter		else:
1619*530e22fdSAdrian Hunter			select_ipc = ""
1620*530e22fdSAdrian Hunter			prep_fn = BranchDataPrep
1621*530e22fdSAdrian Hunter			prep_wa_fn = BranchDataPrepWA
162276099f98SAdrian Hunter		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
162376099f98SAdrian Hunter			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
162476099f98SAdrian Hunter			" ip, symbols.name, sym_offset, dsos.short_name,"
162576099f98SAdrian Hunter			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1626*530e22fdSAdrian Hunter			+ select_ipc +
162776099f98SAdrian Hunter			" FROM samples"
162876099f98SAdrian Hunter			" INNER JOIN comms ON comm_id = comms.id"
162976099f98SAdrian Hunter			" INNER JOIN threads ON thread_id = threads.id"
163076099f98SAdrian Hunter			" INNER JOIN branch_types ON branch_type = branch_types.id"
163176099f98SAdrian Hunter			" INNER JOIN symbols ON symbol_id = symbols.id"
163276099f98SAdrian Hunter			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
163376099f98SAdrian Hunter			" INNER JOIN dsos ON samples.dso_id = dsos.id"
163476099f98SAdrian Hunter			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
163576099f98SAdrian Hunter			" WHERE samples.id > $$last_id$$" + where_clause +
163676099f98SAdrian Hunter			" AND evsel_id = " + str(self.event_id) +
163776099f98SAdrian Hunter			" ORDER BY samples.id"
163876099f98SAdrian Hunter			" LIMIT " + str(glb_chunk_sz))
16398453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
1640*530e22fdSAdrian Hunter			prep = prep_fn
16418453c936SAdrian Hunter		else:
1642*530e22fdSAdrian Hunter			prep = prep_wa_fn
16438453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
164476099f98SAdrian Hunter		self.fetcher.done.connect(self.Update)
164576099f98SAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
164676099f98SAdrian Hunter
1647a448ba23SAdrian Hunter	def GetRoot(self):
1648a448ba23SAdrian Hunter		return BranchRootItem()
1649a448ba23SAdrian Hunter
165076099f98SAdrian Hunter	def columnCount(self, parent=None):
1651*530e22fdSAdrian Hunter		if self.have_ipc:
1652*530e22fdSAdrian Hunter			return 11
1653*530e22fdSAdrian Hunter		else:
165476099f98SAdrian Hunter			return 8
165576099f98SAdrian Hunter
165676099f98SAdrian Hunter	def columnHeader(self, column):
1657*530e22fdSAdrian Hunter		if self.have_ipc:
1658*530e22fdSAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
1659*530e22fdSAdrian Hunter		else:
166076099f98SAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
166176099f98SAdrian Hunter
166276099f98SAdrian Hunter	def columnFont(self, column):
1663*530e22fdSAdrian Hunter		if self.have_ipc:
1664*530e22fdSAdrian Hunter			br_col = 10
1665*530e22fdSAdrian Hunter		else:
1666*530e22fdSAdrian Hunter			br_col = 7
1667*530e22fdSAdrian Hunter		if column != br_col:
166876099f98SAdrian Hunter			return None
166976099f98SAdrian Hunter		return QFont("Monospace")
167076099f98SAdrian Hunter
167176099f98SAdrian Hunter	def DisplayData(self, item, index):
167276099f98SAdrian Hunter		if item.level == 1:
167376099f98SAdrian Hunter			self.FetchIfNeeded(item.row)
167476099f98SAdrian Hunter		return item.getData(index.column())
167576099f98SAdrian Hunter
167676099f98SAdrian Hunter	def AddSample(self, data):
167776099f98SAdrian Hunter		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
167876099f98SAdrian Hunter		self.root.child_items.append(child)
167976099f98SAdrian Hunter		self.populated += 1
168076099f98SAdrian Hunter
168176099f98SAdrian Hunter	def Update(self, fetched):
168276099f98SAdrian Hunter		if not fetched:
168376099f98SAdrian Hunter			self.more = False
168476099f98SAdrian Hunter			self.progress.emit(0)
168576099f98SAdrian Hunter		child_count = self.root.child_count
168676099f98SAdrian Hunter		count = self.populated - child_count
168776099f98SAdrian Hunter		if count > 0:
168876099f98SAdrian Hunter			parent = QModelIndex()
168976099f98SAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
169076099f98SAdrian Hunter			self.insertRows(child_count, count, parent)
169176099f98SAdrian Hunter			self.root.child_count += count
169276099f98SAdrian Hunter			self.endInsertRows()
169376099f98SAdrian Hunter			self.progress.emit(self.root.child_count)
169476099f98SAdrian Hunter
169576099f98SAdrian Hunter	def FetchMoreRecords(self, count):
169676099f98SAdrian Hunter		current = self.root.child_count
169776099f98SAdrian Hunter		if self.more:
169876099f98SAdrian Hunter			self.fetcher.Fetch(count)
169976099f98SAdrian Hunter		else:
170076099f98SAdrian Hunter			self.progress.emit(0)
170176099f98SAdrian Hunter		return current
170276099f98SAdrian Hunter
170376099f98SAdrian Hunter	def HasMoreRecords(self):
170476099f98SAdrian Hunter		return self.more
170576099f98SAdrian Hunter
17060bf0947aSAdrian Hunter# Report Variables
17070bf0947aSAdrian Hunter
17080bf0947aSAdrian Hunterclass ReportVars():
17090bf0947aSAdrian Hunter
1710cd358012SAdrian Hunter	def __init__(self, name = "", where_clause = "", limit = ""):
1711947cc38dSAdrian Hunter		self.name = name
17120bf0947aSAdrian Hunter		self.where_clause = where_clause
1713cd358012SAdrian Hunter		self.limit = limit
17140bf0947aSAdrian Hunter
17150bf0947aSAdrian Hunter	def UniqueId(self):
1716cd358012SAdrian Hunter		return str(self.where_clause + ";" + self.limit)
17170bf0947aSAdrian Hunter
171876099f98SAdrian Hunter# Branch window
171976099f98SAdrian Hunter
172076099f98SAdrian Hunterclass BranchWindow(QMdiSubWindow):
172176099f98SAdrian Hunter
1722947cc38dSAdrian Hunter	def __init__(self, glb, event_id, report_vars, parent=None):
172376099f98SAdrian Hunter		super(BranchWindow, self).__init__(parent)
172476099f98SAdrian Hunter
17250bf0947aSAdrian Hunter		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
172676099f98SAdrian Hunter
17270bf0947aSAdrian Hunter		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
172876099f98SAdrian Hunter
172976099f98SAdrian Hunter		self.view = QTreeView()
173076099f98SAdrian Hunter		self.view.setUniformRowHeights(True)
173196c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
173296c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
173376099f98SAdrian Hunter		self.view.setModel(self.model)
173476099f98SAdrian Hunter
173576099f98SAdrian Hunter		self.ResizeColumnsToContents()
173676099f98SAdrian Hunter
17379bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
17389bc4e4bfSAdrian Hunter
173976099f98SAdrian Hunter		self.find_bar = FindBar(self, self, True)
174076099f98SAdrian Hunter
174176099f98SAdrian Hunter		self.finder = ChildDataItemFinder(self.model.root)
174276099f98SAdrian Hunter
174376099f98SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
174476099f98SAdrian Hunter
174576099f98SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
174676099f98SAdrian Hunter
174776099f98SAdrian Hunter		self.setWidget(self.vbox.Widget())
174876099f98SAdrian Hunter
1749947cc38dSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
175076099f98SAdrian Hunter
175176099f98SAdrian Hunter	def ResizeColumnToContents(self, column, n):
175276099f98SAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
175376099f98SAdrian Hunter		# so implement a crude alternative
175476099f98SAdrian Hunter		mm = "MM" if column else "MMMM"
175576099f98SAdrian Hunter		font = self.view.font()
175676099f98SAdrian Hunter		metrics = QFontMetrics(font)
175776099f98SAdrian Hunter		max = 0
175876099f98SAdrian Hunter		for row in xrange(n):
175976099f98SAdrian Hunter			val = self.model.root.child_items[row].data[column]
176076099f98SAdrian Hunter			len = metrics.width(str(val) + mm)
176176099f98SAdrian Hunter			max = len if len > max else max
176276099f98SAdrian Hunter		val = self.model.columnHeader(column)
176376099f98SAdrian Hunter		len = metrics.width(str(val) + mm)
176476099f98SAdrian Hunter		max = len if len > max else max
176576099f98SAdrian Hunter		self.view.setColumnWidth(column, max)
176676099f98SAdrian Hunter
176776099f98SAdrian Hunter	def ResizeColumnsToContents(self):
176876099f98SAdrian Hunter		n = min(self.model.root.child_count, 100)
176976099f98SAdrian Hunter		if n < 1:
177076099f98SAdrian Hunter			# No data yet, so connect a signal to notify when there is
177176099f98SAdrian Hunter			self.model.rowsInserted.connect(self.UpdateColumnWidths)
177276099f98SAdrian Hunter			return
177376099f98SAdrian Hunter		columns = self.model.columnCount()
177476099f98SAdrian Hunter		for i in xrange(columns):
177576099f98SAdrian Hunter			self.ResizeColumnToContents(i, n)
177676099f98SAdrian Hunter
177776099f98SAdrian Hunter	def UpdateColumnWidths(self, *x):
177876099f98SAdrian Hunter		# This only needs to be done once, so disconnect the signal now
177976099f98SAdrian Hunter		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
178076099f98SAdrian Hunter		self.ResizeColumnsToContents()
178176099f98SAdrian Hunter
178276099f98SAdrian Hunter	def Find(self, value, direction, pattern, context):
178376099f98SAdrian Hunter		self.view.setFocus()
178476099f98SAdrian Hunter		self.find_bar.Busy()
178576099f98SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
178676099f98SAdrian Hunter
178776099f98SAdrian Hunter	def FindDone(self, row):
178876099f98SAdrian Hunter		self.find_bar.Idle()
178976099f98SAdrian Hunter		if row >= 0:
179076099f98SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
179176099f98SAdrian Hunter		else:
179276099f98SAdrian Hunter			self.find_bar.NotFound()
179376099f98SAdrian Hunter
17941c3ca1b3SAdrian Hunter# Line edit data item
17951c3ca1b3SAdrian Hunter
17961c3ca1b3SAdrian Hunterclass LineEditDataItem(object):
17971c3ca1b3SAdrian Hunter
1798cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
17991c3ca1b3SAdrian Hunter		self.glb = glb
18001c3ca1b3SAdrian Hunter		self.label = label
18011c3ca1b3SAdrian Hunter		self.placeholder_text = placeholder_text
18021c3ca1b3SAdrian Hunter		self.parent = parent
18031c3ca1b3SAdrian Hunter		self.id = id
18041c3ca1b3SAdrian Hunter
1805cd358012SAdrian Hunter		self.value = default
18061c3ca1b3SAdrian Hunter
1807cd358012SAdrian Hunter		self.widget = QLineEdit(default)
18081c3ca1b3SAdrian Hunter		self.widget.editingFinished.connect(self.Validate)
18091c3ca1b3SAdrian Hunter		self.widget.textChanged.connect(self.Invalidate)
18101c3ca1b3SAdrian Hunter		self.red = False
18111c3ca1b3SAdrian Hunter		self.error = ""
18121c3ca1b3SAdrian Hunter		self.validated = True
18131c3ca1b3SAdrian Hunter
18141c3ca1b3SAdrian Hunter		if placeholder_text:
18151c3ca1b3SAdrian Hunter			self.widget.setPlaceholderText(placeholder_text)
18161c3ca1b3SAdrian Hunter
18171c3ca1b3SAdrian Hunter	def TurnTextRed(self):
18181c3ca1b3SAdrian Hunter		if not self.red:
18191c3ca1b3SAdrian Hunter			palette = QPalette()
18201c3ca1b3SAdrian Hunter			palette.setColor(QPalette.Text,Qt.red)
18211c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
18221c3ca1b3SAdrian Hunter			self.red = True
18231c3ca1b3SAdrian Hunter
18241c3ca1b3SAdrian Hunter	def TurnTextNormal(self):
18251c3ca1b3SAdrian Hunter		if self.red:
18261c3ca1b3SAdrian Hunter			palette = QPalette()
18271c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
18281c3ca1b3SAdrian Hunter			self.red = False
18291c3ca1b3SAdrian Hunter
18301c3ca1b3SAdrian Hunter	def InvalidValue(self, value):
18311c3ca1b3SAdrian Hunter		self.value = ""
18321c3ca1b3SAdrian Hunter		self.TurnTextRed()
18331c3ca1b3SAdrian Hunter		self.error = self.label + " invalid value '" + value + "'"
18341c3ca1b3SAdrian Hunter		self.parent.ShowMessage(self.error)
18351c3ca1b3SAdrian Hunter
18361c3ca1b3SAdrian Hunter	def Invalidate(self):
18371c3ca1b3SAdrian Hunter		self.validated = False
18381c3ca1b3SAdrian Hunter
18391c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
18401c3ca1b3SAdrian Hunter		self.value = input_string.strip()
18411c3ca1b3SAdrian Hunter
18421c3ca1b3SAdrian Hunter	def Validate(self):
18431c3ca1b3SAdrian Hunter		self.validated = True
18441c3ca1b3SAdrian Hunter		self.error = ""
18451c3ca1b3SAdrian Hunter		self.TurnTextNormal()
18461c3ca1b3SAdrian Hunter		self.parent.ClearMessage()
18471c3ca1b3SAdrian Hunter		input_string = self.widget.text()
18481c3ca1b3SAdrian Hunter		if not len(input_string.strip()):
18491c3ca1b3SAdrian Hunter			self.value = ""
18501c3ca1b3SAdrian Hunter			return
18511c3ca1b3SAdrian Hunter		self.DoValidate(input_string)
18521c3ca1b3SAdrian Hunter
18531c3ca1b3SAdrian Hunter	def IsValid(self):
18541c3ca1b3SAdrian Hunter		if not self.validated:
18551c3ca1b3SAdrian Hunter			self.Validate()
18561c3ca1b3SAdrian Hunter		if len(self.error):
18571c3ca1b3SAdrian Hunter			self.parent.ShowMessage(self.error)
18581c3ca1b3SAdrian Hunter			return False
18591c3ca1b3SAdrian Hunter		return True
18601c3ca1b3SAdrian Hunter
18611c3ca1b3SAdrian Hunter	def IsNumber(self, value):
18621c3ca1b3SAdrian Hunter		try:
18631c3ca1b3SAdrian Hunter			x = int(value)
18641c3ca1b3SAdrian Hunter		except:
18651c3ca1b3SAdrian Hunter			x = 0
18661c3ca1b3SAdrian Hunter		return str(x) == value
18671c3ca1b3SAdrian Hunter
18681c3ca1b3SAdrian Hunter# Non-negative integer ranges dialog data item
18691c3ca1b3SAdrian Hunter
18701c3ca1b3SAdrian Hunterclass NonNegativeIntegerRangesDataItem(LineEditDataItem):
18711c3ca1b3SAdrian Hunter
18721c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
18731c3ca1b3SAdrian Hunter		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
18741c3ca1b3SAdrian Hunter
18751c3ca1b3SAdrian Hunter		self.column_name = column_name
18761c3ca1b3SAdrian Hunter
18771c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
18781c3ca1b3SAdrian Hunter		singles = []
18791c3ca1b3SAdrian Hunter		ranges = []
18801c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
18811c3ca1b3SAdrian Hunter			if "-" in value:
18821c3ca1b3SAdrian Hunter				vrange = value.split("-")
18831c3ca1b3SAdrian Hunter				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
18841c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
18851c3ca1b3SAdrian Hunter				ranges.append(vrange)
18861c3ca1b3SAdrian Hunter			else:
18871c3ca1b3SAdrian Hunter				if not self.IsNumber(value):
18881c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
18891c3ca1b3SAdrian Hunter				singles.append(value)
18901c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
18911c3ca1b3SAdrian Hunter		if len(singles):
18921c3ca1b3SAdrian Hunter			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
18931c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
18941c3ca1b3SAdrian Hunter
1895cd358012SAdrian Hunter# Positive integer dialog data item
1896cd358012SAdrian Hunter
1897cd358012SAdrian Hunterclass PositiveIntegerDataItem(LineEditDataItem):
1898cd358012SAdrian Hunter
1899cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1900cd358012SAdrian Hunter		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1901cd358012SAdrian Hunter
1902cd358012SAdrian Hunter	def DoValidate(self, input_string):
1903cd358012SAdrian Hunter		if not self.IsNumber(input_string.strip()):
1904cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1905cd358012SAdrian Hunter		value = int(input_string.strip())
1906cd358012SAdrian Hunter		if value <= 0:
1907cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1908cd358012SAdrian Hunter		self.value = str(value)
1909cd358012SAdrian Hunter
19101c3ca1b3SAdrian Hunter# Dialog data item converted and validated using a SQL table
19111c3ca1b3SAdrian Hunter
19121c3ca1b3SAdrian Hunterclass SQLTableDataItem(LineEditDataItem):
19131c3ca1b3SAdrian Hunter
19141c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
19151c3ca1b3SAdrian Hunter		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
19161c3ca1b3SAdrian Hunter
19171c3ca1b3SAdrian Hunter		self.table_name = table_name
19181c3ca1b3SAdrian Hunter		self.match_column = match_column
19191c3ca1b3SAdrian Hunter		self.column_name1 = column_name1
19201c3ca1b3SAdrian Hunter		self.column_name2 = column_name2
19211c3ca1b3SAdrian Hunter
19221c3ca1b3SAdrian Hunter	def ValueToIds(self, value):
19231c3ca1b3SAdrian Hunter		ids = []
19241c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
19251c3ca1b3SAdrian Hunter		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
19261c3ca1b3SAdrian Hunter		ret = query.exec_(stmt)
19271c3ca1b3SAdrian Hunter		if ret:
19281c3ca1b3SAdrian Hunter			while query.next():
19291c3ca1b3SAdrian Hunter				ids.append(str(query.value(0)))
19301c3ca1b3SAdrian Hunter		return ids
19311c3ca1b3SAdrian Hunter
19321c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
19331c3ca1b3SAdrian Hunter		all_ids = []
19341c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
19351c3ca1b3SAdrian Hunter			ids = self.ValueToIds(value)
19361c3ca1b3SAdrian Hunter			if len(ids):
19371c3ca1b3SAdrian Hunter				all_ids.extend(ids)
19381c3ca1b3SAdrian Hunter			else:
19391c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
19401c3ca1b3SAdrian Hunter		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
19411c3ca1b3SAdrian Hunter		if self.column_name2:
19421c3ca1b3SAdrian Hunter			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
19431c3ca1b3SAdrian Hunter
19441c3ca1b3SAdrian Hunter# Sample time ranges dialog data item converted and validated using 'samples' SQL table
19451c3ca1b3SAdrian Hunter
19461c3ca1b3SAdrian Hunterclass SampleTimeRangesDataItem(LineEditDataItem):
19471c3ca1b3SAdrian Hunter
19481c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
19491c3ca1b3SAdrian Hunter		self.column_name = column_name
19501c3ca1b3SAdrian Hunter
19511c3ca1b3SAdrian Hunter		self.last_id = 0
19521c3ca1b3SAdrian Hunter		self.first_time = 0
19531c3ca1b3SAdrian Hunter		self.last_time = 2 ** 64
19541c3ca1b3SAdrian Hunter
19551c3ca1b3SAdrian Hunter		query = QSqlQuery(glb.db)
19561c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
19571c3ca1b3SAdrian Hunter		if query.next():
19581c3ca1b3SAdrian Hunter			self.last_id = int(query.value(0))
19591c3ca1b3SAdrian Hunter			self.last_time = int(query.value(1))
19601c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
19611c3ca1b3SAdrian Hunter		if query.next():
19621c3ca1b3SAdrian Hunter			self.first_time = int(query.value(0))
19631c3ca1b3SAdrian Hunter		if placeholder_text:
19641c3ca1b3SAdrian Hunter			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
19651c3ca1b3SAdrian Hunter
19661c3ca1b3SAdrian Hunter		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
19671c3ca1b3SAdrian Hunter
19681c3ca1b3SAdrian Hunter	def IdBetween(self, query, lower_id, higher_id, order):
19691c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
19701c3ca1b3SAdrian Hunter		if query.next():
19711c3ca1b3SAdrian Hunter			return True, int(query.value(0))
19721c3ca1b3SAdrian Hunter		else:
19731c3ca1b3SAdrian Hunter			return False, 0
19741c3ca1b3SAdrian Hunter
19751c3ca1b3SAdrian Hunter	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
19761c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
19771c3ca1b3SAdrian Hunter		while True:
19781c3ca1b3SAdrian Hunter			next_id = int((lower_id + higher_id) / 2)
19791c3ca1b3SAdrian Hunter			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
19801c3ca1b3SAdrian Hunter			if not query.next():
19811c3ca1b3SAdrian Hunter				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
19821c3ca1b3SAdrian Hunter				if not ok:
19831c3ca1b3SAdrian Hunter					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
19841c3ca1b3SAdrian Hunter					if not ok:
19851c3ca1b3SAdrian Hunter						return str(higher_id)
19861c3ca1b3SAdrian Hunter				next_id = dbid
19871c3ca1b3SAdrian Hunter				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
19881c3ca1b3SAdrian Hunter			next_time = int(query.value(0))
19891c3ca1b3SAdrian Hunter			if get_floor:
19901c3ca1b3SAdrian Hunter				if target_time > next_time:
19911c3ca1b3SAdrian Hunter					lower_id = next_id
19921c3ca1b3SAdrian Hunter				else:
19931c3ca1b3SAdrian Hunter					higher_id = next_id
19941c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
19951c3ca1b3SAdrian Hunter					return str(higher_id)
19961c3ca1b3SAdrian Hunter			else:
19971c3ca1b3SAdrian Hunter				if target_time >= next_time:
19981c3ca1b3SAdrian Hunter					lower_id = next_id
19991c3ca1b3SAdrian Hunter				else:
20001c3ca1b3SAdrian Hunter					higher_id = next_id
20011c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
20021c3ca1b3SAdrian Hunter					return str(lower_id)
20031c3ca1b3SAdrian Hunter
20041c3ca1b3SAdrian Hunter	def ConvertRelativeTime(self, val):
20051c3ca1b3SAdrian Hunter		mult = 1
20061c3ca1b3SAdrian Hunter		suffix = val[-2:]
20071c3ca1b3SAdrian Hunter		if suffix == "ms":
20081c3ca1b3SAdrian Hunter			mult = 1000000
20091c3ca1b3SAdrian Hunter		elif suffix == "us":
20101c3ca1b3SAdrian Hunter			mult = 1000
20111c3ca1b3SAdrian Hunter		elif suffix == "ns":
20121c3ca1b3SAdrian Hunter			mult = 1
20131c3ca1b3SAdrian Hunter		else:
20141c3ca1b3SAdrian Hunter			return val
20151c3ca1b3SAdrian Hunter		val = val[:-2].strip()
20161c3ca1b3SAdrian Hunter		if not self.IsNumber(val):
20171c3ca1b3SAdrian Hunter			return val
20181c3ca1b3SAdrian Hunter		val = int(val) * mult
20191c3ca1b3SAdrian Hunter		if val >= 0:
20201c3ca1b3SAdrian Hunter			val += self.first_time
20211c3ca1b3SAdrian Hunter		else:
20221c3ca1b3SAdrian Hunter			val += self.last_time
20231c3ca1b3SAdrian Hunter		return str(val)
20241c3ca1b3SAdrian Hunter
20251c3ca1b3SAdrian Hunter	def ConvertTimeRange(self, vrange):
20261c3ca1b3SAdrian Hunter		if vrange[0] == "":
20271c3ca1b3SAdrian Hunter			vrange[0] = str(self.first_time)
20281c3ca1b3SAdrian Hunter		if vrange[1] == "":
20291c3ca1b3SAdrian Hunter			vrange[1] = str(self.last_time)
20301c3ca1b3SAdrian Hunter		vrange[0] = self.ConvertRelativeTime(vrange[0])
20311c3ca1b3SAdrian Hunter		vrange[1] = self.ConvertRelativeTime(vrange[1])
20321c3ca1b3SAdrian Hunter		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
20331c3ca1b3SAdrian Hunter			return False
20341c3ca1b3SAdrian Hunter		beg_range = max(int(vrange[0]), self.first_time)
20351c3ca1b3SAdrian Hunter		end_range = min(int(vrange[1]), self.last_time)
20361c3ca1b3SAdrian Hunter		if beg_range > self.last_time or end_range < self.first_time:
20371c3ca1b3SAdrian Hunter			return False
20381c3ca1b3SAdrian Hunter		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
20391c3ca1b3SAdrian Hunter		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
20401c3ca1b3SAdrian Hunter		return True
20411c3ca1b3SAdrian Hunter
20421c3ca1b3SAdrian Hunter	def AddTimeRange(self, value, ranges):
20431c3ca1b3SAdrian Hunter		n = value.count("-")
20441c3ca1b3SAdrian Hunter		if n == 1:
20451c3ca1b3SAdrian Hunter			pass
20461c3ca1b3SAdrian Hunter		elif n == 2:
20471c3ca1b3SAdrian Hunter			if value.split("-")[1].strip() == "":
20481c3ca1b3SAdrian Hunter				n = 1
20491c3ca1b3SAdrian Hunter		elif n == 3:
20501c3ca1b3SAdrian Hunter			n = 2
20511c3ca1b3SAdrian Hunter		else:
20521c3ca1b3SAdrian Hunter			return False
20531c3ca1b3SAdrian Hunter		pos = findnth(value, "-", n)
20541c3ca1b3SAdrian Hunter		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
20551c3ca1b3SAdrian Hunter		if self.ConvertTimeRange(vrange):
20561c3ca1b3SAdrian Hunter			ranges.append(vrange)
20571c3ca1b3SAdrian Hunter			return True
20581c3ca1b3SAdrian Hunter		return False
20591c3ca1b3SAdrian Hunter
20601c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
20611c3ca1b3SAdrian Hunter		ranges = []
20621c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
20631c3ca1b3SAdrian Hunter			if not self.AddTimeRange(value, ranges):
20641c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
20651c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
20661c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
20671c3ca1b3SAdrian Hunter
20680924cd68SAdrian Hunter# Report Dialog Base
2069210cf1f9SAdrian Hunter
20700924cd68SAdrian Hunterclass ReportDialogBase(QDialog):
2071210cf1f9SAdrian Hunter
20720924cd68SAdrian Hunter	def __init__(self, glb, title, items, partial, parent=None):
20730924cd68SAdrian Hunter		super(ReportDialogBase, self).__init__(parent)
2074210cf1f9SAdrian Hunter
2075210cf1f9SAdrian Hunter		self.glb = glb
2076210cf1f9SAdrian Hunter
20770bf0947aSAdrian Hunter		self.report_vars = ReportVars()
2078210cf1f9SAdrian Hunter
20790924cd68SAdrian Hunter		self.setWindowTitle(title)
2080210cf1f9SAdrian Hunter		self.setMinimumWidth(600)
2081210cf1f9SAdrian Hunter
20821c3ca1b3SAdrian Hunter		self.data_items = [x(glb, self) for x in items]
2083210cf1f9SAdrian Hunter
20840924cd68SAdrian Hunter		self.partial = partial
20850924cd68SAdrian Hunter
2086210cf1f9SAdrian Hunter		self.grid = QGridLayout()
2087210cf1f9SAdrian Hunter
2088210cf1f9SAdrian Hunter		for row in xrange(len(self.data_items)):
2089210cf1f9SAdrian Hunter			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2090210cf1f9SAdrian Hunter			self.grid.addWidget(self.data_items[row].widget, row, 1)
2091210cf1f9SAdrian Hunter
2092210cf1f9SAdrian Hunter		self.status = QLabel()
2093210cf1f9SAdrian Hunter
2094210cf1f9SAdrian Hunter		self.ok_button = QPushButton("Ok", self)
2095210cf1f9SAdrian Hunter		self.ok_button.setDefault(True)
2096210cf1f9SAdrian Hunter		self.ok_button.released.connect(self.Ok)
2097210cf1f9SAdrian Hunter		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2098210cf1f9SAdrian Hunter
2099210cf1f9SAdrian Hunter		self.cancel_button = QPushButton("Cancel", self)
2100210cf1f9SAdrian Hunter		self.cancel_button.released.connect(self.reject)
2101210cf1f9SAdrian Hunter		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2102210cf1f9SAdrian Hunter
2103210cf1f9SAdrian Hunter		self.hbox = QHBoxLayout()
2104210cf1f9SAdrian Hunter		#self.hbox.addStretch()
2105210cf1f9SAdrian Hunter		self.hbox.addWidget(self.status)
2106210cf1f9SAdrian Hunter		self.hbox.addWidget(self.ok_button)
2107210cf1f9SAdrian Hunter		self.hbox.addWidget(self.cancel_button)
2108210cf1f9SAdrian Hunter
2109210cf1f9SAdrian Hunter		self.vbox = QVBoxLayout()
2110210cf1f9SAdrian Hunter		self.vbox.addLayout(self.grid)
2111210cf1f9SAdrian Hunter		self.vbox.addLayout(self.hbox)
2112210cf1f9SAdrian Hunter
2113210cf1f9SAdrian Hunter		self.setLayout(self.vbox);
2114210cf1f9SAdrian Hunter
2115210cf1f9SAdrian Hunter	def Ok(self):
21160bf0947aSAdrian Hunter		vars = self.report_vars
21171c3ca1b3SAdrian Hunter		for d in self.data_items:
21181c3ca1b3SAdrian Hunter			if d.id == "REPORTNAME":
21191c3ca1b3SAdrian Hunter				vars.name = d.value
2120947cc38dSAdrian Hunter		if not vars.name:
2121210cf1f9SAdrian Hunter			self.ShowMessage("Report name is required")
2122210cf1f9SAdrian Hunter			return
2123210cf1f9SAdrian Hunter		for d in self.data_items:
2124210cf1f9SAdrian Hunter			if not d.IsValid():
2125210cf1f9SAdrian Hunter				return
2126210cf1f9SAdrian Hunter		for d in self.data_items[1:]:
2127cd358012SAdrian Hunter			if d.id == "LIMIT":
2128cd358012SAdrian Hunter				vars.limit = d.value
2129cd358012SAdrian Hunter			elif len(d.value):
21300bf0947aSAdrian Hunter				if len(vars.where_clause):
21310bf0947aSAdrian Hunter					vars.where_clause += " AND "
21320bf0947aSAdrian Hunter				vars.where_clause += d.value
21330bf0947aSAdrian Hunter		if len(vars.where_clause):
21340924cd68SAdrian Hunter			if self.partial:
21350bf0947aSAdrian Hunter				vars.where_clause = " AND ( " + vars.where_clause + " ) "
2136210cf1f9SAdrian Hunter			else:
21370bf0947aSAdrian Hunter				vars.where_clause = " WHERE " + vars.where_clause + " "
2138210cf1f9SAdrian Hunter		self.accept()
2139210cf1f9SAdrian Hunter
2140210cf1f9SAdrian Hunter	def ShowMessage(self, msg):
2141210cf1f9SAdrian Hunter		self.status.setText("<font color=#FF0000>" + msg)
2142210cf1f9SAdrian Hunter
2143210cf1f9SAdrian Hunter	def ClearMessage(self):
2144210cf1f9SAdrian Hunter		self.status.setText("")
2145210cf1f9SAdrian Hunter
21460924cd68SAdrian Hunter# Selected branch report creation dialog
21470924cd68SAdrian Hunter
21480924cd68SAdrian Hunterclass SelectedBranchDialog(ReportDialogBase):
21490924cd68SAdrian Hunter
21500924cd68SAdrian Hunter	def __init__(self, glb, parent=None):
21510924cd68SAdrian Hunter		title = "Selected Branches"
21521c3ca1b3SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
21531c3ca1b3SAdrian Hunter			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
21541c3ca1b3SAdrian Hunter			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
21551c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
21561c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
21571c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
21581c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
21591c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
21601c3ca1b3SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
21610924cd68SAdrian Hunter		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
21620924cd68SAdrian Hunter
216376099f98SAdrian Hunter# Event list
216476099f98SAdrian Hunter
216576099f98SAdrian Hunterdef GetEventList(db):
216676099f98SAdrian Hunter	events = []
216776099f98SAdrian Hunter	query = QSqlQuery(db)
216876099f98SAdrian Hunter	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
216976099f98SAdrian Hunter	while query.next():
217076099f98SAdrian Hunter		events.append(query.value(0))
217176099f98SAdrian Hunter	return events
217276099f98SAdrian Hunter
2173655cb952SAdrian Hunter# Is a table selectable
2174655cb952SAdrian Hunter
2175*530e22fdSAdrian Hunterdef IsSelectable(db, table, sql = "", columns = "*"):
2176655cb952SAdrian Hunter	query = QSqlQuery(db)
2177655cb952SAdrian Hunter	try:
2178*530e22fdSAdrian Hunter		QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
2179655cb952SAdrian Hunter	except:
2180655cb952SAdrian Hunter		return False
2181655cb952SAdrian Hunter	return True
2182655cb952SAdrian Hunter
21838392b74bSAdrian Hunter# SQL table data model item
21848392b74bSAdrian Hunter
21858392b74bSAdrian Hunterclass SQLTableItem():
21868392b74bSAdrian Hunter
21878392b74bSAdrian Hunter	def __init__(self, row, data):
21888392b74bSAdrian Hunter		self.row = row
21898392b74bSAdrian Hunter		self.data = data
21908392b74bSAdrian Hunter
21918392b74bSAdrian Hunter	def getData(self, column):
21928392b74bSAdrian Hunter		return self.data[column]
21938392b74bSAdrian Hunter
21948392b74bSAdrian Hunter# SQL table data model
21958392b74bSAdrian Hunter
21968392b74bSAdrian Hunterclass SQLTableModel(TableModel):
21978392b74bSAdrian Hunter
21988392b74bSAdrian Hunter	progress = Signal(object)
21998392b74bSAdrian Hunter
22008c90fef9SAdrian Hunter	def __init__(self, glb, sql, column_headers, parent=None):
22018392b74bSAdrian Hunter		super(SQLTableModel, self).__init__(parent)
22028392b74bSAdrian Hunter		self.glb = glb
22038392b74bSAdrian Hunter		self.more = True
22048392b74bSAdrian Hunter		self.populated = 0
22058c90fef9SAdrian Hunter		self.column_headers = column_headers
22068453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
22078392b74bSAdrian Hunter		self.fetcher.done.connect(self.Update)
22088392b74bSAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
22098392b74bSAdrian Hunter
22108392b74bSAdrian Hunter	def DisplayData(self, item, index):
22118392b74bSAdrian Hunter		self.FetchIfNeeded(item.row)
22128392b74bSAdrian Hunter		return item.getData(index.column())
22138392b74bSAdrian Hunter
22148392b74bSAdrian Hunter	def AddSample(self, data):
22158392b74bSAdrian Hunter		child = SQLTableItem(self.populated, data)
22168392b74bSAdrian Hunter		self.child_items.append(child)
22178392b74bSAdrian Hunter		self.populated += 1
22188392b74bSAdrian Hunter
22198392b74bSAdrian Hunter	def Update(self, fetched):
22208392b74bSAdrian Hunter		if not fetched:
22218392b74bSAdrian Hunter			self.more = False
22228392b74bSAdrian Hunter			self.progress.emit(0)
22238392b74bSAdrian Hunter		child_count = self.child_count
22248392b74bSAdrian Hunter		count = self.populated - child_count
22258392b74bSAdrian Hunter		if count > 0:
22268392b74bSAdrian Hunter			parent = QModelIndex()
22278392b74bSAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
22288392b74bSAdrian Hunter			self.insertRows(child_count, count, parent)
22298392b74bSAdrian Hunter			self.child_count += count
22308392b74bSAdrian Hunter			self.endInsertRows()
22318392b74bSAdrian Hunter			self.progress.emit(self.child_count)
22328392b74bSAdrian Hunter
22338392b74bSAdrian Hunter	def FetchMoreRecords(self, count):
22348392b74bSAdrian Hunter		current = self.child_count
22358392b74bSAdrian Hunter		if self.more:
22368392b74bSAdrian Hunter			self.fetcher.Fetch(count)
22378392b74bSAdrian Hunter		else:
22388392b74bSAdrian Hunter			self.progress.emit(0)
22398392b74bSAdrian Hunter		return current
22408392b74bSAdrian Hunter
22418392b74bSAdrian Hunter	def HasMoreRecords(self):
22428392b74bSAdrian Hunter		return self.more
22438392b74bSAdrian Hunter
22448c90fef9SAdrian Hunter	def columnCount(self, parent=None):
22458c90fef9SAdrian Hunter		return len(self.column_headers)
22468c90fef9SAdrian Hunter
22478c90fef9SAdrian Hunter	def columnHeader(self, column):
22488c90fef9SAdrian Hunter		return self.column_headers[column]
22498c90fef9SAdrian Hunter
22508453c936SAdrian Hunter	def SQLTableDataPrep(self, query, count):
22518453c936SAdrian Hunter		data = []
22528453c936SAdrian Hunter		for i in xrange(count):
22538453c936SAdrian Hunter			data.append(query.value(i))
22548453c936SAdrian Hunter		return data
22558453c936SAdrian Hunter
22568392b74bSAdrian Hunter# SQL automatic table data model
22578392b74bSAdrian Hunter
22588392b74bSAdrian Hunterclass SQLAutoTableModel(SQLTableModel):
22598392b74bSAdrian Hunter
22608392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
22618392b74bSAdrian Hunter		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
22628392b74bSAdrian Hunter		if table_name == "comm_threads_view":
22638392b74bSAdrian Hunter			# For now, comm_threads_view has no id column
22648392b74bSAdrian Hunter			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
22658c90fef9SAdrian Hunter		column_headers = []
22668392b74bSAdrian Hunter		query = QSqlQuery(glb.db)
22678392b74bSAdrian Hunter		if glb.dbref.is_sqlite3:
22688392b74bSAdrian Hunter			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
22698392b74bSAdrian Hunter			while query.next():
22708c90fef9SAdrian Hunter				column_headers.append(query.value(1))
22718392b74bSAdrian Hunter			if table_name == "sqlite_master":
22728392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
22738392b74bSAdrian Hunter		else:
22748392b74bSAdrian Hunter			if table_name[:19] == "information_schema.":
22758392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
22768392b74bSAdrian Hunter				select_table_name = table_name[19:]
22778392b74bSAdrian Hunter				schema = "information_schema"
22788392b74bSAdrian Hunter			else:
22798392b74bSAdrian Hunter				select_table_name = table_name
22808392b74bSAdrian Hunter				schema = "public"
22818392b74bSAdrian Hunter			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
22828392b74bSAdrian Hunter			while query.next():
22838c90fef9SAdrian Hunter				column_headers.append(query.value(0))
22848453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
22858453c936SAdrian Hunter			if table_name == "samples_view":
22868453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_view_DataPrep
22878453c936SAdrian Hunter			if table_name == "samples":
22888453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_DataPrep
22898c90fef9SAdrian Hunter		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
22908392b74bSAdrian Hunter
22918453c936SAdrian Hunter	def samples_view_DataPrep(self, query, count):
22928453c936SAdrian Hunter		data = []
22938453c936SAdrian Hunter		data.append(query.value(0))
22948453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
22958453c936SAdrian Hunter		data.append("{:>19}".format(query.value(1)))
22968453c936SAdrian Hunter		for i in xrange(2, count):
22978453c936SAdrian Hunter			data.append(query.value(i))
22988453c936SAdrian Hunter		return data
22998453c936SAdrian Hunter
23008453c936SAdrian Hunter	def samples_DataPrep(self, query, count):
23018453c936SAdrian Hunter		data = []
23028453c936SAdrian Hunter		for i in xrange(9):
23038453c936SAdrian Hunter			data.append(query.value(i))
23048453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
23058453c936SAdrian Hunter		data.append("{:>19}".format(query.value(9)))
23068453c936SAdrian Hunter		for i in xrange(10, count):
23078453c936SAdrian Hunter			data.append(query.value(i))
23088453c936SAdrian Hunter		return data
23098453c936SAdrian Hunter
23108392b74bSAdrian Hunter# Base class for custom ResizeColumnsToContents
23118392b74bSAdrian Hunter
23128392b74bSAdrian Hunterclass ResizeColumnsToContentsBase(QObject):
23138392b74bSAdrian Hunter
23148392b74bSAdrian Hunter	def __init__(self, parent=None):
23158392b74bSAdrian Hunter		super(ResizeColumnsToContentsBase, self).__init__(parent)
23168392b74bSAdrian Hunter
23178392b74bSAdrian Hunter	def ResizeColumnToContents(self, column, n):
23188392b74bSAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
23198392b74bSAdrian Hunter		# so implement a crude alternative
23208392b74bSAdrian Hunter		font = self.view.font()
23218392b74bSAdrian Hunter		metrics = QFontMetrics(font)
23228392b74bSAdrian Hunter		max = 0
23238392b74bSAdrian Hunter		for row in xrange(n):
23248392b74bSAdrian Hunter			val = self.data_model.child_items[row].data[column]
23258392b74bSAdrian Hunter			len = metrics.width(str(val) + "MM")
23268392b74bSAdrian Hunter			max = len if len > max else max
23278392b74bSAdrian Hunter		val = self.data_model.columnHeader(column)
23288392b74bSAdrian Hunter		len = metrics.width(str(val) + "MM")
23298392b74bSAdrian Hunter		max = len if len > max else max
23308392b74bSAdrian Hunter		self.view.setColumnWidth(column, max)
23318392b74bSAdrian Hunter
23328392b74bSAdrian Hunter	def ResizeColumnsToContents(self):
23338392b74bSAdrian Hunter		n = min(self.data_model.child_count, 100)
23348392b74bSAdrian Hunter		if n < 1:
23358392b74bSAdrian Hunter			# No data yet, so connect a signal to notify when there is
23368392b74bSAdrian Hunter			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
23378392b74bSAdrian Hunter			return
23388392b74bSAdrian Hunter		columns = self.data_model.columnCount()
23398392b74bSAdrian Hunter		for i in xrange(columns):
23408392b74bSAdrian Hunter			self.ResizeColumnToContents(i, n)
23418392b74bSAdrian Hunter
23428392b74bSAdrian Hunter	def UpdateColumnWidths(self, *x):
23438392b74bSAdrian Hunter		# This only needs to be done once, so disconnect the signal now
23448392b74bSAdrian Hunter		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
23458392b74bSAdrian Hunter		self.ResizeColumnsToContents()
23468392b74bSAdrian Hunter
234796c43b9aSAdrian Hunter# Convert value to CSV
234896c43b9aSAdrian Hunter
234996c43b9aSAdrian Hunterdef ToCSValue(val):
235096c43b9aSAdrian Hunter	if '"' in val:
235196c43b9aSAdrian Hunter		val = val.replace('"', '""')
235296c43b9aSAdrian Hunter	if "," in val or '"' in val:
235396c43b9aSAdrian Hunter		val = '"' + val + '"'
235496c43b9aSAdrian Hunter	return val
235596c43b9aSAdrian Hunter
235696c43b9aSAdrian Hunter# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
235796c43b9aSAdrian Hunter
235896c43b9aSAdrian Hunterglb_max_cols = 1000
235996c43b9aSAdrian Hunter
236096c43b9aSAdrian Hunterdef RowColumnKey(a):
236196c43b9aSAdrian Hunter	return a.row() * glb_max_cols + a.column()
236296c43b9aSAdrian Hunter
236396c43b9aSAdrian Hunter# Copy selected table cells to clipboard
236496c43b9aSAdrian Hunter
236596c43b9aSAdrian Hunterdef CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
236696c43b9aSAdrian Hunter	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
236796c43b9aSAdrian Hunter	idx_cnt = len(indexes)
236896c43b9aSAdrian Hunter	if not idx_cnt:
236996c43b9aSAdrian Hunter		return
237096c43b9aSAdrian Hunter	if idx_cnt == 1:
237196c43b9aSAdrian Hunter		with_hdr=False
237296c43b9aSAdrian Hunter	min_row = indexes[0].row()
237396c43b9aSAdrian Hunter	max_row = indexes[0].row()
237496c43b9aSAdrian Hunter	min_col = indexes[0].column()
237596c43b9aSAdrian Hunter	max_col = indexes[0].column()
237696c43b9aSAdrian Hunter	for i in indexes:
237796c43b9aSAdrian Hunter		min_row = min(min_row, i.row())
237896c43b9aSAdrian Hunter		max_row = max(max_row, i.row())
237996c43b9aSAdrian Hunter		min_col = min(min_col, i.column())
238096c43b9aSAdrian Hunter		max_col = max(max_col, i.column())
238196c43b9aSAdrian Hunter	if max_col > glb_max_cols:
238296c43b9aSAdrian Hunter		raise RuntimeError("glb_max_cols is too low")
238396c43b9aSAdrian Hunter	max_width = [0] * (1 + max_col - min_col)
238496c43b9aSAdrian Hunter	for i in indexes:
238596c43b9aSAdrian Hunter		c = i.column() - min_col
238696c43b9aSAdrian Hunter		max_width[c] = max(max_width[c], len(str(i.data())))
238796c43b9aSAdrian Hunter	text = ""
238896c43b9aSAdrian Hunter	pad = ""
238996c43b9aSAdrian Hunter	sep = ""
239096c43b9aSAdrian Hunter	if with_hdr:
239196c43b9aSAdrian Hunter		model = indexes[0].model()
239296c43b9aSAdrian Hunter		for col in range(min_col, max_col + 1):
239396c43b9aSAdrian Hunter			val = model.headerData(col, Qt.Horizontal)
239496c43b9aSAdrian Hunter			if as_csv:
239596c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
239696c43b9aSAdrian Hunter				sep = ","
239796c43b9aSAdrian Hunter			else:
239896c43b9aSAdrian Hunter				c = col - min_col
239996c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
240096c43b9aSAdrian Hunter				width = max_width[c]
240196c43b9aSAdrian Hunter				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
240296c43b9aSAdrian Hunter				if align & Qt.AlignRight:
240396c43b9aSAdrian Hunter					val = val.rjust(width)
240496c43b9aSAdrian Hunter				text += pad + sep + val
240596c43b9aSAdrian Hunter				pad = " " * (width - len(val))
240696c43b9aSAdrian Hunter				sep = "  "
240796c43b9aSAdrian Hunter		text += "\n"
240896c43b9aSAdrian Hunter		pad = ""
240996c43b9aSAdrian Hunter		sep = ""
241096c43b9aSAdrian Hunter	last_row = min_row
241196c43b9aSAdrian Hunter	for i in indexes:
241296c43b9aSAdrian Hunter		if i.row() > last_row:
241396c43b9aSAdrian Hunter			last_row = i.row()
241496c43b9aSAdrian Hunter			text += "\n"
241596c43b9aSAdrian Hunter			pad = ""
241696c43b9aSAdrian Hunter			sep = ""
241796c43b9aSAdrian Hunter		if as_csv:
241896c43b9aSAdrian Hunter			text += sep + ToCSValue(str(i.data()))
241996c43b9aSAdrian Hunter			sep = ","
242096c43b9aSAdrian Hunter		else:
242196c43b9aSAdrian Hunter			width = max_width[i.column() - min_col]
242296c43b9aSAdrian Hunter			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
242396c43b9aSAdrian Hunter				val = str(i.data()).rjust(width)
242496c43b9aSAdrian Hunter			else:
242596c43b9aSAdrian Hunter				val = str(i.data())
242696c43b9aSAdrian Hunter			text += pad + sep + val
242796c43b9aSAdrian Hunter			pad = " " * (width - len(val))
242896c43b9aSAdrian Hunter			sep = "  "
242996c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
243096c43b9aSAdrian Hunter
243196c43b9aSAdrian Hunterdef CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
243296c43b9aSAdrian Hunter	indexes = view.selectedIndexes()
243396c43b9aSAdrian Hunter	if not len(indexes):
243496c43b9aSAdrian Hunter		return
243596c43b9aSAdrian Hunter
243696c43b9aSAdrian Hunter	selection = view.selectionModel()
243796c43b9aSAdrian Hunter
243896c43b9aSAdrian Hunter	first = None
243996c43b9aSAdrian Hunter	for i in indexes:
244096c43b9aSAdrian Hunter		above = view.indexAbove(i)
244196c43b9aSAdrian Hunter		if not selection.isSelected(above):
244296c43b9aSAdrian Hunter			first = i
244396c43b9aSAdrian Hunter			break
244496c43b9aSAdrian Hunter
244596c43b9aSAdrian Hunter	if first is None:
244696c43b9aSAdrian Hunter		raise RuntimeError("CopyTreeCellsToClipboard internal error")
244796c43b9aSAdrian Hunter
244896c43b9aSAdrian Hunter	model = first.model()
244996c43b9aSAdrian Hunter	row_cnt = 0
245096c43b9aSAdrian Hunter	col_cnt = model.columnCount(first)
245196c43b9aSAdrian Hunter	max_width = [0] * col_cnt
245296c43b9aSAdrian Hunter
245396c43b9aSAdrian Hunter	indent_sz = 2
245496c43b9aSAdrian Hunter	indent_str = " " * indent_sz
245596c43b9aSAdrian Hunter
245696c43b9aSAdrian Hunter	expanded_mark_sz = 2
245796c43b9aSAdrian Hunter	if sys.version_info[0] == 3:
245896c43b9aSAdrian Hunter		expanded_mark = "\u25BC "
245996c43b9aSAdrian Hunter		not_expanded_mark = "\u25B6 "
246096c43b9aSAdrian Hunter	else:
246196c43b9aSAdrian Hunter		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
246296c43b9aSAdrian Hunter		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
246396c43b9aSAdrian Hunter	leaf_mark = "  "
246496c43b9aSAdrian Hunter
246596c43b9aSAdrian Hunter	if not as_csv:
246696c43b9aSAdrian Hunter		pos = first
246796c43b9aSAdrian Hunter		while True:
246896c43b9aSAdrian Hunter			row_cnt += 1
246996c43b9aSAdrian Hunter			row = pos.row()
247096c43b9aSAdrian Hunter			for c in range(col_cnt):
247196c43b9aSAdrian Hunter				i = pos.sibling(row, c)
247296c43b9aSAdrian Hunter				if c:
247396c43b9aSAdrian Hunter					n = len(str(i.data()))
247496c43b9aSAdrian Hunter				else:
247596c43b9aSAdrian Hunter					n = len(str(i.data()).strip())
247696c43b9aSAdrian Hunter					n += (i.internalPointer().level - 1) * indent_sz
247796c43b9aSAdrian Hunter					n += expanded_mark_sz
247896c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], n)
247996c43b9aSAdrian Hunter			pos = view.indexBelow(pos)
248096c43b9aSAdrian Hunter			if not selection.isSelected(pos):
248196c43b9aSAdrian Hunter				break
248296c43b9aSAdrian Hunter
248396c43b9aSAdrian Hunter	text = ""
248496c43b9aSAdrian Hunter	pad = ""
248596c43b9aSAdrian Hunter	sep = ""
248696c43b9aSAdrian Hunter	if with_hdr:
248796c43b9aSAdrian Hunter		for c in range(col_cnt):
248896c43b9aSAdrian Hunter			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
248996c43b9aSAdrian Hunter			if as_csv:
249096c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
249196c43b9aSAdrian Hunter				sep = ","
249296c43b9aSAdrian Hunter			else:
249396c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
249496c43b9aSAdrian Hunter				width = max_width[c]
249596c43b9aSAdrian Hunter				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
249696c43b9aSAdrian Hunter				if align & Qt.AlignRight:
249796c43b9aSAdrian Hunter					val = val.rjust(width)
249896c43b9aSAdrian Hunter				text += pad + sep + val
249996c43b9aSAdrian Hunter				pad = " " * (width - len(val))
250096c43b9aSAdrian Hunter				sep = "   "
250196c43b9aSAdrian Hunter		text += "\n"
250296c43b9aSAdrian Hunter		pad = ""
250396c43b9aSAdrian Hunter		sep = ""
250496c43b9aSAdrian Hunter
250596c43b9aSAdrian Hunter	pos = first
250696c43b9aSAdrian Hunter	while True:
250796c43b9aSAdrian Hunter		row = pos.row()
250896c43b9aSAdrian Hunter		for c in range(col_cnt):
250996c43b9aSAdrian Hunter			i = pos.sibling(row, c)
251096c43b9aSAdrian Hunter			val = str(i.data())
251196c43b9aSAdrian Hunter			if not c:
251296c43b9aSAdrian Hunter				if model.hasChildren(i):
251396c43b9aSAdrian Hunter					if view.isExpanded(i):
251496c43b9aSAdrian Hunter						mark = expanded_mark
251596c43b9aSAdrian Hunter					else:
251696c43b9aSAdrian Hunter						mark = not_expanded_mark
251796c43b9aSAdrian Hunter				else:
251896c43b9aSAdrian Hunter					mark = leaf_mark
251996c43b9aSAdrian Hunter				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
252096c43b9aSAdrian Hunter			if as_csv:
252196c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
252296c43b9aSAdrian Hunter				sep = ","
252396c43b9aSAdrian Hunter			else:
252496c43b9aSAdrian Hunter				width = max_width[c]
252596c43b9aSAdrian Hunter				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
252696c43b9aSAdrian Hunter					val = val.rjust(width)
252796c43b9aSAdrian Hunter				text += pad + sep + val
252896c43b9aSAdrian Hunter				pad = " " * (width - len(val))
252996c43b9aSAdrian Hunter				sep = "   "
253096c43b9aSAdrian Hunter		pos = view.indexBelow(pos)
253196c43b9aSAdrian Hunter		if not selection.isSelected(pos):
253296c43b9aSAdrian Hunter			break
253396c43b9aSAdrian Hunter		text = text.rstrip() + "\n"
253496c43b9aSAdrian Hunter		pad = ""
253596c43b9aSAdrian Hunter		sep = ""
253696c43b9aSAdrian Hunter
253796c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
253896c43b9aSAdrian Hunter
253996c43b9aSAdrian Hunterdef CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
254096c43b9aSAdrian Hunter	view.CopyCellsToClipboard(view, as_csv, with_hdr)
254196c43b9aSAdrian Hunter
254296c43b9aSAdrian Hunterdef CopyCellsToClipboardHdr(view):
254396c43b9aSAdrian Hunter	CopyCellsToClipboard(view, False, True)
254496c43b9aSAdrian Hunter
254596c43b9aSAdrian Hunterdef CopyCellsToClipboardCSV(view):
254696c43b9aSAdrian Hunter	CopyCellsToClipboard(view, True, True)
254796c43b9aSAdrian Hunter
25489bc4e4bfSAdrian Hunter# Context menu
25499bc4e4bfSAdrian Hunter
25509bc4e4bfSAdrian Hunterclass ContextMenu(object):
25519bc4e4bfSAdrian Hunter
25529bc4e4bfSAdrian Hunter	def __init__(self, view):
25539bc4e4bfSAdrian Hunter		self.view = view
25549bc4e4bfSAdrian Hunter		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
25559bc4e4bfSAdrian Hunter		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
25569bc4e4bfSAdrian Hunter
25579bc4e4bfSAdrian Hunter	def ShowContextMenu(self, pos):
25589bc4e4bfSAdrian Hunter		menu = QMenu(self.view)
25599bc4e4bfSAdrian Hunter		self.AddActions(menu)
25609bc4e4bfSAdrian Hunter		menu.exec_(self.view.mapToGlobal(pos))
25619bc4e4bfSAdrian Hunter
25629bc4e4bfSAdrian Hunter	def AddCopy(self, menu):
25639bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
25649bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
25659bc4e4bfSAdrian Hunter
25669bc4e4bfSAdrian Hunter	def AddActions(self, menu):
25679bc4e4bfSAdrian Hunter		self.AddCopy(menu)
25689bc4e4bfSAdrian Hunter
25699bc4e4bfSAdrian Hunterclass TreeContextMenu(ContextMenu):
25709bc4e4bfSAdrian Hunter
25719bc4e4bfSAdrian Hunter	def __init__(self, view):
25729bc4e4bfSAdrian Hunter		super(TreeContextMenu, self).__init__(view)
25739bc4e4bfSAdrian Hunter
25749bc4e4bfSAdrian Hunter	def AddActions(self, menu):
25759bc4e4bfSAdrian Hunter		i = self.view.currentIndex()
25769bc4e4bfSAdrian Hunter		text = str(i.data()).strip()
25779bc4e4bfSAdrian Hunter		if len(text):
25789bc4e4bfSAdrian Hunter			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
25799bc4e4bfSAdrian Hunter		self.AddCopy(menu)
25809bc4e4bfSAdrian Hunter
25818392b74bSAdrian Hunter# Table window
25828392b74bSAdrian Hunter
25838392b74bSAdrian Hunterclass TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
25848392b74bSAdrian Hunter
25858392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
25868392b74bSAdrian Hunter		super(TableWindow, self).__init__(parent)
25878392b74bSAdrian Hunter
25888392b74bSAdrian Hunter		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
25898392b74bSAdrian Hunter
25908392b74bSAdrian Hunter		self.model = QSortFilterProxyModel()
25918392b74bSAdrian Hunter		self.model.setSourceModel(self.data_model)
25928392b74bSAdrian Hunter
25938392b74bSAdrian Hunter		self.view = QTableView()
25948392b74bSAdrian Hunter		self.view.setModel(self.model)
25958392b74bSAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
25968392b74bSAdrian Hunter		self.view.verticalHeader().setVisible(False)
25978392b74bSAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
25988392b74bSAdrian Hunter		self.view.setSortingEnabled(True)
259996c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
260096c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
26018392b74bSAdrian Hunter
26028392b74bSAdrian Hunter		self.ResizeColumnsToContents()
26038392b74bSAdrian Hunter
26049bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
26059bc4e4bfSAdrian Hunter
26068392b74bSAdrian Hunter		self.find_bar = FindBar(self, self, True)
26078392b74bSAdrian Hunter
26088392b74bSAdrian Hunter		self.finder = ChildDataItemFinder(self.data_model)
26098392b74bSAdrian Hunter
26108392b74bSAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
26118392b74bSAdrian Hunter
26128392b74bSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
26138392b74bSAdrian Hunter
26148392b74bSAdrian Hunter		self.setWidget(self.vbox.Widget())
26158392b74bSAdrian Hunter
26168392b74bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
26178392b74bSAdrian Hunter
26188392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context):
26198392b74bSAdrian Hunter		self.view.setFocus()
26208392b74bSAdrian Hunter		self.find_bar.Busy()
26218392b74bSAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
26228392b74bSAdrian Hunter
26238392b74bSAdrian Hunter	def FindDone(self, row):
26248392b74bSAdrian Hunter		self.find_bar.Idle()
26258392b74bSAdrian Hunter		if row >= 0:
262635fa1ceeSAdrian Hunter			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
26278392b74bSAdrian Hunter		else:
26288392b74bSAdrian Hunter			self.find_bar.NotFound()
26298392b74bSAdrian Hunter
26308392b74bSAdrian Hunter# Table list
26318392b74bSAdrian Hunter
26328392b74bSAdrian Hunterdef GetTableList(glb):
26338392b74bSAdrian Hunter	tables = []
26348392b74bSAdrian Hunter	query = QSqlQuery(glb.db)
26358392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
26368392b74bSAdrian Hunter		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
26378392b74bSAdrian Hunter	else:
26388392b74bSAdrian 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")
26398392b74bSAdrian Hunter	while query.next():
26408392b74bSAdrian Hunter		tables.append(query.value(0))
26418392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
26428392b74bSAdrian Hunter		tables.append("sqlite_master")
26438392b74bSAdrian Hunter	else:
26448392b74bSAdrian Hunter		tables.append("information_schema.tables")
26458392b74bSAdrian Hunter		tables.append("information_schema.views")
26468392b74bSAdrian Hunter		tables.append("information_schema.columns")
26478392b74bSAdrian Hunter	return tables
26488392b74bSAdrian Hunter
2649cd358012SAdrian Hunter# Top Calls data model
2650cd358012SAdrian Hunter
2651cd358012SAdrian Hunterclass TopCallsModel(SQLTableModel):
2652cd358012SAdrian Hunter
2653cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2654cd358012SAdrian Hunter		text = ""
2655cd358012SAdrian Hunter		if not glb.dbref.is_sqlite3:
2656cd358012SAdrian Hunter			text = "::text"
2657cd358012SAdrian Hunter		limit = ""
2658cd358012SAdrian Hunter		if len(report_vars.limit):
2659cd358012SAdrian Hunter			limit = " LIMIT " + report_vars.limit
2660cd358012SAdrian Hunter		sql = ("SELECT comm, pid, tid, name,"
2661cd358012SAdrian Hunter			" CASE"
2662cd358012SAdrian Hunter			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2663cd358012SAdrian Hunter			" ELSE short_name"
2664cd358012SAdrian Hunter			" END AS dso,"
2665cd358012SAdrian Hunter			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2666cd358012SAdrian Hunter			" CASE"
2667cd358012SAdrian Hunter			" WHEN (calls.flags = 1) THEN 'no call'" + text +
2668cd358012SAdrian Hunter			" WHEN (calls.flags = 2) THEN 'no return'" + text +
2669cd358012SAdrian Hunter			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2670cd358012SAdrian Hunter			" ELSE ''" + text +
2671cd358012SAdrian Hunter			" END AS flags"
2672cd358012SAdrian Hunter			" FROM calls"
2673cd358012SAdrian Hunter			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2674cd358012SAdrian Hunter			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2675cd358012SAdrian Hunter			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
2676cd358012SAdrian Hunter			" INNER JOIN comms ON calls.comm_id = comms.id"
2677cd358012SAdrian Hunter			" INNER JOIN threads ON calls.thread_id = threads.id" +
2678cd358012SAdrian Hunter			report_vars.where_clause +
2679cd358012SAdrian Hunter			" ORDER BY elapsed_time DESC" +
2680cd358012SAdrian Hunter			limit
2681cd358012SAdrian Hunter			)
2682cd358012SAdrian Hunter		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2683cd358012SAdrian Hunter		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2684cd358012SAdrian Hunter		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2685cd358012SAdrian Hunter
2686cd358012SAdrian Hunter	def columnAlignment(self, column):
2687cd358012SAdrian Hunter		return self.alignment[column]
2688cd358012SAdrian Hunter
2689cd358012SAdrian Hunter# Top Calls report creation dialog
2690cd358012SAdrian Hunter
2691cd358012SAdrian Hunterclass TopCallsDialog(ReportDialogBase):
2692cd358012SAdrian Hunter
2693cd358012SAdrian Hunter	def __init__(self, glb, parent=None):
2694cd358012SAdrian Hunter		title = "Top Calls by Elapsed Time"
2695cd358012SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2696cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2697cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2698cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2699cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2700cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2701cd358012SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2702cd358012SAdrian Hunter			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2703cd358012SAdrian Hunter		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2704cd358012SAdrian Hunter
2705cd358012SAdrian Hunter# Top Calls window
2706cd358012SAdrian Hunter
2707cd358012SAdrian Hunterclass TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2708cd358012SAdrian Hunter
2709cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2710cd358012SAdrian Hunter		super(TopCallsWindow, self).__init__(parent)
2711cd358012SAdrian Hunter
2712cd358012SAdrian Hunter		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2713cd358012SAdrian Hunter		self.model = self.data_model
2714cd358012SAdrian Hunter
2715cd358012SAdrian Hunter		self.view = QTableView()
2716cd358012SAdrian Hunter		self.view.setModel(self.model)
2717cd358012SAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2718cd358012SAdrian Hunter		self.view.verticalHeader().setVisible(False)
271996c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
272096c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2721cd358012SAdrian Hunter
27229bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
27239bc4e4bfSAdrian Hunter
2724cd358012SAdrian Hunter		self.ResizeColumnsToContents()
2725cd358012SAdrian Hunter
2726cd358012SAdrian Hunter		self.find_bar = FindBar(self, self, True)
2727cd358012SAdrian Hunter
2728cd358012SAdrian Hunter		self.finder = ChildDataItemFinder(self.model)
2729cd358012SAdrian Hunter
2730cd358012SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2731cd358012SAdrian Hunter
2732cd358012SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2733cd358012SAdrian Hunter
2734cd358012SAdrian Hunter		self.setWidget(self.vbox.Widget())
2735cd358012SAdrian Hunter
2736cd358012SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2737cd358012SAdrian Hunter
2738cd358012SAdrian Hunter	def Find(self, value, direction, pattern, context):
2739cd358012SAdrian Hunter		self.view.setFocus()
2740cd358012SAdrian Hunter		self.find_bar.Busy()
2741cd358012SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
2742cd358012SAdrian Hunter
2743cd358012SAdrian Hunter	def FindDone(self, row):
2744cd358012SAdrian Hunter		self.find_bar.Idle()
2745cd358012SAdrian Hunter		if row >= 0:
2746cd358012SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2747cd358012SAdrian Hunter		else:
2748cd358012SAdrian Hunter			self.find_bar.NotFound()
2749cd358012SAdrian Hunter
27501beb5c7bSAdrian Hunter# Action Definition
27511beb5c7bSAdrian Hunter
27521beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None):
27531beb5c7bSAdrian Hunter	action = QAction(label, parent)
27541beb5c7bSAdrian Hunter	if shortcut != None:
27551beb5c7bSAdrian Hunter		action.setShortcuts(shortcut)
27561beb5c7bSAdrian Hunter	action.setStatusTip(tip)
27571beb5c7bSAdrian Hunter	action.triggered.connect(callback)
27581beb5c7bSAdrian Hunter	return action
27591beb5c7bSAdrian Hunter
27601beb5c7bSAdrian Hunter# Typical application actions
27611beb5c7bSAdrian Hunter
27621beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None):
27631beb5c7bSAdrian Hunter	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
27641beb5c7bSAdrian Hunter
27651beb5c7bSAdrian Hunter# Typical MDI actions
27661beb5c7bSAdrian Hunter
27671beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area):
27681beb5c7bSAdrian Hunter	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
27691beb5c7bSAdrian Hunter
27701beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area):
27711beb5c7bSAdrian Hunter	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
27721beb5c7bSAdrian Hunter
27731beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area):
27741beb5c7bSAdrian Hunter	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
27751beb5c7bSAdrian Hunter
27761beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area):
27771beb5c7bSAdrian Hunter	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
27781beb5c7bSAdrian Hunter
27791beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area):
27801beb5c7bSAdrian Hunter	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
27811beb5c7bSAdrian Hunter
27821beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area):
27831beb5c7bSAdrian Hunter	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
27841beb5c7bSAdrian Hunter
27851beb5c7bSAdrian Hunter# Typical MDI window menu
27861beb5c7bSAdrian Hunter
27871beb5c7bSAdrian Hunterclass WindowMenu():
27881beb5c7bSAdrian Hunter
27891beb5c7bSAdrian Hunter	def __init__(self, mdi_area, menu):
27901beb5c7bSAdrian Hunter		self.mdi_area = mdi_area
27911beb5c7bSAdrian Hunter		self.window_menu = menu.addMenu("&Windows")
27921beb5c7bSAdrian Hunter		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
27931beb5c7bSAdrian Hunter		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
27941beb5c7bSAdrian Hunter		self.tile_windows = CreateTileWindowsAction(mdi_area)
27951beb5c7bSAdrian Hunter		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
27961beb5c7bSAdrian Hunter		self.next_window = CreateNextWindowAction(mdi_area)
27971beb5c7bSAdrian Hunter		self.previous_window = CreatePreviousWindowAction(mdi_area)
27981beb5c7bSAdrian Hunter		self.window_menu.aboutToShow.connect(self.Update)
27991beb5c7bSAdrian Hunter
28001beb5c7bSAdrian Hunter	def Update(self):
28011beb5c7bSAdrian Hunter		self.window_menu.clear()
28021beb5c7bSAdrian Hunter		sub_window_count = len(self.mdi_area.subWindowList())
28031beb5c7bSAdrian Hunter		have_sub_windows = sub_window_count != 0
28041beb5c7bSAdrian Hunter		self.close_active_window.setEnabled(have_sub_windows)
28051beb5c7bSAdrian Hunter		self.close_all_windows.setEnabled(have_sub_windows)
28061beb5c7bSAdrian Hunter		self.tile_windows.setEnabled(have_sub_windows)
28071beb5c7bSAdrian Hunter		self.cascade_windows.setEnabled(have_sub_windows)
28081beb5c7bSAdrian Hunter		self.next_window.setEnabled(have_sub_windows)
28091beb5c7bSAdrian Hunter		self.previous_window.setEnabled(have_sub_windows)
28101beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_active_window)
28111beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_all_windows)
28121beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
28131beb5c7bSAdrian Hunter		self.window_menu.addAction(self.tile_windows)
28141beb5c7bSAdrian Hunter		self.window_menu.addAction(self.cascade_windows)
28151beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
28161beb5c7bSAdrian Hunter		self.window_menu.addAction(self.next_window)
28171beb5c7bSAdrian Hunter		self.window_menu.addAction(self.previous_window)
28181beb5c7bSAdrian Hunter		if sub_window_count == 0:
28191beb5c7bSAdrian Hunter			return
28201beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
28211beb5c7bSAdrian Hunter		nr = 1
28221beb5c7bSAdrian Hunter		for sub_window in self.mdi_area.subWindowList():
28231beb5c7bSAdrian Hunter			label = str(nr) + " " + sub_window.name
28241beb5c7bSAdrian Hunter			if nr < 10:
28251beb5c7bSAdrian Hunter				label = "&" + label
28261beb5c7bSAdrian Hunter			action = self.window_menu.addAction(label)
28271beb5c7bSAdrian Hunter			action.setCheckable(True)
28281beb5c7bSAdrian Hunter			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2829df8ea22aSAdrian Hunter			action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
28301beb5c7bSAdrian Hunter			self.window_menu.addAction(action)
28311beb5c7bSAdrian Hunter			nr += 1
28321beb5c7bSAdrian Hunter
28331beb5c7bSAdrian Hunter	def setActiveSubWindow(self, nr):
28341beb5c7bSAdrian Hunter		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
28351beb5c7bSAdrian Hunter
283665b24292SAdrian Hunter# Help text
283765b24292SAdrian Hunter
283865b24292SAdrian Hunterglb_help_text = """
283965b24292SAdrian Hunter<h1>Contents</h1>
284065b24292SAdrian Hunter<style>
284165b24292SAdrian Hunterp.c1 {
284265b24292SAdrian Hunter    text-indent: 40px;
284365b24292SAdrian Hunter}
284465b24292SAdrian Hunterp.c2 {
284565b24292SAdrian Hunter    text-indent: 80px;
284665b24292SAdrian Hunter}
284765b24292SAdrian Hunter}
284865b24292SAdrian Hunter</style>
284965b24292SAdrian Hunter<p class=c1><a href=#reports>1. Reports</a></p>
285065b24292SAdrian Hunter<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2851ae8b887cSAdrian Hunter<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2852ae8b887cSAdrian Hunter<p class=c2><a href=#allbranches>1.3 All branches</a></p>
2853ae8b887cSAdrian Hunter<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2854ae8b887cSAdrian Hunter<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
285565b24292SAdrian Hunter<p class=c1><a href=#tables>2. Tables</a></p>
285665b24292SAdrian Hunter<h1 id=reports>1. Reports</h1>
285765b24292SAdrian Hunter<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
285865b24292SAdrian HunterThe result is a GUI window with a tree representing a context-sensitive
285965b24292SAdrian Huntercall-graph. Expanding a couple of levels of the tree and adjusting column
286065b24292SAdrian Hunterwidths to suit will display something like:
286165b24292SAdrian Hunter<pre>
286265b24292SAdrian Hunter                                         Call Graph: pt_example
286365b24292SAdrian HunterCall Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
286465b24292SAdrian Hunterv- ls
286565b24292SAdrian Hunter    v- 2638:2638
286665b24292SAdrian Hunter        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
286765b24292SAdrian Hunter          |- unknown               unknown       1        13198     0.1              1              0.0
286865b24292SAdrian Hunter          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
286965b24292SAdrian Hunter          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
287065b24292SAdrian Hunter          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
287165b24292SAdrian Hunter             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
287265b24292SAdrian Hunter             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
287365b24292SAdrian Hunter             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
287465b24292SAdrian Hunter             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
287565b24292SAdrian Hunter             v- main               ls            1      8182043    99.6         180254             99.9
287665b24292SAdrian Hunter</pre>
287765b24292SAdrian Hunter<h3>Points to note:</h3>
287865b24292SAdrian Hunter<ul>
287965b24292SAdrian Hunter<li>The top level is a command name (comm)</li>
288065b24292SAdrian Hunter<li>The next level is a thread (pid:tid)</li>
288165b24292SAdrian Hunter<li>Subsequent levels are functions</li>
288265b24292SAdrian Hunter<li>'Count' is the number of calls</li>
288365b24292SAdrian Hunter<li>'Time' is the elapsed time until the function returns</li>
288465b24292SAdrian Hunter<li>Percentages are relative to the level above</li>
288565b24292SAdrian Hunter<li>'Branch Count' is the total number of branches for that function and all functions that it calls
288665b24292SAdrian Hunter</ul>
288765b24292SAdrian Hunter<h3>Find</h3>
288865b24292SAdrian HunterCtrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
288965b24292SAdrian HunterThe pattern matching symbols are ? for any character and * for zero or more characters.
2890ae8b887cSAdrian Hunter<h2 id=calltree>1.2 Call Tree</h2>
2891ae8b887cSAdrian HunterThe Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2892ae8b887cSAdrian HunterAlso the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2893ae8b887cSAdrian Hunter<h2 id=allbranches>1.3 All branches</h2>
289465b24292SAdrian HunterThe All branches report displays all branches in chronological order.
289565b24292SAdrian HunterNot all data is fetched immediately. More records can be fetched using the Fetch bar provided.
289665b24292SAdrian Hunter<h3>Disassembly</h3>
289765b24292SAdrian HunterOpen a branch to display disassembly. This only works if:
289865b24292SAdrian Hunter<ol>
289965b24292SAdrian Hunter<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
290065b24292SAdrian Hunter<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
290165b24292SAdrian HunterThe default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
290265b24292SAdrian HunterOne exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
290365b24292SAdrian Hunteror alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
290465b24292SAdrian Hunter</ol>
290565b24292SAdrian Hunter<h4 id=xed>Intel XED Setup</h4>
290665b24292SAdrian HunterTo use Intel XED, libxed.so must be present.  To build and install libxed.so:
290765b24292SAdrian Hunter<pre>
290865b24292SAdrian Huntergit clone https://github.com/intelxed/mbuild.git mbuild
290965b24292SAdrian Huntergit clone https://github.com/intelxed/xed
291065b24292SAdrian Huntercd xed
291165b24292SAdrian Hunter./mfile.py --share
291265b24292SAdrian Huntersudo ./mfile.py --prefix=/usr/local install
291365b24292SAdrian Huntersudo ldconfig
291465b24292SAdrian Hunter</pre>
2915*530e22fdSAdrian Hunter<h3>Instructions per Cycle (IPC)</h3>
2916*530e22fdSAdrian HunterIf available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
2917*530e22fdSAdrian Hunter<p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
2918*530e22fdSAdrian HunterDue to the granularity of timing information, the number of cycles for some code blocks will not be known.
2919*530e22fdSAdrian HunterIn that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
2920*530e22fdSAdrian Huntersince the previous displayed 'IPC'.
292165b24292SAdrian Hunter<h3>Find</h3>
292265b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
292365b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
292465b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
2925ae8b887cSAdrian Hunter<h2 id=selectedbranches>1.4 Selected branches</h2>
292665b24292SAdrian HunterThis is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
292765b24292SAdrian Hunterby various selection criteria. A dialog box displays available criteria which are AND'ed together.
2928ae8b887cSAdrian Hunter<h3>1.4.1 Time ranges</h3>
292965b24292SAdrian HunterThe time ranges hint text shows the total time range. Relative time ranges can also be entered in
293065b24292SAdrian Hunterms, us or ns. Also, negative values are relative to the end of trace.  Examples:
293165b24292SAdrian Hunter<pre>
293265b24292SAdrian Hunter	81073085947329-81073085958238	From 81073085947329 to 81073085958238
293365b24292SAdrian Hunter	100us-200us		From 100us to 200us
293465b24292SAdrian Hunter	10ms-			From 10ms to the end
293565b24292SAdrian Hunter	-100ns			The first 100ns
293665b24292SAdrian Hunter	-10ms-			The last 10ms
293765b24292SAdrian Hunter</pre>
293865b24292SAdrian HunterN.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2939ae8b887cSAdrian Hunter<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2940cd358012SAdrian HunterThe Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
2941cd358012SAdrian HunterThe data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2942cd358012SAdrian HunterIf not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
294365b24292SAdrian Hunter<h1 id=tables>2. Tables</h1>
294465b24292SAdrian HunterThe Tables menu shows all tables and views in the database. Most tables have an associated view
294565b24292SAdrian Hunterwhich displays the information in a more friendly way. Not all data for large tables is fetched
294665b24292SAdrian Hunterimmediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
294765b24292SAdrian Hunterbut that can be slow for large tables.
294865b24292SAdrian Hunter<p>There are also tables of database meta-information.
294965b24292SAdrian HunterFor SQLite3 databases, the sqlite_master table is included.
295065b24292SAdrian HunterFor PostgreSQL databases, information_schema.tables/views/columns are included.
295165b24292SAdrian Hunter<h3>Find</h3>
295265b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
295365b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
295465b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
295535fa1ceeSAdrian Hunter<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
295635fa1ceeSAdrian Hunterwill go to the next/previous result in id order, instead of display order.
295765b24292SAdrian Hunter"""
295865b24292SAdrian Hunter
295965b24292SAdrian Hunter# Help window
296065b24292SAdrian Hunter
296165b24292SAdrian Hunterclass HelpWindow(QMdiSubWindow):
296265b24292SAdrian Hunter
296365b24292SAdrian Hunter	def __init__(self, glb, parent=None):
296465b24292SAdrian Hunter		super(HelpWindow, self).__init__(parent)
296565b24292SAdrian Hunter
296665b24292SAdrian Hunter		self.text = QTextBrowser()
296765b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
296865b24292SAdrian Hunter		self.text.setReadOnly(True)
296965b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
297065b24292SAdrian Hunter
297165b24292SAdrian Hunter		self.setWidget(self.text)
297265b24292SAdrian Hunter
297365b24292SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
297465b24292SAdrian Hunter
297565b24292SAdrian Hunter# Main window that only displays the help text
297665b24292SAdrian Hunter
297765b24292SAdrian Hunterclass HelpOnlyWindow(QMainWindow):
297865b24292SAdrian Hunter
297965b24292SAdrian Hunter	def __init__(self, parent=None):
298065b24292SAdrian Hunter		super(HelpOnlyWindow, self).__init__(parent)
298165b24292SAdrian Hunter
298265b24292SAdrian Hunter		self.setMinimumSize(200, 100)
298365b24292SAdrian Hunter		self.resize(800, 600)
298465b24292SAdrian Hunter		self.setWindowTitle("Exported SQL Viewer Help")
298565b24292SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
298665b24292SAdrian Hunter
298765b24292SAdrian Hunter		self.text = QTextBrowser()
298865b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
298965b24292SAdrian Hunter		self.text.setReadOnly(True)
299065b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
299165b24292SAdrian Hunter
299265b24292SAdrian Hunter		self.setCentralWidget(self.text)
299365b24292SAdrian Hunter
2994b62d18abSAdrian Hunter# PostqreSQL server version
2995b62d18abSAdrian Hunter
2996b62d18abSAdrian Hunterdef PostqreSQLServerVersion(db):
2997b62d18abSAdrian Hunter	query = QSqlQuery(db)
2998b62d18abSAdrian Hunter	QueryExec(query, "SELECT VERSION()")
2999b62d18abSAdrian Hunter	if query.next():
3000b62d18abSAdrian Hunter		v_str = query.value(0)
3001b62d18abSAdrian Hunter		v_list = v_str.strip().split(" ")
3002b62d18abSAdrian Hunter		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
3003b62d18abSAdrian Hunter			return v_list[1]
3004b62d18abSAdrian Hunter		return v_str
3005b62d18abSAdrian Hunter	return "Unknown"
3006b62d18abSAdrian Hunter
3007b62d18abSAdrian Hunter# SQLite version
3008b62d18abSAdrian Hunter
3009b62d18abSAdrian Hunterdef SQLiteVersion(db):
3010b62d18abSAdrian Hunter	query = QSqlQuery(db)
3011b62d18abSAdrian Hunter	QueryExec(query, "SELECT sqlite_version()")
3012b62d18abSAdrian Hunter	if query.next():
3013b62d18abSAdrian Hunter		return query.value(0)
3014b62d18abSAdrian Hunter	return "Unknown"
3015b62d18abSAdrian Hunter
3016b62d18abSAdrian Hunter# About dialog
3017b62d18abSAdrian Hunter
3018b62d18abSAdrian Hunterclass AboutDialog(QDialog):
3019b62d18abSAdrian Hunter
3020b62d18abSAdrian Hunter	def __init__(self, glb, parent=None):
3021b62d18abSAdrian Hunter		super(AboutDialog, self).__init__(parent)
3022b62d18abSAdrian Hunter
3023b62d18abSAdrian Hunter		self.setWindowTitle("About Exported SQL Viewer")
3024b62d18abSAdrian Hunter		self.setMinimumWidth(300)
3025b62d18abSAdrian Hunter
3026b62d18abSAdrian Hunter		pyside_version = "1" if pyside_version_1 else "2"
3027b62d18abSAdrian Hunter
3028b62d18abSAdrian Hunter		text = "<pre>"
3029b62d18abSAdrian Hunter		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
3030b62d18abSAdrian Hunter		text += "PySide version:     " + pyside_version + "\n"
3031b62d18abSAdrian Hunter		text += "Qt version:         " + qVersion() + "\n"
3032b62d18abSAdrian Hunter		if glb.dbref.is_sqlite3:
3033b62d18abSAdrian Hunter			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
3034b62d18abSAdrian Hunter		else:
3035b62d18abSAdrian Hunter			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
3036b62d18abSAdrian Hunter		text += "</pre>"
3037b62d18abSAdrian Hunter
3038b62d18abSAdrian Hunter		self.text = QTextBrowser()
3039b62d18abSAdrian Hunter		self.text.setHtml(text)
3040b62d18abSAdrian Hunter		self.text.setReadOnly(True)
3041b62d18abSAdrian Hunter		self.text.setOpenExternalLinks(True)
3042b62d18abSAdrian Hunter
3043b62d18abSAdrian Hunter		self.vbox = QVBoxLayout()
3044b62d18abSAdrian Hunter		self.vbox.addWidget(self.text)
3045b62d18abSAdrian Hunter
3046b62d18abSAdrian Hunter		self.setLayout(self.vbox);
3047b62d18abSAdrian Hunter
304882f68e28SAdrian Hunter# Font resize
304982f68e28SAdrian Hunter
305082f68e28SAdrian Hunterdef ResizeFont(widget, diff):
305182f68e28SAdrian Hunter	font = widget.font()
305282f68e28SAdrian Hunter	sz = font.pointSize()
305382f68e28SAdrian Hunter	font.setPointSize(sz + diff)
305482f68e28SAdrian Hunter	widget.setFont(font)
305582f68e28SAdrian Hunter
305682f68e28SAdrian Hunterdef ShrinkFont(widget):
305782f68e28SAdrian Hunter	ResizeFont(widget, -1)
305882f68e28SAdrian Hunter
305982f68e28SAdrian Hunterdef EnlargeFont(widget):
306082f68e28SAdrian Hunter	ResizeFont(widget, 1)
306182f68e28SAdrian Hunter
30621beb5c7bSAdrian Hunter# Unique name for sub-windows
30631beb5c7bSAdrian Hunter
30641beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr):
30651beb5c7bSAdrian Hunter	if nr > 1:
30661beb5c7bSAdrian Hunter		name += " <" + str(nr) + ">"
30671beb5c7bSAdrian Hunter	return name
30681beb5c7bSAdrian Hunter
30691beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name):
30701beb5c7bSAdrian Hunter	nr = 1
30711beb5c7bSAdrian Hunter	while True:
30721beb5c7bSAdrian Hunter		unique_name = NumberedWindowName(name, nr)
30731beb5c7bSAdrian Hunter		ok = True
30741beb5c7bSAdrian Hunter		for sub_window in mdi_area.subWindowList():
30751beb5c7bSAdrian Hunter			if sub_window.name == unique_name:
30761beb5c7bSAdrian Hunter				ok = False
30771beb5c7bSAdrian Hunter				break
30781beb5c7bSAdrian Hunter		if ok:
30791beb5c7bSAdrian Hunter			return unique_name
30801beb5c7bSAdrian Hunter		nr += 1
30811beb5c7bSAdrian Hunter
30821beb5c7bSAdrian Hunter# Add a sub-window
30831beb5c7bSAdrian Hunter
30841beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name):
30851beb5c7bSAdrian Hunter	unique_name = UniqueSubWindowName(mdi_area, name)
30861beb5c7bSAdrian Hunter	sub_window.setMinimumSize(200, 100)
30871beb5c7bSAdrian Hunter	sub_window.resize(800, 600)
30881beb5c7bSAdrian Hunter	sub_window.setWindowTitle(unique_name)
30891beb5c7bSAdrian Hunter	sub_window.setAttribute(Qt.WA_DeleteOnClose)
30901beb5c7bSAdrian Hunter	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
30911beb5c7bSAdrian Hunter	sub_window.name = unique_name
30921beb5c7bSAdrian Hunter	mdi_area.addSubWindow(sub_window)
30931beb5c7bSAdrian Hunter	sub_window.show()
30941beb5c7bSAdrian Hunter
3095031c2a00SAdrian Hunter# Main window
3096031c2a00SAdrian Hunter
3097031c2a00SAdrian Hunterclass MainWindow(QMainWindow):
3098031c2a00SAdrian Hunter
3099031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
3100031c2a00SAdrian Hunter		super(MainWindow, self).__init__(parent)
3101031c2a00SAdrian Hunter
3102031c2a00SAdrian Hunter		self.glb = glb
3103031c2a00SAdrian Hunter
31041beb5c7bSAdrian Hunter		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3105031c2a00SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3106031c2a00SAdrian Hunter		self.setMinimumSize(200, 100)
3107031c2a00SAdrian Hunter
31081beb5c7bSAdrian Hunter		self.mdi_area = QMdiArea()
31091beb5c7bSAdrian Hunter		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
31101beb5c7bSAdrian Hunter		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3111031c2a00SAdrian Hunter
31121beb5c7bSAdrian Hunter		self.setCentralWidget(self.mdi_area)
3113031c2a00SAdrian Hunter
31141beb5c7bSAdrian Hunter		menu = self.menuBar()
3115031c2a00SAdrian Hunter
31161beb5c7bSAdrian Hunter		file_menu = menu.addMenu("&File")
31171beb5c7bSAdrian Hunter		file_menu.addAction(CreateExitAction(glb.app, self))
31181beb5c7bSAdrian Hunter
3119ebd70c7dSAdrian Hunter		edit_menu = menu.addMenu("&Edit")
312096c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
312196c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3122ebd70c7dSAdrian Hunter		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
31238392b74bSAdrian Hunter		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
312482f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
312582f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3126ebd70c7dSAdrian Hunter
31271beb5c7bSAdrian Hunter		reports_menu = menu.addMenu("&Reports")
3128655cb952SAdrian Hunter		if IsSelectable(glb.db, "calls"):
31291beb5c7bSAdrian Hunter			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
31301beb5c7bSAdrian Hunter
3131ae8b887cSAdrian Hunter		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3132ae8b887cSAdrian Hunter			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3133ae8b887cSAdrian Hunter
313476099f98SAdrian Hunter		self.EventMenu(GetEventList(glb.db), reports_menu)
313576099f98SAdrian Hunter
3136cd358012SAdrian Hunter		if IsSelectable(glb.db, "calls"):
3137cd358012SAdrian Hunter			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3138cd358012SAdrian Hunter
31398392b74bSAdrian Hunter		self.TableMenu(GetTableList(glb), menu)
31408392b74bSAdrian Hunter
31411beb5c7bSAdrian Hunter		self.window_menu = WindowMenu(self.mdi_area, menu)
31421beb5c7bSAdrian Hunter
314365b24292SAdrian Hunter		help_menu = menu.addMenu("&Help")
314465b24292SAdrian Hunter		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3145b62d18abSAdrian Hunter		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
314665b24292SAdrian Hunter
31474b208453SAdrian Hunter	def Try(self, fn):
31484b208453SAdrian Hunter		win = self.mdi_area.activeSubWindow()
31494b208453SAdrian Hunter		if win:
31504b208453SAdrian Hunter			try:
31514b208453SAdrian Hunter				fn(win.view)
31524b208453SAdrian Hunter			except:
31534b208453SAdrian Hunter				pass
31544b208453SAdrian Hunter
315596c43b9aSAdrian Hunter	def CopyToClipboard(self):
315696c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardHdr)
315796c43b9aSAdrian Hunter
315896c43b9aSAdrian Hunter	def CopyToClipboardCSV(self):
315996c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardCSV)
316096c43b9aSAdrian Hunter
3161ebd70c7dSAdrian Hunter	def Find(self):
3162ebd70c7dSAdrian Hunter		win = self.mdi_area.activeSubWindow()
3163ebd70c7dSAdrian Hunter		if win:
3164ebd70c7dSAdrian Hunter			try:
3165ebd70c7dSAdrian Hunter				win.find_bar.Activate()
3166ebd70c7dSAdrian Hunter			except:
3167ebd70c7dSAdrian Hunter				pass
3168ebd70c7dSAdrian Hunter
31698392b74bSAdrian Hunter	def FetchMoreRecords(self):
31708392b74bSAdrian Hunter		win = self.mdi_area.activeSubWindow()
31718392b74bSAdrian Hunter		if win:
31728392b74bSAdrian Hunter			try:
31738392b74bSAdrian Hunter				win.fetch_bar.Activate()
31748392b74bSAdrian Hunter			except:
31758392b74bSAdrian Hunter				pass
31768392b74bSAdrian Hunter
317782f68e28SAdrian Hunter	def ShrinkFont(self):
31784b208453SAdrian Hunter		self.Try(ShrinkFont)
317982f68e28SAdrian Hunter
318082f68e28SAdrian Hunter	def EnlargeFont(self):
31814b208453SAdrian Hunter		self.Try(EnlargeFont)
318282f68e28SAdrian Hunter
318376099f98SAdrian Hunter	def EventMenu(self, events, reports_menu):
318476099f98SAdrian Hunter		branches_events = 0
318576099f98SAdrian Hunter		for event in events:
318676099f98SAdrian Hunter			event = event.split(":")[0]
318776099f98SAdrian Hunter			if event == "branches":
318876099f98SAdrian Hunter				branches_events += 1
318976099f98SAdrian Hunter		dbid = 0
319076099f98SAdrian Hunter		for event in events:
319176099f98SAdrian Hunter			dbid += 1
319276099f98SAdrian Hunter			event = event.split(":")[0]
319376099f98SAdrian Hunter			if event == "branches":
319476099f98SAdrian Hunter				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3195df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
3196210cf1f9SAdrian Hunter				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3197df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
319876099f98SAdrian Hunter
31998392b74bSAdrian Hunter	def TableMenu(self, tables, menu):
32008392b74bSAdrian Hunter		table_menu = menu.addMenu("&Tables")
32018392b74bSAdrian Hunter		for table in tables:
3202df8ea22aSAdrian Hunter			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
32038392b74bSAdrian Hunter
32041beb5c7bSAdrian Hunter	def NewCallGraph(self):
32051beb5c7bSAdrian Hunter		CallGraphWindow(self.glb, self)
3206031c2a00SAdrian Hunter
3207ae8b887cSAdrian Hunter	def NewCallTree(self):
3208ae8b887cSAdrian Hunter		CallTreeWindow(self.glb, self)
3209ae8b887cSAdrian Hunter
3210cd358012SAdrian Hunter	def NewTopCalls(self):
3211cd358012SAdrian Hunter		dialog = TopCallsDialog(self.glb, self)
3212cd358012SAdrian Hunter		ret = dialog.exec_()
3213cd358012SAdrian Hunter		if ret:
3214cd358012SAdrian Hunter			TopCallsWindow(self.glb, dialog.report_vars, self)
3215cd358012SAdrian Hunter
321676099f98SAdrian Hunter	def NewBranchView(self, event_id):
3217947cc38dSAdrian Hunter		BranchWindow(self.glb, event_id, ReportVars(), self)
321876099f98SAdrian Hunter
3219210cf1f9SAdrian Hunter	def NewSelectedBranchView(self, event_id):
3220210cf1f9SAdrian Hunter		dialog = SelectedBranchDialog(self.glb, self)
3221210cf1f9SAdrian Hunter		ret = dialog.exec_()
3222210cf1f9SAdrian Hunter		if ret:
3223947cc38dSAdrian Hunter			BranchWindow(self.glb, event_id, dialog.report_vars, self)
3224210cf1f9SAdrian Hunter
32258392b74bSAdrian Hunter	def NewTableView(self, table_name):
32268392b74bSAdrian Hunter		TableWindow(self.glb, table_name, self)
32278392b74bSAdrian Hunter
322865b24292SAdrian Hunter	def Help(self):
322965b24292SAdrian Hunter		HelpWindow(self.glb, self)
323065b24292SAdrian Hunter
3231b62d18abSAdrian Hunter	def About(self):
3232b62d18abSAdrian Hunter		dialog = AboutDialog(self.glb, self)
3233b62d18abSAdrian Hunter		dialog.exec_()
3234b62d18abSAdrian Hunter
323576099f98SAdrian Hunter# XED Disassembler
323676099f98SAdrian Hunter
323776099f98SAdrian Hunterclass xed_state_t(Structure):
323876099f98SAdrian Hunter
323976099f98SAdrian Hunter	_fields_ = [
324076099f98SAdrian Hunter		("mode", c_int),
324176099f98SAdrian Hunter		("width", c_int)
324276099f98SAdrian Hunter	]
324376099f98SAdrian Hunter
324476099f98SAdrian Hunterclass XEDInstruction():
324576099f98SAdrian Hunter
324676099f98SAdrian Hunter	def __init__(self, libxed):
324776099f98SAdrian Hunter		# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
324876099f98SAdrian Hunter		xedd_t = c_byte * 512
324976099f98SAdrian Hunter		self.xedd = xedd_t()
325076099f98SAdrian Hunter		self.xedp = addressof(self.xedd)
325176099f98SAdrian Hunter		libxed.xed_decoded_inst_zero(self.xedp)
325276099f98SAdrian Hunter		self.state = xed_state_t()
325376099f98SAdrian Hunter		self.statep = addressof(self.state)
325476099f98SAdrian Hunter		# Buffer for disassembled instruction text
325576099f98SAdrian Hunter		self.buffer = create_string_buffer(256)
325676099f98SAdrian Hunter		self.bufferp = addressof(self.buffer)
325776099f98SAdrian Hunter
325876099f98SAdrian Hunterclass LibXED():
325976099f98SAdrian Hunter
326076099f98SAdrian Hunter	def __init__(self):
32615ed4419dSAdrian Hunter		try:
326276099f98SAdrian Hunter			self.libxed = CDLL("libxed.so")
32635ed4419dSAdrian Hunter		except:
32645ed4419dSAdrian Hunter			self.libxed = None
32655ed4419dSAdrian Hunter		if not self.libxed:
32665ed4419dSAdrian Hunter			self.libxed = CDLL("/usr/local/lib/libxed.so")
326776099f98SAdrian Hunter
326876099f98SAdrian Hunter		self.xed_tables_init = self.libxed.xed_tables_init
326976099f98SAdrian Hunter		self.xed_tables_init.restype = None
327076099f98SAdrian Hunter		self.xed_tables_init.argtypes = []
327176099f98SAdrian Hunter
327276099f98SAdrian Hunter		self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
327376099f98SAdrian Hunter		self.xed_decoded_inst_zero.restype = None
327476099f98SAdrian Hunter		self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
327576099f98SAdrian Hunter
327676099f98SAdrian Hunter		self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
327776099f98SAdrian Hunter		self.xed_operand_values_set_mode.restype = None
327876099f98SAdrian Hunter		self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
327976099f98SAdrian Hunter
328076099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
328176099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.restype = None
328276099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
328376099f98SAdrian Hunter
328476099f98SAdrian Hunter		self.xed_decode = self.libxed.xed_decode
328576099f98SAdrian Hunter		self.xed_decode.restype = c_int
328676099f98SAdrian Hunter		self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
328776099f98SAdrian Hunter
328876099f98SAdrian Hunter		self.xed_format_context = self.libxed.xed_format_context
328976099f98SAdrian Hunter		self.xed_format_context.restype = c_uint
329076099f98SAdrian Hunter		self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
329176099f98SAdrian Hunter
329276099f98SAdrian Hunter		self.xed_tables_init()
329376099f98SAdrian Hunter
329476099f98SAdrian Hunter	def Instruction(self):
329576099f98SAdrian Hunter		return XEDInstruction(self)
329676099f98SAdrian Hunter
329776099f98SAdrian Hunter	def SetMode(self, inst, mode):
329876099f98SAdrian Hunter		if mode:
329976099f98SAdrian Hunter			inst.state.mode = 4 # 32-bit
330076099f98SAdrian Hunter			inst.state.width = 4 # 4 bytes
330176099f98SAdrian Hunter		else:
330276099f98SAdrian Hunter			inst.state.mode = 1 # 64-bit
330376099f98SAdrian Hunter			inst.state.width = 8 # 8 bytes
330476099f98SAdrian Hunter		self.xed_operand_values_set_mode(inst.xedp, inst.statep)
330576099f98SAdrian Hunter
330676099f98SAdrian Hunter	def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
330776099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode(inst.xedp)
330876099f98SAdrian Hunter		err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
330976099f98SAdrian Hunter		if err:
331076099f98SAdrian Hunter			return 0, ""
331176099f98SAdrian Hunter		# Use AT&T mode (2), alternative is Intel (3)
331276099f98SAdrian Hunter		ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
331376099f98SAdrian Hunter		if not ok:
331476099f98SAdrian Hunter			return 0, ""
3315606bd60aSAdrian Hunter		if sys.version_info[0] == 2:
3316606bd60aSAdrian Hunter			result = inst.buffer.value
3317606bd60aSAdrian Hunter		else:
3318606bd60aSAdrian Hunter			result = inst.buffer.value.decode()
331976099f98SAdrian Hunter		# Return instruction length and the disassembled instruction text
332076099f98SAdrian Hunter		# For now, assume the length is in byte 166
3321606bd60aSAdrian Hunter		return inst.xedd[166], result
332276099f98SAdrian Hunter
332376099f98SAdrian Hunterdef TryOpen(file_name):
332476099f98SAdrian Hunter	try:
332576099f98SAdrian Hunter		return open(file_name, "rb")
332676099f98SAdrian Hunter	except:
332776099f98SAdrian Hunter		return None
332876099f98SAdrian Hunter
332976099f98SAdrian Hunterdef Is64Bit(f):
333076099f98SAdrian Hunter	result = sizeof(c_void_p)
333176099f98SAdrian Hunter	# ELF support only
333276099f98SAdrian Hunter	pos = f.tell()
333376099f98SAdrian Hunter	f.seek(0)
333476099f98SAdrian Hunter	header = f.read(7)
333576099f98SAdrian Hunter	f.seek(pos)
333676099f98SAdrian Hunter	magic = header[0:4]
3337606bd60aSAdrian Hunter	if sys.version_info[0] == 2:
333876099f98SAdrian Hunter		eclass = ord(header[4])
333976099f98SAdrian Hunter		encoding = ord(header[5])
334076099f98SAdrian Hunter		version = ord(header[6])
3341606bd60aSAdrian Hunter	else:
3342606bd60aSAdrian Hunter		eclass = header[4]
3343606bd60aSAdrian Hunter		encoding = header[5]
3344606bd60aSAdrian Hunter		version = header[6]
334576099f98SAdrian Hunter	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
334676099f98SAdrian Hunter		result = True if eclass == 2 else False
334776099f98SAdrian Hunter	return result
334876099f98SAdrian Hunter
3349031c2a00SAdrian Hunter# Global data
3350031c2a00SAdrian Hunter
3351031c2a00SAdrian Hunterclass Glb():
3352031c2a00SAdrian Hunter
3353031c2a00SAdrian Hunter	def __init__(self, dbref, db, dbname):
3354031c2a00SAdrian Hunter		self.dbref = dbref
3355031c2a00SAdrian Hunter		self.db = db
3356031c2a00SAdrian Hunter		self.dbname = dbname
335776099f98SAdrian Hunter		self.home_dir = os.path.expanduser("~")
335876099f98SAdrian Hunter		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
335976099f98SAdrian Hunter		if self.buildid_dir:
336076099f98SAdrian Hunter			self.buildid_dir += "/.build-id/"
336176099f98SAdrian Hunter		else:
336276099f98SAdrian Hunter			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3363031c2a00SAdrian Hunter		self.app = None
3364031c2a00SAdrian Hunter		self.mainwindow = None
33658392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit = weakref.WeakSet()
336676099f98SAdrian Hunter		try:
336776099f98SAdrian Hunter			self.disassembler = LibXED()
336876099f98SAdrian Hunter			self.have_disassembler = True
336976099f98SAdrian Hunter		except:
337076099f98SAdrian Hunter			self.have_disassembler = False
337176099f98SAdrian Hunter
337276099f98SAdrian Hunter	def FileFromBuildId(self, build_id):
337376099f98SAdrian Hunter		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
337476099f98SAdrian Hunter		return TryOpen(file_name)
337576099f98SAdrian Hunter
337676099f98SAdrian Hunter	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
337776099f98SAdrian Hunter		# Assume current machine i.e. no support for virtualization
337876099f98SAdrian Hunter		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
337976099f98SAdrian Hunter			file_name = os.getenv("PERF_KCORE")
338076099f98SAdrian Hunter			f = TryOpen(file_name) if file_name else None
338176099f98SAdrian Hunter			if f:
338276099f98SAdrian Hunter				return f
338376099f98SAdrian Hunter			# For now, no special handling if long_name is /proc/kcore
338476099f98SAdrian Hunter			f = TryOpen(long_name)
338576099f98SAdrian Hunter			if f:
338676099f98SAdrian Hunter				return f
338776099f98SAdrian Hunter		f = self.FileFromBuildId(build_id)
338876099f98SAdrian Hunter		if f:
338976099f98SAdrian Hunter			return f
339076099f98SAdrian Hunter		return None
33918392b74bSAdrian Hunter
33928392b74bSAdrian Hunter	def AddInstanceToShutdownOnExit(self, instance):
33938392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit.add(instance)
33948392b74bSAdrian Hunter
33958392b74bSAdrian Hunter	# Shutdown any background processes or threads
33968392b74bSAdrian Hunter	def ShutdownInstances(self):
33978392b74bSAdrian Hunter		for x in self.instances_to_shutdown_on_exit:
33988392b74bSAdrian Hunter			try:
33998392b74bSAdrian Hunter				x.Shutdown()
34008392b74bSAdrian Hunter			except:
34018392b74bSAdrian Hunter				pass
3402031c2a00SAdrian Hunter
3403031c2a00SAdrian Hunter# Database reference
3404031c2a00SAdrian Hunter
3405031c2a00SAdrian Hunterclass DBRef():
3406031c2a00SAdrian Hunter
3407031c2a00SAdrian Hunter	def __init__(self, is_sqlite3, dbname):
3408031c2a00SAdrian Hunter		self.is_sqlite3 = is_sqlite3
3409031c2a00SAdrian Hunter		self.dbname = dbname
3410031c2a00SAdrian Hunter
3411031c2a00SAdrian Hunter	def Open(self, connection_name):
3412031c2a00SAdrian Hunter		dbname = self.dbname
3413031c2a00SAdrian Hunter		if self.is_sqlite3:
3414031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3415031c2a00SAdrian Hunter		else:
3416031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3417031c2a00SAdrian Hunter			opts = dbname.split()
3418031c2a00SAdrian Hunter			for opt in opts:
3419031c2a00SAdrian Hunter				if "=" in opt:
3420031c2a00SAdrian Hunter					opt = opt.split("=")
3421031c2a00SAdrian Hunter					if opt[0] == "hostname":
3422031c2a00SAdrian Hunter						db.setHostName(opt[1])
3423031c2a00SAdrian Hunter					elif opt[0] == "port":
3424031c2a00SAdrian Hunter						db.setPort(int(opt[1]))
3425031c2a00SAdrian Hunter					elif opt[0] == "username":
3426031c2a00SAdrian Hunter						db.setUserName(opt[1])
3427031c2a00SAdrian Hunter					elif opt[0] == "password":
3428031c2a00SAdrian Hunter						db.setPassword(opt[1])
3429031c2a00SAdrian Hunter					elif opt[0] == "dbname":
3430031c2a00SAdrian Hunter						dbname = opt[1]
3431031c2a00SAdrian Hunter				else:
3432031c2a00SAdrian Hunter					dbname = opt
3433031c2a00SAdrian Hunter
3434031c2a00SAdrian Hunter		db.setDatabaseName(dbname)
3435031c2a00SAdrian Hunter		if not db.open():
3436031c2a00SAdrian Hunter			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3437031c2a00SAdrian Hunter		return db, dbname
3438031c2a00SAdrian Hunter
3439031c2a00SAdrian Hunter# Main
3440031c2a00SAdrian Hunter
3441031c2a00SAdrian Hunterdef Main():
34421ed7f47fSAdrian Hunter	usage_str =	"exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
34431ed7f47fSAdrian Hunter			"   or: exported-sql-viewer.py --help-only"
34441ed7f47fSAdrian Hunter	ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
3445df8ea22aSAdrian Hunter	ap.add_argument("--pyside-version-1", action='store_true')
34461ed7f47fSAdrian Hunter	ap.add_argument("dbname", nargs="?")
34471ed7f47fSAdrian Hunter	ap.add_argument("--help-only", action='store_true')
34481ed7f47fSAdrian Hunter	args = ap.parse_args()
3449031c2a00SAdrian Hunter
34501ed7f47fSAdrian Hunter	if args.help_only:
345165b24292SAdrian Hunter		app = QApplication(sys.argv)
345265b24292SAdrian Hunter		mainwindow = HelpOnlyWindow()
345365b24292SAdrian Hunter		mainwindow.show()
345465b24292SAdrian Hunter		err = app.exec_()
345565b24292SAdrian Hunter		sys.exit(err)
3456031c2a00SAdrian Hunter
34571ed7f47fSAdrian Hunter	dbname = args.dbname
34581ed7f47fSAdrian Hunter	if dbname is None:
34591ed7f47fSAdrian Hunter		ap.print_usage()
34601ed7f47fSAdrian Hunter		print("Too few arguments")
34611ed7f47fSAdrian Hunter		sys.exit(1)
34621ed7f47fSAdrian Hunter
3463031c2a00SAdrian Hunter	is_sqlite3 = False
3464031c2a00SAdrian Hunter	try:
3465beda0e72STony Jones		f = open(dbname, "rb")
3466beda0e72STony Jones		if f.read(15) == b'SQLite format 3':
3467031c2a00SAdrian Hunter			is_sqlite3 = True
3468031c2a00SAdrian Hunter		f.close()
3469031c2a00SAdrian Hunter	except:
3470031c2a00SAdrian Hunter		pass
3471031c2a00SAdrian Hunter
3472031c2a00SAdrian Hunter	dbref = DBRef(is_sqlite3, dbname)
3473031c2a00SAdrian Hunter	db, dbname = dbref.Open("main")
3474031c2a00SAdrian Hunter	glb = Glb(dbref, db, dbname)
3475031c2a00SAdrian Hunter	app = QApplication(sys.argv)
3476031c2a00SAdrian Hunter	glb.app = app
3477031c2a00SAdrian Hunter	mainwindow = MainWindow(glb)
3478031c2a00SAdrian Hunter	glb.mainwindow = mainwindow
3479031c2a00SAdrian Hunter	mainwindow.show()
3480031c2a00SAdrian Hunter	err = app.exec_()
34818392b74bSAdrian Hunter	glb.ShutdownInstances()
3482031c2a00SAdrian Hunter	db.close()
3483031c2a00SAdrian Hunter	sys.exit(err)
3484031c2a00SAdrian Hunter
3485031c2a00SAdrian Hunterif __name__ == "__main__":
3486031c2a00SAdrian Hunter	Main()
3487