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
203*4a0979d4SAdrian Hunter	def __init__(self, glb, params, parent=None):
204031c2a00SAdrian Hunter		super(TreeModel, self).__init__(parent)
205a448ba23SAdrian Hunter		self.glb = glb
206*4a0979d4SAdrian Hunter		self.params = params
207a448ba23SAdrian Hunter		self.root = self.GetRoot()
208031c2a00SAdrian Hunter		self.last_row_read = 0
209031c2a00SAdrian Hunter
210031c2a00SAdrian Hunter	def Item(self, parent):
211031c2a00SAdrian Hunter		if parent.isValid():
212031c2a00SAdrian Hunter			return parent.internalPointer()
213031c2a00SAdrian Hunter		else:
214031c2a00SAdrian Hunter			return self.root
215031c2a00SAdrian Hunter
216031c2a00SAdrian Hunter	def rowCount(self, parent):
217031c2a00SAdrian Hunter		result = self.Item(parent).childCount()
218031c2a00SAdrian Hunter		if result < 0:
219031c2a00SAdrian Hunter			result = 0
220031c2a00SAdrian Hunter			self.dataChanged.emit(parent, parent)
221031c2a00SAdrian Hunter		return result
222031c2a00SAdrian Hunter
223031c2a00SAdrian Hunter	def hasChildren(self, parent):
224031c2a00SAdrian Hunter		return self.Item(parent).hasChildren()
225031c2a00SAdrian Hunter
226031c2a00SAdrian Hunter	def headerData(self, section, orientation, role):
227031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
228031c2a00SAdrian Hunter			return self.columnAlignment(section)
229031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
230031c2a00SAdrian Hunter			return None
231031c2a00SAdrian Hunter		if orientation != Qt.Horizontal:
232031c2a00SAdrian Hunter			return None
233031c2a00SAdrian Hunter		return self.columnHeader(section)
234031c2a00SAdrian Hunter
235031c2a00SAdrian Hunter	def parent(self, child):
236031c2a00SAdrian Hunter		child_item = child.internalPointer()
237031c2a00SAdrian Hunter		if child_item is self.root:
238031c2a00SAdrian Hunter			return QModelIndex()
239031c2a00SAdrian Hunter		parent_item = child_item.getParentItem()
240031c2a00SAdrian Hunter		return self.createIndex(parent_item.getRow(), 0, parent_item)
241031c2a00SAdrian Hunter
242031c2a00SAdrian Hunter	def index(self, row, column, parent):
243031c2a00SAdrian Hunter		child_item = self.Item(parent).getChildItem(row)
244031c2a00SAdrian Hunter		return self.createIndex(row, column, child_item)
245031c2a00SAdrian Hunter
246031c2a00SAdrian Hunter	def DisplayData(self, item, index):
247031c2a00SAdrian Hunter		return item.getData(index.column())
248031c2a00SAdrian Hunter
2498392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2508392b74bSAdrian Hunter		if row > self.last_row_read:
2518392b74bSAdrian Hunter			self.last_row_read = row
2528392b74bSAdrian Hunter			if row + 10 >= self.root.child_count:
2538392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2548392b74bSAdrian Hunter
2558392b74bSAdrian Hunter	def columnAlignment(self, column):
2568392b74bSAdrian Hunter		return Qt.AlignLeft
2578392b74bSAdrian Hunter
2588392b74bSAdrian Hunter	def columnFont(self, column):
2598392b74bSAdrian Hunter		return None
2608392b74bSAdrian Hunter
2618392b74bSAdrian Hunter	def data(self, index, role):
2628392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2638392b74bSAdrian Hunter			return self.columnAlignment(index.column())
2648392b74bSAdrian Hunter		if role == Qt.FontRole:
2658392b74bSAdrian Hunter			return self.columnFont(index.column())
2668392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2678392b74bSAdrian Hunter			return None
2688392b74bSAdrian Hunter		item = index.internalPointer()
2698392b74bSAdrian Hunter		return self.DisplayData(item, index)
2708392b74bSAdrian Hunter
2718392b74bSAdrian Hunter# Table data model
2728392b74bSAdrian Hunter
2738392b74bSAdrian Hunterclass TableModel(QAbstractTableModel):
2748392b74bSAdrian Hunter
2758392b74bSAdrian Hunter	def __init__(self, parent=None):
2768392b74bSAdrian Hunter		super(TableModel, self).__init__(parent)
2778392b74bSAdrian Hunter		self.child_count = 0
2788392b74bSAdrian Hunter		self.child_items = []
2798392b74bSAdrian Hunter		self.last_row_read = 0
2808392b74bSAdrian Hunter
2818392b74bSAdrian Hunter	def Item(self, parent):
2828392b74bSAdrian Hunter		if parent.isValid():
2838392b74bSAdrian Hunter			return parent.internalPointer()
2848392b74bSAdrian Hunter		else:
2858392b74bSAdrian Hunter			return self
2868392b74bSAdrian Hunter
2878392b74bSAdrian Hunter	def rowCount(self, parent):
2888392b74bSAdrian Hunter		return self.child_count
2898392b74bSAdrian Hunter
2908392b74bSAdrian Hunter	def headerData(self, section, orientation, role):
2918392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2928392b74bSAdrian Hunter			return self.columnAlignment(section)
2938392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2948392b74bSAdrian Hunter			return None
2958392b74bSAdrian Hunter		if orientation != Qt.Horizontal:
2968392b74bSAdrian Hunter			return None
2978392b74bSAdrian Hunter		return self.columnHeader(section)
2988392b74bSAdrian Hunter
2998392b74bSAdrian Hunter	def index(self, row, column, parent):
3008392b74bSAdrian Hunter		return self.createIndex(row, column, self.child_items[row])
3018392b74bSAdrian Hunter
3028392b74bSAdrian Hunter	def DisplayData(self, item, index):
3038392b74bSAdrian Hunter		return item.getData(index.column())
3048392b74bSAdrian Hunter
3058392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
3068392b74bSAdrian Hunter		if row > self.last_row_read:
3078392b74bSAdrian Hunter			self.last_row_read = row
3088392b74bSAdrian Hunter			if row + 10 >= self.child_count:
3098392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
3108392b74bSAdrian Hunter
311031c2a00SAdrian Hunter	def columnAlignment(self, column):
312031c2a00SAdrian Hunter		return Qt.AlignLeft
313031c2a00SAdrian Hunter
314031c2a00SAdrian Hunter	def columnFont(self, column):
315031c2a00SAdrian Hunter		return None
316031c2a00SAdrian Hunter
317031c2a00SAdrian Hunter	def data(self, index, role):
318031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
319031c2a00SAdrian Hunter			return self.columnAlignment(index.column())
320031c2a00SAdrian Hunter		if role == Qt.FontRole:
321031c2a00SAdrian Hunter			return self.columnFont(index.column())
322031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
323031c2a00SAdrian Hunter			return None
324031c2a00SAdrian Hunter		item = index.internalPointer()
325031c2a00SAdrian Hunter		return self.DisplayData(item, index)
326031c2a00SAdrian Hunter
3271beb5c7bSAdrian Hunter# Model cache
3281beb5c7bSAdrian Hunter
3291beb5c7bSAdrian Huntermodel_cache = weakref.WeakValueDictionary()
3301beb5c7bSAdrian Huntermodel_cache_lock = threading.Lock()
3311beb5c7bSAdrian Hunter
3321beb5c7bSAdrian Hunterdef LookupCreateModel(model_name, create_fn):
3331beb5c7bSAdrian Hunter	model_cache_lock.acquire()
3341beb5c7bSAdrian Hunter	try:
3351beb5c7bSAdrian Hunter		model = model_cache[model_name]
3361beb5c7bSAdrian Hunter	except:
3371beb5c7bSAdrian Hunter		model = None
3381beb5c7bSAdrian Hunter	if model is None:
3391beb5c7bSAdrian Hunter		model = create_fn()
3401beb5c7bSAdrian Hunter		model_cache[model_name] = model
3411beb5c7bSAdrian Hunter	model_cache_lock.release()
3421beb5c7bSAdrian Hunter	return model
3431beb5c7bSAdrian Hunter
344ebd70c7dSAdrian Hunter# Find bar
345ebd70c7dSAdrian Hunter
346ebd70c7dSAdrian Hunterclass FindBar():
347ebd70c7dSAdrian Hunter
348ebd70c7dSAdrian Hunter	def __init__(self, parent, finder, is_reg_expr=False):
349ebd70c7dSAdrian Hunter		self.finder = finder
350ebd70c7dSAdrian Hunter		self.context = []
351ebd70c7dSAdrian Hunter		self.last_value = None
352ebd70c7dSAdrian Hunter		self.last_pattern = None
353ebd70c7dSAdrian Hunter
354ebd70c7dSAdrian Hunter		label = QLabel("Find:")
355ebd70c7dSAdrian Hunter		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
356ebd70c7dSAdrian Hunter
357ebd70c7dSAdrian Hunter		self.textbox = QComboBox()
358ebd70c7dSAdrian Hunter		self.textbox.setEditable(True)
359ebd70c7dSAdrian Hunter		self.textbox.currentIndexChanged.connect(self.ValueChanged)
360ebd70c7dSAdrian Hunter
361ebd70c7dSAdrian Hunter		self.progress = QProgressBar()
362ebd70c7dSAdrian Hunter		self.progress.setRange(0, 0)
363ebd70c7dSAdrian Hunter		self.progress.hide()
364ebd70c7dSAdrian Hunter
365ebd70c7dSAdrian Hunter		if is_reg_expr:
366ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Regular Expression")
367ebd70c7dSAdrian Hunter		else:
368ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Pattern")
369ebd70c7dSAdrian Hunter		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
370ebd70c7dSAdrian Hunter
371ebd70c7dSAdrian Hunter		self.next_button = QToolButton()
372ebd70c7dSAdrian Hunter		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
373ebd70c7dSAdrian Hunter		self.next_button.released.connect(lambda: self.NextPrev(1))
374ebd70c7dSAdrian Hunter
375ebd70c7dSAdrian Hunter		self.prev_button = QToolButton()
376ebd70c7dSAdrian Hunter		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
377ebd70c7dSAdrian Hunter		self.prev_button.released.connect(lambda: self.NextPrev(-1))
378ebd70c7dSAdrian Hunter
379ebd70c7dSAdrian Hunter		self.close_button = QToolButton()
380ebd70c7dSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
381ebd70c7dSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
382ebd70c7dSAdrian Hunter
383ebd70c7dSAdrian Hunter		self.hbox = QHBoxLayout()
384ebd70c7dSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
385ebd70c7dSAdrian Hunter
386ebd70c7dSAdrian Hunter		self.hbox.addWidget(label)
387ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.textbox)
388ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.progress)
389ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.pattern)
390ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.next_button)
391ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.prev_button)
392ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.close_button)
393ebd70c7dSAdrian Hunter
394ebd70c7dSAdrian Hunter		self.bar = QWidget()
395ebd70c7dSAdrian Hunter		self.bar.setLayout(self.hbox);
396ebd70c7dSAdrian Hunter		self.bar.hide()
397ebd70c7dSAdrian Hunter
398ebd70c7dSAdrian Hunter	def Widget(self):
399ebd70c7dSAdrian Hunter		return self.bar
400ebd70c7dSAdrian Hunter
401ebd70c7dSAdrian Hunter	def Activate(self):
402ebd70c7dSAdrian Hunter		self.bar.show()
403ebd70c7dSAdrian Hunter		self.textbox.setFocus()
404ebd70c7dSAdrian Hunter
405ebd70c7dSAdrian Hunter	def Deactivate(self):
406ebd70c7dSAdrian Hunter		self.bar.hide()
407ebd70c7dSAdrian Hunter
408ebd70c7dSAdrian Hunter	def Busy(self):
409ebd70c7dSAdrian Hunter		self.textbox.setEnabled(False)
410ebd70c7dSAdrian Hunter		self.pattern.hide()
411ebd70c7dSAdrian Hunter		self.next_button.hide()
412ebd70c7dSAdrian Hunter		self.prev_button.hide()
413ebd70c7dSAdrian Hunter		self.progress.show()
414ebd70c7dSAdrian Hunter
415ebd70c7dSAdrian Hunter	def Idle(self):
416ebd70c7dSAdrian Hunter		self.textbox.setEnabled(True)
417ebd70c7dSAdrian Hunter		self.progress.hide()
418ebd70c7dSAdrian Hunter		self.pattern.show()
419ebd70c7dSAdrian Hunter		self.next_button.show()
420ebd70c7dSAdrian Hunter		self.prev_button.show()
421ebd70c7dSAdrian Hunter
422ebd70c7dSAdrian Hunter	def Find(self, direction):
423ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
424ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
425ebd70c7dSAdrian Hunter		self.last_value = value
426ebd70c7dSAdrian Hunter		self.last_pattern = pattern
427ebd70c7dSAdrian Hunter		self.finder.Find(value, direction, pattern, self.context)
428ebd70c7dSAdrian Hunter
429ebd70c7dSAdrian Hunter	def ValueChanged(self):
430ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
431ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
432ebd70c7dSAdrian Hunter		index = self.textbox.currentIndex()
433ebd70c7dSAdrian Hunter		data = self.textbox.itemData(index)
434ebd70c7dSAdrian Hunter		# Store the pattern in the combo box to keep it with the text value
435ebd70c7dSAdrian Hunter		if data == None:
436ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
437ebd70c7dSAdrian Hunter		else:
438ebd70c7dSAdrian Hunter			self.pattern.setChecked(data)
439ebd70c7dSAdrian Hunter		self.Find(0)
440ebd70c7dSAdrian Hunter
441ebd70c7dSAdrian Hunter	def NextPrev(self, direction):
442ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
443ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
444ebd70c7dSAdrian Hunter		if value != self.last_value:
445ebd70c7dSAdrian Hunter			index = self.textbox.findText(value)
446ebd70c7dSAdrian Hunter			# Allow for a button press before the value has been added to the combo box
447ebd70c7dSAdrian Hunter			if index < 0:
448ebd70c7dSAdrian Hunter				index = self.textbox.count()
449ebd70c7dSAdrian Hunter				self.textbox.addItem(value, pattern)
450ebd70c7dSAdrian Hunter				self.textbox.setCurrentIndex(index)
451ebd70c7dSAdrian Hunter				return
452ebd70c7dSAdrian Hunter			else:
453ebd70c7dSAdrian Hunter				self.textbox.setItemData(index, pattern)
454ebd70c7dSAdrian Hunter		elif pattern != self.last_pattern:
455ebd70c7dSAdrian Hunter			# Keep the pattern recorded in the combo box up to date
456ebd70c7dSAdrian Hunter			index = self.textbox.currentIndex()
457ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
458ebd70c7dSAdrian Hunter		self.Find(direction)
459ebd70c7dSAdrian Hunter
460ebd70c7dSAdrian Hunter	def NotFound(self):
461ebd70c7dSAdrian Hunter		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
462ebd70c7dSAdrian Hunter
463031c2a00SAdrian Hunter# Context-sensitive call graph data model item base
464031c2a00SAdrian Hunter
465031c2a00SAdrian Hunterclass CallGraphLevelItemBase(object):
466031c2a00SAdrian Hunter
467*4a0979d4SAdrian Hunter	def __init__(self, glb, params, row, parent_item):
468031c2a00SAdrian Hunter		self.glb = glb
469*4a0979d4SAdrian Hunter		self.params = params
470031c2a00SAdrian Hunter		self.row = row
471031c2a00SAdrian Hunter		self.parent_item = parent_item
472031c2a00SAdrian Hunter		self.query_done = False;
473031c2a00SAdrian Hunter		self.child_count = 0
474031c2a00SAdrian Hunter		self.child_items = []
4753ac641f4SAdrian Hunter		if parent_item:
4763ac641f4SAdrian Hunter			self.level = parent_item.level + 1
4773ac641f4SAdrian Hunter		else:
4783ac641f4SAdrian Hunter			self.level = 0
479031c2a00SAdrian Hunter
480031c2a00SAdrian Hunter	def getChildItem(self, row):
481031c2a00SAdrian Hunter		return self.child_items[row]
482031c2a00SAdrian Hunter
483031c2a00SAdrian Hunter	def getParentItem(self):
484031c2a00SAdrian Hunter		return self.parent_item
485031c2a00SAdrian Hunter
486031c2a00SAdrian Hunter	def getRow(self):
487031c2a00SAdrian Hunter		return self.row
488031c2a00SAdrian Hunter
489031c2a00SAdrian Hunter	def childCount(self):
490031c2a00SAdrian Hunter		if not self.query_done:
491031c2a00SAdrian Hunter			self.Select()
492031c2a00SAdrian Hunter			if not self.child_count:
493031c2a00SAdrian Hunter				return -1
494031c2a00SAdrian Hunter		return self.child_count
495031c2a00SAdrian Hunter
496031c2a00SAdrian Hunter	def hasChildren(self):
497031c2a00SAdrian Hunter		if not self.query_done:
498031c2a00SAdrian Hunter			return True
499031c2a00SAdrian Hunter		return self.child_count > 0
500031c2a00SAdrian Hunter
501031c2a00SAdrian Hunter	def getData(self, column):
502031c2a00SAdrian Hunter		return self.data[column]
503031c2a00SAdrian Hunter
504031c2a00SAdrian Hunter# Context-sensitive call graph data model level 2+ item base
505031c2a00SAdrian Hunter
506031c2a00SAdrian Hunterclass CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
507031c2a00SAdrian Hunter
508*4a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
509*4a0979d4SAdrian Hunter		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
510031c2a00SAdrian Hunter		self.comm_id = comm_id
511031c2a00SAdrian Hunter		self.thread_id = thread_id
512031c2a00SAdrian Hunter		self.call_path_id = call_path_id
513031c2a00SAdrian Hunter		self.branch_count = branch_count
514031c2a00SAdrian Hunter		self.time = time
515031c2a00SAdrian Hunter
516031c2a00SAdrian Hunter	def Select(self):
517031c2a00SAdrian Hunter		self.query_done = True;
518031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
519031c2a00SAdrian Hunter		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
520031c2a00SAdrian Hunter					" FROM calls"
521031c2a00SAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
522031c2a00SAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
523031c2a00SAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
524031c2a00SAdrian Hunter					" WHERE parent_call_path_id = " + str(self.call_path_id) +
525031c2a00SAdrian Hunter					" AND comm_id = " + str(self.comm_id) +
526031c2a00SAdrian Hunter					" AND thread_id = " + str(self.thread_id) +
527031c2a00SAdrian Hunter					" GROUP BY call_path_id, name, short_name"
528031c2a00SAdrian Hunter					" ORDER BY call_path_id")
529031c2a00SAdrian Hunter		while query.next():
530*4a0979d4SAdrian Hunter			child_item = CallGraphLevelThreeItem(self.glb, self.params, 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)
531031c2a00SAdrian Hunter			self.child_items.append(child_item)
532031c2a00SAdrian Hunter			self.child_count += 1
533031c2a00SAdrian Hunter
534031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item
535031c2a00SAdrian Hunter
536031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
537031c2a00SAdrian Hunter
538*4a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
539*4a0979d4SAdrian Hunter		super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
540031c2a00SAdrian Hunter		dso = dsoname(dso)
541031c2a00SAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
542031c2a00SAdrian Hunter		self.dbid = call_path_id
543031c2a00SAdrian Hunter
544031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item
545031c2a00SAdrian Hunter
546031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
547031c2a00SAdrian Hunter
548*4a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
549*4a0979d4SAdrian Hunter		super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, parent_item)
550031c2a00SAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
551031c2a00SAdrian Hunter		self.dbid = thread_id
552031c2a00SAdrian Hunter
553031c2a00SAdrian Hunter	def Select(self):
554031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).Select()
555031c2a00SAdrian Hunter		for child_item in self.child_items:
556031c2a00SAdrian Hunter			self.time += child_item.time
557031c2a00SAdrian Hunter			self.branch_count += child_item.branch_count
558031c2a00SAdrian Hunter		for child_item in self.child_items:
559031c2a00SAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
560031c2a00SAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
561031c2a00SAdrian Hunter
562031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item
563031c2a00SAdrian Hunter
564031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase):
565031c2a00SAdrian Hunter
566*4a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, comm, parent_item):
567*4a0979d4SAdrian Hunter		super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
568031c2a00SAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
569031c2a00SAdrian Hunter		self.dbid = comm_id
570031c2a00SAdrian Hunter
571031c2a00SAdrian Hunter	def Select(self):
572031c2a00SAdrian Hunter		self.query_done = True;
573031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
574031c2a00SAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
575031c2a00SAdrian Hunter					" FROM comm_threads"
576031c2a00SAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
577031c2a00SAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
578031c2a00SAdrian Hunter		while query.next():
579*4a0979d4SAdrian Hunter			child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
580031c2a00SAdrian Hunter			self.child_items.append(child_item)
581031c2a00SAdrian Hunter			self.child_count += 1
582031c2a00SAdrian Hunter
583031c2a00SAdrian Hunter# Context-sensitive call graph data model root item
584031c2a00SAdrian Hunter
585031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase):
586031c2a00SAdrian Hunter
587*4a0979d4SAdrian Hunter	def __init__(self, glb, params):
588*4a0979d4SAdrian Hunter		super(CallGraphRootItem, self).__init__(glb, params, 0, None)
589031c2a00SAdrian Hunter		self.dbid = 0
590031c2a00SAdrian Hunter		self.query_done = True;
591031c2a00SAdrian Hunter		query = QSqlQuery(glb.db)
592031c2a00SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
593031c2a00SAdrian Hunter		while query.next():
594031c2a00SAdrian Hunter			if not query.value(0):
595031c2a00SAdrian Hunter				continue
596*4a0979d4SAdrian Hunter			child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
597031c2a00SAdrian Hunter			self.child_items.append(child_item)
598031c2a00SAdrian Hunter			self.child_count += 1
599031c2a00SAdrian Hunter
600*4a0979d4SAdrian Hunter# Call graph model parameters
601*4a0979d4SAdrian Hunter
602*4a0979d4SAdrian Hunterclass CallGraphModelParams():
603*4a0979d4SAdrian Hunter
604*4a0979d4SAdrian Hunter	def __init__(self, glb, parent=None):
605*4a0979d4SAdrian Hunter		self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
606*4a0979d4SAdrian Hunter
607254c0d82SAdrian Hunter# Context-sensitive call graph data model base
608031c2a00SAdrian Hunter
609254c0d82SAdrian Hunterclass CallGraphModelBase(TreeModel):
610031c2a00SAdrian Hunter
611031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
612*4a0979d4SAdrian Hunter		super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
613031c2a00SAdrian Hunter
614ebd70c7dSAdrian Hunter	def FindSelect(self, value, pattern, query):
615ebd70c7dSAdrian Hunter		if pattern:
616ebd70c7dSAdrian Hunter			# postgresql and sqlite pattern patching differences:
617ebd70c7dSAdrian Hunter			#   postgresql LIKE is case sensitive but sqlite LIKE is not
618ebd70c7dSAdrian Hunter			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
619ebd70c7dSAdrian Hunter			#   postgresql supports ILIKE which is case insensitive
620ebd70c7dSAdrian Hunter			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
621ebd70c7dSAdrian Hunter			if not self.glb.dbref.is_sqlite3:
622ebd70c7dSAdrian Hunter				# Escape % and _
623ebd70c7dSAdrian Hunter				s = value.replace("%", "\%")
624ebd70c7dSAdrian Hunter				s = s.replace("_", "\_")
625ebd70c7dSAdrian Hunter				# Translate * and ? into SQL LIKE pattern characters % and _
626ebd70c7dSAdrian Hunter				trans = string.maketrans("*?", "%_")
627ebd70c7dSAdrian Hunter				match = " LIKE '" + str(s).translate(trans) + "'"
628ebd70c7dSAdrian Hunter			else:
629ebd70c7dSAdrian Hunter				match = " GLOB '" + str(value) + "'"
630ebd70c7dSAdrian Hunter		else:
631ebd70c7dSAdrian Hunter			match = " = '" + str(value) + "'"
632254c0d82SAdrian Hunter		self.DoFindSelect(query, match)
633ebd70c7dSAdrian Hunter
634ebd70c7dSAdrian Hunter	def Found(self, query, found):
635ebd70c7dSAdrian Hunter		if found:
636ebd70c7dSAdrian Hunter			return self.FindPath(query)
637ebd70c7dSAdrian Hunter		return []
638ebd70c7dSAdrian Hunter
639ebd70c7dSAdrian Hunter	def FindValue(self, value, pattern, query, last_value, last_pattern):
640ebd70c7dSAdrian Hunter		if last_value == value and pattern == last_pattern:
641ebd70c7dSAdrian Hunter			found = query.first()
642ebd70c7dSAdrian Hunter		else:
643ebd70c7dSAdrian Hunter			self.FindSelect(value, pattern, query)
644ebd70c7dSAdrian Hunter			found = query.next()
645ebd70c7dSAdrian Hunter		return self.Found(query, found)
646ebd70c7dSAdrian Hunter
647ebd70c7dSAdrian Hunter	def FindNext(self, query):
648ebd70c7dSAdrian Hunter		found = query.next()
649ebd70c7dSAdrian Hunter		if not found:
650ebd70c7dSAdrian Hunter			found = query.first()
651ebd70c7dSAdrian Hunter		return self.Found(query, found)
652ebd70c7dSAdrian Hunter
653ebd70c7dSAdrian Hunter	def FindPrev(self, query):
654ebd70c7dSAdrian Hunter		found = query.previous()
655ebd70c7dSAdrian Hunter		if not found:
656ebd70c7dSAdrian Hunter			found = query.last()
657ebd70c7dSAdrian Hunter		return self.Found(query, found)
658ebd70c7dSAdrian Hunter
659ebd70c7dSAdrian Hunter	def FindThread(self, c):
660ebd70c7dSAdrian Hunter		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
661ebd70c7dSAdrian Hunter			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
662ebd70c7dSAdrian Hunter		elif c.direction > 0:
663ebd70c7dSAdrian Hunter			ids = self.FindNext(c.query)
664ebd70c7dSAdrian Hunter		else:
665ebd70c7dSAdrian Hunter			ids = self.FindPrev(c.query)
666ebd70c7dSAdrian Hunter		return (True, ids)
667ebd70c7dSAdrian Hunter
668ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
669ebd70c7dSAdrian Hunter		class Context():
670ebd70c7dSAdrian Hunter			def __init__(self, *x):
671ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
672ebd70c7dSAdrian Hunter			def Update(self, *x):
673ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
674ebd70c7dSAdrian Hunter		if len(context):
675ebd70c7dSAdrian Hunter			context[0].Update(value, direction, pattern)
676ebd70c7dSAdrian Hunter		else:
677ebd70c7dSAdrian Hunter			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
678ebd70c7dSAdrian Hunter		# Use a thread so the UI is not blocked during the SELECT
679ebd70c7dSAdrian Hunter		thread = Thread(self.FindThread, context[0])
680ebd70c7dSAdrian Hunter		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
681ebd70c7dSAdrian Hunter		thread.start()
682ebd70c7dSAdrian Hunter
683ebd70c7dSAdrian Hunter	def FindDone(self, thread, callback, ids):
684ebd70c7dSAdrian Hunter		callback(ids)
685ebd70c7dSAdrian Hunter
686254c0d82SAdrian Hunter# Context-sensitive call graph data model
687254c0d82SAdrian Hunter
688254c0d82SAdrian Hunterclass CallGraphModel(CallGraphModelBase):
689254c0d82SAdrian Hunter
690254c0d82SAdrian Hunter	def __init__(self, glb, parent=None):
691254c0d82SAdrian Hunter		super(CallGraphModel, self).__init__(glb, parent)
692254c0d82SAdrian Hunter
693254c0d82SAdrian Hunter	def GetRoot(self):
694*4a0979d4SAdrian Hunter		return CallGraphRootItem(self.glb, self.params)
695254c0d82SAdrian Hunter
696254c0d82SAdrian Hunter	def columnCount(self, parent=None):
697254c0d82SAdrian Hunter		return 7
698254c0d82SAdrian Hunter
699254c0d82SAdrian Hunter	def columnHeader(self, column):
700254c0d82SAdrian Hunter		headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
701254c0d82SAdrian Hunter		return headers[column]
702254c0d82SAdrian Hunter
703254c0d82SAdrian Hunter	def columnAlignment(self, column):
704254c0d82SAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
705254c0d82SAdrian Hunter		return alignment[column]
706254c0d82SAdrian Hunter
707254c0d82SAdrian Hunter	def DoFindSelect(self, query, match):
708254c0d82SAdrian Hunter		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
709254c0d82SAdrian Hunter						" FROM calls"
710254c0d82SAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
711254c0d82SAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
712254c0d82SAdrian Hunter						" WHERE symbols.name" + match +
713254c0d82SAdrian Hunter						" GROUP BY comm_id, thread_id, call_path_id"
714254c0d82SAdrian Hunter						" ORDER BY comm_id, thread_id, call_path_id")
715254c0d82SAdrian Hunter
716254c0d82SAdrian Hunter	def FindPath(self, query):
717254c0d82SAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
718254c0d82SAdrian Hunter		# to open the tree at the right place.
719254c0d82SAdrian Hunter		ids = []
720254c0d82SAdrian Hunter		parent_id = query.value(0)
721254c0d82SAdrian Hunter		while parent_id:
722254c0d82SAdrian Hunter			ids.insert(0, parent_id)
723254c0d82SAdrian Hunter			q2 = QSqlQuery(self.glb.db)
724254c0d82SAdrian Hunter			QueryExec(q2, "SELECT parent_id"
725254c0d82SAdrian Hunter					" FROM call_paths"
726254c0d82SAdrian Hunter					" WHERE id = " + str(parent_id))
727254c0d82SAdrian Hunter			if not q2.next():
728254c0d82SAdrian Hunter				break
729254c0d82SAdrian Hunter			parent_id = q2.value(0)
730254c0d82SAdrian Hunter		# The call path root is not used
731254c0d82SAdrian Hunter		if ids[0] == 1:
732254c0d82SAdrian Hunter			del ids[0]
733254c0d82SAdrian Hunter		ids.insert(0, query.value(2))
734254c0d82SAdrian Hunter		ids.insert(0, query.value(1))
735254c0d82SAdrian Hunter		return ids
736254c0d82SAdrian Hunter
737ae8b887cSAdrian Hunter# Call tree data model level 2+ item base
738ae8b887cSAdrian Hunter
739ae8b887cSAdrian Hunterclass CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
740ae8b887cSAdrian Hunter
741*4a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
742*4a0979d4SAdrian Hunter		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
743ae8b887cSAdrian Hunter		self.comm_id = comm_id
744ae8b887cSAdrian Hunter		self.thread_id = thread_id
745ae8b887cSAdrian Hunter		self.calls_id = calls_id
746ae8b887cSAdrian Hunter		self.branch_count = branch_count
747ae8b887cSAdrian Hunter		self.time = time
748ae8b887cSAdrian Hunter
749ae8b887cSAdrian Hunter	def Select(self):
750ae8b887cSAdrian Hunter		self.query_done = True;
751ae8b887cSAdrian Hunter		if self.calls_id == 0:
752ae8b887cSAdrian Hunter			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
753ae8b887cSAdrian Hunter		else:
754ae8b887cSAdrian Hunter			comm_thread = ""
755ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
756ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
757ae8b887cSAdrian Hunter					" FROM calls"
758ae8b887cSAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
759ae8b887cSAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
760ae8b887cSAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
761ae8b887cSAdrian Hunter					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
762ae8b887cSAdrian Hunter					" ORDER BY call_time, calls.id")
763ae8b887cSAdrian Hunter		while query.next():
764*4a0979d4SAdrian Hunter			child_item = CallTreeLevelThreeItem(self.glb, self.params, 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)
765ae8b887cSAdrian Hunter			self.child_items.append(child_item)
766ae8b887cSAdrian Hunter			self.child_count += 1
767ae8b887cSAdrian Hunter
768ae8b887cSAdrian Hunter# Call tree data model level three item
769ae8b887cSAdrian Hunter
770ae8b887cSAdrian Hunterclass CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
771ae8b887cSAdrian Hunter
772*4a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
773*4a0979d4SAdrian Hunter		super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
774ae8b887cSAdrian Hunter		dso = dsoname(dso)
775ae8b887cSAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
776ae8b887cSAdrian Hunter		self.dbid = calls_id
777ae8b887cSAdrian Hunter
778ae8b887cSAdrian Hunter# Call tree data model level two item
779ae8b887cSAdrian Hunter
780ae8b887cSAdrian Hunterclass CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
781ae8b887cSAdrian Hunter
782*4a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
783*4a0979d4SAdrian Hunter		super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, parent_item)
784ae8b887cSAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
785ae8b887cSAdrian Hunter		self.dbid = thread_id
786ae8b887cSAdrian Hunter
787ae8b887cSAdrian Hunter	def Select(self):
788ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).Select()
789ae8b887cSAdrian Hunter		for child_item in self.child_items:
790ae8b887cSAdrian Hunter			self.time += child_item.time
791ae8b887cSAdrian Hunter			self.branch_count += child_item.branch_count
792ae8b887cSAdrian Hunter		for child_item in self.child_items:
793ae8b887cSAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
794ae8b887cSAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
795ae8b887cSAdrian Hunter
796ae8b887cSAdrian Hunter# Call tree data model level one item
797ae8b887cSAdrian Hunter
798ae8b887cSAdrian Hunterclass CallTreeLevelOneItem(CallGraphLevelItemBase):
799ae8b887cSAdrian Hunter
800*4a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, comm, parent_item):
801*4a0979d4SAdrian Hunter		super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
802ae8b887cSAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
803ae8b887cSAdrian Hunter		self.dbid = comm_id
804ae8b887cSAdrian Hunter
805ae8b887cSAdrian Hunter	def Select(self):
806ae8b887cSAdrian Hunter		self.query_done = True;
807ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
808ae8b887cSAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
809ae8b887cSAdrian Hunter					" FROM comm_threads"
810ae8b887cSAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
811ae8b887cSAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
812ae8b887cSAdrian Hunter		while query.next():
813*4a0979d4SAdrian Hunter			child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
814ae8b887cSAdrian Hunter			self.child_items.append(child_item)
815ae8b887cSAdrian Hunter			self.child_count += 1
816ae8b887cSAdrian Hunter
817ae8b887cSAdrian Hunter# Call tree data model root item
818ae8b887cSAdrian Hunter
819ae8b887cSAdrian Hunterclass CallTreeRootItem(CallGraphLevelItemBase):
820ae8b887cSAdrian Hunter
821*4a0979d4SAdrian Hunter	def __init__(self, glb, params):
822*4a0979d4SAdrian Hunter		super(CallTreeRootItem, self).__init__(glb, params, 0, None)
823ae8b887cSAdrian Hunter		self.dbid = 0
824ae8b887cSAdrian Hunter		self.query_done = True;
825ae8b887cSAdrian Hunter		query = QSqlQuery(glb.db)
826ae8b887cSAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
827ae8b887cSAdrian Hunter		while query.next():
828ae8b887cSAdrian Hunter			if not query.value(0):
829ae8b887cSAdrian Hunter				continue
830*4a0979d4SAdrian Hunter			child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
831ae8b887cSAdrian Hunter			self.child_items.append(child_item)
832ae8b887cSAdrian Hunter			self.child_count += 1
833ae8b887cSAdrian Hunter
834ae8b887cSAdrian Hunter# Call Tree data model
835ae8b887cSAdrian Hunter
836ae8b887cSAdrian Hunterclass CallTreeModel(CallGraphModelBase):
837ae8b887cSAdrian Hunter
838ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
839ae8b887cSAdrian Hunter		super(CallTreeModel, self).__init__(glb, parent)
840ae8b887cSAdrian Hunter
841ae8b887cSAdrian Hunter	def GetRoot(self):
842*4a0979d4SAdrian Hunter		return CallTreeRootItem(self.glb, self.params)
843ae8b887cSAdrian Hunter
844ae8b887cSAdrian Hunter	def columnCount(self, parent=None):
845ae8b887cSAdrian Hunter		return 7
846ae8b887cSAdrian Hunter
847ae8b887cSAdrian Hunter	def columnHeader(self, column):
848ae8b887cSAdrian Hunter		headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
849ae8b887cSAdrian Hunter		return headers[column]
850ae8b887cSAdrian Hunter
851ae8b887cSAdrian Hunter	def columnAlignment(self, column):
852ae8b887cSAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
853ae8b887cSAdrian Hunter		return alignment[column]
854ae8b887cSAdrian Hunter
855ae8b887cSAdrian Hunter	def DoFindSelect(self, query, match):
856ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
857ae8b887cSAdrian Hunter						" FROM calls"
858ae8b887cSAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
859ae8b887cSAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
860ae8b887cSAdrian Hunter						" WHERE symbols.name" + match +
861ae8b887cSAdrian Hunter						" ORDER BY comm_id, thread_id, call_time, calls.id")
862ae8b887cSAdrian Hunter
863ae8b887cSAdrian Hunter	def FindPath(self, query):
864ae8b887cSAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
865ae8b887cSAdrian Hunter		# to open the tree at the right place.
866ae8b887cSAdrian Hunter		ids = []
867ae8b887cSAdrian Hunter		parent_id = query.value(0)
868ae8b887cSAdrian Hunter		while parent_id:
869ae8b887cSAdrian Hunter			ids.insert(0, parent_id)
870ae8b887cSAdrian Hunter			q2 = QSqlQuery(self.glb.db)
871ae8b887cSAdrian Hunter			QueryExec(q2, "SELECT parent_id"
872ae8b887cSAdrian Hunter					" FROM calls"
873ae8b887cSAdrian Hunter					" WHERE id = " + str(parent_id))
874ae8b887cSAdrian Hunter			if not q2.next():
875ae8b887cSAdrian Hunter				break
876ae8b887cSAdrian Hunter			parent_id = q2.value(0)
877ae8b887cSAdrian Hunter		ids.insert(0, query.value(2))
878ae8b887cSAdrian Hunter		ids.insert(0, query.value(1))
879ae8b887cSAdrian Hunter		return ids
880ae8b887cSAdrian Hunter
881ebd70c7dSAdrian Hunter# Vertical widget layout
882ebd70c7dSAdrian Hunter
883ebd70c7dSAdrian Hunterclass VBox():
884ebd70c7dSAdrian Hunter
885ebd70c7dSAdrian Hunter	def __init__(self, w1, w2, w3=None):
886ebd70c7dSAdrian Hunter		self.vbox = QWidget()
887ebd70c7dSAdrian Hunter		self.vbox.setLayout(QVBoxLayout());
888ebd70c7dSAdrian Hunter
889ebd70c7dSAdrian Hunter		self.vbox.layout().setContentsMargins(0, 0, 0, 0)
890ebd70c7dSAdrian Hunter
891ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w1)
892ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w2)
893ebd70c7dSAdrian Hunter		if w3:
894ebd70c7dSAdrian Hunter			self.vbox.layout().addWidget(w3)
895ebd70c7dSAdrian Hunter
896ebd70c7dSAdrian Hunter	def Widget(self):
897ebd70c7dSAdrian Hunter		return self.vbox
898ebd70c7dSAdrian Hunter
899a731cc4cSAdrian Hunter# Tree window base
9001beb5c7bSAdrian Hunter
901a731cc4cSAdrian Hunterclass TreeWindowBase(QMdiSubWindow):
9021beb5c7bSAdrian Hunter
903a731cc4cSAdrian Hunter	def __init__(self, parent=None):
904a731cc4cSAdrian Hunter		super(TreeWindowBase, self).__init__(parent)
9051beb5c7bSAdrian Hunter
906a731cc4cSAdrian Hunter		self.model = None
907a731cc4cSAdrian Hunter		self.find_bar = None
9081beb5c7bSAdrian Hunter
909be6e7471SAdrian Hunter		self.view = QTreeView()
91096c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
91196c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
912be6e7471SAdrian Hunter
9139bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
9149bc4e4bfSAdrian Hunter
915ebd70c7dSAdrian Hunter	def DisplayFound(self, ids):
916ebd70c7dSAdrian Hunter		if not len(ids):
917ebd70c7dSAdrian Hunter			return False
918ebd70c7dSAdrian Hunter		parent = QModelIndex()
919ebd70c7dSAdrian Hunter		for dbid in ids:
920ebd70c7dSAdrian Hunter			found = False
921ebd70c7dSAdrian Hunter			n = self.model.rowCount(parent)
922ebd70c7dSAdrian Hunter			for row in xrange(n):
923ebd70c7dSAdrian Hunter				child = self.model.index(row, 0, parent)
924ebd70c7dSAdrian Hunter				if child.internalPointer().dbid == dbid:
925ebd70c7dSAdrian Hunter					found = True
926ebd70c7dSAdrian Hunter					self.view.setCurrentIndex(child)
927ebd70c7dSAdrian Hunter					parent = child
928ebd70c7dSAdrian Hunter					break
929ebd70c7dSAdrian Hunter			if not found:
930ebd70c7dSAdrian Hunter				break
931ebd70c7dSAdrian Hunter		return found
932ebd70c7dSAdrian Hunter
933ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context):
934ebd70c7dSAdrian Hunter		self.view.setFocus()
935ebd70c7dSAdrian Hunter		self.find_bar.Busy()
936ebd70c7dSAdrian Hunter		self.model.Find(value, direction, pattern, context, self.FindDone)
937ebd70c7dSAdrian Hunter
938ebd70c7dSAdrian Hunter	def FindDone(self, ids):
939ebd70c7dSAdrian Hunter		found = True
940ebd70c7dSAdrian Hunter		if not self.DisplayFound(ids):
941ebd70c7dSAdrian Hunter			found = False
942ebd70c7dSAdrian Hunter		self.find_bar.Idle()
943ebd70c7dSAdrian Hunter		if not found:
944ebd70c7dSAdrian Hunter			self.find_bar.NotFound()
945ebd70c7dSAdrian Hunter
946a731cc4cSAdrian Hunter
947a731cc4cSAdrian Hunter# Context-sensitive call graph window
948a731cc4cSAdrian Hunter
949a731cc4cSAdrian Hunterclass CallGraphWindow(TreeWindowBase):
950a731cc4cSAdrian Hunter
951a731cc4cSAdrian Hunter	def __init__(self, glb, parent=None):
952a731cc4cSAdrian Hunter		super(CallGraphWindow, self).__init__(parent)
953a731cc4cSAdrian Hunter
954a731cc4cSAdrian Hunter		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
955a731cc4cSAdrian Hunter
956a731cc4cSAdrian Hunter		self.view.setModel(self.model)
957a731cc4cSAdrian Hunter
958a731cc4cSAdrian Hunter		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
959a731cc4cSAdrian Hunter			self.view.setColumnWidth(c, w)
960a731cc4cSAdrian Hunter
961a731cc4cSAdrian Hunter		self.find_bar = FindBar(self, self)
962a731cc4cSAdrian Hunter
963a731cc4cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
964a731cc4cSAdrian Hunter
965a731cc4cSAdrian Hunter		self.setWidget(self.vbox.Widget())
966a731cc4cSAdrian Hunter
967a731cc4cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
968a731cc4cSAdrian Hunter
969ae8b887cSAdrian Hunter# Call tree window
970ae8b887cSAdrian Hunter
971ae8b887cSAdrian Hunterclass CallTreeWindow(TreeWindowBase):
972ae8b887cSAdrian Hunter
973ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
974ae8b887cSAdrian Hunter		super(CallTreeWindow, self).__init__(parent)
975ae8b887cSAdrian Hunter
976ae8b887cSAdrian Hunter		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
977ae8b887cSAdrian Hunter
978ae8b887cSAdrian Hunter		self.view.setModel(self.model)
979ae8b887cSAdrian Hunter
980ae8b887cSAdrian Hunter		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
981ae8b887cSAdrian Hunter			self.view.setColumnWidth(c, w)
982ae8b887cSAdrian Hunter
983ae8b887cSAdrian Hunter		self.find_bar = FindBar(self, self)
984ae8b887cSAdrian Hunter
985ae8b887cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
986ae8b887cSAdrian Hunter
987ae8b887cSAdrian Hunter		self.setWidget(self.vbox.Widget())
988ae8b887cSAdrian Hunter
989ae8b887cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
990ae8b887cSAdrian Hunter
9918392b74bSAdrian Hunter# Child data item  finder
9928392b74bSAdrian Hunter
9938392b74bSAdrian Hunterclass ChildDataItemFinder():
9948392b74bSAdrian Hunter
9958392b74bSAdrian Hunter	def __init__(self, root):
9968392b74bSAdrian Hunter		self.root = root
9978392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
9988392b74bSAdrian Hunter		self.rows = []
9998392b74bSAdrian Hunter		self.pos = 0
10008392b74bSAdrian Hunter
10018392b74bSAdrian Hunter	def FindSelect(self):
10028392b74bSAdrian Hunter		self.rows = []
10038392b74bSAdrian Hunter		if self.pattern:
10048392b74bSAdrian Hunter			pattern = re.compile(self.value)
10058392b74bSAdrian Hunter			for child in self.root.child_items:
10068392b74bSAdrian Hunter				for column_data in child.data:
10078392b74bSAdrian Hunter					if re.search(pattern, str(column_data)) is not None:
10088392b74bSAdrian Hunter						self.rows.append(child.row)
10098392b74bSAdrian Hunter						break
10108392b74bSAdrian Hunter		else:
10118392b74bSAdrian Hunter			for child in self.root.child_items:
10128392b74bSAdrian Hunter				for column_data in child.data:
10138392b74bSAdrian Hunter					if self.value in str(column_data):
10148392b74bSAdrian Hunter						self.rows.append(child.row)
10158392b74bSAdrian Hunter						break
10168392b74bSAdrian Hunter
10178392b74bSAdrian Hunter	def FindValue(self):
10188392b74bSAdrian Hunter		self.pos = 0
10198392b74bSAdrian Hunter		if self.last_value != self.value or self.pattern != self.last_pattern:
10208392b74bSAdrian Hunter			self.FindSelect()
10218392b74bSAdrian Hunter		if not len(self.rows):
10228392b74bSAdrian Hunter			return -1
10238392b74bSAdrian Hunter		return self.rows[self.pos]
10248392b74bSAdrian Hunter
10258392b74bSAdrian Hunter	def FindThread(self):
10268392b74bSAdrian Hunter		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
10278392b74bSAdrian Hunter			row = self.FindValue()
10288392b74bSAdrian Hunter		elif len(self.rows):
10298392b74bSAdrian Hunter			if self.direction > 0:
10308392b74bSAdrian Hunter				self.pos += 1
10318392b74bSAdrian Hunter				if self.pos >= len(self.rows):
10328392b74bSAdrian Hunter					self.pos = 0
10338392b74bSAdrian Hunter			else:
10348392b74bSAdrian Hunter				self.pos -= 1
10358392b74bSAdrian Hunter				if self.pos < 0:
10368392b74bSAdrian Hunter					self.pos = len(self.rows) - 1
10378392b74bSAdrian Hunter			row = self.rows[self.pos]
10388392b74bSAdrian Hunter		else:
10398392b74bSAdrian Hunter			row = -1
10408392b74bSAdrian Hunter		return (True, row)
10418392b74bSAdrian Hunter
10428392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
10438392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
10448392b74bSAdrian Hunter		# Use a thread so the UI is not blocked
10458392b74bSAdrian Hunter		thread = Thread(self.FindThread)
10468392b74bSAdrian Hunter		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
10478392b74bSAdrian Hunter		thread.start()
10488392b74bSAdrian Hunter
10498392b74bSAdrian Hunter	def FindDone(self, thread, callback, row):
10508392b74bSAdrian Hunter		callback(row)
10518392b74bSAdrian Hunter
10528392b74bSAdrian Hunter# Number of database records to fetch in one go
10538392b74bSAdrian Hunter
10548392b74bSAdrian Hunterglb_chunk_sz = 10000
10558392b74bSAdrian Hunter
10568392b74bSAdrian Hunter# Background process for SQL data fetcher
10578392b74bSAdrian Hunter
10588392b74bSAdrian Hunterclass SQLFetcherProcess():
10598392b74bSAdrian Hunter
10608392b74bSAdrian Hunter	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
10618392b74bSAdrian Hunter		# Need a unique connection name
10628392b74bSAdrian Hunter		conn_name = "SQLFetcher" + str(os.getpid())
10638392b74bSAdrian Hunter		self.db, dbname = dbref.Open(conn_name)
10648392b74bSAdrian Hunter		self.sql = sql
10658392b74bSAdrian Hunter		self.buffer = buffer
10668392b74bSAdrian Hunter		self.head = head
10678392b74bSAdrian Hunter		self.tail = tail
10688392b74bSAdrian Hunter		self.fetch_count = fetch_count
10698392b74bSAdrian Hunter		self.fetching_done = fetching_done
10708392b74bSAdrian Hunter		self.process_target = process_target
10718392b74bSAdrian Hunter		self.wait_event = wait_event
10728392b74bSAdrian Hunter		self.fetched_event = fetched_event
10738392b74bSAdrian Hunter		self.prep = prep
10748392b74bSAdrian Hunter		self.query = QSqlQuery(self.db)
10758392b74bSAdrian Hunter		self.query_limit = 0 if "$$last_id$$" in sql else 2
10768392b74bSAdrian Hunter		self.last_id = -1
10778392b74bSAdrian Hunter		self.fetched = 0
10788392b74bSAdrian Hunter		self.more = True
10798392b74bSAdrian Hunter		self.local_head = self.head.value
10808392b74bSAdrian Hunter		self.local_tail = self.tail.value
10818392b74bSAdrian Hunter
10828392b74bSAdrian Hunter	def Select(self):
10838392b74bSAdrian Hunter		if self.query_limit:
10848392b74bSAdrian Hunter			if self.query_limit == 1:
10858392b74bSAdrian Hunter				return
10868392b74bSAdrian Hunter			self.query_limit -= 1
10878392b74bSAdrian Hunter		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
10888392b74bSAdrian Hunter		QueryExec(self.query, stmt)
10898392b74bSAdrian Hunter
10908392b74bSAdrian Hunter	def Next(self):
10918392b74bSAdrian Hunter		if not self.query.next():
10928392b74bSAdrian Hunter			self.Select()
10938392b74bSAdrian Hunter			if not self.query.next():
10948392b74bSAdrian Hunter				return None
10958392b74bSAdrian Hunter		self.last_id = self.query.value(0)
10968392b74bSAdrian Hunter		return self.prep(self.query)
10978392b74bSAdrian Hunter
10988392b74bSAdrian Hunter	def WaitForTarget(self):
10998392b74bSAdrian Hunter		while True:
11008392b74bSAdrian Hunter			self.wait_event.clear()
11018392b74bSAdrian Hunter			target = self.process_target.value
11028392b74bSAdrian Hunter			if target > self.fetched or target < 0:
11038392b74bSAdrian Hunter				break
11048392b74bSAdrian Hunter			self.wait_event.wait()
11058392b74bSAdrian Hunter		return target
11068392b74bSAdrian Hunter
11078392b74bSAdrian Hunter	def HasSpace(self, sz):
11088392b74bSAdrian Hunter		if self.local_tail <= self.local_head:
11098392b74bSAdrian Hunter			space = len(self.buffer) - self.local_head
11108392b74bSAdrian Hunter			if space > sz:
11118392b74bSAdrian Hunter				return True
11128392b74bSAdrian Hunter			if space >= glb_nsz:
11138392b74bSAdrian Hunter				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1114beda0e72STony Jones				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
11158392b74bSAdrian Hunter				self.buffer[self.local_head : self.local_head + len(nd)] = nd
11168392b74bSAdrian Hunter			self.local_head = 0
11178392b74bSAdrian Hunter		if self.local_tail - self.local_head > sz:
11188392b74bSAdrian Hunter			return True
11198392b74bSAdrian Hunter		return False
11208392b74bSAdrian Hunter
11218392b74bSAdrian Hunter	def WaitForSpace(self, sz):
11228392b74bSAdrian Hunter		if self.HasSpace(sz):
11238392b74bSAdrian Hunter			return
11248392b74bSAdrian Hunter		while True:
11258392b74bSAdrian Hunter			self.wait_event.clear()
11268392b74bSAdrian Hunter			self.local_tail = self.tail.value
11278392b74bSAdrian Hunter			if self.HasSpace(sz):
11288392b74bSAdrian Hunter				return
11298392b74bSAdrian Hunter			self.wait_event.wait()
11308392b74bSAdrian Hunter
11318392b74bSAdrian Hunter	def AddToBuffer(self, obj):
1132beda0e72STony Jones		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
11338392b74bSAdrian Hunter		n = len(d)
1134beda0e72STony Jones		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
11358392b74bSAdrian Hunter		sz = n + glb_nsz
11368392b74bSAdrian Hunter		self.WaitForSpace(sz)
11378392b74bSAdrian Hunter		pos = self.local_head
11388392b74bSAdrian Hunter		self.buffer[pos : pos + len(nd)] = nd
11398392b74bSAdrian Hunter		self.buffer[pos + glb_nsz : pos + sz] = d
11408392b74bSAdrian Hunter		self.local_head += sz
11418392b74bSAdrian Hunter
11428392b74bSAdrian Hunter	def FetchBatch(self, batch_size):
11438392b74bSAdrian Hunter		fetched = 0
11448392b74bSAdrian Hunter		while batch_size > fetched:
11458392b74bSAdrian Hunter			obj = self.Next()
11468392b74bSAdrian Hunter			if obj is None:
11478392b74bSAdrian Hunter				self.more = False
11488392b74bSAdrian Hunter				break
11498392b74bSAdrian Hunter			self.AddToBuffer(obj)
11508392b74bSAdrian Hunter			fetched += 1
11518392b74bSAdrian Hunter		if fetched:
11528392b74bSAdrian Hunter			self.fetched += fetched
11538392b74bSAdrian Hunter			with self.fetch_count.get_lock():
11548392b74bSAdrian Hunter				self.fetch_count.value += fetched
11558392b74bSAdrian Hunter			self.head.value = self.local_head
11568392b74bSAdrian Hunter			self.fetched_event.set()
11578392b74bSAdrian Hunter
11588392b74bSAdrian Hunter	def Run(self):
11598392b74bSAdrian Hunter		while self.more:
11608392b74bSAdrian Hunter			target = self.WaitForTarget()
11618392b74bSAdrian Hunter			if target < 0:
11628392b74bSAdrian Hunter				break
11638392b74bSAdrian Hunter			batch_size = min(glb_chunk_sz, target - self.fetched)
11648392b74bSAdrian Hunter			self.FetchBatch(batch_size)
11658392b74bSAdrian Hunter		self.fetching_done.value = True
11668392b74bSAdrian Hunter		self.fetched_event.set()
11678392b74bSAdrian Hunter
11688392b74bSAdrian Hunterdef SQLFetcherFn(*x):
11698392b74bSAdrian Hunter	process = SQLFetcherProcess(*x)
11708392b74bSAdrian Hunter	process.Run()
11718392b74bSAdrian Hunter
11728392b74bSAdrian Hunter# SQL data fetcher
11738392b74bSAdrian Hunter
11748392b74bSAdrian Hunterclass SQLFetcher(QObject):
11758392b74bSAdrian Hunter
11768392b74bSAdrian Hunter	done = Signal(object)
11778392b74bSAdrian Hunter
11788392b74bSAdrian Hunter	def __init__(self, glb, sql, prep, process_data, parent=None):
11798392b74bSAdrian Hunter		super(SQLFetcher, self).__init__(parent)
11808392b74bSAdrian Hunter		self.process_data = process_data
11818392b74bSAdrian Hunter		self.more = True
11828392b74bSAdrian Hunter		self.target = 0
11838392b74bSAdrian Hunter		self.last_target = 0
11848392b74bSAdrian Hunter		self.fetched = 0
11858392b74bSAdrian Hunter		self.buffer_size = 16 * 1024 * 1024
11868392b74bSAdrian Hunter		self.buffer = Array(c_char, self.buffer_size, lock=False)
11878392b74bSAdrian Hunter		self.head = Value(c_longlong)
11888392b74bSAdrian Hunter		self.tail = Value(c_longlong)
11898392b74bSAdrian Hunter		self.local_tail = 0
11908392b74bSAdrian Hunter		self.fetch_count = Value(c_longlong)
11918392b74bSAdrian Hunter		self.fetching_done = Value(c_bool)
11928392b74bSAdrian Hunter		self.last_count = 0
11938392b74bSAdrian Hunter		self.process_target = Value(c_longlong)
11948392b74bSAdrian Hunter		self.wait_event = Event()
11958392b74bSAdrian Hunter		self.fetched_event = Event()
11968392b74bSAdrian Hunter		glb.AddInstanceToShutdownOnExit(self)
11978392b74bSAdrian 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))
11988392b74bSAdrian Hunter		self.process.start()
11998392b74bSAdrian Hunter		self.thread = Thread(self.Thread)
12008392b74bSAdrian Hunter		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
12018392b74bSAdrian Hunter		self.thread.start()
12028392b74bSAdrian Hunter
12038392b74bSAdrian Hunter	def Shutdown(self):
12048392b74bSAdrian Hunter		# Tell the thread and process to exit
12058392b74bSAdrian Hunter		self.process_target.value = -1
12068392b74bSAdrian Hunter		self.wait_event.set()
12078392b74bSAdrian Hunter		self.more = False
12088392b74bSAdrian Hunter		self.fetching_done.value = True
12098392b74bSAdrian Hunter		self.fetched_event.set()
12108392b74bSAdrian Hunter
12118392b74bSAdrian Hunter	def Thread(self):
12128392b74bSAdrian Hunter		if not self.more:
12138392b74bSAdrian Hunter			return True, 0
12148392b74bSAdrian Hunter		while True:
12158392b74bSAdrian Hunter			self.fetched_event.clear()
12168392b74bSAdrian Hunter			fetch_count = self.fetch_count.value
12178392b74bSAdrian Hunter			if fetch_count != self.last_count:
12188392b74bSAdrian Hunter				break
12198392b74bSAdrian Hunter			if self.fetching_done.value:
12208392b74bSAdrian Hunter				self.more = False
12218392b74bSAdrian Hunter				return True, 0
12228392b74bSAdrian Hunter			self.fetched_event.wait()
12238392b74bSAdrian Hunter		count = fetch_count - self.last_count
12248392b74bSAdrian Hunter		self.last_count = fetch_count
12258392b74bSAdrian Hunter		self.fetched += count
12268392b74bSAdrian Hunter		return False, count
12278392b74bSAdrian Hunter
12288392b74bSAdrian Hunter	def Fetch(self, nr):
12298392b74bSAdrian Hunter		if not self.more:
12308392b74bSAdrian Hunter			# -1 inidcates there are no more
12318392b74bSAdrian Hunter			return -1
12328392b74bSAdrian Hunter		result = self.fetched
12338392b74bSAdrian Hunter		extra = result + nr - self.target
12348392b74bSAdrian Hunter		if extra > 0:
12358392b74bSAdrian Hunter			self.target += extra
12368392b74bSAdrian Hunter			# process_target < 0 indicates shutting down
12378392b74bSAdrian Hunter			if self.process_target.value >= 0:
12388392b74bSAdrian Hunter				self.process_target.value = self.target
12398392b74bSAdrian Hunter			self.wait_event.set()
12408392b74bSAdrian Hunter		return result
12418392b74bSAdrian Hunter
12428392b74bSAdrian Hunter	def RemoveFromBuffer(self):
12438392b74bSAdrian Hunter		pos = self.local_tail
12448392b74bSAdrian Hunter		if len(self.buffer) - pos < glb_nsz:
12458392b74bSAdrian Hunter			pos = 0
1246beda0e72STony Jones		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
12478392b74bSAdrian Hunter		if n == 0:
12488392b74bSAdrian Hunter			pos = 0
1249beda0e72STony Jones			n = pickle.loads(self.buffer[0 : glb_nsz])
12508392b74bSAdrian Hunter		pos += glb_nsz
1251beda0e72STony Jones		obj = pickle.loads(self.buffer[pos : pos + n])
12528392b74bSAdrian Hunter		self.local_tail = pos + n
12538392b74bSAdrian Hunter		return obj
12548392b74bSAdrian Hunter
12558392b74bSAdrian Hunter	def ProcessData(self, count):
12568392b74bSAdrian Hunter		for i in xrange(count):
12578392b74bSAdrian Hunter			obj = self.RemoveFromBuffer()
12588392b74bSAdrian Hunter			self.process_data(obj)
12598392b74bSAdrian Hunter		self.tail.value = self.local_tail
12608392b74bSAdrian Hunter		self.wait_event.set()
12618392b74bSAdrian Hunter		self.done.emit(count)
12628392b74bSAdrian Hunter
12638392b74bSAdrian Hunter# Fetch more records bar
12648392b74bSAdrian Hunter
12658392b74bSAdrian Hunterclass FetchMoreRecordsBar():
12668392b74bSAdrian Hunter
12678392b74bSAdrian Hunter	def __init__(self, model, parent):
12688392b74bSAdrian Hunter		self.model = model
12698392b74bSAdrian Hunter
12708392b74bSAdrian Hunter		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
12718392b74bSAdrian Hunter		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12728392b74bSAdrian Hunter
12738392b74bSAdrian Hunter		self.fetch_count = QSpinBox()
12748392b74bSAdrian Hunter		self.fetch_count.setRange(1, 1000000)
12758392b74bSAdrian Hunter		self.fetch_count.setValue(10)
12768392b74bSAdrian Hunter		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12778392b74bSAdrian Hunter
12788392b74bSAdrian Hunter		self.fetch = QPushButton("Go!")
12798392b74bSAdrian Hunter		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
12808392b74bSAdrian Hunter		self.fetch.released.connect(self.FetchMoreRecords)
12818392b74bSAdrian Hunter
12828392b74bSAdrian Hunter		self.progress = QProgressBar()
12838392b74bSAdrian Hunter		self.progress.setRange(0, 100)
12848392b74bSAdrian Hunter		self.progress.hide()
12858392b74bSAdrian Hunter
12868392b74bSAdrian Hunter		self.done_label = QLabel("All records fetched")
12878392b74bSAdrian Hunter		self.done_label.hide()
12888392b74bSAdrian Hunter
12898392b74bSAdrian Hunter		self.spacer = QLabel("")
12908392b74bSAdrian Hunter
12918392b74bSAdrian Hunter		self.close_button = QToolButton()
12928392b74bSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
12938392b74bSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
12948392b74bSAdrian Hunter
12958392b74bSAdrian Hunter		self.hbox = QHBoxLayout()
12968392b74bSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
12978392b74bSAdrian Hunter
12988392b74bSAdrian Hunter		self.hbox.addWidget(self.label)
12998392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch_count)
13008392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch)
13018392b74bSAdrian Hunter		self.hbox.addWidget(self.spacer)
13028392b74bSAdrian Hunter		self.hbox.addWidget(self.progress)
13038392b74bSAdrian Hunter		self.hbox.addWidget(self.done_label)
13048392b74bSAdrian Hunter		self.hbox.addWidget(self.close_button)
13058392b74bSAdrian Hunter
13068392b74bSAdrian Hunter		self.bar = QWidget()
13078392b74bSAdrian Hunter		self.bar.setLayout(self.hbox);
13088392b74bSAdrian Hunter		self.bar.show()
13098392b74bSAdrian Hunter
13108392b74bSAdrian Hunter		self.in_progress = False
13118392b74bSAdrian Hunter		self.model.progress.connect(self.Progress)
13128392b74bSAdrian Hunter
13138392b74bSAdrian Hunter		self.done = False
13148392b74bSAdrian Hunter
13158392b74bSAdrian Hunter		if not model.HasMoreRecords():
13168392b74bSAdrian Hunter			self.Done()
13178392b74bSAdrian Hunter
13188392b74bSAdrian Hunter	def Widget(self):
13198392b74bSAdrian Hunter		return self.bar
13208392b74bSAdrian Hunter
13218392b74bSAdrian Hunter	def Activate(self):
13228392b74bSAdrian Hunter		self.bar.show()
13238392b74bSAdrian Hunter		self.fetch.setFocus()
13248392b74bSAdrian Hunter
13258392b74bSAdrian Hunter	def Deactivate(self):
13268392b74bSAdrian Hunter		self.bar.hide()
13278392b74bSAdrian Hunter
13288392b74bSAdrian Hunter	def Enable(self, enable):
13298392b74bSAdrian Hunter		self.fetch.setEnabled(enable)
13308392b74bSAdrian Hunter		self.fetch_count.setEnabled(enable)
13318392b74bSAdrian Hunter
13328392b74bSAdrian Hunter	def Busy(self):
13338392b74bSAdrian Hunter		self.Enable(False)
13348392b74bSAdrian Hunter		self.fetch.hide()
13358392b74bSAdrian Hunter		self.spacer.hide()
13368392b74bSAdrian Hunter		self.progress.show()
13378392b74bSAdrian Hunter
13388392b74bSAdrian Hunter	def Idle(self):
13398392b74bSAdrian Hunter		self.in_progress = False
13408392b74bSAdrian Hunter		self.Enable(True)
13418392b74bSAdrian Hunter		self.progress.hide()
13428392b74bSAdrian Hunter		self.fetch.show()
13438392b74bSAdrian Hunter		self.spacer.show()
13448392b74bSAdrian Hunter
13458392b74bSAdrian Hunter	def Target(self):
13468392b74bSAdrian Hunter		return self.fetch_count.value() * glb_chunk_sz
13478392b74bSAdrian Hunter
13488392b74bSAdrian Hunter	def Done(self):
13498392b74bSAdrian Hunter		self.done = True
13508392b74bSAdrian Hunter		self.Idle()
13518392b74bSAdrian Hunter		self.label.hide()
13528392b74bSAdrian Hunter		self.fetch_count.hide()
13538392b74bSAdrian Hunter		self.fetch.hide()
13548392b74bSAdrian Hunter		self.spacer.hide()
13558392b74bSAdrian Hunter		self.done_label.show()
13568392b74bSAdrian Hunter
13578392b74bSAdrian Hunter	def Progress(self, count):
13588392b74bSAdrian Hunter		if self.in_progress:
13598392b74bSAdrian Hunter			if count:
13608392b74bSAdrian Hunter				percent = ((count - self.start) * 100) / self.Target()
13618392b74bSAdrian Hunter				if percent >= 100:
13628392b74bSAdrian Hunter					self.Idle()
13638392b74bSAdrian Hunter				else:
13648392b74bSAdrian Hunter					self.progress.setValue(percent)
13658392b74bSAdrian Hunter		if not count:
13668392b74bSAdrian Hunter			# Count value of zero means no more records
13678392b74bSAdrian Hunter			self.Done()
13688392b74bSAdrian Hunter
13698392b74bSAdrian Hunter	def FetchMoreRecords(self):
13708392b74bSAdrian Hunter		if self.done:
13718392b74bSAdrian Hunter			return
13728392b74bSAdrian Hunter		self.progress.setValue(0)
13738392b74bSAdrian Hunter		self.Busy()
13748392b74bSAdrian Hunter		self.in_progress = True
13758392b74bSAdrian Hunter		self.start = self.model.FetchMoreRecords(self.Target())
13768392b74bSAdrian Hunter
137776099f98SAdrian Hunter# Brance data model level two item
137876099f98SAdrian Hunter
137976099f98SAdrian Hunterclass BranchLevelTwoItem():
138076099f98SAdrian Hunter
1381530e22fdSAdrian Hunter	def __init__(self, row, col, text, parent_item):
138276099f98SAdrian Hunter		self.row = row
138376099f98SAdrian Hunter		self.parent_item = parent_item
1384530e22fdSAdrian Hunter		self.data = [""] * (col + 1)
1385530e22fdSAdrian Hunter		self.data[col] = text
138676099f98SAdrian Hunter		self.level = 2
138776099f98SAdrian Hunter
138876099f98SAdrian Hunter	def getParentItem(self):
138976099f98SAdrian Hunter		return self.parent_item
139076099f98SAdrian Hunter
139176099f98SAdrian Hunter	def getRow(self):
139276099f98SAdrian Hunter		return self.row
139376099f98SAdrian Hunter
139476099f98SAdrian Hunter	def childCount(self):
139576099f98SAdrian Hunter		return 0
139676099f98SAdrian Hunter
139776099f98SAdrian Hunter	def hasChildren(self):
139876099f98SAdrian Hunter		return False
139976099f98SAdrian Hunter
140076099f98SAdrian Hunter	def getData(self, column):
140176099f98SAdrian Hunter		return self.data[column]
140276099f98SAdrian Hunter
140376099f98SAdrian Hunter# Brance data model level one item
140476099f98SAdrian Hunter
140576099f98SAdrian Hunterclass BranchLevelOneItem():
140676099f98SAdrian Hunter
140776099f98SAdrian Hunter	def __init__(self, glb, row, data, parent_item):
140876099f98SAdrian Hunter		self.glb = glb
140976099f98SAdrian Hunter		self.row = row
141076099f98SAdrian Hunter		self.parent_item = parent_item
141176099f98SAdrian Hunter		self.child_count = 0
141276099f98SAdrian Hunter		self.child_items = []
141376099f98SAdrian Hunter		self.data = data[1:]
141476099f98SAdrian Hunter		self.dbid = data[0]
141576099f98SAdrian Hunter		self.level = 1
141676099f98SAdrian Hunter		self.query_done = False
1417530e22fdSAdrian Hunter		self.br_col = len(self.data) - 1
141876099f98SAdrian Hunter
141976099f98SAdrian Hunter	def getChildItem(self, row):
142076099f98SAdrian Hunter		return self.child_items[row]
142176099f98SAdrian Hunter
142276099f98SAdrian Hunter	def getParentItem(self):
142376099f98SAdrian Hunter		return self.parent_item
142476099f98SAdrian Hunter
142576099f98SAdrian Hunter	def getRow(self):
142676099f98SAdrian Hunter		return self.row
142776099f98SAdrian Hunter
142876099f98SAdrian Hunter	def Select(self):
142976099f98SAdrian Hunter		self.query_done = True
143076099f98SAdrian Hunter
143176099f98SAdrian Hunter		if not self.glb.have_disassembler:
143276099f98SAdrian Hunter			return
143376099f98SAdrian Hunter
143476099f98SAdrian Hunter		query = QSqlQuery(self.glb.db)
143576099f98SAdrian Hunter
143676099f98SAdrian Hunter		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
143776099f98SAdrian Hunter				  " FROM samples"
143876099f98SAdrian Hunter				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
143976099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
144076099f98SAdrian Hunter				  " WHERE samples.id = " + str(self.dbid))
144176099f98SAdrian Hunter		if not query.next():
144276099f98SAdrian Hunter			return
144376099f98SAdrian Hunter		cpu = query.value(0)
144476099f98SAdrian Hunter		dso = query.value(1)
144576099f98SAdrian Hunter		sym = query.value(2)
144676099f98SAdrian Hunter		if dso == 0 or sym == 0:
144776099f98SAdrian Hunter			return
144876099f98SAdrian Hunter		off = query.value(3)
144976099f98SAdrian Hunter		short_name = query.value(4)
145076099f98SAdrian Hunter		long_name = query.value(5)
145176099f98SAdrian Hunter		build_id = query.value(6)
145276099f98SAdrian Hunter		sym_start = query.value(7)
145376099f98SAdrian Hunter		ip = query.value(8)
145476099f98SAdrian Hunter
145576099f98SAdrian Hunter		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
145676099f98SAdrian Hunter				  " FROM samples"
145776099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
145876099f98SAdrian Hunter				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
145976099f98SAdrian Hunter				  " ORDER BY samples.id"
146076099f98SAdrian Hunter				  " LIMIT 1")
146176099f98SAdrian Hunter		if not query.next():
146276099f98SAdrian Hunter			return
146376099f98SAdrian Hunter		if query.value(0) != dso:
146476099f98SAdrian Hunter			# Cannot disassemble from one dso to another
146576099f98SAdrian Hunter			return
146676099f98SAdrian Hunter		bsym = query.value(1)
146776099f98SAdrian Hunter		boff = query.value(2)
146876099f98SAdrian Hunter		bsym_start = query.value(3)
146976099f98SAdrian Hunter		if bsym == 0:
147076099f98SAdrian Hunter			return
147176099f98SAdrian Hunter		tot = bsym_start + boff + 1 - sym_start - off
147276099f98SAdrian Hunter		if tot <= 0 or tot > 16384:
147376099f98SAdrian Hunter			return
147476099f98SAdrian Hunter
147576099f98SAdrian Hunter		inst = self.glb.disassembler.Instruction()
147676099f98SAdrian Hunter		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
147776099f98SAdrian Hunter		if not f:
147876099f98SAdrian Hunter			return
147976099f98SAdrian Hunter		mode = 0 if Is64Bit(f) else 1
148076099f98SAdrian Hunter		self.glb.disassembler.SetMode(inst, mode)
148176099f98SAdrian Hunter
148276099f98SAdrian Hunter		buf_sz = tot + 16
148376099f98SAdrian Hunter		buf = create_string_buffer(tot + 16)
148476099f98SAdrian Hunter		f.seek(sym_start + off)
148576099f98SAdrian Hunter		buf.value = f.read(buf_sz)
148676099f98SAdrian Hunter		buf_ptr = addressof(buf)
148776099f98SAdrian Hunter		i = 0
148876099f98SAdrian Hunter		while tot > 0:
148976099f98SAdrian Hunter			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
149076099f98SAdrian Hunter			if cnt:
149176099f98SAdrian Hunter				byte_str = tohex(ip).rjust(16)
149276099f98SAdrian Hunter				for k in xrange(cnt):
149376099f98SAdrian Hunter					byte_str += " %02x" % ord(buf[i])
149476099f98SAdrian Hunter					i += 1
149576099f98SAdrian Hunter				while k < 15:
149676099f98SAdrian Hunter					byte_str += "   "
149776099f98SAdrian Hunter					k += 1
1498530e22fdSAdrian Hunter				self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
149976099f98SAdrian Hunter				self.child_count += 1
150076099f98SAdrian Hunter			else:
150176099f98SAdrian Hunter				return
150276099f98SAdrian Hunter			buf_ptr += cnt
150376099f98SAdrian Hunter			tot -= cnt
150476099f98SAdrian Hunter			buf_sz -= cnt
150576099f98SAdrian Hunter			ip += cnt
150676099f98SAdrian Hunter
150776099f98SAdrian Hunter	def childCount(self):
150876099f98SAdrian Hunter		if not self.query_done:
150976099f98SAdrian Hunter			self.Select()
151076099f98SAdrian Hunter			if not self.child_count:
151176099f98SAdrian Hunter				return -1
151276099f98SAdrian Hunter		return self.child_count
151376099f98SAdrian Hunter
151476099f98SAdrian Hunter	def hasChildren(self):
151576099f98SAdrian Hunter		if not self.query_done:
151676099f98SAdrian Hunter			return True
151776099f98SAdrian Hunter		return self.child_count > 0
151876099f98SAdrian Hunter
151976099f98SAdrian Hunter	def getData(self, column):
152076099f98SAdrian Hunter		return self.data[column]
152176099f98SAdrian Hunter
152276099f98SAdrian Hunter# Brance data model root item
152376099f98SAdrian Hunter
152476099f98SAdrian Hunterclass BranchRootItem():
152576099f98SAdrian Hunter
152676099f98SAdrian Hunter	def __init__(self):
152776099f98SAdrian Hunter		self.child_count = 0
152876099f98SAdrian Hunter		self.child_items = []
152976099f98SAdrian Hunter		self.level = 0
153076099f98SAdrian Hunter
153176099f98SAdrian Hunter	def getChildItem(self, row):
153276099f98SAdrian Hunter		return self.child_items[row]
153376099f98SAdrian Hunter
153476099f98SAdrian Hunter	def getParentItem(self):
153576099f98SAdrian Hunter		return None
153676099f98SAdrian Hunter
153776099f98SAdrian Hunter	def getRow(self):
153876099f98SAdrian Hunter		return 0
153976099f98SAdrian Hunter
154076099f98SAdrian Hunter	def childCount(self):
154176099f98SAdrian Hunter		return self.child_count
154276099f98SAdrian Hunter
154376099f98SAdrian Hunter	def hasChildren(self):
154476099f98SAdrian Hunter		return self.child_count > 0
154576099f98SAdrian Hunter
154676099f98SAdrian Hunter	def getData(self, column):
154776099f98SAdrian Hunter		return ""
154876099f98SAdrian Hunter
1549530e22fdSAdrian Hunter# Calculate instructions per cycle
1550530e22fdSAdrian Hunter
1551530e22fdSAdrian Hunterdef CalcIPC(cyc_cnt, insn_cnt):
1552530e22fdSAdrian Hunter	if cyc_cnt and insn_cnt:
1553530e22fdSAdrian Hunter		ipc = Decimal(float(insn_cnt) / cyc_cnt)
1554530e22fdSAdrian Hunter		ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
1555530e22fdSAdrian Hunter	else:
1556530e22fdSAdrian Hunter		ipc = "0"
1557530e22fdSAdrian Hunter	return ipc
1558530e22fdSAdrian Hunter
155976099f98SAdrian Hunter# Branch data preparation
156076099f98SAdrian Hunter
1561530e22fdSAdrian Hunterdef BranchDataPrepBr(query, data):
1562530e22fdSAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1563530e22fdSAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
1564530e22fdSAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1565530e22fdSAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
1566530e22fdSAdrian Hunter
1567530e22fdSAdrian Hunterdef BranchDataPrepIPC(query, data):
1568530e22fdSAdrian Hunter	insn_cnt = query.value(16)
1569530e22fdSAdrian Hunter	cyc_cnt = query.value(17)
1570530e22fdSAdrian Hunter	ipc = CalcIPC(cyc_cnt, insn_cnt)
1571530e22fdSAdrian Hunter	data.append(insn_cnt)
1572530e22fdSAdrian Hunter	data.append(cyc_cnt)
1573530e22fdSAdrian Hunter	data.append(ipc)
1574530e22fdSAdrian Hunter
157576099f98SAdrian Hunterdef BranchDataPrep(query):
157676099f98SAdrian Hunter	data = []
157776099f98SAdrian Hunter	for i in xrange(0, 8):
157876099f98SAdrian Hunter		data.append(query.value(i))
1579530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
158076099f98SAdrian Hunter	return data
158176099f98SAdrian Hunter
15828453c936SAdrian Hunterdef BranchDataPrepWA(query):
15838453c936SAdrian Hunter	data = []
15848453c936SAdrian Hunter	data.append(query.value(0))
15858453c936SAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
15868453c936SAdrian Hunter	data.append("{:>19}".format(query.value(1)))
15878453c936SAdrian Hunter	for i in xrange(2, 8):
15888453c936SAdrian Hunter		data.append(query.value(i))
1589530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
1590530e22fdSAdrian Hunter	return data
1591530e22fdSAdrian Hunter
1592530e22fdSAdrian Hunterdef BranchDataWithIPCPrep(query):
1593530e22fdSAdrian Hunter	data = []
1594530e22fdSAdrian Hunter	for i in xrange(0, 8):
1595530e22fdSAdrian Hunter		data.append(query.value(i))
1596530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
1597530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
1598530e22fdSAdrian Hunter	return data
1599530e22fdSAdrian Hunter
1600530e22fdSAdrian Hunterdef BranchDataWithIPCPrepWA(query):
1601530e22fdSAdrian Hunter	data = []
1602530e22fdSAdrian Hunter	data.append(query.value(0))
1603530e22fdSAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1604530e22fdSAdrian Hunter	data.append("{:>19}".format(query.value(1)))
1605530e22fdSAdrian Hunter	for i in xrange(2, 8):
1606530e22fdSAdrian Hunter		data.append(query.value(i))
1607530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
1608530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
16098453c936SAdrian Hunter	return data
16108453c936SAdrian Hunter
161176099f98SAdrian Hunter# Branch data model
161276099f98SAdrian Hunter
161376099f98SAdrian Hunterclass BranchModel(TreeModel):
161476099f98SAdrian Hunter
161576099f98SAdrian Hunter	progress = Signal(object)
161676099f98SAdrian Hunter
161776099f98SAdrian Hunter	def __init__(self, glb, event_id, where_clause, parent=None):
1618*4a0979d4SAdrian Hunter		super(BranchModel, self).__init__(glb, None, parent)
161976099f98SAdrian Hunter		self.event_id = event_id
162076099f98SAdrian Hunter		self.more = True
162176099f98SAdrian Hunter		self.populated = 0
1622530e22fdSAdrian Hunter		self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
1623530e22fdSAdrian Hunter		if self.have_ipc:
1624530e22fdSAdrian Hunter			select_ipc = ", insn_count, cyc_count"
1625530e22fdSAdrian Hunter			prep_fn = BranchDataWithIPCPrep
1626530e22fdSAdrian Hunter			prep_wa_fn = BranchDataWithIPCPrepWA
1627530e22fdSAdrian Hunter		else:
1628530e22fdSAdrian Hunter			select_ipc = ""
1629530e22fdSAdrian Hunter			prep_fn = BranchDataPrep
1630530e22fdSAdrian Hunter			prep_wa_fn = BranchDataPrepWA
163176099f98SAdrian Hunter		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
163276099f98SAdrian Hunter			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
163376099f98SAdrian Hunter			" ip, symbols.name, sym_offset, dsos.short_name,"
163476099f98SAdrian Hunter			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1635530e22fdSAdrian Hunter			+ select_ipc +
163676099f98SAdrian Hunter			" FROM samples"
163776099f98SAdrian Hunter			" INNER JOIN comms ON comm_id = comms.id"
163876099f98SAdrian Hunter			" INNER JOIN threads ON thread_id = threads.id"
163976099f98SAdrian Hunter			" INNER JOIN branch_types ON branch_type = branch_types.id"
164076099f98SAdrian Hunter			" INNER JOIN symbols ON symbol_id = symbols.id"
164176099f98SAdrian Hunter			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
164276099f98SAdrian Hunter			" INNER JOIN dsos ON samples.dso_id = dsos.id"
164376099f98SAdrian Hunter			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
164476099f98SAdrian Hunter			" WHERE samples.id > $$last_id$$" + where_clause +
164576099f98SAdrian Hunter			" AND evsel_id = " + str(self.event_id) +
164676099f98SAdrian Hunter			" ORDER BY samples.id"
164776099f98SAdrian Hunter			" LIMIT " + str(glb_chunk_sz))
16488453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
1649530e22fdSAdrian Hunter			prep = prep_fn
16508453c936SAdrian Hunter		else:
1651530e22fdSAdrian Hunter			prep = prep_wa_fn
16528453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
165376099f98SAdrian Hunter		self.fetcher.done.connect(self.Update)
165476099f98SAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
165576099f98SAdrian Hunter
1656a448ba23SAdrian Hunter	def GetRoot(self):
1657a448ba23SAdrian Hunter		return BranchRootItem()
1658a448ba23SAdrian Hunter
165976099f98SAdrian Hunter	def columnCount(self, parent=None):
1660530e22fdSAdrian Hunter		if self.have_ipc:
1661530e22fdSAdrian Hunter			return 11
1662530e22fdSAdrian Hunter		else:
166376099f98SAdrian Hunter			return 8
166476099f98SAdrian Hunter
166576099f98SAdrian Hunter	def columnHeader(self, column):
1666530e22fdSAdrian Hunter		if self.have_ipc:
1667530e22fdSAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
1668530e22fdSAdrian Hunter		else:
166976099f98SAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
167076099f98SAdrian Hunter
167176099f98SAdrian Hunter	def columnFont(self, column):
1672530e22fdSAdrian Hunter		if self.have_ipc:
1673530e22fdSAdrian Hunter			br_col = 10
1674530e22fdSAdrian Hunter		else:
1675530e22fdSAdrian Hunter			br_col = 7
1676530e22fdSAdrian Hunter		if column != br_col:
167776099f98SAdrian Hunter			return None
167876099f98SAdrian Hunter		return QFont("Monospace")
167976099f98SAdrian Hunter
168076099f98SAdrian Hunter	def DisplayData(self, item, index):
168176099f98SAdrian Hunter		if item.level == 1:
168276099f98SAdrian Hunter			self.FetchIfNeeded(item.row)
168376099f98SAdrian Hunter		return item.getData(index.column())
168476099f98SAdrian Hunter
168576099f98SAdrian Hunter	def AddSample(self, data):
168676099f98SAdrian Hunter		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
168776099f98SAdrian Hunter		self.root.child_items.append(child)
168876099f98SAdrian Hunter		self.populated += 1
168976099f98SAdrian Hunter
169076099f98SAdrian Hunter	def Update(self, fetched):
169176099f98SAdrian Hunter		if not fetched:
169276099f98SAdrian Hunter			self.more = False
169376099f98SAdrian Hunter			self.progress.emit(0)
169476099f98SAdrian Hunter		child_count = self.root.child_count
169576099f98SAdrian Hunter		count = self.populated - child_count
169676099f98SAdrian Hunter		if count > 0:
169776099f98SAdrian Hunter			parent = QModelIndex()
169876099f98SAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
169976099f98SAdrian Hunter			self.insertRows(child_count, count, parent)
170076099f98SAdrian Hunter			self.root.child_count += count
170176099f98SAdrian Hunter			self.endInsertRows()
170276099f98SAdrian Hunter			self.progress.emit(self.root.child_count)
170376099f98SAdrian Hunter
170476099f98SAdrian Hunter	def FetchMoreRecords(self, count):
170576099f98SAdrian Hunter		current = self.root.child_count
170676099f98SAdrian Hunter		if self.more:
170776099f98SAdrian Hunter			self.fetcher.Fetch(count)
170876099f98SAdrian Hunter		else:
170976099f98SAdrian Hunter			self.progress.emit(0)
171076099f98SAdrian Hunter		return current
171176099f98SAdrian Hunter
171276099f98SAdrian Hunter	def HasMoreRecords(self):
171376099f98SAdrian Hunter		return self.more
171476099f98SAdrian Hunter
17150bf0947aSAdrian Hunter# Report Variables
17160bf0947aSAdrian Hunter
17170bf0947aSAdrian Hunterclass ReportVars():
17180bf0947aSAdrian Hunter
1719cd358012SAdrian Hunter	def __init__(self, name = "", where_clause = "", limit = ""):
1720947cc38dSAdrian Hunter		self.name = name
17210bf0947aSAdrian Hunter		self.where_clause = where_clause
1722cd358012SAdrian Hunter		self.limit = limit
17230bf0947aSAdrian Hunter
17240bf0947aSAdrian Hunter	def UniqueId(self):
1725cd358012SAdrian Hunter		return str(self.where_clause + ";" + self.limit)
17260bf0947aSAdrian Hunter
172776099f98SAdrian Hunter# Branch window
172876099f98SAdrian Hunter
172976099f98SAdrian Hunterclass BranchWindow(QMdiSubWindow):
173076099f98SAdrian Hunter
1731947cc38dSAdrian Hunter	def __init__(self, glb, event_id, report_vars, parent=None):
173276099f98SAdrian Hunter		super(BranchWindow, self).__init__(parent)
173376099f98SAdrian Hunter
17340bf0947aSAdrian Hunter		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
173576099f98SAdrian Hunter
17360bf0947aSAdrian Hunter		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
173776099f98SAdrian Hunter
173876099f98SAdrian Hunter		self.view = QTreeView()
173976099f98SAdrian Hunter		self.view.setUniformRowHeights(True)
174096c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
174196c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
174276099f98SAdrian Hunter		self.view.setModel(self.model)
174376099f98SAdrian Hunter
174476099f98SAdrian Hunter		self.ResizeColumnsToContents()
174576099f98SAdrian Hunter
17469bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
17479bc4e4bfSAdrian Hunter
174876099f98SAdrian Hunter		self.find_bar = FindBar(self, self, True)
174976099f98SAdrian Hunter
175076099f98SAdrian Hunter		self.finder = ChildDataItemFinder(self.model.root)
175176099f98SAdrian Hunter
175276099f98SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
175376099f98SAdrian Hunter
175476099f98SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
175576099f98SAdrian Hunter
175676099f98SAdrian Hunter		self.setWidget(self.vbox.Widget())
175776099f98SAdrian Hunter
1758947cc38dSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
175976099f98SAdrian Hunter
176076099f98SAdrian Hunter	def ResizeColumnToContents(self, column, n):
176176099f98SAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
176276099f98SAdrian Hunter		# so implement a crude alternative
176376099f98SAdrian Hunter		mm = "MM" if column else "MMMM"
176476099f98SAdrian Hunter		font = self.view.font()
176576099f98SAdrian Hunter		metrics = QFontMetrics(font)
176676099f98SAdrian Hunter		max = 0
176776099f98SAdrian Hunter		for row in xrange(n):
176876099f98SAdrian Hunter			val = self.model.root.child_items[row].data[column]
176976099f98SAdrian Hunter			len = metrics.width(str(val) + mm)
177076099f98SAdrian Hunter			max = len if len > max else max
177176099f98SAdrian Hunter		val = self.model.columnHeader(column)
177276099f98SAdrian Hunter		len = metrics.width(str(val) + mm)
177376099f98SAdrian Hunter		max = len if len > max else max
177476099f98SAdrian Hunter		self.view.setColumnWidth(column, max)
177576099f98SAdrian Hunter
177676099f98SAdrian Hunter	def ResizeColumnsToContents(self):
177776099f98SAdrian Hunter		n = min(self.model.root.child_count, 100)
177876099f98SAdrian Hunter		if n < 1:
177976099f98SAdrian Hunter			# No data yet, so connect a signal to notify when there is
178076099f98SAdrian Hunter			self.model.rowsInserted.connect(self.UpdateColumnWidths)
178176099f98SAdrian Hunter			return
178276099f98SAdrian Hunter		columns = self.model.columnCount()
178376099f98SAdrian Hunter		for i in xrange(columns):
178476099f98SAdrian Hunter			self.ResizeColumnToContents(i, n)
178576099f98SAdrian Hunter
178676099f98SAdrian Hunter	def UpdateColumnWidths(self, *x):
178776099f98SAdrian Hunter		# This only needs to be done once, so disconnect the signal now
178876099f98SAdrian Hunter		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
178976099f98SAdrian Hunter		self.ResizeColumnsToContents()
179076099f98SAdrian Hunter
179176099f98SAdrian Hunter	def Find(self, value, direction, pattern, context):
179276099f98SAdrian Hunter		self.view.setFocus()
179376099f98SAdrian Hunter		self.find_bar.Busy()
179476099f98SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
179576099f98SAdrian Hunter
179676099f98SAdrian Hunter	def FindDone(self, row):
179776099f98SAdrian Hunter		self.find_bar.Idle()
179876099f98SAdrian Hunter		if row >= 0:
179976099f98SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
180076099f98SAdrian Hunter		else:
180176099f98SAdrian Hunter			self.find_bar.NotFound()
180276099f98SAdrian Hunter
18031c3ca1b3SAdrian Hunter# Line edit data item
18041c3ca1b3SAdrian Hunter
18051c3ca1b3SAdrian Hunterclass LineEditDataItem(object):
18061c3ca1b3SAdrian Hunter
1807cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
18081c3ca1b3SAdrian Hunter		self.glb = glb
18091c3ca1b3SAdrian Hunter		self.label = label
18101c3ca1b3SAdrian Hunter		self.placeholder_text = placeholder_text
18111c3ca1b3SAdrian Hunter		self.parent = parent
18121c3ca1b3SAdrian Hunter		self.id = id
18131c3ca1b3SAdrian Hunter
1814cd358012SAdrian Hunter		self.value = default
18151c3ca1b3SAdrian Hunter
1816cd358012SAdrian Hunter		self.widget = QLineEdit(default)
18171c3ca1b3SAdrian Hunter		self.widget.editingFinished.connect(self.Validate)
18181c3ca1b3SAdrian Hunter		self.widget.textChanged.connect(self.Invalidate)
18191c3ca1b3SAdrian Hunter		self.red = False
18201c3ca1b3SAdrian Hunter		self.error = ""
18211c3ca1b3SAdrian Hunter		self.validated = True
18221c3ca1b3SAdrian Hunter
18231c3ca1b3SAdrian Hunter		if placeholder_text:
18241c3ca1b3SAdrian Hunter			self.widget.setPlaceholderText(placeholder_text)
18251c3ca1b3SAdrian Hunter
18261c3ca1b3SAdrian Hunter	def TurnTextRed(self):
18271c3ca1b3SAdrian Hunter		if not self.red:
18281c3ca1b3SAdrian Hunter			palette = QPalette()
18291c3ca1b3SAdrian Hunter			palette.setColor(QPalette.Text,Qt.red)
18301c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
18311c3ca1b3SAdrian Hunter			self.red = True
18321c3ca1b3SAdrian Hunter
18331c3ca1b3SAdrian Hunter	def TurnTextNormal(self):
18341c3ca1b3SAdrian Hunter		if self.red:
18351c3ca1b3SAdrian Hunter			palette = QPalette()
18361c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
18371c3ca1b3SAdrian Hunter			self.red = False
18381c3ca1b3SAdrian Hunter
18391c3ca1b3SAdrian Hunter	def InvalidValue(self, value):
18401c3ca1b3SAdrian Hunter		self.value = ""
18411c3ca1b3SAdrian Hunter		self.TurnTextRed()
18421c3ca1b3SAdrian Hunter		self.error = self.label + " invalid value '" + value + "'"
18431c3ca1b3SAdrian Hunter		self.parent.ShowMessage(self.error)
18441c3ca1b3SAdrian Hunter
18451c3ca1b3SAdrian Hunter	def Invalidate(self):
18461c3ca1b3SAdrian Hunter		self.validated = False
18471c3ca1b3SAdrian Hunter
18481c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
18491c3ca1b3SAdrian Hunter		self.value = input_string.strip()
18501c3ca1b3SAdrian Hunter
18511c3ca1b3SAdrian Hunter	def Validate(self):
18521c3ca1b3SAdrian Hunter		self.validated = True
18531c3ca1b3SAdrian Hunter		self.error = ""
18541c3ca1b3SAdrian Hunter		self.TurnTextNormal()
18551c3ca1b3SAdrian Hunter		self.parent.ClearMessage()
18561c3ca1b3SAdrian Hunter		input_string = self.widget.text()
18571c3ca1b3SAdrian Hunter		if not len(input_string.strip()):
18581c3ca1b3SAdrian Hunter			self.value = ""
18591c3ca1b3SAdrian Hunter			return
18601c3ca1b3SAdrian Hunter		self.DoValidate(input_string)
18611c3ca1b3SAdrian Hunter
18621c3ca1b3SAdrian Hunter	def IsValid(self):
18631c3ca1b3SAdrian Hunter		if not self.validated:
18641c3ca1b3SAdrian Hunter			self.Validate()
18651c3ca1b3SAdrian Hunter		if len(self.error):
18661c3ca1b3SAdrian Hunter			self.parent.ShowMessage(self.error)
18671c3ca1b3SAdrian Hunter			return False
18681c3ca1b3SAdrian Hunter		return True
18691c3ca1b3SAdrian Hunter
18701c3ca1b3SAdrian Hunter	def IsNumber(self, value):
18711c3ca1b3SAdrian Hunter		try:
18721c3ca1b3SAdrian Hunter			x = int(value)
18731c3ca1b3SAdrian Hunter		except:
18741c3ca1b3SAdrian Hunter			x = 0
18751c3ca1b3SAdrian Hunter		return str(x) == value
18761c3ca1b3SAdrian Hunter
18771c3ca1b3SAdrian Hunter# Non-negative integer ranges dialog data item
18781c3ca1b3SAdrian Hunter
18791c3ca1b3SAdrian Hunterclass NonNegativeIntegerRangesDataItem(LineEditDataItem):
18801c3ca1b3SAdrian Hunter
18811c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
18821c3ca1b3SAdrian Hunter		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
18831c3ca1b3SAdrian Hunter
18841c3ca1b3SAdrian Hunter		self.column_name = column_name
18851c3ca1b3SAdrian Hunter
18861c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
18871c3ca1b3SAdrian Hunter		singles = []
18881c3ca1b3SAdrian Hunter		ranges = []
18891c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
18901c3ca1b3SAdrian Hunter			if "-" in value:
18911c3ca1b3SAdrian Hunter				vrange = value.split("-")
18921c3ca1b3SAdrian Hunter				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
18931c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
18941c3ca1b3SAdrian Hunter				ranges.append(vrange)
18951c3ca1b3SAdrian Hunter			else:
18961c3ca1b3SAdrian Hunter				if not self.IsNumber(value):
18971c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
18981c3ca1b3SAdrian Hunter				singles.append(value)
18991c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
19001c3ca1b3SAdrian Hunter		if len(singles):
19011c3ca1b3SAdrian Hunter			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
19021c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
19031c3ca1b3SAdrian Hunter
1904cd358012SAdrian Hunter# Positive integer dialog data item
1905cd358012SAdrian Hunter
1906cd358012SAdrian Hunterclass PositiveIntegerDataItem(LineEditDataItem):
1907cd358012SAdrian Hunter
1908cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1909cd358012SAdrian Hunter		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1910cd358012SAdrian Hunter
1911cd358012SAdrian Hunter	def DoValidate(self, input_string):
1912cd358012SAdrian Hunter		if not self.IsNumber(input_string.strip()):
1913cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1914cd358012SAdrian Hunter		value = int(input_string.strip())
1915cd358012SAdrian Hunter		if value <= 0:
1916cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1917cd358012SAdrian Hunter		self.value = str(value)
1918cd358012SAdrian Hunter
19191c3ca1b3SAdrian Hunter# Dialog data item converted and validated using a SQL table
19201c3ca1b3SAdrian Hunter
19211c3ca1b3SAdrian Hunterclass SQLTableDataItem(LineEditDataItem):
19221c3ca1b3SAdrian Hunter
19231c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
19241c3ca1b3SAdrian Hunter		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
19251c3ca1b3SAdrian Hunter
19261c3ca1b3SAdrian Hunter		self.table_name = table_name
19271c3ca1b3SAdrian Hunter		self.match_column = match_column
19281c3ca1b3SAdrian Hunter		self.column_name1 = column_name1
19291c3ca1b3SAdrian Hunter		self.column_name2 = column_name2
19301c3ca1b3SAdrian Hunter
19311c3ca1b3SAdrian Hunter	def ValueToIds(self, value):
19321c3ca1b3SAdrian Hunter		ids = []
19331c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
19341c3ca1b3SAdrian Hunter		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
19351c3ca1b3SAdrian Hunter		ret = query.exec_(stmt)
19361c3ca1b3SAdrian Hunter		if ret:
19371c3ca1b3SAdrian Hunter			while query.next():
19381c3ca1b3SAdrian Hunter				ids.append(str(query.value(0)))
19391c3ca1b3SAdrian Hunter		return ids
19401c3ca1b3SAdrian Hunter
19411c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
19421c3ca1b3SAdrian Hunter		all_ids = []
19431c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
19441c3ca1b3SAdrian Hunter			ids = self.ValueToIds(value)
19451c3ca1b3SAdrian Hunter			if len(ids):
19461c3ca1b3SAdrian Hunter				all_ids.extend(ids)
19471c3ca1b3SAdrian Hunter			else:
19481c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
19491c3ca1b3SAdrian Hunter		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
19501c3ca1b3SAdrian Hunter		if self.column_name2:
19511c3ca1b3SAdrian Hunter			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
19521c3ca1b3SAdrian Hunter
19531c3ca1b3SAdrian Hunter# Sample time ranges dialog data item converted and validated using 'samples' SQL table
19541c3ca1b3SAdrian Hunter
19551c3ca1b3SAdrian Hunterclass SampleTimeRangesDataItem(LineEditDataItem):
19561c3ca1b3SAdrian Hunter
19571c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
19581c3ca1b3SAdrian Hunter		self.column_name = column_name
19591c3ca1b3SAdrian Hunter
19601c3ca1b3SAdrian Hunter		self.last_id = 0
19611c3ca1b3SAdrian Hunter		self.first_time = 0
19621c3ca1b3SAdrian Hunter		self.last_time = 2 ** 64
19631c3ca1b3SAdrian Hunter
19641c3ca1b3SAdrian Hunter		query = QSqlQuery(glb.db)
19651c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
19661c3ca1b3SAdrian Hunter		if query.next():
19671c3ca1b3SAdrian Hunter			self.last_id = int(query.value(0))
19681c3ca1b3SAdrian Hunter			self.last_time = int(query.value(1))
19691c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
19701c3ca1b3SAdrian Hunter		if query.next():
19711c3ca1b3SAdrian Hunter			self.first_time = int(query.value(0))
19721c3ca1b3SAdrian Hunter		if placeholder_text:
19731c3ca1b3SAdrian Hunter			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
19741c3ca1b3SAdrian Hunter
19751c3ca1b3SAdrian Hunter		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
19761c3ca1b3SAdrian Hunter
19771c3ca1b3SAdrian Hunter	def IdBetween(self, query, lower_id, higher_id, order):
19781c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
19791c3ca1b3SAdrian Hunter		if query.next():
19801c3ca1b3SAdrian Hunter			return True, int(query.value(0))
19811c3ca1b3SAdrian Hunter		else:
19821c3ca1b3SAdrian Hunter			return False, 0
19831c3ca1b3SAdrian Hunter
19841c3ca1b3SAdrian Hunter	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
19851c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
19861c3ca1b3SAdrian Hunter		while True:
19871c3ca1b3SAdrian Hunter			next_id = int((lower_id + higher_id) / 2)
19881c3ca1b3SAdrian Hunter			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
19891c3ca1b3SAdrian Hunter			if not query.next():
19901c3ca1b3SAdrian Hunter				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
19911c3ca1b3SAdrian Hunter				if not ok:
19921c3ca1b3SAdrian Hunter					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
19931c3ca1b3SAdrian Hunter					if not ok:
19941c3ca1b3SAdrian Hunter						return str(higher_id)
19951c3ca1b3SAdrian Hunter				next_id = dbid
19961c3ca1b3SAdrian Hunter				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
19971c3ca1b3SAdrian Hunter			next_time = int(query.value(0))
19981c3ca1b3SAdrian Hunter			if get_floor:
19991c3ca1b3SAdrian Hunter				if target_time > next_time:
20001c3ca1b3SAdrian Hunter					lower_id = next_id
20011c3ca1b3SAdrian Hunter				else:
20021c3ca1b3SAdrian Hunter					higher_id = next_id
20031c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
20041c3ca1b3SAdrian Hunter					return str(higher_id)
20051c3ca1b3SAdrian Hunter			else:
20061c3ca1b3SAdrian Hunter				if target_time >= next_time:
20071c3ca1b3SAdrian Hunter					lower_id = next_id
20081c3ca1b3SAdrian Hunter				else:
20091c3ca1b3SAdrian Hunter					higher_id = next_id
20101c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
20111c3ca1b3SAdrian Hunter					return str(lower_id)
20121c3ca1b3SAdrian Hunter
20131c3ca1b3SAdrian Hunter	def ConvertRelativeTime(self, val):
20141c3ca1b3SAdrian Hunter		mult = 1
20151c3ca1b3SAdrian Hunter		suffix = val[-2:]
20161c3ca1b3SAdrian Hunter		if suffix == "ms":
20171c3ca1b3SAdrian Hunter			mult = 1000000
20181c3ca1b3SAdrian Hunter		elif suffix == "us":
20191c3ca1b3SAdrian Hunter			mult = 1000
20201c3ca1b3SAdrian Hunter		elif suffix == "ns":
20211c3ca1b3SAdrian Hunter			mult = 1
20221c3ca1b3SAdrian Hunter		else:
20231c3ca1b3SAdrian Hunter			return val
20241c3ca1b3SAdrian Hunter		val = val[:-2].strip()
20251c3ca1b3SAdrian Hunter		if not self.IsNumber(val):
20261c3ca1b3SAdrian Hunter			return val
20271c3ca1b3SAdrian Hunter		val = int(val) * mult
20281c3ca1b3SAdrian Hunter		if val >= 0:
20291c3ca1b3SAdrian Hunter			val += self.first_time
20301c3ca1b3SAdrian Hunter		else:
20311c3ca1b3SAdrian Hunter			val += self.last_time
20321c3ca1b3SAdrian Hunter		return str(val)
20331c3ca1b3SAdrian Hunter
20341c3ca1b3SAdrian Hunter	def ConvertTimeRange(self, vrange):
20351c3ca1b3SAdrian Hunter		if vrange[0] == "":
20361c3ca1b3SAdrian Hunter			vrange[0] = str(self.first_time)
20371c3ca1b3SAdrian Hunter		if vrange[1] == "":
20381c3ca1b3SAdrian Hunter			vrange[1] = str(self.last_time)
20391c3ca1b3SAdrian Hunter		vrange[0] = self.ConvertRelativeTime(vrange[0])
20401c3ca1b3SAdrian Hunter		vrange[1] = self.ConvertRelativeTime(vrange[1])
20411c3ca1b3SAdrian Hunter		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
20421c3ca1b3SAdrian Hunter			return False
20431c3ca1b3SAdrian Hunter		beg_range = max(int(vrange[0]), self.first_time)
20441c3ca1b3SAdrian Hunter		end_range = min(int(vrange[1]), self.last_time)
20451c3ca1b3SAdrian Hunter		if beg_range > self.last_time or end_range < self.first_time:
20461c3ca1b3SAdrian Hunter			return False
20471c3ca1b3SAdrian Hunter		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
20481c3ca1b3SAdrian Hunter		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
20491c3ca1b3SAdrian Hunter		return True
20501c3ca1b3SAdrian Hunter
20511c3ca1b3SAdrian Hunter	def AddTimeRange(self, value, ranges):
20521c3ca1b3SAdrian Hunter		n = value.count("-")
20531c3ca1b3SAdrian Hunter		if n == 1:
20541c3ca1b3SAdrian Hunter			pass
20551c3ca1b3SAdrian Hunter		elif n == 2:
20561c3ca1b3SAdrian Hunter			if value.split("-")[1].strip() == "":
20571c3ca1b3SAdrian Hunter				n = 1
20581c3ca1b3SAdrian Hunter		elif n == 3:
20591c3ca1b3SAdrian Hunter			n = 2
20601c3ca1b3SAdrian Hunter		else:
20611c3ca1b3SAdrian Hunter			return False
20621c3ca1b3SAdrian Hunter		pos = findnth(value, "-", n)
20631c3ca1b3SAdrian Hunter		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
20641c3ca1b3SAdrian Hunter		if self.ConvertTimeRange(vrange):
20651c3ca1b3SAdrian Hunter			ranges.append(vrange)
20661c3ca1b3SAdrian Hunter			return True
20671c3ca1b3SAdrian Hunter		return False
20681c3ca1b3SAdrian Hunter
20691c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
20701c3ca1b3SAdrian Hunter		ranges = []
20711c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
20721c3ca1b3SAdrian Hunter			if not self.AddTimeRange(value, ranges):
20731c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
20741c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
20751c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
20761c3ca1b3SAdrian Hunter
20770924cd68SAdrian Hunter# Report Dialog Base
2078210cf1f9SAdrian Hunter
20790924cd68SAdrian Hunterclass ReportDialogBase(QDialog):
2080210cf1f9SAdrian Hunter
20810924cd68SAdrian Hunter	def __init__(self, glb, title, items, partial, parent=None):
20820924cd68SAdrian Hunter		super(ReportDialogBase, self).__init__(parent)
2083210cf1f9SAdrian Hunter
2084210cf1f9SAdrian Hunter		self.glb = glb
2085210cf1f9SAdrian Hunter
20860bf0947aSAdrian Hunter		self.report_vars = ReportVars()
2087210cf1f9SAdrian Hunter
20880924cd68SAdrian Hunter		self.setWindowTitle(title)
2089210cf1f9SAdrian Hunter		self.setMinimumWidth(600)
2090210cf1f9SAdrian Hunter
20911c3ca1b3SAdrian Hunter		self.data_items = [x(glb, self) for x in items]
2092210cf1f9SAdrian Hunter
20930924cd68SAdrian Hunter		self.partial = partial
20940924cd68SAdrian Hunter
2095210cf1f9SAdrian Hunter		self.grid = QGridLayout()
2096210cf1f9SAdrian Hunter
2097210cf1f9SAdrian Hunter		for row in xrange(len(self.data_items)):
2098210cf1f9SAdrian Hunter			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2099210cf1f9SAdrian Hunter			self.grid.addWidget(self.data_items[row].widget, row, 1)
2100210cf1f9SAdrian Hunter
2101210cf1f9SAdrian Hunter		self.status = QLabel()
2102210cf1f9SAdrian Hunter
2103210cf1f9SAdrian Hunter		self.ok_button = QPushButton("Ok", self)
2104210cf1f9SAdrian Hunter		self.ok_button.setDefault(True)
2105210cf1f9SAdrian Hunter		self.ok_button.released.connect(self.Ok)
2106210cf1f9SAdrian Hunter		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2107210cf1f9SAdrian Hunter
2108210cf1f9SAdrian Hunter		self.cancel_button = QPushButton("Cancel", self)
2109210cf1f9SAdrian Hunter		self.cancel_button.released.connect(self.reject)
2110210cf1f9SAdrian Hunter		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2111210cf1f9SAdrian Hunter
2112210cf1f9SAdrian Hunter		self.hbox = QHBoxLayout()
2113210cf1f9SAdrian Hunter		#self.hbox.addStretch()
2114210cf1f9SAdrian Hunter		self.hbox.addWidget(self.status)
2115210cf1f9SAdrian Hunter		self.hbox.addWidget(self.ok_button)
2116210cf1f9SAdrian Hunter		self.hbox.addWidget(self.cancel_button)
2117210cf1f9SAdrian Hunter
2118210cf1f9SAdrian Hunter		self.vbox = QVBoxLayout()
2119210cf1f9SAdrian Hunter		self.vbox.addLayout(self.grid)
2120210cf1f9SAdrian Hunter		self.vbox.addLayout(self.hbox)
2121210cf1f9SAdrian Hunter
2122210cf1f9SAdrian Hunter		self.setLayout(self.vbox);
2123210cf1f9SAdrian Hunter
2124210cf1f9SAdrian Hunter	def Ok(self):
21250bf0947aSAdrian Hunter		vars = self.report_vars
21261c3ca1b3SAdrian Hunter		for d in self.data_items:
21271c3ca1b3SAdrian Hunter			if d.id == "REPORTNAME":
21281c3ca1b3SAdrian Hunter				vars.name = d.value
2129947cc38dSAdrian Hunter		if not vars.name:
2130210cf1f9SAdrian Hunter			self.ShowMessage("Report name is required")
2131210cf1f9SAdrian Hunter			return
2132210cf1f9SAdrian Hunter		for d in self.data_items:
2133210cf1f9SAdrian Hunter			if not d.IsValid():
2134210cf1f9SAdrian Hunter				return
2135210cf1f9SAdrian Hunter		for d in self.data_items[1:]:
2136cd358012SAdrian Hunter			if d.id == "LIMIT":
2137cd358012SAdrian Hunter				vars.limit = d.value
2138cd358012SAdrian Hunter			elif len(d.value):
21390bf0947aSAdrian Hunter				if len(vars.where_clause):
21400bf0947aSAdrian Hunter					vars.where_clause += " AND "
21410bf0947aSAdrian Hunter				vars.where_clause += d.value
21420bf0947aSAdrian Hunter		if len(vars.where_clause):
21430924cd68SAdrian Hunter			if self.partial:
21440bf0947aSAdrian Hunter				vars.where_clause = " AND ( " + vars.where_clause + " ) "
2145210cf1f9SAdrian Hunter			else:
21460bf0947aSAdrian Hunter				vars.where_clause = " WHERE " + vars.where_clause + " "
2147210cf1f9SAdrian Hunter		self.accept()
2148210cf1f9SAdrian Hunter
2149210cf1f9SAdrian Hunter	def ShowMessage(self, msg):
2150210cf1f9SAdrian Hunter		self.status.setText("<font color=#FF0000>" + msg)
2151210cf1f9SAdrian Hunter
2152210cf1f9SAdrian Hunter	def ClearMessage(self):
2153210cf1f9SAdrian Hunter		self.status.setText("")
2154210cf1f9SAdrian Hunter
21550924cd68SAdrian Hunter# Selected branch report creation dialog
21560924cd68SAdrian Hunter
21570924cd68SAdrian Hunterclass SelectedBranchDialog(ReportDialogBase):
21580924cd68SAdrian Hunter
21590924cd68SAdrian Hunter	def __init__(self, glb, parent=None):
21600924cd68SAdrian Hunter		title = "Selected Branches"
21611c3ca1b3SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
21621c3ca1b3SAdrian Hunter			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
21631c3ca1b3SAdrian Hunter			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
21641c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
21651c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
21661c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
21671c3ca1b3SAdrian 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),
21681c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
21691c3ca1b3SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
21700924cd68SAdrian Hunter		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
21710924cd68SAdrian Hunter
217276099f98SAdrian Hunter# Event list
217376099f98SAdrian Hunter
217476099f98SAdrian Hunterdef GetEventList(db):
217576099f98SAdrian Hunter	events = []
217676099f98SAdrian Hunter	query = QSqlQuery(db)
217776099f98SAdrian Hunter	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
217876099f98SAdrian Hunter	while query.next():
217976099f98SAdrian Hunter		events.append(query.value(0))
218076099f98SAdrian Hunter	return events
218176099f98SAdrian Hunter
2182655cb952SAdrian Hunter# Is a table selectable
2183655cb952SAdrian Hunter
2184530e22fdSAdrian Hunterdef IsSelectable(db, table, sql = "", columns = "*"):
2185655cb952SAdrian Hunter	query = QSqlQuery(db)
2186655cb952SAdrian Hunter	try:
2187530e22fdSAdrian Hunter		QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
2188655cb952SAdrian Hunter	except:
2189655cb952SAdrian Hunter		return False
2190655cb952SAdrian Hunter	return True
2191655cb952SAdrian Hunter
21928392b74bSAdrian Hunter# SQL table data model item
21938392b74bSAdrian Hunter
21948392b74bSAdrian Hunterclass SQLTableItem():
21958392b74bSAdrian Hunter
21968392b74bSAdrian Hunter	def __init__(self, row, data):
21978392b74bSAdrian Hunter		self.row = row
21988392b74bSAdrian Hunter		self.data = data
21998392b74bSAdrian Hunter
22008392b74bSAdrian Hunter	def getData(self, column):
22018392b74bSAdrian Hunter		return self.data[column]
22028392b74bSAdrian Hunter
22038392b74bSAdrian Hunter# SQL table data model
22048392b74bSAdrian Hunter
22058392b74bSAdrian Hunterclass SQLTableModel(TableModel):
22068392b74bSAdrian Hunter
22078392b74bSAdrian Hunter	progress = Signal(object)
22088392b74bSAdrian Hunter
22098c90fef9SAdrian Hunter	def __init__(self, glb, sql, column_headers, parent=None):
22108392b74bSAdrian Hunter		super(SQLTableModel, self).__init__(parent)
22118392b74bSAdrian Hunter		self.glb = glb
22128392b74bSAdrian Hunter		self.more = True
22138392b74bSAdrian Hunter		self.populated = 0
22148c90fef9SAdrian Hunter		self.column_headers = column_headers
22158453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
22168392b74bSAdrian Hunter		self.fetcher.done.connect(self.Update)
22178392b74bSAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
22188392b74bSAdrian Hunter
22198392b74bSAdrian Hunter	def DisplayData(self, item, index):
22208392b74bSAdrian Hunter		self.FetchIfNeeded(item.row)
22218392b74bSAdrian Hunter		return item.getData(index.column())
22228392b74bSAdrian Hunter
22238392b74bSAdrian Hunter	def AddSample(self, data):
22248392b74bSAdrian Hunter		child = SQLTableItem(self.populated, data)
22258392b74bSAdrian Hunter		self.child_items.append(child)
22268392b74bSAdrian Hunter		self.populated += 1
22278392b74bSAdrian Hunter
22288392b74bSAdrian Hunter	def Update(self, fetched):
22298392b74bSAdrian Hunter		if not fetched:
22308392b74bSAdrian Hunter			self.more = False
22318392b74bSAdrian Hunter			self.progress.emit(0)
22328392b74bSAdrian Hunter		child_count = self.child_count
22338392b74bSAdrian Hunter		count = self.populated - child_count
22348392b74bSAdrian Hunter		if count > 0:
22358392b74bSAdrian Hunter			parent = QModelIndex()
22368392b74bSAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
22378392b74bSAdrian Hunter			self.insertRows(child_count, count, parent)
22388392b74bSAdrian Hunter			self.child_count += count
22398392b74bSAdrian Hunter			self.endInsertRows()
22408392b74bSAdrian Hunter			self.progress.emit(self.child_count)
22418392b74bSAdrian Hunter
22428392b74bSAdrian Hunter	def FetchMoreRecords(self, count):
22438392b74bSAdrian Hunter		current = self.child_count
22448392b74bSAdrian Hunter		if self.more:
22458392b74bSAdrian Hunter			self.fetcher.Fetch(count)
22468392b74bSAdrian Hunter		else:
22478392b74bSAdrian Hunter			self.progress.emit(0)
22488392b74bSAdrian Hunter		return current
22498392b74bSAdrian Hunter
22508392b74bSAdrian Hunter	def HasMoreRecords(self):
22518392b74bSAdrian Hunter		return self.more
22528392b74bSAdrian Hunter
22538c90fef9SAdrian Hunter	def columnCount(self, parent=None):
22548c90fef9SAdrian Hunter		return len(self.column_headers)
22558c90fef9SAdrian Hunter
22568c90fef9SAdrian Hunter	def columnHeader(self, column):
22578c90fef9SAdrian Hunter		return self.column_headers[column]
22588c90fef9SAdrian Hunter
22598453c936SAdrian Hunter	def SQLTableDataPrep(self, query, count):
22608453c936SAdrian Hunter		data = []
22618453c936SAdrian Hunter		for i in xrange(count):
22628453c936SAdrian Hunter			data.append(query.value(i))
22638453c936SAdrian Hunter		return data
22648453c936SAdrian Hunter
22658392b74bSAdrian Hunter# SQL automatic table data model
22668392b74bSAdrian Hunter
22678392b74bSAdrian Hunterclass SQLAutoTableModel(SQLTableModel):
22688392b74bSAdrian Hunter
22698392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
22708392b74bSAdrian Hunter		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
22718392b74bSAdrian Hunter		if table_name == "comm_threads_view":
22728392b74bSAdrian Hunter			# For now, comm_threads_view has no id column
22738392b74bSAdrian Hunter			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
22748c90fef9SAdrian Hunter		column_headers = []
22758392b74bSAdrian Hunter		query = QSqlQuery(glb.db)
22768392b74bSAdrian Hunter		if glb.dbref.is_sqlite3:
22778392b74bSAdrian Hunter			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
22788392b74bSAdrian Hunter			while query.next():
22798c90fef9SAdrian Hunter				column_headers.append(query.value(1))
22808392b74bSAdrian Hunter			if table_name == "sqlite_master":
22818392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
22828392b74bSAdrian Hunter		else:
22838392b74bSAdrian Hunter			if table_name[:19] == "information_schema.":
22848392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
22858392b74bSAdrian Hunter				select_table_name = table_name[19:]
22868392b74bSAdrian Hunter				schema = "information_schema"
22878392b74bSAdrian Hunter			else:
22888392b74bSAdrian Hunter				select_table_name = table_name
22898392b74bSAdrian Hunter				schema = "public"
22908392b74bSAdrian Hunter			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
22918392b74bSAdrian Hunter			while query.next():
22928c90fef9SAdrian Hunter				column_headers.append(query.value(0))
22938453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
22948453c936SAdrian Hunter			if table_name == "samples_view":
22958453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_view_DataPrep
22968453c936SAdrian Hunter			if table_name == "samples":
22978453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_DataPrep
22988c90fef9SAdrian Hunter		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
22998392b74bSAdrian Hunter
23008453c936SAdrian Hunter	def samples_view_DataPrep(self, query, count):
23018453c936SAdrian Hunter		data = []
23028453c936SAdrian Hunter		data.append(query.value(0))
23038453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
23048453c936SAdrian Hunter		data.append("{:>19}".format(query.value(1)))
23058453c936SAdrian Hunter		for i in xrange(2, count):
23068453c936SAdrian Hunter			data.append(query.value(i))
23078453c936SAdrian Hunter		return data
23088453c936SAdrian Hunter
23098453c936SAdrian Hunter	def samples_DataPrep(self, query, count):
23108453c936SAdrian Hunter		data = []
23118453c936SAdrian Hunter		for i in xrange(9):
23128453c936SAdrian Hunter			data.append(query.value(i))
23138453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
23148453c936SAdrian Hunter		data.append("{:>19}".format(query.value(9)))
23158453c936SAdrian Hunter		for i in xrange(10, count):
23168453c936SAdrian Hunter			data.append(query.value(i))
23178453c936SAdrian Hunter		return data
23188453c936SAdrian Hunter
23198392b74bSAdrian Hunter# Base class for custom ResizeColumnsToContents
23208392b74bSAdrian Hunter
23218392b74bSAdrian Hunterclass ResizeColumnsToContentsBase(QObject):
23228392b74bSAdrian Hunter
23238392b74bSAdrian Hunter	def __init__(self, parent=None):
23248392b74bSAdrian Hunter		super(ResizeColumnsToContentsBase, self).__init__(parent)
23258392b74bSAdrian Hunter
23268392b74bSAdrian Hunter	def ResizeColumnToContents(self, column, n):
23278392b74bSAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
23288392b74bSAdrian Hunter		# so implement a crude alternative
23298392b74bSAdrian Hunter		font = self.view.font()
23308392b74bSAdrian Hunter		metrics = QFontMetrics(font)
23318392b74bSAdrian Hunter		max = 0
23328392b74bSAdrian Hunter		for row in xrange(n):
23338392b74bSAdrian Hunter			val = self.data_model.child_items[row].data[column]
23348392b74bSAdrian Hunter			len = metrics.width(str(val) + "MM")
23358392b74bSAdrian Hunter			max = len if len > max else max
23368392b74bSAdrian Hunter		val = self.data_model.columnHeader(column)
23378392b74bSAdrian Hunter		len = metrics.width(str(val) + "MM")
23388392b74bSAdrian Hunter		max = len if len > max else max
23398392b74bSAdrian Hunter		self.view.setColumnWidth(column, max)
23408392b74bSAdrian Hunter
23418392b74bSAdrian Hunter	def ResizeColumnsToContents(self):
23428392b74bSAdrian Hunter		n = min(self.data_model.child_count, 100)
23438392b74bSAdrian Hunter		if n < 1:
23448392b74bSAdrian Hunter			# No data yet, so connect a signal to notify when there is
23458392b74bSAdrian Hunter			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
23468392b74bSAdrian Hunter			return
23478392b74bSAdrian Hunter		columns = self.data_model.columnCount()
23488392b74bSAdrian Hunter		for i in xrange(columns):
23498392b74bSAdrian Hunter			self.ResizeColumnToContents(i, n)
23508392b74bSAdrian Hunter
23518392b74bSAdrian Hunter	def UpdateColumnWidths(self, *x):
23528392b74bSAdrian Hunter		# This only needs to be done once, so disconnect the signal now
23538392b74bSAdrian Hunter		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
23548392b74bSAdrian Hunter		self.ResizeColumnsToContents()
23558392b74bSAdrian Hunter
235696c43b9aSAdrian Hunter# Convert value to CSV
235796c43b9aSAdrian Hunter
235896c43b9aSAdrian Hunterdef ToCSValue(val):
235996c43b9aSAdrian Hunter	if '"' in val:
236096c43b9aSAdrian Hunter		val = val.replace('"', '""')
236196c43b9aSAdrian Hunter	if "," in val or '"' in val:
236296c43b9aSAdrian Hunter		val = '"' + val + '"'
236396c43b9aSAdrian Hunter	return val
236496c43b9aSAdrian Hunter
236596c43b9aSAdrian Hunter# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
236696c43b9aSAdrian Hunter
236796c43b9aSAdrian Hunterglb_max_cols = 1000
236896c43b9aSAdrian Hunter
236996c43b9aSAdrian Hunterdef RowColumnKey(a):
237096c43b9aSAdrian Hunter	return a.row() * glb_max_cols + a.column()
237196c43b9aSAdrian Hunter
237296c43b9aSAdrian Hunter# Copy selected table cells to clipboard
237396c43b9aSAdrian Hunter
237496c43b9aSAdrian Hunterdef CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
237596c43b9aSAdrian Hunter	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
237696c43b9aSAdrian Hunter	idx_cnt = len(indexes)
237796c43b9aSAdrian Hunter	if not idx_cnt:
237896c43b9aSAdrian Hunter		return
237996c43b9aSAdrian Hunter	if idx_cnt == 1:
238096c43b9aSAdrian Hunter		with_hdr=False
238196c43b9aSAdrian Hunter	min_row = indexes[0].row()
238296c43b9aSAdrian Hunter	max_row = indexes[0].row()
238396c43b9aSAdrian Hunter	min_col = indexes[0].column()
238496c43b9aSAdrian Hunter	max_col = indexes[0].column()
238596c43b9aSAdrian Hunter	for i in indexes:
238696c43b9aSAdrian Hunter		min_row = min(min_row, i.row())
238796c43b9aSAdrian Hunter		max_row = max(max_row, i.row())
238896c43b9aSAdrian Hunter		min_col = min(min_col, i.column())
238996c43b9aSAdrian Hunter		max_col = max(max_col, i.column())
239096c43b9aSAdrian Hunter	if max_col > glb_max_cols:
239196c43b9aSAdrian Hunter		raise RuntimeError("glb_max_cols is too low")
239296c43b9aSAdrian Hunter	max_width = [0] * (1 + max_col - min_col)
239396c43b9aSAdrian Hunter	for i in indexes:
239496c43b9aSAdrian Hunter		c = i.column() - min_col
239596c43b9aSAdrian Hunter		max_width[c] = max(max_width[c], len(str(i.data())))
239696c43b9aSAdrian Hunter	text = ""
239796c43b9aSAdrian Hunter	pad = ""
239896c43b9aSAdrian Hunter	sep = ""
239996c43b9aSAdrian Hunter	if with_hdr:
240096c43b9aSAdrian Hunter		model = indexes[0].model()
240196c43b9aSAdrian Hunter		for col in range(min_col, max_col + 1):
240296c43b9aSAdrian Hunter			val = model.headerData(col, Qt.Horizontal)
240396c43b9aSAdrian Hunter			if as_csv:
240496c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
240596c43b9aSAdrian Hunter				sep = ","
240696c43b9aSAdrian Hunter			else:
240796c43b9aSAdrian Hunter				c = col - min_col
240896c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
240996c43b9aSAdrian Hunter				width = max_width[c]
241096c43b9aSAdrian Hunter				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
241196c43b9aSAdrian Hunter				if align & Qt.AlignRight:
241296c43b9aSAdrian Hunter					val = val.rjust(width)
241396c43b9aSAdrian Hunter				text += pad + sep + val
241496c43b9aSAdrian Hunter				pad = " " * (width - len(val))
241596c43b9aSAdrian Hunter				sep = "  "
241696c43b9aSAdrian Hunter		text += "\n"
241796c43b9aSAdrian Hunter		pad = ""
241896c43b9aSAdrian Hunter		sep = ""
241996c43b9aSAdrian Hunter	last_row = min_row
242096c43b9aSAdrian Hunter	for i in indexes:
242196c43b9aSAdrian Hunter		if i.row() > last_row:
242296c43b9aSAdrian Hunter			last_row = i.row()
242396c43b9aSAdrian Hunter			text += "\n"
242496c43b9aSAdrian Hunter			pad = ""
242596c43b9aSAdrian Hunter			sep = ""
242696c43b9aSAdrian Hunter		if as_csv:
242796c43b9aSAdrian Hunter			text += sep + ToCSValue(str(i.data()))
242896c43b9aSAdrian Hunter			sep = ","
242996c43b9aSAdrian Hunter		else:
243096c43b9aSAdrian Hunter			width = max_width[i.column() - min_col]
243196c43b9aSAdrian Hunter			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
243296c43b9aSAdrian Hunter				val = str(i.data()).rjust(width)
243396c43b9aSAdrian Hunter			else:
243496c43b9aSAdrian Hunter				val = str(i.data())
243596c43b9aSAdrian Hunter			text += pad + sep + val
243696c43b9aSAdrian Hunter			pad = " " * (width - len(val))
243796c43b9aSAdrian Hunter			sep = "  "
243896c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
243996c43b9aSAdrian Hunter
244096c43b9aSAdrian Hunterdef CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
244196c43b9aSAdrian Hunter	indexes = view.selectedIndexes()
244296c43b9aSAdrian Hunter	if not len(indexes):
244396c43b9aSAdrian Hunter		return
244496c43b9aSAdrian Hunter
244596c43b9aSAdrian Hunter	selection = view.selectionModel()
244696c43b9aSAdrian Hunter
244796c43b9aSAdrian Hunter	first = None
244896c43b9aSAdrian Hunter	for i in indexes:
244996c43b9aSAdrian Hunter		above = view.indexAbove(i)
245096c43b9aSAdrian Hunter		if not selection.isSelected(above):
245196c43b9aSAdrian Hunter			first = i
245296c43b9aSAdrian Hunter			break
245396c43b9aSAdrian Hunter
245496c43b9aSAdrian Hunter	if first is None:
245596c43b9aSAdrian Hunter		raise RuntimeError("CopyTreeCellsToClipboard internal error")
245696c43b9aSAdrian Hunter
245796c43b9aSAdrian Hunter	model = first.model()
245896c43b9aSAdrian Hunter	row_cnt = 0
245996c43b9aSAdrian Hunter	col_cnt = model.columnCount(first)
246096c43b9aSAdrian Hunter	max_width = [0] * col_cnt
246196c43b9aSAdrian Hunter
246296c43b9aSAdrian Hunter	indent_sz = 2
246396c43b9aSAdrian Hunter	indent_str = " " * indent_sz
246496c43b9aSAdrian Hunter
246596c43b9aSAdrian Hunter	expanded_mark_sz = 2
246696c43b9aSAdrian Hunter	if sys.version_info[0] == 3:
246796c43b9aSAdrian Hunter		expanded_mark = "\u25BC "
246896c43b9aSAdrian Hunter		not_expanded_mark = "\u25B6 "
246996c43b9aSAdrian Hunter	else:
247096c43b9aSAdrian Hunter		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
247196c43b9aSAdrian Hunter		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
247296c43b9aSAdrian Hunter	leaf_mark = "  "
247396c43b9aSAdrian Hunter
247496c43b9aSAdrian Hunter	if not as_csv:
247596c43b9aSAdrian Hunter		pos = first
247696c43b9aSAdrian Hunter		while True:
247796c43b9aSAdrian Hunter			row_cnt += 1
247896c43b9aSAdrian Hunter			row = pos.row()
247996c43b9aSAdrian Hunter			for c in range(col_cnt):
248096c43b9aSAdrian Hunter				i = pos.sibling(row, c)
248196c43b9aSAdrian Hunter				if c:
248296c43b9aSAdrian Hunter					n = len(str(i.data()))
248396c43b9aSAdrian Hunter				else:
248496c43b9aSAdrian Hunter					n = len(str(i.data()).strip())
248596c43b9aSAdrian Hunter					n += (i.internalPointer().level - 1) * indent_sz
248696c43b9aSAdrian Hunter					n += expanded_mark_sz
248796c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], n)
248896c43b9aSAdrian Hunter			pos = view.indexBelow(pos)
248996c43b9aSAdrian Hunter			if not selection.isSelected(pos):
249096c43b9aSAdrian Hunter				break
249196c43b9aSAdrian Hunter
249296c43b9aSAdrian Hunter	text = ""
249396c43b9aSAdrian Hunter	pad = ""
249496c43b9aSAdrian Hunter	sep = ""
249596c43b9aSAdrian Hunter	if with_hdr:
249696c43b9aSAdrian Hunter		for c in range(col_cnt):
249796c43b9aSAdrian Hunter			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
249896c43b9aSAdrian Hunter			if as_csv:
249996c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
250096c43b9aSAdrian Hunter				sep = ","
250196c43b9aSAdrian Hunter			else:
250296c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
250396c43b9aSAdrian Hunter				width = max_width[c]
250496c43b9aSAdrian Hunter				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
250596c43b9aSAdrian Hunter				if align & Qt.AlignRight:
250696c43b9aSAdrian Hunter					val = val.rjust(width)
250796c43b9aSAdrian Hunter				text += pad + sep + val
250896c43b9aSAdrian Hunter				pad = " " * (width - len(val))
250996c43b9aSAdrian Hunter				sep = "   "
251096c43b9aSAdrian Hunter		text += "\n"
251196c43b9aSAdrian Hunter		pad = ""
251296c43b9aSAdrian Hunter		sep = ""
251396c43b9aSAdrian Hunter
251496c43b9aSAdrian Hunter	pos = first
251596c43b9aSAdrian Hunter	while True:
251696c43b9aSAdrian Hunter		row = pos.row()
251796c43b9aSAdrian Hunter		for c in range(col_cnt):
251896c43b9aSAdrian Hunter			i = pos.sibling(row, c)
251996c43b9aSAdrian Hunter			val = str(i.data())
252096c43b9aSAdrian Hunter			if not c:
252196c43b9aSAdrian Hunter				if model.hasChildren(i):
252296c43b9aSAdrian Hunter					if view.isExpanded(i):
252396c43b9aSAdrian Hunter						mark = expanded_mark
252496c43b9aSAdrian Hunter					else:
252596c43b9aSAdrian Hunter						mark = not_expanded_mark
252696c43b9aSAdrian Hunter				else:
252796c43b9aSAdrian Hunter					mark = leaf_mark
252896c43b9aSAdrian Hunter				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
252996c43b9aSAdrian Hunter			if as_csv:
253096c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
253196c43b9aSAdrian Hunter				sep = ","
253296c43b9aSAdrian Hunter			else:
253396c43b9aSAdrian Hunter				width = max_width[c]
253496c43b9aSAdrian Hunter				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
253596c43b9aSAdrian Hunter					val = val.rjust(width)
253696c43b9aSAdrian Hunter				text += pad + sep + val
253796c43b9aSAdrian Hunter				pad = " " * (width - len(val))
253896c43b9aSAdrian Hunter				sep = "   "
253996c43b9aSAdrian Hunter		pos = view.indexBelow(pos)
254096c43b9aSAdrian Hunter		if not selection.isSelected(pos):
254196c43b9aSAdrian Hunter			break
254296c43b9aSAdrian Hunter		text = text.rstrip() + "\n"
254396c43b9aSAdrian Hunter		pad = ""
254496c43b9aSAdrian Hunter		sep = ""
254596c43b9aSAdrian Hunter
254696c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
254796c43b9aSAdrian Hunter
254896c43b9aSAdrian Hunterdef CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
254996c43b9aSAdrian Hunter	view.CopyCellsToClipboard(view, as_csv, with_hdr)
255096c43b9aSAdrian Hunter
255196c43b9aSAdrian Hunterdef CopyCellsToClipboardHdr(view):
255296c43b9aSAdrian Hunter	CopyCellsToClipboard(view, False, True)
255396c43b9aSAdrian Hunter
255496c43b9aSAdrian Hunterdef CopyCellsToClipboardCSV(view):
255596c43b9aSAdrian Hunter	CopyCellsToClipboard(view, True, True)
255696c43b9aSAdrian Hunter
25579bc4e4bfSAdrian Hunter# Context menu
25589bc4e4bfSAdrian Hunter
25599bc4e4bfSAdrian Hunterclass ContextMenu(object):
25609bc4e4bfSAdrian Hunter
25619bc4e4bfSAdrian Hunter	def __init__(self, view):
25629bc4e4bfSAdrian Hunter		self.view = view
25639bc4e4bfSAdrian Hunter		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
25649bc4e4bfSAdrian Hunter		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
25659bc4e4bfSAdrian Hunter
25669bc4e4bfSAdrian Hunter	def ShowContextMenu(self, pos):
25679bc4e4bfSAdrian Hunter		menu = QMenu(self.view)
25689bc4e4bfSAdrian Hunter		self.AddActions(menu)
25699bc4e4bfSAdrian Hunter		menu.exec_(self.view.mapToGlobal(pos))
25709bc4e4bfSAdrian Hunter
25719bc4e4bfSAdrian Hunter	def AddCopy(self, menu):
25729bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
25739bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
25749bc4e4bfSAdrian Hunter
25759bc4e4bfSAdrian Hunter	def AddActions(self, menu):
25769bc4e4bfSAdrian Hunter		self.AddCopy(menu)
25779bc4e4bfSAdrian Hunter
25789bc4e4bfSAdrian Hunterclass TreeContextMenu(ContextMenu):
25799bc4e4bfSAdrian Hunter
25809bc4e4bfSAdrian Hunter	def __init__(self, view):
25819bc4e4bfSAdrian Hunter		super(TreeContextMenu, self).__init__(view)
25829bc4e4bfSAdrian Hunter
25839bc4e4bfSAdrian Hunter	def AddActions(self, menu):
25849bc4e4bfSAdrian Hunter		i = self.view.currentIndex()
25859bc4e4bfSAdrian Hunter		text = str(i.data()).strip()
25869bc4e4bfSAdrian Hunter		if len(text):
25879bc4e4bfSAdrian Hunter			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
25889bc4e4bfSAdrian Hunter		self.AddCopy(menu)
25899bc4e4bfSAdrian Hunter
25908392b74bSAdrian Hunter# Table window
25918392b74bSAdrian Hunter
25928392b74bSAdrian Hunterclass TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
25938392b74bSAdrian Hunter
25948392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
25958392b74bSAdrian Hunter		super(TableWindow, self).__init__(parent)
25968392b74bSAdrian Hunter
25978392b74bSAdrian Hunter		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
25988392b74bSAdrian Hunter
25998392b74bSAdrian Hunter		self.model = QSortFilterProxyModel()
26008392b74bSAdrian Hunter		self.model.setSourceModel(self.data_model)
26018392b74bSAdrian Hunter
26028392b74bSAdrian Hunter		self.view = QTableView()
26038392b74bSAdrian Hunter		self.view.setModel(self.model)
26048392b74bSAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
26058392b74bSAdrian Hunter		self.view.verticalHeader().setVisible(False)
26068392b74bSAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
26078392b74bSAdrian Hunter		self.view.setSortingEnabled(True)
260896c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
260996c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
26108392b74bSAdrian Hunter
26118392b74bSAdrian Hunter		self.ResizeColumnsToContents()
26128392b74bSAdrian Hunter
26139bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
26149bc4e4bfSAdrian Hunter
26158392b74bSAdrian Hunter		self.find_bar = FindBar(self, self, True)
26168392b74bSAdrian Hunter
26178392b74bSAdrian Hunter		self.finder = ChildDataItemFinder(self.data_model)
26188392b74bSAdrian Hunter
26198392b74bSAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
26208392b74bSAdrian Hunter
26218392b74bSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
26228392b74bSAdrian Hunter
26238392b74bSAdrian Hunter		self.setWidget(self.vbox.Widget())
26248392b74bSAdrian Hunter
26258392b74bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
26268392b74bSAdrian Hunter
26278392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context):
26288392b74bSAdrian Hunter		self.view.setFocus()
26298392b74bSAdrian Hunter		self.find_bar.Busy()
26308392b74bSAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
26318392b74bSAdrian Hunter
26328392b74bSAdrian Hunter	def FindDone(self, row):
26338392b74bSAdrian Hunter		self.find_bar.Idle()
26348392b74bSAdrian Hunter		if row >= 0:
263535fa1ceeSAdrian Hunter			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
26368392b74bSAdrian Hunter		else:
26378392b74bSAdrian Hunter			self.find_bar.NotFound()
26388392b74bSAdrian Hunter
26398392b74bSAdrian Hunter# Table list
26408392b74bSAdrian Hunter
26418392b74bSAdrian Hunterdef GetTableList(glb):
26428392b74bSAdrian Hunter	tables = []
26438392b74bSAdrian Hunter	query = QSqlQuery(glb.db)
26448392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
26458392b74bSAdrian Hunter		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
26468392b74bSAdrian Hunter	else:
26478392b74bSAdrian 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")
26488392b74bSAdrian Hunter	while query.next():
26498392b74bSAdrian Hunter		tables.append(query.value(0))
26508392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
26518392b74bSAdrian Hunter		tables.append("sqlite_master")
26528392b74bSAdrian Hunter	else:
26538392b74bSAdrian Hunter		tables.append("information_schema.tables")
26548392b74bSAdrian Hunter		tables.append("information_schema.views")
26558392b74bSAdrian Hunter		tables.append("information_schema.columns")
26568392b74bSAdrian Hunter	return tables
26578392b74bSAdrian Hunter
2658cd358012SAdrian Hunter# Top Calls data model
2659cd358012SAdrian Hunter
2660cd358012SAdrian Hunterclass TopCallsModel(SQLTableModel):
2661cd358012SAdrian Hunter
2662cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2663cd358012SAdrian Hunter		text = ""
2664cd358012SAdrian Hunter		if not glb.dbref.is_sqlite3:
2665cd358012SAdrian Hunter			text = "::text"
2666cd358012SAdrian Hunter		limit = ""
2667cd358012SAdrian Hunter		if len(report_vars.limit):
2668cd358012SAdrian Hunter			limit = " LIMIT " + report_vars.limit
2669cd358012SAdrian Hunter		sql = ("SELECT comm, pid, tid, name,"
2670cd358012SAdrian Hunter			" CASE"
2671cd358012SAdrian Hunter			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2672cd358012SAdrian Hunter			" ELSE short_name"
2673cd358012SAdrian Hunter			" END AS dso,"
2674cd358012SAdrian Hunter			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2675cd358012SAdrian Hunter			" CASE"
2676cd358012SAdrian Hunter			" WHEN (calls.flags = 1) THEN 'no call'" + text +
2677cd358012SAdrian Hunter			" WHEN (calls.flags = 2) THEN 'no return'" + text +
2678cd358012SAdrian Hunter			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2679cd358012SAdrian Hunter			" ELSE ''" + text +
2680cd358012SAdrian Hunter			" END AS flags"
2681cd358012SAdrian Hunter			" FROM calls"
2682cd358012SAdrian Hunter			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2683cd358012SAdrian Hunter			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2684cd358012SAdrian Hunter			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
2685cd358012SAdrian Hunter			" INNER JOIN comms ON calls.comm_id = comms.id"
2686cd358012SAdrian Hunter			" INNER JOIN threads ON calls.thread_id = threads.id" +
2687cd358012SAdrian Hunter			report_vars.where_clause +
2688cd358012SAdrian Hunter			" ORDER BY elapsed_time DESC" +
2689cd358012SAdrian Hunter			limit
2690cd358012SAdrian Hunter			)
2691cd358012SAdrian Hunter		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2692cd358012SAdrian Hunter		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2693cd358012SAdrian Hunter		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2694cd358012SAdrian Hunter
2695cd358012SAdrian Hunter	def columnAlignment(self, column):
2696cd358012SAdrian Hunter		return self.alignment[column]
2697cd358012SAdrian Hunter
2698cd358012SAdrian Hunter# Top Calls report creation dialog
2699cd358012SAdrian Hunter
2700cd358012SAdrian Hunterclass TopCallsDialog(ReportDialogBase):
2701cd358012SAdrian Hunter
2702cd358012SAdrian Hunter	def __init__(self, glb, parent=None):
2703cd358012SAdrian Hunter		title = "Top Calls by Elapsed Time"
2704cd358012SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2705cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2706cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2707cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2708cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2709cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2710cd358012SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2711cd358012SAdrian Hunter			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2712cd358012SAdrian Hunter		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2713cd358012SAdrian Hunter
2714cd358012SAdrian Hunter# Top Calls window
2715cd358012SAdrian Hunter
2716cd358012SAdrian Hunterclass TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2717cd358012SAdrian Hunter
2718cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2719cd358012SAdrian Hunter		super(TopCallsWindow, self).__init__(parent)
2720cd358012SAdrian Hunter
2721cd358012SAdrian Hunter		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2722cd358012SAdrian Hunter		self.model = self.data_model
2723cd358012SAdrian Hunter
2724cd358012SAdrian Hunter		self.view = QTableView()
2725cd358012SAdrian Hunter		self.view.setModel(self.model)
2726cd358012SAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2727cd358012SAdrian Hunter		self.view.verticalHeader().setVisible(False)
272896c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
272996c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2730cd358012SAdrian Hunter
27319bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
27329bc4e4bfSAdrian Hunter
2733cd358012SAdrian Hunter		self.ResizeColumnsToContents()
2734cd358012SAdrian Hunter
2735cd358012SAdrian Hunter		self.find_bar = FindBar(self, self, True)
2736cd358012SAdrian Hunter
2737cd358012SAdrian Hunter		self.finder = ChildDataItemFinder(self.model)
2738cd358012SAdrian Hunter
2739cd358012SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2740cd358012SAdrian Hunter
2741cd358012SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2742cd358012SAdrian Hunter
2743cd358012SAdrian Hunter		self.setWidget(self.vbox.Widget())
2744cd358012SAdrian Hunter
2745cd358012SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2746cd358012SAdrian Hunter
2747cd358012SAdrian Hunter	def Find(self, value, direction, pattern, context):
2748cd358012SAdrian Hunter		self.view.setFocus()
2749cd358012SAdrian Hunter		self.find_bar.Busy()
2750cd358012SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
2751cd358012SAdrian Hunter
2752cd358012SAdrian Hunter	def FindDone(self, row):
2753cd358012SAdrian Hunter		self.find_bar.Idle()
2754cd358012SAdrian Hunter		if row >= 0:
2755cd358012SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2756cd358012SAdrian Hunter		else:
2757cd358012SAdrian Hunter			self.find_bar.NotFound()
2758cd358012SAdrian Hunter
27591beb5c7bSAdrian Hunter# Action Definition
27601beb5c7bSAdrian Hunter
27611beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None):
27621beb5c7bSAdrian Hunter	action = QAction(label, parent)
27631beb5c7bSAdrian Hunter	if shortcut != None:
27641beb5c7bSAdrian Hunter		action.setShortcuts(shortcut)
27651beb5c7bSAdrian Hunter	action.setStatusTip(tip)
27661beb5c7bSAdrian Hunter	action.triggered.connect(callback)
27671beb5c7bSAdrian Hunter	return action
27681beb5c7bSAdrian Hunter
27691beb5c7bSAdrian Hunter# Typical application actions
27701beb5c7bSAdrian Hunter
27711beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None):
27721beb5c7bSAdrian Hunter	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
27731beb5c7bSAdrian Hunter
27741beb5c7bSAdrian Hunter# Typical MDI actions
27751beb5c7bSAdrian Hunter
27761beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area):
27771beb5c7bSAdrian Hunter	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
27781beb5c7bSAdrian Hunter
27791beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area):
27801beb5c7bSAdrian Hunter	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
27811beb5c7bSAdrian Hunter
27821beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area):
27831beb5c7bSAdrian Hunter	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
27841beb5c7bSAdrian Hunter
27851beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area):
27861beb5c7bSAdrian Hunter	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
27871beb5c7bSAdrian Hunter
27881beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area):
27891beb5c7bSAdrian Hunter	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
27901beb5c7bSAdrian Hunter
27911beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area):
27921beb5c7bSAdrian Hunter	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
27931beb5c7bSAdrian Hunter
27941beb5c7bSAdrian Hunter# Typical MDI window menu
27951beb5c7bSAdrian Hunter
27961beb5c7bSAdrian Hunterclass WindowMenu():
27971beb5c7bSAdrian Hunter
27981beb5c7bSAdrian Hunter	def __init__(self, mdi_area, menu):
27991beb5c7bSAdrian Hunter		self.mdi_area = mdi_area
28001beb5c7bSAdrian Hunter		self.window_menu = menu.addMenu("&Windows")
28011beb5c7bSAdrian Hunter		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
28021beb5c7bSAdrian Hunter		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
28031beb5c7bSAdrian Hunter		self.tile_windows = CreateTileWindowsAction(mdi_area)
28041beb5c7bSAdrian Hunter		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
28051beb5c7bSAdrian Hunter		self.next_window = CreateNextWindowAction(mdi_area)
28061beb5c7bSAdrian Hunter		self.previous_window = CreatePreviousWindowAction(mdi_area)
28071beb5c7bSAdrian Hunter		self.window_menu.aboutToShow.connect(self.Update)
28081beb5c7bSAdrian Hunter
28091beb5c7bSAdrian Hunter	def Update(self):
28101beb5c7bSAdrian Hunter		self.window_menu.clear()
28111beb5c7bSAdrian Hunter		sub_window_count = len(self.mdi_area.subWindowList())
28121beb5c7bSAdrian Hunter		have_sub_windows = sub_window_count != 0
28131beb5c7bSAdrian Hunter		self.close_active_window.setEnabled(have_sub_windows)
28141beb5c7bSAdrian Hunter		self.close_all_windows.setEnabled(have_sub_windows)
28151beb5c7bSAdrian Hunter		self.tile_windows.setEnabled(have_sub_windows)
28161beb5c7bSAdrian Hunter		self.cascade_windows.setEnabled(have_sub_windows)
28171beb5c7bSAdrian Hunter		self.next_window.setEnabled(have_sub_windows)
28181beb5c7bSAdrian Hunter		self.previous_window.setEnabled(have_sub_windows)
28191beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_active_window)
28201beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_all_windows)
28211beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
28221beb5c7bSAdrian Hunter		self.window_menu.addAction(self.tile_windows)
28231beb5c7bSAdrian Hunter		self.window_menu.addAction(self.cascade_windows)
28241beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
28251beb5c7bSAdrian Hunter		self.window_menu.addAction(self.next_window)
28261beb5c7bSAdrian Hunter		self.window_menu.addAction(self.previous_window)
28271beb5c7bSAdrian Hunter		if sub_window_count == 0:
28281beb5c7bSAdrian Hunter			return
28291beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
28301beb5c7bSAdrian Hunter		nr = 1
28311beb5c7bSAdrian Hunter		for sub_window in self.mdi_area.subWindowList():
28321beb5c7bSAdrian Hunter			label = str(nr) + " " + sub_window.name
28331beb5c7bSAdrian Hunter			if nr < 10:
28341beb5c7bSAdrian Hunter				label = "&" + label
28351beb5c7bSAdrian Hunter			action = self.window_menu.addAction(label)
28361beb5c7bSAdrian Hunter			action.setCheckable(True)
28371beb5c7bSAdrian Hunter			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2838df8ea22aSAdrian Hunter			action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
28391beb5c7bSAdrian Hunter			self.window_menu.addAction(action)
28401beb5c7bSAdrian Hunter			nr += 1
28411beb5c7bSAdrian Hunter
28421beb5c7bSAdrian Hunter	def setActiveSubWindow(self, nr):
28431beb5c7bSAdrian Hunter		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
28441beb5c7bSAdrian Hunter
284565b24292SAdrian Hunter# Help text
284665b24292SAdrian Hunter
284765b24292SAdrian Hunterglb_help_text = """
284865b24292SAdrian Hunter<h1>Contents</h1>
284965b24292SAdrian Hunter<style>
285065b24292SAdrian Hunterp.c1 {
285165b24292SAdrian Hunter    text-indent: 40px;
285265b24292SAdrian Hunter}
285365b24292SAdrian Hunterp.c2 {
285465b24292SAdrian Hunter    text-indent: 80px;
285565b24292SAdrian Hunter}
285665b24292SAdrian Hunter}
285765b24292SAdrian Hunter</style>
285865b24292SAdrian Hunter<p class=c1><a href=#reports>1. Reports</a></p>
285965b24292SAdrian Hunter<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2860ae8b887cSAdrian Hunter<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2861ae8b887cSAdrian Hunter<p class=c2><a href=#allbranches>1.3 All branches</a></p>
2862ae8b887cSAdrian Hunter<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2863ae8b887cSAdrian Hunter<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
286465b24292SAdrian Hunter<p class=c1><a href=#tables>2. Tables</a></p>
286565b24292SAdrian Hunter<h1 id=reports>1. Reports</h1>
286665b24292SAdrian Hunter<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
286765b24292SAdrian HunterThe result is a GUI window with a tree representing a context-sensitive
286865b24292SAdrian Huntercall-graph. Expanding a couple of levels of the tree and adjusting column
286965b24292SAdrian Hunterwidths to suit will display something like:
287065b24292SAdrian Hunter<pre>
287165b24292SAdrian Hunter                                         Call Graph: pt_example
287265b24292SAdrian HunterCall Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
287365b24292SAdrian Hunterv- ls
287465b24292SAdrian Hunter    v- 2638:2638
287565b24292SAdrian Hunter        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
287665b24292SAdrian Hunter          |- unknown               unknown       1        13198     0.1              1              0.0
287765b24292SAdrian Hunter          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
287865b24292SAdrian Hunter          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
287965b24292SAdrian Hunter          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
288065b24292SAdrian Hunter             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
288165b24292SAdrian Hunter             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
288265b24292SAdrian Hunter             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
288365b24292SAdrian Hunter             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
288465b24292SAdrian Hunter             v- main               ls            1      8182043    99.6         180254             99.9
288565b24292SAdrian Hunter</pre>
288665b24292SAdrian Hunter<h3>Points to note:</h3>
288765b24292SAdrian Hunter<ul>
288865b24292SAdrian Hunter<li>The top level is a command name (comm)</li>
288965b24292SAdrian Hunter<li>The next level is a thread (pid:tid)</li>
289065b24292SAdrian Hunter<li>Subsequent levels are functions</li>
289165b24292SAdrian Hunter<li>'Count' is the number of calls</li>
289265b24292SAdrian Hunter<li>'Time' is the elapsed time until the function returns</li>
289365b24292SAdrian Hunter<li>Percentages are relative to the level above</li>
289465b24292SAdrian Hunter<li>'Branch Count' is the total number of branches for that function and all functions that it calls
289565b24292SAdrian Hunter</ul>
289665b24292SAdrian Hunter<h3>Find</h3>
289765b24292SAdrian HunterCtrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
289865b24292SAdrian HunterThe pattern matching symbols are ? for any character and * for zero or more characters.
2899ae8b887cSAdrian Hunter<h2 id=calltree>1.2 Call Tree</h2>
2900ae8b887cSAdrian HunterThe Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2901ae8b887cSAdrian HunterAlso the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2902ae8b887cSAdrian Hunter<h2 id=allbranches>1.3 All branches</h2>
290365b24292SAdrian HunterThe All branches report displays all branches in chronological order.
290465b24292SAdrian HunterNot all data is fetched immediately. More records can be fetched using the Fetch bar provided.
290565b24292SAdrian Hunter<h3>Disassembly</h3>
290665b24292SAdrian HunterOpen a branch to display disassembly. This only works if:
290765b24292SAdrian Hunter<ol>
290865b24292SAdrian Hunter<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
290965b24292SAdrian Hunter<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
291065b24292SAdrian HunterThe default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
291165b24292SAdrian HunterOne exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
291265b24292SAdrian Hunteror alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
291365b24292SAdrian Hunter</ol>
291465b24292SAdrian Hunter<h4 id=xed>Intel XED Setup</h4>
291565b24292SAdrian HunterTo use Intel XED, libxed.so must be present.  To build and install libxed.so:
291665b24292SAdrian Hunter<pre>
291765b24292SAdrian Huntergit clone https://github.com/intelxed/mbuild.git mbuild
291865b24292SAdrian Huntergit clone https://github.com/intelxed/xed
291965b24292SAdrian Huntercd xed
292065b24292SAdrian Hunter./mfile.py --share
292165b24292SAdrian Huntersudo ./mfile.py --prefix=/usr/local install
292265b24292SAdrian Huntersudo ldconfig
292365b24292SAdrian Hunter</pre>
2924530e22fdSAdrian Hunter<h3>Instructions per Cycle (IPC)</h3>
2925530e22fdSAdrian HunterIf available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
2926530e22fdSAdrian Hunter<p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
2927530e22fdSAdrian HunterDue to the granularity of timing information, the number of cycles for some code blocks will not be known.
2928530e22fdSAdrian HunterIn that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
2929530e22fdSAdrian Huntersince the previous displayed 'IPC'.
293065b24292SAdrian Hunter<h3>Find</h3>
293165b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
293265b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
293365b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
2934ae8b887cSAdrian Hunter<h2 id=selectedbranches>1.4 Selected branches</h2>
293565b24292SAdrian HunterThis is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
293665b24292SAdrian Hunterby various selection criteria. A dialog box displays available criteria which are AND'ed together.
2937ae8b887cSAdrian Hunter<h3>1.4.1 Time ranges</h3>
293865b24292SAdrian HunterThe time ranges hint text shows the total time range. Relative time ranges can also be entered in
293965b24292SAdrian Hunterms, us or ns. Also, negative values are relative to the end of trace.  Examples:
294065b24292SAdrian Hunter<pre>
294165b24292SAdrian Hunter	81073085947329-81073085958238	From 81073085947329 to 81073085958238
294265b24292SAdrian Hunter	100us-200us		From 100us to 200us
294365b24292SAdrian Hunter	10ms-			From 10ms to the end
294465b24292SAdrian Hunter	-100ns			The first 100ns
294565b24292SAdrian Hunter	-10ms-			The last 10ms
294665b24292SAdrian Hunter</pre>
294765b24292SAdrian HunterN.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2948ae8b887cSAdrian Hunter<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2949cd358012SAdrian 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.
2950cd358012SAdrian HunterThe data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2951cd358012SAdrian HunterIf not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
295265b24292SAdrian Hunter<h1 id=tables>2. Tables</h1>
295365b24292SAdrian HunterThe Tables menu shows all tables and views in the database. Most tables have an associated view
295465b24292SAdrian Hunterwhich displays the information in a more friendly way. Not all data for large tables is fetched
295565b24292SAdrian Hunterimmediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
295665b24292SAdrian Hunterbut that can be slow for large tables.
295765b24292SAdrian Hunter<p>There are also tables of database meta-information.
295865b24292SAdrian HunterFor SQLite3 databases, the sqlite_master table is included.
295965b24292SAdrian HunterFor PostgreSQL databases, information_schema.tables/views/columns are included.
296065b24292SAdrian Hunter<h3>Find</h3>
296165b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
296265b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
296365b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
296435fa1ceeSAdrian Hunter<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
296535fa1ceeSAdrian Hunterwill go to the next/previous result in id order, instead of display order.
296665b24292SAdrian Hunter"""
296765b24292SAdrian Hunter
296865b24292SAdrian Hunter# Help window
296965b24292SAdrian Hunter
297065b24292SAdrian Hunterclass HelpWindow(QMdiSubWindow):
297165b24292SAdrian Hunter
297265b24292SAdrian Hunter	def __init__(self, glb, parent=None):
297365b24292SAdrian Hunter		super(HelpWindow, self).__init__(parent)
297465b24292SAdrian Hunter
297565b24292SAdrian Hunter		self.text = QTextBrowser()
297665b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
297765b24292SAdrian Hunter		self.text.setReadOnly(True)
297865b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
297965b24292SAdrian Hunter
298065b24292SAdrian Hunter		self.setWidget(self.text)
298165b24292SAdrian Hunter
298265b24292SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
298365b24292SAdrian Hunter
298465b24292SAdrian Hunter# Main window that only displays the help text
298565b24292SAdrian Hunter
298665b24292SAdrian Hunterclass HelpOnlyWindow(QMainWindow):
298765b24292SAdrian Hunter
298865b24292SAdrian Hunter	def __init__(self, parent=None):
298965b24292SAdrian Hunter		super(HelpOnlyWindow, self).__init__(parent)
299065b24292SAdrian Hunter
299165b24292SAdrian Hunter		self.setMinimumSize(200, 100)
299265b24292SAdrian Hunter		self.resize(800, 600)
299365b24292SAdrian Hunter		self.setWindowTitle("Exported SQL Viewer Help")
299465b24292SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
299565b24292SAdrian Hunter
299665b24292SAdrian Hunter		self.text = QTextBrowser()
299765b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
299865b24292SAdrian Hunter		self.text.setReadOnly(True)
299965b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
300065b24292SAdrian Hunter
300165b24292SAdrian Hunter		self.setCentralWidget(self.text)
300265b24292SAdrian Hunter
3003b62d18abSAdrian Hunter# PostqreSQL server version
3004b62d18abSAdrian Hunter
3005b62d18abSAdrian Hunterdef PostqreSQLServerVersion(db):
3006b62d18abSAdrian Hunter	query = QSqlQuery(db)
3007b62d18abSAdrian Hunter	QueryExec(query, "SELECT VERSION()")
3008b62d18abSAdrian Hunter	if query.next():
3009b62d18abSAdrian Hunter		v_str = query.value(0)
3010b62d18abSAdrian Hunter		v_list = v_str.strip().split(" ")
3011b62d18abSAdrian Hunter		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
3012b62d18abSAdrian Hunter			return v_list[1]
3013b62d18abSAdrian Hunter		return v_str
3014b62d18abSAdrian Hunter	return "Unknown"
3015b62d18abSAdrian Hunter
3016b62d18abSAdrian Hunter# SQLite version
3017b62d18abSAdrian Hunter
3018b62d18abSAdrian Hunterdef SQLiteVersion(db):
3019b62d18abSAdrian Hunter	query = QSqlQuery(db)
3020b62d18abSAdrian Hunter	QueryExec(query, "SELECT sqlite_version()")
3021b62d18abSAdrian Hunter	if query.next():
3022b62d18abSAdrian Hunter		return query.value(0)
3023b62d18abSAdrian Hunter	return "Unknown"
3024b62d18abSAdrian Hunter
3025b62d18abSAdrian Hunter# About dialog
3026b62d18abSAdrian Hunter
3027b62d18abSAdrian Hunterclass AboutDialog(QDialog):
3028b62d18abSAdrian Hunter
3029b62d18abSAdrian Hunter	def __init__(self, glb, parent=None):
3030b62d18abSAdrian Hunter		super(AboutDialog, self).__init__(parent)
3031b62d18abSAdrian Hunter
3032b62d18abSAdrian Hunter		self.setWindowTitle("About Exported SQL Viewer")
3033b62d18abSAdrian Hunter		self.setMinimumWidth(300)
3034b62d18abSAdrian Hunter
3035b62d18abSAdrian Hunter		pyside_version = "1" if pyside_version_1 else "2"
3036b62d18abSAdrian Hunter
3037b62d18abSAdrian Hunter		text = "<pre>"
3038b62d18abSAdrian Hunter		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
3039b62d18abSAdrian Hunter		text += "PySide version:     " + pyside_version + "\n"
3040b62d18abSAdrian Hunter		text += "Qt version:         " + qVersion() + "\n"
3041b62d18abSAdrian Hunter		if glb.dbref.is_sqlite3:
3042b62d18abSAdrian Hunter			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
3043b62d18abSAdrian Hunter		else:
3044b62d18abSAdrian Hunter			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
3045b62d18abSAdrian Hunter		text += "</pre>"
3046b62d18abSAdrian Hunter
3047b62d18abSAdrian Hunter		self.text = QTextBrowser()
3048b62d18abSAdrian Hunter		self.text.setHtml(text)
3049b62d18abSAdrian Hunter		self.text.setReadOnly(True)
3050b62d18abSAdrian Hunter		self.text.setOpenExternalLinks(True)
3051b62d18abSAdrian Hunter
3052b62d18abSAdrian Hunter		self.vbox = QVBoxLayout()
3053b62d18abSAdrian Hunter		self.vbox.addWidget(self.text)
3054b62d18abSAdrian Hunter
3055b62d18abSAdrian Hunter		self.setLayout(self.vbox);
3056b62d18abSAdrian Hunter
305782f68e28SAdrian Hunter# Font resize
305882f68e28SAdrian Hunter
305982f68e28SAdrian Hunterdef ResizeFont(widget, diff):
306082f68e28SAdrian Hunter	font = widget.font()
306182f68e28SAdrian Hunter	sz = font.pointSize()
306282f68e28SAdrian Hunter	font.setPointSize(sz + diff)
306382f68e28SAdrian Hunter	widget.setFont(font)
306482f68e28SAdrian Hunter
306582f68e28SAdrian Hunterdef ShrinkFont(widget):
306682f68e28SAdrian Hunter	ResizeFont(widget, -1)
306782f68e28SAdrian Hunter
306882f68e28SAdrian Hunterdef EnlargeFont(widget):
306982f68e28SAdrian Hunter	ResizeFont(widget, 1)
307082f68e28SAdrian Hunter
30711beb5c7bSAdrian Hunter# Unique name for sub-windows
30721beb5c7bSAdrian Hunter
30731beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr):
30741beb5c7bSAdrian Hunter	if nr > 1:
30751beb5c7bSAdrian Hunter		name += " <" + str(nr) + ">"
30761beb5c7bSAdrian Hunter	return name
30771beb5c7bSAdrian Hunter
30781beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name):
30791beb5c7bSAdrian Hunter	nr = 1
30801beb5c7bSAdrian Hunter	while True:
30811beb5c7bSAdrian Hunter		unique_name = NumberedWindowName(name, nr)
30821beb5c7bSAdrian Hunter		ok = True
30831beb5c7bSAdrian Hunter		for sub_window in mdi_area.subWindowList():
30841beb5c7bSAdrian Hunter			if sub_window.name == unique_name:
30851beb5c7bSAdrian Hunter				ok = False
30861beb5c7bSAdrian Hunter				break
30871beb5c7bSAdrian Hunter		if ok:
30881beb5c7bSAdrian Hunter			return unique_name
30891beb5c7bSAdrian Hunter		nr += 1
30901beb5c7bSAdrian Hunter
30911beb5c7bSAdrian Hunter# Add a sub-window
30921beb5c7bSAdrian Hunter
30931beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name):
30941beb5c7bSAdrian Hunter	unique_name = UniqueSubWindowName(mdi_area, name)
30951beb5c7bSAdrian Hunter	sub_window.setMinimumSize(200, 100)
30961beb5c7bSAdrian Hunter	sub_window.resize(800, 600)
30971beb5c7bSAdrian Hunter	sub_window.setWindowTitle(unique_name)
30981beb5c7bSAdrian Hunter	sub_window.setAttribute(Qt.WA_DeleteOnClose)
30991beb5c7bSAdrian Hunter	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
31001beb5c7bSAdrian Hunter	sub_window.name = unique_name
31011beb5c7bSAdrian Hunter	mdi_area.addSubWindow(sub_window)
31021beb5c7bSAdrian Hunter	sub_window.show()
31031beb5c7bSAdrian Hunter
3104031c2a00SAdrian Hunter# Main window
3105031c2a00SAdrian Hunter
3106031c2a00SAdrian Hunterclass MainWindow(QMainWindow):
3107031c2a00SAdrian Hunter
3108031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
3109031c2a00SAdrian Hunter		super(MainWindow, self).__init__(parent)
3110031c2a00SAdrian Hunter
3111031c2a00SAdrian Hunter		self.glb = glb
3112031c2a00SAdrian Hunter
31131beb5c7bSAdrian Hunter		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3114031c2a00SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3115031c2a00SAdrian Hunter		self.setMinimumSize(200, 100)
3116031c2a00SAdrian Hunter
31171beb5c7bSAdrian Hunter		self.mdi_area = QMdiArea()
31181beb5c7bSAdrian Hunter		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
31191beb5c7bSAdrian Hunter		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3120031c2a00SAdrian Hunter
31211beb5c7bSAdrian Hunter		self.setCentralWidget(self.mdi_area)
3122031c2a00SAdrian Hunter
31231beb5c7bSAdrian Hunter		menu = self.menuBar()
3124031c2a00SAdrian Hunter
31251beb5c7bSAdrian Hunter		file_menu = menu.addMenu("&File")
31261beb5c7bSAdrian Hunter		file_menu.addAction(CreateExitAction(glb.app, self))
31271beb5c7bSAdrian Hunter
3128ebd70c7dSAdrian Hunter		edit_menu = menu.addMenu("&Edit")
312996c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
313096c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3131ebd70c7dSAdrian Hunter		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
31328392b74bSAdrian Hunter		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
313382f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
313482f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3135ebd70c7dSAdrian Hunter
31361beb5c7bSAdrian Hunter		reports_menu = menu.addMenu("&Reports")
3137655cb952SAdrian Hunter		if IsSelectable(glb.db, "calls"):
31381beb5c7bSAdrian Hunter			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
31391beb5c7bSAdrian Hunter
3140ae8b887cSAdrian Hunter		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3141ae8b887cSAdrian Hunter			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3142ae8b887cSAdrian Hunter
314376099f98SAdrian Hunter		self.EventMenu(GetEventList(glb.db), reports_menu)
314476099f98SAdrian Hunter
3145cd358012SAdrian Hunter		if IsSelectable(glb.db, "calls"):
3146cd358012SAdrian Hunter			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3147cd358012SAdrian Hunter
31488392b74bSAdrian Hunter		self.TableMenu(GetTableList(glb), menu)
31498392b74bSAdrian Hunter
31501beb5c7bSAdrian Hunter		self.window_menu = WindowMenu(self.mdi_area, menu)
31511beb5c7bSAdrian Hunter
315265b24292SAdrian Hunter		help_menu = menu.addMenu("&Help")
315365b24292SAdrian Hunter		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3154b62d18abSAdrian Hunter		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
315565b24292SAdrian Hunter
31564b208453SAdrian Hunter	def Try(self, fn):
31574b208453SAdrian Hunter		win = self.mdi_area.activeSubWindow()
31584b208453SAdrian Hunter		if win:
31594b208453SAdrian Hunter			try:
31604b208453SAdrian Hunter				fn(win.view)
31614b208453SAdrian Hunter			except:
31624b208453SAdrian Hunter				pass
31634b208453SAdrian Hunter
316496c43b9aSAdrian Hunter	def CopyToClipboard(self):
316596c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardHdr)
316696c43b9aSAdrian Hunter
316796c43b9aSAdrian Hunter	def CopyToClipboardCSV(self):
316896c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardCSV)
316996c43b9aSAdrian Hunter
3170ebd70c7dSAdrian Hunter	def Find(self):
3171ebd70c7dSAdrian Hunter		win = self.mdi_area.activeSubWindow()
3172ebd70c7dSAdrian Hunter		if win:
3173ebd70c7dSAdrian Hunter			try:
3174ebd70c7dSAdrian Hunter				win.find_bar.Activate()
3175ebd70c7dSAdrian Hunter			except:
3176ebd70c7dSAdrian Hunter				pass
3177ebd70c7dSAdrian Hunter
31788392b74bSAdrian Hunter	def FetchMoreRecords(self):
31798392b74bSAdrian Hunter		win = self.mdi_area.activeSubWindow()
31808392b74bSAdrian Hunter		if win:
31818392b74bSAdrian Hunter			try:
31828392b74bSAdrian Hunter				win.fetch_bar.Activate()
31838392b74bSAdrian Hunter			except:
31848392b74bSAdrian Hunter				pass
31858392b74bSAdrian Hunter
318682f68e28SAdrian Hunter	def ShrinkFont(self):
31874b208453SAdrian Hunter		self.Try(ShrinkFont)
318882f68e28SAdrian Hunter
318982f68e28SAdrian Hunter	def EnlargeFont(self):
31904b208453SAdrian Hunter		self.Try(EnlargeFont)
319182f68e28SAdrian Hunter
319276099f98SAdrian Hunter	def EventMenu(self, events, reports_menu):
319376099f98SAdrian Hunter		branches_events = 0
319476099f98SAdrian Hunter		for event in events:
319576099f98SAdrian Hunter			event = event.split(":")[0]
319676099f98SAdrian Hunter			if event == "branches":
319776099f98SAdrian Hunter				branches_events += 1
319876099f98SAdrian Hunter		dbid = 0
319976099f98SAdrian Hunter		for event in events:
320076099f98SAdrian Hunter			dbid += 1
320176099f98SAdrian Hunter			event = event.split(":")[0]
320276099f98SAdrian Hunter			if event == "branches":
320376099f98SAdrian Hunter				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3204df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
3205210cf1f9SAdrian Hunter				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3206df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
320776099f98SAdrian Hunter
32088392b74bSAdrian Hunter	def TableMenu(self, tables, menu):
32098392b74bSAdrian Hunter		table_menu = menu.addMenu("&Tables")
32108392b74bSAdrian Hunter		for table in tables:
3211df8ea22aSAdrian Hunter			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
32128392b74bSAdrian Hunter
32131beb5c7bSAdrian Hunter	def NewCallGraph(self):
32141beb5c7bSAdrian Hunter		CallGraphWindow(self.glb, self)
3215031c2a00SAdrian Hunter
3216ae8b887cSAdrian Hunter	def NewCallTree(self):
3217ae8b887cSAdrian Hunter		CallTreeWindow(self.glb, self)
3218ae8b887cSAdrian Hunter
3219cd358012SAdrian Hunter	def NewTopCalls(self):
3220cd358012SAdrian Hunter		dialog = TopCallsDialog(self.glb, self)
3221cd358012SAdrian Hunter		ret = dialog.exec_()
3222cd358012SAdrian Hunter		if ret:
3223cd358012SAdrian Hunter			TopCallsWindow(self.glb, dialog.report_vars, self)
3224cd358012SAdrian Hunter
322576099f98SAdrian Hunter	def NewBranchView(self, event_id):
3226947cc38dSAdrian Hunter		BranchWindow(self.glb, event_id, ReportVars(), self)
322776099f98SAdrian Hunter
3228210cf1f9SAdrian Hunter	def NewSelectedBranchView(self, event_id):
3229210cf1f9SAdrian Hunter		dialog = SelectedBranchDialog(self.glb, self)
3230210cf1f9SAdrian Hunter		ret = dialog.exec_()
3231210cf1f9SAdrian Hunter		if ret:
3232947cc38dSAdrian Hunter			BranchWindow(self.glb, event_id, dialog.report_vars, self)
3233210cf1f9SAdrian Hunter
32348392b74bSAdrian Hunter	def NewTableView(self, table_name):
32358392b74bSAdrian Hunter		TableWindow(self.glb, table_name, self)
32368392b74bSAdrian Hunter
323765b24292SAdrian Hunter	def Help(self):
323865b24292SAdrian Hunter		HelpWindow(self.glb, self)
323965b24292SAdrian Hunter
3240b62d18abSAdrian Hunter	def About(self):
3241b62d18abSAdrian Hunter		dialog = AboutDialog(self.glb, self)
3242b62d18abSAdrian Hunter		dialog.exec_()
3243b62d18abSAdrian Hunter
324476099f98SAdrian Hunter# XED Disassembler
324576099f98SAdrian Hunter
324676099f98SAdrian Hunterclass xed_state_t(Structure):
324776099f98SAdrian Hunter
324876099f98SAdrian Hunter	_fields_ = [
324976099f98SAdrian Hunter		("mode", c_int),
325076099f98SAdrian Hunter		("width", c_int)
325176099f98SAdrian Hunter	]
325276099f98SAdrian Hunter
325376099f98SAdrian Hunterclass XEDInstruction():
325476099f98SAdrian Hunter
325576099f98SAdrian Hunter	def __init__(self, libxed):
325676099f98SAdrian Hunter		# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
325776099f98SAdrian Hunter		xedd_t = c_byte * 512
325876099f98SAdrian Hunter		self.xedd = xedd_t()
325976099f98SAdrian Hunter		self.xedp = addressof(self.xedd)
326076099f98SAdrian Hunter		libxed.xed_decoded_inst_zero(self.xedp)
326176099f98SAdrian Hunter		self.state = xed_state_t()
326276099f98SAdrian Hunter		self.statep = addressof(self.state)
326376099f98SAdrian Hunter		# Buffer for disassembled instruction text
326476099f98SAdrian Hunter		self.buffer = create_string_buffer(256)
326576099f98SAdrian Hunter		self.bufferp = addressof(self.buffer)
326676099f98SAdrian Hunter
326776099f98SAdrian Hunterclass LibXED():
326876099f98SAdrian Hunter
326976099f98SAdrian Hunter	def __init__(self):
32705ed4419dSAdrian Hunter		try:
327176099f98SAdrian Hunter			self.libxed = CDLL("libxed.so")
32725ed4419dSAdrian Hunter		except:
32735ed4419dSAdrian Hunter			self.libxed = None
32745ed4419dSAdrian Hunter		if not self.libxed:
32755ed4419dSAdrian Hunter			self.libxed = CDLL("/usr/local/lib/libxed.so")
327676099f98SAdrian Hunter
327776099f98SAdrian Hunter		self.xed_tables_init = self.libxed.xed_tables_init
327876099f98SAdrian Hunter		self.xed_tables_init.restype = None
327976099f98SAdrian Hunter		self.xed_tables_init.argtypes = []
328076099f98SAdrian Hunter
328176099f98SAdrian Hunter		self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
328276099f98SAdrian Hunter		self.xed_decoded_inst_zero.restype = None
328376099f98SAdrian Hunter		self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
328476099f98SAdrian Hunter
328576099f98SAdrian Hunter		self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
328676099f98SAdrian Hunter		self.xed_operand_values_set_mode.restype = None
328776099f98SAdrian Hunter		self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
328876099f98SAdrian Hunter
328976099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
329076099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.restype = None
329176099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
329276099f98SAdrian Hunter
329376099f98SAdrian Hunter		self.xed_decode = self.libxed.xed_decode
329476099f98SAdrian Hunter		self.xed_decode.restype = c_int
329576099f98SAdrian Hunter		self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
329676099f98SAdrian Hunter
329776099f98SAdrian Hunter		self.xed_format_context = self.libxed.xed_format_context
329876099f98SAdrian Hunter		self.xed_format_context.restype = c_uint
329976099f98SAdrian Hunter		self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
330076099f98SAdrian Hunter
330176099f98SAdrian Hunter		self.xed_tables_init()
330276099f98SAdrian Hunter
330376099f98SAdrian Hunter	def Instruction(self):
330476099f98SAdrian Hunter		return XEDInstruction(self)
330576099f98SAdrian Hunter
330676099f98SAdrian Hunter	def SetMode(self, inst, mode):
330776099f98SAdrian Hunter		if mode:
330876099f98SAdrian Hunter			inst.state.mode = 4 # 32-bit
330976099f98SAdrian Hunter			inst.state.width = 4 # 4 bytes
331076099f98SAdrian Hunter		else:
331176099f98SAdrian Hunter			inst.state.mode = 1 # 64-bit
331276099f98SAdrian Hunter			inst.state.width = 8 # 8 bytes
331376099f98SAdrian Hunter		self.xed_operand_values_set_mode(inst.xedp, inst.statep)
331476099f98SAdrian Hunter
331576099f98SAdrian Hunter	def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
331676099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode(inst.xedp)
331776099f98SAdrian Hunter		err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
331876099f98SAdrian Hunter		if err:
331976099f98SAdrian Hunter			return 0, ""
332076099f98SAdrian Hunter		# Use AT&T mode (2), alternative is Intel (3)
332176099f98SAdrian Hunter		ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
332276099f98SAdrian Hunter		if not ok:
332376099f98SAdrian Hunter			return 0, ""
3324606bd60aSAdrian Hunter		if sys.version_info[0] == 2:
3325606bd60aSAdrian Hunter			result = inst.buffer.value
3326606bd60aSAdrian Hunter		else:
3327606bd60aSAdrian Hunter			result = inst.buffer.value.decode()
332876099f98SAdrian Hunter		# Return instruction length and the disassembled instruction text
332976099f98SAdrian Hunter		# For now, assume the length is in byte 166
3330606bd60aSAdrian Hunter		return inst.xedd[166], result
333176099f98SAdrian Hunter
333276099f98SAdrian Hunterdef TryOpen(file_name):
333376099f98SAdrian Hunter	try:
333476099f98SAdrian Hunter		return open(file_name, "rb")
333576099f98SAdrian Hunter	except:
333676099f98SAdrian Hunter		return None
333776099f98SAdrian Hunter
333876099f98SAdrian Hunterdef Is64Bit(f):
333976099f98SAdrian Hunter	result = sizeof(c_void_p)
334076099f98SAdrian Hunter	# ELF support only
334176099f98SAdrian Hunter	pos = f.tell()
334276099f98SAdrian Hunter	f.seek(0)
334376099f98SAdrian Hunter	header = f.read(7)
334476099f98SAdrian Hunter	f.seek(pos)
334576099f98SAdrian Hunter	magic = header[0:4]
3346606bd60aSAdrian Hunter	if sys.version_info[0] == 2:
334776099f98SAdrian Hunter		eclass = ord(header[4])
334876099f98SAdrian Hunter		encoding = ord(header[5])
334976099f98SAdrian Hunter		version = ord(header[6])
3350606bd60aSAdrian Hunter	else:
3351606bd60aSAdrian Hunter		eclass = header[4]
3352606bd60aSAdrian Hunter		encoding = header[5]
3353606bd60aSAdrian Hunter		version = header[6]
335476099f98SAdrian Hunter	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
335576099f98SAdrian Hunter		result = True if eclass == 2 else False
335676099f98SAdrian Hunter	return result
335776099f98SAdrian Hunter
3358031c2a00SAdrian Hunter# Global data
3359031c2a00SAdrian Hunter
3360031c2a00SAdrian Hunterclass Glb():
3361031c2a00SAdrian Hunter
3362031c2a00SAdrian Hunter	def __init__(self, dbref, db, dbname):
3363031c2a00SAdrian Hunter		self.dbref = dbref
3364031c2a00SAdrian Hunter		self.db = db
3365031c2a00SAdrian Hunter		self.dbname = dbname
336676099f98SAdrian Hunter		self.home_dir = os.path.expanduser("~")
336776099f98SAdrian Hunter		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
336876099f98SAdrian Hunter		if self.buildid_dir:
336976099f98SAdrian Hunter			self.buildid_dir += "/.build-id/"
337076099f98SAdrian Hunter		else:
337176099f98SAdrian Hunter			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3372031c2a00SAdrian Hunter		self.app = None
3373031c2a00SAdrian Hunter		self.mainwindow = None
33748392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit = weakref.WeakSet()
337576099f98SAdrian Hunter		try:
337676099f98SAdrian Hunter			self.disassembler = LibXED()
337776099f98SAdrian Hunter			self.have_disassembler = True
337876099f98SAdrian Hunter		except:
337976099f98SAdrian Hunter			self.have_disassembler = False
338076099f98SAdrian Hunter
338176099f98SAdrian Hunter	def FileFromBuildId(self, build_id):
338276099f98SAdrian Hunter		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
338376099f98SAdrian Hunter		return TryOpen(file_name)
338476099f98SAdrian Hunter
338576099f98SAdrian Hunter	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
338676099f98SAdrian Hunter		# Assume current machine i.e. no support for virtualization
338776099f98SAdrian Hunter		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
338876099f98SAdrian Hunter			file_name = os.getenv("PERF_KCORE")
338976099f98SAdrian Hunter			f = TryOpen(file_name) if file_name else None
339076099f98SAdrian Hunter			if f:
339176099f98SAdrian Hunter				return f
339276099f98SAdrian Hunter			# For now, no special handling if long_name is /proc/kcore
339376099f98SAdrian Hunter			f = TryOpen(long_name)
339476099f98SAdrian Hunter			if f:
339576099f98SAdrian Hunter				return f
339676099f98SAdrian Hunter		f = self.FileFromBuildId(build_id)
339776099f98SAdrian Hunter		if f:
339876099f98SAdrian Hunter			return f
339976099f98SAdrian Hunter		return None
34008392b74bSAdrian Hunter
34018392b74bSAdrian Hunter	def AddInstanceToShutdownOnExit(self, instance):
34028392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit.add(instance)
34038392b74bSAdrian Hunter
34048392b74bSAdrian Hunter	# Shutdown any background processes or threads
34058392b74bSAdrian Hunter	def ShutdownInstances(self):
34068392b74bSAdrian Hunter		for x in self.instances_to_shutdown_on_exit:
34078392b74bSAdrian Hunter			try:
34088392b74bSAdrian Hunter				x.Shutdown()
34098392b74bSAdrian Hunter			except:
34108392b74bSAdrian Hunter				pass
3411031c2a00SAdrian Hunter
3412031c2a00SAdrian Hunter# Database reference
3413031c2a00SAdrian Hunter
3414031c2a00SAdrian Hunterclass DBRef():
3415031c2a00SAdrian Hunter
3416031c2a00SAdrian Hunter	def __init__(self, is_sqlite3, dbname):
3417031c2a00SAdrian Hunter		self.is_sqlite3 = is_sqlite3
3418031c2a00SAdrian Hunter		self.dbname = dbname
3419031c2a00SAdrian Hunter
3420031c2a00SAdrian Hunter	def Open(self, connection_name):
3421031c2a00SAdrian Hunter		dbname = self.dbname
3422031c2a00SAdrian Hunter		if self.is_sqlite3:
3423031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3424031c2a00SAdrian Hunter		else:
3425031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3426031c2a00SAdrian Hunter			opts = dbname.split()
3427031c2a00SAdrian Hunter			for opt in opts:
3428031c2a00SAdrian Hunter				if "=" in opt:
3429031c2a00SAdrian Hunter					opt = opt.split("=")
3430031c2a00SAdrian Hunter					if opt[0] == "hostname":
3431031c2a00SAdrian Hunter						db.setHostName(opt[1])
3432031c2a00SAdrian Hunter					elif opt[0] == "port":
3433031c2a00SAdrian Hunter						db.setPort(int(opt[1]))
3434031c2a00SAdrian Hunter					elif opt[0] == "username":
3435031c2a00SAdrian Hunter						db.setUserName(opt[1])
3436031c2a00SAdrian Hunter					elif opt[0] == "password":
3437031c2a00SAdrian Hunter						db.setPassword(opt[1])
3438031c2a00SAdrian Hunter					elif opt[0] == "dbname":
3439031c2a00SAdrian Hunter						dbname = opt[1]
3440031c2a00SAdrian Hunter				else:
3441031c2a00SAdrian Hunter					dbname = opt
3442031c2a00SAdrian Hunter
3443031c2a00SAdrian Hunter		db.setDatabaseName(dbname)
3444031c2a00SAdrian Hunter		if not db.open():
3445031c2a00SAdrian Hunter			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3446031c2a00SAdrian Hunter		return db, dbname
3447031c2a00SAdrian Hunter
3448031c2a00SAdrian Hunter# Main
3449031c2a00SAdrian Hunter
3450031c2a00SAdrian Hunterdef Main():
34511ed7f47fSAdrian Hunter	usage_str =	"exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
34521ed7f47fSAdrian Hunter			"   or: exported-sql-viewer.py --help-only"
34531ed7f47fSAdrian Hunter	ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
3454df8ea22aSAdrian Hunter	ap.add_argument("--pyside-version-1", action='store_true')
34551ed7f47fSAdrian Hunter	ap.add_argument("dbname", nargs="?")
34561ed7f47fSAdrian Hunter	ap.add_argument("--help-only", action='store_true')
34571ed7f47fSAdrian Hunter	args = ap.parse_args()
3458031c2a00SAdrian Hunter
34591ed7f47fSAdrian Hunter	if args.help_only:
346065b24292SAdrian Hunter		app = QApplication(sys.argv)
346165b24292SAdrian Hunter		mainwindow = HelpOnlyWindow()
346265b24292SAdrian Hunter		mainwindow.show()
346365b24292SAdrian Hunter		err = app.exec_()
346465b24292SAdrian Hunter		sys.exit(err)
3465031c2a00SAdrian Hunter
34661ed7f47fSAdrian Hunter	dbname = args.dbname
34671ed7f47fSAdrian Hunter	if dbname is None:
34681ed7f47fSAdrian Hunter		ap.print_usage()
34691ed7f47fSAdrian Hunter		print("Too few arguments")
34701ed7f47fSAdrian Hunter		sys.exit(1)
34711ed7f47fSAdrian Hunter
3472031c2a00SAdrian Hunter	is_sqlite3 = False
3473031c2a00SAdrian Hunter	try:
3474beda0e72STony Jones		f = open(dbname, "rb")
3475beda0e72STony Jones		if f.read(15) == b'SQLite format 3':
3476031c2a00SAdrian Hunter			is_sqlite3 = True
3477031c2a00SAdrian Hunter		f.close()
3478031c2a00SAdrian Hunter	except:
3479031c2a00SAdrian Hunter		pass
3480031c2a00SAdrian Hunter
3481031c2a00SAdrian Hunter	dbref = DBRef(is_sqlite3, dbname)
3482031c2a00SAdrian Hunter	db, dbname = dbref.Open("main")
3483031c2a00SAdrian Hunter	glb = Glb(dbref, db, dbname)
3484031c2a00SAdrian Hunter	app = QApplication(sys.argv)
3485031c2a00SAdrian Hunter	glb.app = app
3486031c2a00SAdrian Hunter	mainwindow = MainWindow(glb)
3487031c2a00SAdrian Hunter	glb.mainwindow = mainwindow
3488031c2a00SAdrian Hunter	mainwindow.show()
3489031c2a00SAdrian Hunter	err = app.exec_()
34908392b74bSAdrian Hunter	glb.ShutdownInstances()
3491031c2a00SAdrian Hunter	db.close()
3492031c2a00SAdrian Hunter	sys.exit(err)
3493031c2a00SAdrian Hunter
3494031c2a00SAdrian Hunterif __name__ == "__main__":
3495031c2a00SAdrian Hunter	Main()
3496