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
2034a0979d4SAdrian Hunter	def __init__(self, glb, params, parent=None):
204031c2a00SAdrian Hunter		super(TreeModel, self).__init__(parent)
205a448ba23SAdrian Hunter		self.glb = glb
2064a0979d4SAdrian 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
4674a0979d4SAdrian Hunter	def __init__(self, glb, params, row, parent_item):
468031c2a00SAdrian Hunter		self.glb = glb
4694a0979d4SAdrian 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*38a846d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
5094a0979d4SAdrian 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
513*38a846d4SAdrian Hunter		self.insn_cnt = insn_cnt
514*38a846d4SAdrian Hunter		self.cyc_cnt = cyc_cnt
515031c2a00SAdrian Hunter		self.branch_count = branch_count
516031c2a00SAdrian Hunter		self.time = time
517031c2a00SAdrian Hunter
518031c2a00SAdrian Hunter	def Select(self):
519031c2a00SAdrian Hunter		self.query_done = True;
520031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
521*38a846d4SAdrian Hunter		if self.params.have_ipc:
522*38a846d4SAdrian Hunter			ipc_str = ", SUM(insn_count), SUM(cyc_count)"
523*38a846d4SAdrian Hunter		else:
524*38a846d4SAdrian Hunter			ipc_str = ""
525*38a846d4SAdrian Hunter		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
526031c2a00SAdrian Hunter					" FROM calls"
527031c2a00SAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
528031c2a00SAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
529031c2a00SAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
530031c2a00SAdrian Hunter					" WHERE parent_call_path_id = " + str(self.call_path_id) +
531031c2a00SAdrian Hunter					" AND comm_id = " + str(self.comm_id) +
532031c2a00SAdrian Hunter					" AND thread_id = " + str(self.thread_id) +
533031c2a00SAdrian Hunter					" GROUP BY call_path_id, name, short_name"
534031c2a00SAdrian Hunter					" ORDER BY call_path_id")
535031c2a00SAdrian Hunter		while query.next():
536*38a846d4SAdrian Hunter			if self.params.have_ipc:
537*38a846d4SAdrian Hunter				insn_cnt = int(query.value(5))
538*38a846d4SAdrian Hunter				cyc_cnt = int(query.value(6))
539*38a846d4SAdrian Hunter				branch_count = int(query.value(7))
540*38a846d4SAdrian Hunter			else:
541*38a846d4SAdrian Hunter				insn_cnt = 0
542*38a846d4SAdrian Hunter				cyc_cnt = 0
543*38a846d4SAdrian Hunter				branch_count = int(query.value(5))
544*38a846d4SAdrian 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)), insn_cnt, cyc_cnt, branch_count, self)
545031c2a00SAdrian Hunter			self.child_items.append(child_item)
546031c2a00SAdrian Hunter			self.child_count += 1
547031c2a00SAdrian Hunter
548031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item
549031c2a00SAdrian Hunter
550031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
551031c2a00SAdrian Hunter
552*38a846d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item):
553*38a846d4SAdrian Hunter		super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
554031c2a00SAdrian Hunter		dso = dsoname(dso)
555*38a846d4SAdrian Hunter		if self.params.have_ipc:
556*38a846d4SAdrian Hunter			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
557*38a846d4SAdrian Hunter			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
558*38a846d4SAdrian Hunter			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
559*38a846d4SAdrian Hunter			ipc = CalcIPC(cyc_cnt, insn_cnt)
560*38a846d4SAdrian Hunter			self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
561*38a846d4SAdrian Hunter		else:
562031c2a00SAdrian Hunter			self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
563031c2a00SAdrian Hunter		self.dbid = call_path_id
564031c2a00SAdrian Hunter
565031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item
566031c2a00SAdrian Hunter
567031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
568031c2a00SAdrian Hunter
5694a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
570*38a846d4SAdrian Hunter		super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
571*38a846d4SAdrian Hunter		if self.params.have_ipc:
572*38a846d4SAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
573*38a846d4SAdrian Hunter		else:
574031c2a00SAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
575031c2a00SAdrian Hunter		self.dbid = thread_id
576031c2a00SAdrian Hunter
577031c2a00SAdrian Hunter	def Select(self):
578031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).Select()
579031c2a00SAdrian Hunter		for child_item in self.child_items:
580031c2a00SAdrian Hunter			self.time += child_item.time
581*38a846d4SAdrian Hunter			self.insn_cnt += child_item.insn_cnt
582*38a846d4SAdrian Hunter			self.cyc_cnt += child_item.cyc_cnt
583031c2a00SAdrian Hunter			self.branch_count += child_item.branch_count
584031c2a00SAdrian Hunter		for child_item in self.child_items:
585031c2a00SAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
586*38a846d4SAdrian Hunter			if self.params.have_ipc:
587*38a846d4SAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
588*38a846d4SAdrian Hunter				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
589*38a846d4SAdrian Hunter				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
590*38a846d4SAdrian Hunter			else:
591031c2a00SAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
592031c2a00SAdrian Hunter
593031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item
594031c2a00SAdrian Hunter
595031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase):
596031c2a00SAdrian Hunter
5974a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, comm, parent_item):
5984a0979d4SAdrian Hunter		super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
599*38a846d4SAdrian Hunter		if self.params.have_ipc:
600*38a846d4SAdrian Hunter			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
601*38a846d4SAdrian Hunter		else:
602031c2a00SAdrian Hunter			self.data = [comm, "", "", "", "", "", ""]
603031c2a00SAdrian Hunter		self.dbid = comm_id
604031c2a00SAdrian Hunter
605031c2a00SAdrian Hunter	def Select(self):
606031c2a00SAdrian Hunter		self.query_done = True;
607031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
608031c2a00SAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
609031c2a00SAdrian Hunter					" FROM comm_threads"
610031c2a00SAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
611031c2a00SAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
612031c2a00SAdrian Hunter		while query.next():
6134a0979d4SAdrian Hunter			child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
614031c2a00SAdrian Hunter			self.child_items.append(child_item)
615031c2a00SAdrian Hunter			self.child_count += 1
616031c2a00SAdrian Hunter
617031c2a00SAdrian Hunter# Context-sensitive call graph data model root item
618031c2a00SAdrian Hunter
619031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase):
620031c2a00SAdrian Hunter
6214a0979d4SAdrian Hunter	def __init__(self, glb, params):
6224a0979d4SAdrian Hunter		super(CallGraphRootItem, self).__init__(glb, params, 0, None)
623031c2a00SAdrian Hunter		self.dbid = 0
624031c2a00SAdrian Hunter		self.query_done = True;
625031c2a00SAdrian Hunter		query = QSqlQuery(glb.db)
626031c2a00SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
627031c2a00SAdrian Hunter		while query.next():
628031c2a00SAdrian Hunter			if not query.value(0):
629031c2a00SAdrian Hunter				continue
6304a0979d4SAdrian Hunter			child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
631031c2a00SAdrian Hunter			self.child_items.append(child_item)
632031c2a00SAdrian Hunter			self.child_count += 1
633031c2a00SAdrian Hunter
6344a0979d4SAdrian Hunter# Call graph model parameters
6354a0979d4SAdrian Hunter
6364a0979d4SAdrian Hunterclass CallGraphModelParams():
6374a0979d4SAdrian Hunter
6384a0979d4SAdrian Hunter	def __init__(self, glb, parent=None):
6394a0979d4SAdrian Hunter		self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
6404a0979d4SAdrian Hunter
641254c0d82SAdrian Hunter# Context-sensitive call graph data model base
642031c2a00SAdrian Hunter
643254c0d82SAdrian Hunterclass CallGraphModelBase(TreeModel):
644031c2a00SAdrian Hunter
645031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
6464a0979d4SAdrian Hunter		super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
647031c2a00SAdrian Hunter
648ebd70c7dSAdrian Hunter	def FindSelect(self, value, pattern, query):
649ebd70c7dSAdrian Hunter		if pattern:
650ebd70c7dSAdrian Hunter			# postgresql and sqlite pattern patching differences:
651ebd70c7dSAdrian Hunter			#   postgresql LIKE is case sensitive but sqlite LIKE is not
652ebd70c7dSAdrian Hunter			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
653ebd70c7dSAdrian Hunter			#   postgresql supports ILIKE which is case insensitive
654ebd70c7dSAdrian Hunter			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
655ebd70c7dSAdrian Hunter			if not self.glb.dbref.is_sqlite3:
656ebd70c7dSAdrian Hunter				# Escape % and _
657ebd70c7dSAdrian Hunter				s = value.replace("%", "\%")
658ebd70c7dSAdrian Hunter				s = s.replace("_", "\_")
659ebd70c7dSAdrian Hunter				# Translate * and ? into SQL LIKE pattern characters % and _
660ebd70c7dSAdrian Hunter				trans = string.maketrans("*?", "%_")
661ebd70c7dSAdrian Hunter				match = " LIKE '" + str(s).translate(trans) + "'"
662ebd70c7dSAdrian Hunter			else:
663ebd70c7dSAdrian Hunter				match = " GLOB '" + str(value) + "'"
664ebd70c7dSAdrian Hunter		else:
665ebd70c7dSAdrian Hunter			match = " = '" + str(value) + "'"
666254c0d82SAdrian Hunter		self.DoFindSelect(query, match)
667ebd70c7dSAdrian Hunter
668ebd70c7dSAdrian Hunter	def Found(self, query, found):
669ebd70c7dSAdrian Hunter		if found:
670ebd70c7dSAdrian Hunter			return self.FindPath(query)
671ebd70c7dSAdrian Hunter		return []
672ebd70c7dSAdrian Hunter
673ebd70c7dSAdrian Hunter	def FindValue(self, value, pattern, query, last_value, last_pattern):
674ebd70c7dSAdrian Hunter		if last_value == value and pattern == last_pattern:
675ebd70c7dSAdrian Hunter			found = query.first()
676ebd70c7dSAdrian Hunter		else:
677ebd70c7dSAdrian Hunter			self.FindSelect(value, pattern, query)
678ebd70c7dSAdrian Hunter			found = query.next()
679ebd70c7dSAdrian Hunter		return self.Found(query, found)
680ebd70c7dSAdrian Hunter
681ebd70c7dSAdrian Hunter	def FindNext(self, query):
682ebd70c7dSAdrian Hunter		found = query.next()
683ebd70c7dSAdrian Hunter		if not found:
684ebd70c7dSAdrian Hunter			found = query.first()
685ebd70c7dSAdrian Hunter		return self.Found(query, found)
686ebd70c7dSAdrian Hunter
687ebd70c7dSAdrian Hunter	def FindPrev(self, query):
688ebd70c7dSAdrian Hunter		found = query.previous()
689ebd70c7dSAdrian Hunter		if not found:
690ebd70c7dSAdrian Hunter			found = query.last()
691ebd70c7dSAdrian Hunter		return self.Found(query, found)
692ebd70c7dSAdrian Hunter
693ebd70c7dSAdrian Hunter	def FindThread(self, c):
694ebd70c7dSAdrian Hunter		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
695ebd70c7dSAdrian Hunter			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
696ebd70c7dSAdrian Hunter		elif c.direction > 0:
697ebd70c7dSAdrian Hunter			ids = self.FindNext(c.query)
698ebd70c7dSAdrian Hunter		else:
699ebd70c7dSAdrian Hunter			ids = self.FindPrev(c.query)
700ebd70c7dSAdrian Hunter		return (True, ids)
701ebd70c7dSAdrian Hunter
702ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
703ebd70c7dSAdrian Hunter		class Context():
704ebd70c7dSAdrian Hunter			def __init__(self, *x):
705ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
706ebd70c7dSAdrian Hunter			def Update(self, *x):
707ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
708ebd70c7dSAdrian Hunter		if len(context):
709ebd70c7dSAdrian Hunter			context[0].Update(value, direction, pattern)
710ebd70c7dSAdrian Hunter		else:
711ebd70c7dSAdrian Hunter			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
712ebd70c7dSAdrian Hunter		# Use a thread so the UI is not blocked during the SELECT
713ebd70c7dSAdrian Hunter		thread = Thread(self.FindThread, context[0])
714ebd70c7dSAdrian Hunter		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
715ebd70c7dSAdrian Hunter		thread.start()
716ebd70c7dSAdrian Hunter
717ebd70c7dSAdrian Hunter	def FindDone(self, thread, callback, ids):
718ebd70c7dSAdrian Hunter		callback(ids)
719ebd70c7dSAdrian Hunter
720254c0d82SAdrian Hunter# Context-sensitive call graph data model
721254c0d82SAdrian Hunter
722254c0d82SAdrian Hunterclass CallGraphModel(CallGraphModelBase):
723254c0d82SAdrian Hunter
724254c0d82SAdrian Hunter	def __init__(self, glb, parent=None):
725254c0d82SAdrian Hunter		super(CallGraphModel, self).__init__(glb, parent)
726254c0d82SAdrian Hunter
727254c0d82SAdrian Hunter	def GetRoot(self):
7284a0979d4SAdrian Hunter		return CallGraphRootItem(self.glb, self.params)
729254c0d82SAdrian Hunter
730254c0d82SAdrian Hunter	def columnCount(self, parent=None):
731*38a846d4SAdrian Hunter		if self.params.have_ipc:
732*38a846d4SAdrian Hunter			return 12
733*38a846d4SAdrian Hunter		else:
734254c0d82SAdrian Hunter			return 7
735254c0d82SAdrian Hunter
736254c0d82SAdrian Hunter	def columnHeader(self, column):
737*38a846d4SAdrian Hunter		if self.params.have_ipc:
738*38a846d4SAdrian Hunter			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
739*38a846d4SAdrian Hunter		else:
740254c0d82SAdrian Hunter			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
741254c0d82SAdrian Hunter		return headers[column]
742254c0d82SAdrian Hunter
743254c0d82SAdrian Hunter	def columnAlignment(self, column):
744*38a846d4SAdrian Hunter		if self.params.have_ipc:
745*38a846d4SAdrian Hunter			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
746*38a846d4SAdrian Hunter		else:
747254c0d82SAdrian Hunter			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
748254c0d82SAdrian Hunter		return alignment[column]
749254c0d82SAdrian Hunter
750254c0d82SAdrian Hunter	def DoFindSelect(self, query, match):
751254c0d82SAdrian Hunter		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
752254c0d82SAdrian Hunter						" FROM calls"
753254c0d82SAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
754254c0d82SAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
755254c0d82SAdrian Hunter						" WHERE symbols.name" + match +
756254c0d82SAdrian Hunter						" GROUP BY comm_id, thread_id, call_path_id"
757254c0d82SAdrian Hunter						" ORDER BY comm_id, thread_id, call_path_id")
758254c0d82SAdrian Hunter
759254c0d82SAdrian Hunter	def FindPath(self, query):
760254c0d82SAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
761254c0d82SAdrian Hunter		# to open the tree at the right place.
762254c0d82SAdrian Hunter		ids = []
763254c0d82SAdrian Hunter		parent_id = query.value(0)
764254c0d82SAdrian Hunter		while parent_id:
765254c0d82SAdrian Hunter			ids.insert(0, parent_id)
766254c0d82SAdrian Hunter			q2 = QSqlQuery(self.glb.db)
767254c0d82SAdrian Hunter			QueryExec(q2, "SELECT parent_id"
768254c0d82SAdrian Hunter					" FROM call_paths"
769254c0d82SAdrian Hunter					" WHERE id = " + str(parent_id))
770254c0d82SAdrian Hunter			if not q2.next():
771254c0d82SAdrian Hunter				break
772254c0d82SAdrian Hunter			parent_id = q2.value(0)
773254c0d82SAdrian Hunter		# The call path root is not used
774254c0d82SAdrian Hunter		if ids[0] == 1:
775254c0d82SAdrian Hunter			del ids[0]
776254c0d82SAdrian Hunter		ids.insert(0, query.value(2))
777254c0d82SAdrian Hunter		ids.insert(0, query.value(1))
778254c0d82SAdrian Hunter		return ids
779254c0d82SAdrian Hunter
780ae8b887cSAdrian Hunter# Call tree data model level 2+ item base
781ae8b887cSAdrian Hunter
782ae8b887cSAdrian Hunterclass CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
783ae8b887cSAdrian Hunter
7844a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
7854a0979d4SAdrian Hunter		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
786ae8b887cSAdrian Hunter		self.comm_id = comm_id
787ae8b887cSAdrian Hunter		self.thread_id = thread_id
788ae8b887cSAdrian Hunter		self.calls_id = calls_id
789ae8b887cSAdrian Hunter		self.branch_count = branch_count
790ae8b887cSAdrian Hunter		self.time = time
791ae8b887cSAdrian Hunter
792ae8b887cSAdrian Hunter	def Select(self):
793ae8b887cSAdrian Hunter		self.query_done = True;
794ae8b887cSAdrian Hunter		if self.calls_id == 0:
795ae8b887cSAdrian Hunter			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
796ae8b887cSAdrian Hunter		else:
797ae8b887cSAdrian Hunter			comm_thread = ""
798ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
799ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
800ae8b887cSAdrian Hunter					" FROM calls"
801ae8b887cSAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
802ae8b887cSAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
803ae8b887cSAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
804ae8b887cSAdrian Hunter					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
805ae8b887cSAdrian Hunter					" ORDER BY call_time, calls.id")
806ae8b887cSAdrian Hunter		while query.next():
8074a0979d4SAdrian 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)
808ae8b887cSAdrian Hunter			self.child_items.append(child_item)
809ae8b887cSAdrian Hunter			self.child_count += 1
810ae8b887cSAdrian Hunter
811ae8b887cSAdrian Hunter# Call tree data model level three item
812ae8b887cSAdrian Hunter
813ae8b887cSAdrian Hunterclass CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
814ae8b887cSAdrian Hunter
8154a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
8164a0979d4SAdrian Hunter		super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
817ae8b887cSAdrian Hunter		dso = dsoname(dso)
818ae8b887cSAdrian Hunter		self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
819ae8b887cSAdrian Hunter		self.dbid = calls_id
820ae8b887cSAdrian Hunter
821ae8b887cSAdrian Hunter# Call tree data model level two item
822ae8b887cSAdrian Hunter
823ae8b887cSAdrian Hunterclass CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
824ae8b887cSAdrian Hunter
8254a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
8264a0979d4SAdrian Hunter		super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, parent_item)
827ae8b887cSAdrian Hunter		self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
828ae8b887cSAdrian Hunter		self.dbid = thread_id
829ae8b887cSAdrian Hunter
830ae8b887cSAdrian Hunter	def Select(self):
831ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).Select()
832ae8b887cSAdrian Hunter		for child_item in self.child_items:
833ae8b887cSAdrian Hunter			self.time += child_item.time
834ae8b887cSAdrian Hunter			self.branch_count += child_item.branch_count
835ae8b887cSAdrian Hunter		for child_item in self.child_items:
836ae8b887cSAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
837ae8b887cSAdrian Hunter			child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
838ae8b887cSAdrian Hunter
839ae8b887cSAdrian Hunter# Call tree data model level one item
840ae8b887cSAdrian Hunter
841ae8b887cSAdrian Hunterclass CallTreeLevelOneItem(CallGraphLevelItemBase):
842ae8b887cSAdrian Hunter
8434a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, comm, parent_item):
8444a0979d4SAdrian Hunter		super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
845ae8b887cSAdrian Hunter		self.data = [comm, "", "", "", "", "", ""]
846ae8b887cSAdrian Hunter		self.dbid = comm_id
847ae8b887cSAdrian Hunter
848ae8b887cSAdrian Hunter	def Select(self):
849ae8b887cSAdrian Hunter		self.query_done = True;
850ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
851ae8b887cSAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
852ae8b887cSAdrian Hunter					" FROM comm_threads"
853ae8b887cSAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
854ae8b887cSAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
855ae8b887cSAdrian Hunter		while query.next():
8564a0979d4SAdrian Hunter			child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
857ae8b887cSAdrian Hunter			self.child_items.append(child_item)
858ae8b887cSAdrian Hunter			self.child_count += 1
859ae8b887cSAdrian Hunter
860ae8b887cSAdrian Hunter# Call tree data model root item
861ae8b887cSAdrian Hunter
862ae8b887cSAdrian Hunterclass CallTreeRootItem(CallGraphLevelItemBase):
863ae8b887cSAdrian Hunter
8644a0979d4SAdrian Hunter	def __init__(self, glb, params):
8654a0979d4SAdrian Hunter		super(CallTreeRootItem, self).__init__(glb, params, 0, None)
866ae8b887cSAdrian Hunter		self.dbid = 0
867ae8b887cSAdrian Hunter		self.query_done = True;
868ae8b887cSAdrian Hunter		query = QSqlQuery(glb.db)
869ae8b887cSAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms")
870ae8b887cSAdrian Hunter		while query.next():
871ae8b887cSAdrian Hunter			if not query.value(0):
872ae8b887cSAdrian Hunter				continue
8734a0979d4SAdrian Hunter			child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
874ae8b887cSAdrian Hunter			self.child_items.append(child_item)
875ae8b887cSAdrian Hunter			self.child_count += 1
876ae8b887cSAdrian Hunter
877ae8b887cSAdrian Hunter# Call Tree data model
878ae8b887cSAdrian Hunter
879ae8b887cSAdrian Hunterclass CallTreeModel(CallGraphModelBase):
880ae8b887cSAdrian Hunter
881ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
882ae8b887cSAdrian Hunter		super(CallTreeModel, self).__init__(glb, parent)
883ae8b887cSAdrian Hunter
884ae8b887cSAdrian Hunter	def GetRoot(self):
8854a0979d4SAdrian Hunter		return CallTreeRootItem(self.glb, self.params)
886ae8b887cSAdrian Hunter
887ae8b887cSAdrian Hunter	def columnCount(self, parent=None):
888ae8b887cSAdrian Hunter		return 7
889ae8b887cSAdrian Hunter
890ae8b887cSAdrian Hunter	def columnHeader(self, column):
891ae8b887cSAdrian Hunter		headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
892ae8b887cSAdrian Hunter		return headers[column]
893ae8b887cSAdrian Hunter
894ae8b887cSAdrian Hunter	def columnAlignment(self, column):
895ae8b887cSAdrian Hunter		alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
896ae8b887cSAdrian Hunter		return alignment[column]
897ae8b887cSAdrian Hunter
898ae8b887cSAdrian Hunter	def DoFindSelect(self, query, match):
899ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
900ae8b887cSAdrian Hunter						" FROM calls"
901ae8b887cSAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
902ae8b887cSAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
903ae8b887cSAdrian Hunter						" WHERE symbols.name" + match +
904ae8b887cSAdrian Hunter						" ORDER BY comm_id, thread_id, call_time, calls.id")
905ae8b887cSAdrian Hunter
906ae8b887cSAdrian Hunter	def FindPath(self, query):
907ae8b887cSAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
908ae8b887cSAdrian Hunter		# to open the tree at the right place.
909ae8b887cSAdrian Hunter		ids = []
910ae8b887cSAdrian Hunter		parent_id = query.value(0)
911ae8b887cSAdrian Hunter		while parent_id:
912ae8b887cSAdrian Hunter			ids.insert(0, parent_id)
913ae8b887cSAdrian Hunter			q2 = QSqlQuery(self.glb.db)
914ae8b887cSAdrian Hunter			QueryExec(q2, "SELECT parent_id"
915ae8b887cSAdrian Hunter					" FROM calls"
916ae8b887cSAdrian Hunter					" WHERE id = " + str(parent_id))
917ae8b887cSAdrian Hunter			if not q2.next():
918ae8b887cSAdrian Hunter				break
919ae8b887cSAdrian Hunter			parent_id = q2.value(0)
920ae8b887cSAdrian Hunter		ids.insert(0, query.value(2))
921ae8b887cSAdrian Hunter		ids.insert(0, query.value(1))
922ae8b887cSAdrian Hunter		return ids
923ae8b887cSAdrian Hunter
924ebd70c7dSAdrian Hunter# Vertical widget layout
925ebd70c7dSAdrian Hunter
926ebd70c7dSAdrian Hunterclass VBox():
927ebd70c7dSAdrian Hunter
928ebd70c7dSAdrian Hunter	def __init__(self, w1, w2, w3=None):
929ebd70c7dSAdrian Hunter		self.vbox = QWidget()
930ebd70c7dSAdrian Hunter		self.vbox.setLayout(QVBoxLayout());
931ebd70c7dSAdrian Hunter
932ebd70c7dSAdrian Hunter		self.vbox.layout().setContentsMargins(0, 0, 0, 0)
933ebd70c7dSAdrian Hunter
934ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w1)
935ebd70c7dSAdrian Hunter		self.vbox.layout().addWidget(w2)
936ebd70c7dSAdrian Hunter		if w3:
937ebd70c7dSAdrian Hunter			self.vbox.layout().addWidget(w3)
938ebd70c7dSAdrian Hunter
939ebd70c7dSAdrian Hunter	def Widget(self):
940ebd70c7dSAdrian Hunter		return self.vbox
941ebd70c7dSAdrian Hunter
942a731cc4cSAdrian Hunter# Tree window base
9431beb5c7bSAdrian Hunter
944a731cc4cSAdrian Hunterclass TreeWindowBase(QMdiSubWindow):
9451beb5c7bSAdrian Hunter
946a731cc4cSAdrian Hunter	def __init__(self, parent=None):
947a731cc4cSAdrian Hunter		super(TreeWindowBase, self).__init__(parent)
9481beb5c7bSAdrian Hunter
949a731cc4cSAdrian Hunter		self.model = None
950a731cc4cSAdrian Hunter		self.find_bar = None
9511beb5c7bSAdrian Hunter
952be6e7471SAdrian Hunter		self.view = QTreeView()
95396c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
95496c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
955be6e7471SAdrian Hunter
9569bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
9579bc4e4bfSAdrian Hunter
958ebd70c7dSAdrian Hunter	def DisplayFound(self, ids):
959ebd70c7dSAdrian Hunter		if not len(ids):
960ebd70c7dSAdrian Hunter			return False
961ebd70c7dSAdrian Hunter		parent = QModelIndex()
962ebd70c7dSAdrian Hunter		for dbid in ids:
963ebd70c7dSAdrian Hunter			found = False
964ebd70c7dSAdrian Hunter			n = self.model.rowCount(parent)
965ebd70c7dSAdrian Hunter			for row in xrange(n):
966ebd70c7dSAdrian Hunter				child = self.model.index(row, 0, parent)
967ebd70c7dSAdrian Hunter				if child.internalPointer().dbid == dbid:
968ebd70c7dSAdrian Hunter					found = True
969ebd70c7dSAdrian Hunter					self.view.setCurrentIndex(child)
970ebd70c7dSAdrian Hunter					parent = child
971ebd70c7dSAdrian Hunter					break
972ebd70c7dSAdrian Hunter			if not found:
973ebd70c7dSAdrian Hunter				break
974ebd70c7dSAdrian Hunter		return found
975ebd70c7dSAdrian Hunter
976ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context):
977ebd70c7dSAdrian Hunter		self.view.setFocus()
978ebd70c7dSAdrian Hunter		self.find_bar.Busy()
979ebd70c7dSAdrian Hunter		self.model.Find(value, direction, pattern, context, self.FindDone)
980ebd70c7dSAdrian Hunter
981ebd70c7dSAdrian Hunter	def FindDone(self, ids):
982ebd70c7dSAdrian Hunter		found = True
983ebd70c7dSAdrian Hunter		if not self.DisplayFound(ids):
984ebd70c7dSAdrian Hunter			found = False
985ebd70c7dSAdrian Hunter		self.find_bar.Idle()
986ebd70c7dSAdrian Hunter		if not found:
987ebd70c7dSAdrian Hunter			self.find_bar.NotFound()
988ebd70c7dSAdrian Hunter
989a731cc4cSAdrian Hunter
990a731cc4cSAdrian Hunter# Context-sensitive call graph window
991a731cc4cSAdrian Hunter
992a731cc4cSAdrian Hunterclass CallGraphWindow(TreeWindowBase):
993a731cc4cSAdrian Hunter
994a731cc4cSAdrian Hunter	def __init__(self, glb, parent=None):
995a731cc4cSAdrian Hunter		super(CallGraphWindow, self).__init__(parent)
996a731cc4cSAdrian Hunter
997a731cc4cSAdrian Hunter		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
998a731cc4cSAdrian Hunter
999a731cc4cSAdrian Hunter		self.view.setModel(self.model)
1000a731cc4cSAdrian Hunter
1001a731cc4cSAdrian Hunter		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1002a731cc4cSAdrian Hunter			self.view.setColumnWidth(c, w)
1003a731cc4cSAdrian Hunter
1004a731cc4cSAdrian Hunter		self.find_bar = FindBar(self, self)
1005a731cc4cSAdrian Hunter
1006a731cc4cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
1007a731cc4cSAdrian Hunter
1008a731cc4cSAdrian Hunter		self.setWidget(self.vbox.Widget())
1009a731cc4cSAdrian Hunter
1010a731cc4cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
1011a731cc4cSAdrian Hunter
1012ae8b887cSAdrian Hunter# Call tree window
1013ae8b887cSAdrian Hunter
1014ae8b887cSAdrian Hunterclass CallTreeWindow(TreeWindowBase):
1015ae8b887cSAdrian Hunter
1016ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
1017ae8b887cSAdrian Hunter		super(CallTreeWindow, self).__init__(parent)
1018ae8b887cSAdrian Hunter
1019ae8b887cSAdrian Hunter		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
1020ae8b887cSAdrian Hunter
1021ae8b887cSAdrian Hunter		self.view.setModel(self.model)
1022ae8b887cSAdrian Hunter
1023ae8b887cSAdrian Hunter		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1024ae8b887cSAdrian Hunter			self.view.setColumnWidth(c, w)
1025ae8b887cSAdrian Hunter
1026ae8b887cSAdrian Hunter		self.find_bar = FindBar(self, self)
1027ae8b887cSAdrian Hunter
1028ae8b887cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
1029ae8b887cSAdrian Hunter
1030ae8b887cSAdrian Hunter		self.setWidget(self.vbox.Widget())
1031ae8b887cSAdrian Hunter
1032ae8b887cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
1033ae8b887cSAdrian Hunter
10348392b74bSAdrian Hunter# Child data item  finder
10358392b74bSAdrian Hunter
10368392b74bSAdrian Hunterclass ChildDataItemFinder():
10378392b74bSAdrian Hunter
10388392b74bSAdrian Hunter	def __init__(self, root):
10398392b74bSAdrian Hunter		self.root = root
10408392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
10418392b74bSAdrian Hunter		self.rows = []
10428392b74bSAdrian Hunter		self.pos = 0
10438392b74bSAdrian Hunter
10448392b74bSAdrian Hunter	def FindSelect(self):
10458392b74bSAdrian Hunter		self.rows = []
10468392b74bSAdrian Hunter		if self.pattern:
10478392b74bSAdrian Hunter			pattern = re.compile(self.value)
10488392b74bSAdrian Hunter			for child in self.root.child_items:
10498392b74bSAdrian Hunter				for column_data in child.data:
10508392b74bSAdrian Hunter					if re.search(pattern, str(column_data)) is not None:
10518392b74bSAdrian Hunter						self.rows.append(child.row)
10528392b74bSAdrian Hunter						break
10538392b74bSAdrian Hunter		else:
10548392b74bSAdrian Hunter			for child in self.root.child_items:
10558392b74bSAdrian Hunter				for column_data in child.data:
10568392b74bSAdrian Hunter					if self.value in str(column_data):
10578392b74bSAdrian Hunter						self.rows.append(child.row)
10588392b74bSAdrian Hunter						break
10598392b74bSAdrian Hunter
10608392b74bSAdrian Hunter	def FindValue(self):
10618392b74bSAdrian Hunter		self.pos = 0
10628392b74bSAdrian Hunter		if self.last_value != self.value or self.pattern != self.last_pattern:
10638392b74bSAdrian Hunter			self.FindSelect()
10648392b74bSAdrian Hunter		if not len(self.rows):
10658392b74bSAdrian Hunter			return -1
10668392b74bSAdrian Hunter		return self.rows[self.pos]
10678392b74bSAdrian Hunter
10688392b74bSAdrian Hunter	def FindThread(self):
10698392b74bSAdrian Hunter		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
10708392b74bSAdrian Hunter			row = self.FindValue()
10718392b74bSAdrian Hunter		elif len(self.rows):
10728392b74bSAdrian Hunter			if self.direction > 0:
10738392b74bSAdrian Hunter				self.pos += 1
10748392b74bSAdrian Hunter				if self.pos >= len(self.rows):
10758392b74bSAdrian Hunter					self.pos = 0
10768392b74bSAdrian Hunter			else:
10778392b74bSAdrian Hunter				self.pos -= 1
10788392b74bSAdrian Hunter				if self.pos < 0:
10798392b74bSAdrian Hunter					self.pos = len(self.rows) - 1
10808392b74bSAdrian Hunter			row = self.rows[self.pos]
10818392b74bSAdrian Hunter		else:
10828392b74bSAdrian Hunter			row = -1
10838392b74bSAdrian Hunter		return (True, row)
10848392b74bSAdrian Hunter
10858392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
10868392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
10878392b74bSAdrian Hunter		# Use a thread so the UI is not blocked
10888392b74bSAdrian Hunter		thread = Thread(self.FindThread)
10898392b74bSAdrian Hunter		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
10908392b74bSAdrian Hunter		thread.start()
10918392b74bSAdrian Hunter
10928392b74bSAdrian Hunter	def FindDone(self, thread, callback, row):
10938392b74bSAdrian Hunter		callback(row)
10948392b74bSAdrian Hunter
10958392b74bSAdrian Hunter# Number of database records to fetch in one go
10968392b74bSAdrian Hunter
10978392b74bSAdrian Hunterglb_chunk_sz = 10000
10988392b74bSAdrian Hunter
10998392b74bSAdrian Hunter# Background process for SQL data fetcher
11008392b74bSAdrian Hunter
11018392b74bSAdrian Hunterclass SQLFetcherProcess():
11028392b74bSAdrian Hunter
11038392b74bSAdrian Hunter	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
11048392b74bSAdrian Hunter		# Need a unique connection name
11058392b74bSAdrian Hunter		conn_name = "SQLFetcher" + str(os.getpid())
11068392b74bSAdrian Hunter		self.db, dbname = dbref.Open(conn_name)
11078392b74bSAdrian Hunter		self.sql = sql
11088392b74bSAdrian Hunter		self.buffer = buffer
11098392b74bSAdrian Hunter		self.head = head
11108392b74bSAdrian Hunter		self.tail = tail
11118392b74bSAdrian Hunter		self.fetch_count = fetch_count
11128392b74bSAdrian Hunter		self.fetching_done = fetching_done
11138392b74bSAdrian Hunter		self.process_target = process_target
11148392b74bSAdrian Hunter		self.wait_event = wait_event
11158392b74bSAdrian Hunter		self.fetched_event = fetched_event
11168392b74bSAdrian Hunter		self.prep = prep
11178392b74bSAdrian Hunter		self.query = QSqlQuery(self.db)
11188392b74bSAdrian Hunter		self.query_limit = 0 if "$$last_id$$" in sql else 2
11198392b74bSAdrian Hunter		self.last_id = -1
11208392b74bSAdrian Hunter		self.fetched = 0
11218392b74bSAdrian Hunter		self.more = True
11228392b74bSAdrian Hunter		self.local_head = self.head.value
11238392b74bSAdrian Hunter		self.local_tail = self.tail.value
11248392b74bSAdrian Hunter
11258392b74bSAdrian Hunter	def Select(self):
11268392b74bSAdrian Hunter		if self.query_limit:
11278392b74bSAdrian Hunter			if self.query_limit == 1:
11288392b74bSAdrian Hunter				return
11298392b74bSAdrian Hunter			self.query_limit -= 1
11308392b74bSAdrian Hunter		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
11318392b74bSAdrian Hunter		QueryExec(self.query, stmt)
11328392b74bSAdrian Hunter
11338392b74bSAdrian Hunter	def Next(self):
11348392b74bSAdrian Hunter		if not self.query.next():
11358392b74bSAdrian Hunter			self.Select()
11368392b74bSAdrian Hunter			if not self.query.next():
11378392b74bSAdrian Hunter				return None
11388392b74bSAdrian Hunter		self.last_id = self.query.value(0)
11398392b74bSAdrian Hunter		return self.prep(self.query)
11408392b74bSAdrian Hunter
11418392b74bSAdrian Hunter	def WaitForTarget(self):
11428392b74bSAdrian Hunter		while True:
11438392b74bSAdrian Hunter			self.wait_event.clear()
11448392b74bSAdrian Hunter			target = self.process_target.value
11458392b74bSAdrian Hunter			if target > self.fetched or target < 0:
11468392b74bSAdrian Hunter				break
11478392b74bSAdrian Hunter			self.wait_event.wait()
11488392b74bSAdrian Hunter		return target
11498392b74bSAdrian Hunter
11508392b74bSAdrian Hunter	def HasSpace(self, sz):
11518392b74bSAdrian Hunter		if self.local_tail <= self.local_head:
11528392b74bSAdrian Hunter			space = len(self.buffer) - self.local_head
11538392b74bSAdrian Hunter			if space > sz:
11548392b74bSAdrian Hunter				return True
11558392b74bSAdrian Hunter			if space >= glb_nsz:
11568392b74bSAdrian Hunter				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1157beda0e72STony Jones				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
11588392b74bSAdrian Hunter				self.buffer[self.local_head : self.local_head + len(nd)] = nd
11598392b74bSAdrian Hunter			self.local_head = 0
11608392b74bSAdrian Hunter		if self.local_tail - self.local_head > sz:
11618392b74bSAdrian Hunter			return True
11628392b74bSAdrian Hunter		return False
11638392b74bSAdrian Hunter
11648392b74bSAdrian Hunter	def WaitForSpace(self, sz):
11658392b74bSAdrian Hunter		if self.HasSpace(sz):
11668392b74bSAdrian Hunter			return
11678392b74bSAdrian Hunter		while True:
11688392b74bSAdrian Hunter			self.wait_event.clear()
11698392b74bSAdrian Hunter			self.local_tail = self.tail.value
11708392b74bSAdrian Hunter			if self.HasSpace(sz):
11718392b74bSAdrian Hunter				return
11728392b74bSAdrian Hunter			self.wait_event.wait()
11738392b74bSAdrian Hunter
11748392b74bSAdrian Hunter	def AddToBuffer(self, obj):
1175beda0e72STony Jones		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
11768392b74bSAdrian Hunter		n = len(d)
1177beda0e72STony Jones		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
11788392b74bSAdrian Hunter		sz = n + glb_nsz
11798392b74bSAdrian Hunter		self.WaitForSpace(sz)
11808392b74bSAdrian Hunter		pos = self.local_head
11818392b74bSAdrian Hunter		self.buffer[pos : pos + len(nd)] = nd
11828392b74bSAdrian Hunter		self.buffer[pos + glb_nsz : pos + sz] = d
11838392b74bSAdrian Hunter		self.local_head += sz
11848392b74bSAdrian Hunter
11858392b74bSAdrian Hunter	def FetchBatch(self, batch_size):
11868392b74bSAdrian Hunter		fetched = 0
11878392b74bSAdrian Hunter		while batch_size > fetched:
11888392b74bSAdrian Hunter			obj = self.Next()
11898392b74bSAdrian Hunter			if obj is None:
11908392b74bSAdrian Hunter				self.more = False
11918392b74bSAdrian Hunter				break
11928392b74bSAdrian Hunter			self.AddToBuffer(obj)
11938392b74bSAdrian Hunter			fetched += 1
11948392b74bSAdrian Hunter		if fetched:
11958392b74bSAdrian Hunter			self.fetched += fetched
11968392b74bSAdrian Hunter			with self.fetch_count.get_lock():
11978392b74bSAdrian Hunter				self.fetch_count.value += fetched
11988392b74bSAdrian Hunter			self.head.value = self.local_head
11998392b74bSAdrian Hunter			self.fetched_event.set()
12008392b74bSAdrian Hunter
12018392b74bSAdrian Hunter	def Run(self):
12028392b74bSAdrian Hunter		while self.more:
12038392b74bSAdrian Hunter			target = self.WaitForTarget()
12048392b74bSAdrian Hunter			if target < 0:
12058392b74bSAdrian Hunter				break
12068392b74bSAdrian Hunter			batch_size = min(glb_chunk_sz, target - self.fetched)
12078392b74bSAdrian Hunter			self.FetchBatch(batch_size)
12088392b74bSAdrian Hunter		self.fetching_done.value = True
12098392b74bSAdrian Hunter		self.fetched_event.set()
12108392b74bSAdrian Hunter
12118392b74bSAdrian Hunterdef SQLFetcherFn(*x):
12128392b74bSAdrian Hunter	process = SQLFetcherProcess(*x)
12138392b74bSAdrian Hunter	process.Run()
12148392b74bSAdrian Hunter
12158392b74bSAdrian Hunter# SQL data fetcher
12168392b74bSAdrian Hunter
12178392b74bSAdrian Hunterclass SQLFetcher(QObject):
12188392b74bSAdrian Hunter
12198392b74bSAdrian Hunter	done = Signal(object)
12208392b74bSAdrian Hunter
12218392b74bSAdrian Hunter	def __init__(self, glb, sql, prep, process_data, parent=None):
12228392b74bSAdrian Hunter		super(SQLFetcher, self).__init__(parent)
12238392b74bSAdrian Hunter		self.process_data = process_data
12248392b74bSAdrian Hunter		self.more = True
12258392b74bSAdrian Hunter		self.target = 0
12268392b74bSAdrian Hunter		self.last_target = 0
12278392b74bSAdrian Hunter		self.fetched = 0
12288392b74bSAdrian Hunter		self.buffer_size = 16 * 1024 * 1024
12298392b74bSAdrian Hunter		self.buffer = Array(c_char, self.buffer_size, lock=False)
12308392b74bSAdrian Hunter		self.head = Value(c_longlong)
12318392b74bSAdrian Hunter		self.tail = Value(c_longlong)
12328392b74bSAdrian Hunter		self.local_tail = 0
12338392b74bSAdrian Hunter		self.fetch_count = Value(c_longlong)
12348392b74bSAdrian Hunter		self.fetching_done = Value(c_bool)
12358392b74bSAdrian Hunter		self.last_count = 0
12368392b74bSAdrian Hunter		self.process_target = Value(c_longlong)
12378392b74bSAdrian Hunter		self.wait_event = Event()
12388392b74bSAdrian Hunter		self.fetched_event = Event()
12398392b74bSAdrian Hunter		glb.AddInstanceToShutdownOnExit(self)
12408392b74bSAdrian 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))
12418392b74bSAdrian Hunter		self.process.start()
12428392b74bSAdrian Hunter		self.thread = Thread(self.Thread)
12438392b74bSAdrian Hunter		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
12448392b74bSAdrian Hunter		self.thread.start()
12458392b74bSAdrian Hunter
12468392b74bSAdrian Hunter	def Shutdown(self):
12478392b74bSAdrian Hunter		# Tell the thread and process to exit
12488392b74bSAdrian Hunter		self.process_target.value = -1
12498392b74bSAdrian Hunter		self.wait_event.set()
12508392b74bSAdrian Hunter		self.more = False
12518392b74bSAdrian Hunter		self.fetching_done.value = True
12528392b74bSAdrian Hunter		self.fetched_event.set()
12538392b74bSAdrian Hunter
12548392b74bSAdrian Hunter	def Thread(self):
12558392b74bSAdrian Hunter		if not self.more:
12568392b74bSAdrian Hunter			return True, 0
12578392b74bSAdrian Hunter		while True:
12588392b74bSAdrian Hunter			self.fetched_event.clear()
12598392b74bSAdrian Hunter			fetch_count = self.fetch_count.value
12608392b74bSAdrian Hunter			if fetch_count != self.last_count:
12618392b74bSAdrian Hunter				break
12628392b74bSAdrian Hunter			if self.fetching_done.value:
12638392b74bSAdrian Hunter				self.more = False
12648392b74bSAdrian Hunter				return True, 0
12658392b74bSAdrian Hunter			self.fetched_event.wait()
12668392b74bSAdrian Hunter		count = fetch_count - self.last_count
12678392b74bSAdrian Hunter		self.last_count = fetch_count
12688392b74bSAdrian Hunter		self.fetched += count
12698392b74bSAdrian Hunter		return False, count
12708392b74bSAdrian Hunter
12718392b74bSAdrian Hunter	def Fetch(self, nr):
12728392b74bSAdrian Hunter		if not self.more:
12738392b74bSAdrian Hunter			# -1 inidcates there are no more
12748392b74bSAdrian Hunter			return -1
12758392b74bSAdrian Hunter		result = self.fetched
12768392b74bSAdrian Hunter		extra = result + nr - self.target
12778392b74bSAdrian Hunter		if extra > 0:
12788392b74bSAdrian Hunter			self.target += extra
12798392b74bSAdrian Hunter			# process_target < 0 indicates shutting down
12808392b74bSAdrian Hunter			if self.process_target.value >= 0:
12818392b74bSAdrian Hunter				self.process_target.value = self.target
12828392b74bSAdrian Hunter			self.wait_event.set()
12838392b74bSAdrian Hunter		return result
12848392b74bSAdrian Hunter
12858392b74bSAdrian Hunter	def RemoveFromBuffer(self):
12868392b74bSAdrian Hunter		pos = self.local_tail
12878392b74bSAdrian Hunter		if len(self.buffer) - pos < glb_nsz:
12888392b74bSAdrian Hunter			pos = 0
1289beda0e72STony Jones		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
12908392b74bSAdrian Hunter		if n == 0:
12918392b74bSAdrian Hunter			pos = 0
1292beda0e72STony Jones			n = pickle.loads(self.buffer[0 : glb_nsz])
12938392b74bSAdrian Hunter		pos += glb_nsz
1294beda0e72STony Jones		obj = pickle.loads(self.buffer[pos : pos + n])
12958392b74bSAdrian Hunter		self.local_tail = pos + n
12968392b74bSAdrian Hunter		return obj
12978392b74bSAdrian Hunter
12988392b74bSAdrian Hunter	def ProcessData(self, count):
12998392b74bSAdrian Hunter		for i in xrange(count):
13008392b74bSAdrian Hunter			obj = self.RemoveFromBuffer()
13018392b74bSAdrian Hunter			self.process_data(obj)
13028392b74bSAdrian Hunter		self.tail.value = self.local_tail
13038392b74bSAdrian Hunter		self.wait_event.set()
13048392b74bSAdrian Hunter		self.done.emit(count)
13058392b74bSAdrian Hunter
13068392b74bSAdrian Hunter# Fetch more records bar
13078392b74bSAdrian Hunter
13088392b74bSAdrian Hunterclass FetchMoreRecordsBar():
13098392b74bSAdrian Hunter
13108392b74bSAdrian Hunter	def __init__(self, model, parent):
13118392b74bSAdrian Hunter		self.model = model
13128392b74bSAdrian Hunter
13138392b74bSAdrian Hunter		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
13148392b74bSAdrian Hunter		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
13158392b74bSAdrian Hunter
13168392b74bSAdrian Hunter		self.fetch_count = QSpinBox()
13178392b74bSAdrian Hunter		self.fetch_count.setRange(1, 1000000)
13188392b74bSAdrian Hunter		self.fetch_count.setValue(10)
13198392b74bSAdrian Hunter		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
13208392b74bSAdrian Hunter
13218392b74bSAdrian Hunter		self.fetch = QPushButton("Go!")
13228392b74bSAdrian Hunter		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
13238392b74bSAdrian Hunter		self.fetch.released.connect(self.FetchMoreRecords)
13248392b74bSAdrian Hunter
13258392b74bSAdrian Hunter		self.progress = QProgressBar()
13268392b74bSAdrian Hunter		self.progress.setRange(0, 100)
13278392b74bSAdrian Hunter		self.progress.hide()
13288392b74bSAdrian Hunter
13298392b74bSAdrian Hunter		self.done_label = QLabel("All records fetched")
13308392b74bSAdrian Hunter		self.done_label.hide()
13318392b74bSAdrian Hunter
13328392b74bSAdrian Hunter		self.spacer = QLabel("")
13338392b74bSAdrian Hunter
13348392b74bSAdrian Hunter		self.close_button = QToolButton()
13358392b74bSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
13368392b74bSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
13378392b74bSAdrian Hunter
13388392b74bSAdrian Hunter		self.hbox = QHBoxLayout()
13398392b74bSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
13408392b74bSAdrian Hunter
13418392b74bSAdrian Hunter		self.hbox.addWidget(self.label)
13428392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch_count)
13438392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch)
13448392b74bSAdrian Hunter		self.hbox.addWidget(self.spacer)
13458392b74bSAdrian Hunter		self.hbox.addWidget(self.progress)
13468392b74bSAdrian Hunter		self.hbox.addWidget(self.done_label)
13478392b74bSAdrian Hunter		self.hbox.addWidget(self.close_button)
13488392b74bSAdrian Hunter
13498392b74bSAdrian Hunter		self.bar = QWidget()
13508392b74bSAdrian Hunter		self.bar.setLayout(self.hbox);
13518392b74bSAdrian Hunter		self.bar.show()
13528392b74bSAdrian Hunter
13538392b74bSAdrian Hunter		self.in_progress = False
13548392b74bSAdrian Hunter		self.model.progress.connect(self.Progress)
13558392b74bSAdrian Hunter
13568392b74bSAdrian Hunter		self.done = False
13578392b74bSAdrian Hunter
13588392b74bSAdrian Hunter		if not model.HasMoreRecords():
13598392b74bSAdrian Hunter			self.Done()
13608392b74bSAdrian Hunter
13618392b74bSAdrian Hunter	def Widget(self):
13628392b74bSAdrian Hunter		return self.bar
13638392b74bSAdrian Hunter
13648392b74bSAdrian Hunter	def Activate(self):
13658392b74bSAdrian Hunter		self.bar.show()
13668392b74bSAdrian Hunter		self.fetch.setFocus()
13678392b74bSAdrian Hunter
13688392b74bSAdrian Hunter	def Deactivate(self):
13698392b74bSAdrian Hunter		self.bar.hide()
13708392b74bSAdrian Hunter
13718392b74bSAdrian Hunter	def Enable(self, enable):
13728392b74bSAdrian Hunter		self.fetch.setEnabled(enable)
13738392b74bSAdrian Hunter		self.fetch_count.setEnabled(enable)
13748392b74bSAdrian Hunter
13758392b74bSAdrian Hunter	def Busy(self):
13768392b74bSAdrian Hunter		self.Enable(False)
13778392b74bSAdrian Hunter		self.fetch.hide()
13788392b74bSAdrian Hunter		self.spacer.hide()
13798392b74bSAdrian Hunter		self.progress.show()
13808392b74bSAdrian Hunter
13818392b74bSAdrian Hunter	def Idle(self):
13828392b74bSAdrian Hunter		self.in_progress = False
13838392b74bSAdrian Hunter		self.Enable(True)
13848392b74bSAdrian Hunter		self.progress.hide()
13858392b74bSAdrian Hunter		self.fetch.show()
13868392b74bSAdrian Hunter		self.spacer.show()
13878392b74bSAdrian Hunter
13888392b74bSAdrian Hunter	def Target(self):
13898392b74bSAdrian Hunter		return self.fetch_count.value() * glb_chunk_sz
13908392b74bSAdrian Hunter
13918392b74bSAdrian Hunter	def Done(self):
13928392b74bSAdrian Hunter		self.done = True
13938392b74bSAdrian Hunter		self.Idle()
13948392b74bSAdrian Hunter		self.label.hide()
13958392b74bSAdrian Hunter		self.fetch_count.hide()
13968392b74bSAdrian Hunter		self.fetch.hide()
13978392b74bSAdrian Hunter		self.spacer.hide()
13988392b74bSAdrian Hunter		self.done_label.show()
13998392b74bSAdrian Hunter
14008392b74bSAdrian Hunter	def Progress(self, count):
14018392b74bSAdrian Hunter		if self.in_progress:
14028392b74bSAdrian Hunter			if count:
14038392b74bSAdrian Hunter				percent = ((count - self.start) * 100) / self.Target()
14048392b74bSAdrian Hunter				if percent >= 100:
14058392b74bSAdrian Hunter					self.Idle()
14068392b74bSAdrian Hunter				else:
14078392b74bSAdrian Hunter					self.progress.setValue(percent)
14088392b74bSAdrian Hunter		if not count:
14098392b74bSAdrian Hunter			# Count value of zero means no more records
14108392b74bSAdrian Hunter			self.Done()
14118392b74bSAdrian Hunter
14128392b74bSAdrian Hunter	def FetchMoreRecords(self):
14138392b74bSAdrian Hunter		if self.done:
14148392b74bSAdrian Hunter			return
14158392b74bSAdrian Hunter		self.progress.setValue(0)
14168392b74bSAdrian Hunter		self.Busy()
14178392b74bSAdrian Hunter		self.in_progress = True
14188392b74bSAdrian Hunter		self.start = self.model.FetchMoreRecords(self.Target())
14198392b74bSAdrian Hunter
142076099f98SAdrian Hunter# Brance data model level two item
142176099f98SAdrian Hunter
142276099f98SAdrian Hunterclass BranchLevelTwoItem():
142376099f98SAdrian Hunter
1424530e22fdSAdrian Hunter	def __init__(self, row, col, text, parent_item):
142576099f98SAdrian Hunter		self.row = row
142676099f98SAdrian Hunter		self.parent_item = parent_item
1427530e22fdSAdrian Hunter		self.data = [""] * (col + 1)
1428530e22fdSAdrian Hunter		self.data[col] = text
142976099f98SAdrian Hunter		self.level = 2
143076099f98SAdrian Hunter
143176099f98SAdrian Hunter	def getParentItem(self):
143276099f98SAdrian Hunter		return self.parent_item
143376099f98SAdrian Hunter
143476099f98SAdrian Hunter	def getRow(self):
143576099f98SAdrian Hunter		return self.row
143676099f98SAdrian Hunter
143776099f98SAdrian Hunter	def childCount(self):
143876099f98SAdrian Hunter		return 0
143976099f98SAdrian Hunter
144076099f98SAdrian Hunter	def hasChildren(self):
144176099f98SAdrian Hunter		return False
144276099f98SAdrian Hunter
144376099f98SAdrian Hunter	def getData(self, column):
144476099f98SAdrian Hunter		return self.data[column]
144576099f98SAdrian Hunter
144676099f98SAdrian Hunter# Brance data model level one item
144776099f98SAdrian Hunter
144876099f98SAdrian Hunterclass BranchLevelOneItem():
144976099f98SAdrian Hunter
145076099f98SAdrian Hunter	def __init__(self, glb, row, data, parent_item):
145176099f98SAdrian Hunter		self.glb = glb
145276099f98SAdrian Hunter		self.row = row
145376099f98SAdrian Hunter		self.parent_item = parent_item
145476099f98SAdrian Hunter		self.child_count = 0
145576099f98SAdrian Hunter		self.child_items = []
145676099f98SAdrian Hunter		self.data = data[1:]
145776099f98SAdrian Hunter		self.dbid = data[0]
145876099f98SAdrian Hunter		self.level = 1
145976099f98SAdrian Hunter		self.query_done = False
1460530e22fdSAdrian Hunter		self.br_col = len(self.data) - 1
146176099f98SAdrian Hunter
146276099f98SAdrian Hunter	def getChildItem(self, row):
146376099f98SAdrian Hunter		return self.child_items[row]
146476099f98SAdrian Hunter
146576099f98SAdrian Hunter	def getParentItem(self):
146676099f98SAdrian Hunter		return self.parent_item
146776099f98SAdrian Hunter
146876099f98SAdrian Hunter	def getRow(self):
146976099f98SAdrian Hunter		return self.row
147076099f98SAdrian Hunter
147176099f98SAdrian Hunter	def Select(self):
147276099f98SAdrian Hunter		self.query_done = True
147376099f98SAdrian Hunter
147476099f98SAdrian Hunter		if not self.glb.have_disassembler:
147576099f98SAdrian Hunter			return
147676099f98SAdrian Hunter
147776099f98SAdrian Hunter		query = QSqlQuery(self.glb.db)
147876099f98SAdrian Hunter
147976099f98SAdrian Hunter		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
148076099f98SAdrian Hunter				  " FROM samples"
148176099f98SAdrian Hunter				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
148276099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
148376099f98SAdrian Hunter				  " WHERE samples.id = " + str(self.dbid))
148476099f98SAdrian Hunter		if not query.next():
148576099f98SAdrian Hunter			return
148676099f98SAdrian Hunter		cpu = query.value(0)
148776099f98SAdrian Hunter		dso = query.value(1)
148876099f98SAdrian Hunter		sym = query.value(2)
148976099f98SAdrian Hunter		if dso == 0 or sym == 0:
149076099f98SAdrian Hunter			return
149176099f98SAdrian Hunter		off = query.value(3)
149276099f98SAdrian Hunter		short_name = query.value(4)
149376099f98SAdrian Hunter		long_name = query.value(5)
149476099f98SAdrian Hunter		build_id = query.value(6)
149576099f98SAdrian Hunter		sym_start = query.value(7)
149676099f98SAdrian Hunter		ip = query.value(8)
149776099f98SAdrian Hunter
149876099f98SAdrian Hunter		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
149976099f98SAdrian Hunter				  " FROM samples"
150076099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
150176099f98SAdrian Hunter				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
150276099f98SAdrian Hunter				  " ORDER BY samples.id"
150376099f98SAdrian Hunter				  " LIMIT 1")
150476099f98SAdrian Hunter		if not query.next():
150576099f98SAdrian Hunter			return
150676099f98SAdrian Hunter		if query.value(0) != dso:
150776099f98SAdrian Hunter			# Cannot disassemble from one dso to another
150876099f98SAdrian Hunter			return
150976099f98SAdrian Hunter		bsym = query.value(1)
151076099f98SAdrian Hunter		boff = query.value(2)
151176099f98SAdrian Hunter		bsym_start = query.value(3)
151276099f98SAdrian Hunter		if bsym == 0:
151376099f98SAdrian Hunter			return
151476099f98SAdrian Hunter		tot = bsym_start + boff + 1 - sym_start - off
151576099f98SAdrian Hunter		if tot <= 0 or tot > 16384:
151676099f98SAdrian Hunter			return
151776099f98SAdrian Hunter
151876099f98SAdrian Hunter		inst = self.glb.disassembler.Instruction()
151976099f98SAdrian Hunter		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
152076099f98SAdrian Hunter		if not f:
152176099f98SAdrian Hunter			return
152276099f98SAdrian Hunter		mode = 0 if Is64Bit(f) else 1
152376099f98SAdrian Hunter		self.glb.disassembler.SetMode(inst, mode)
152476099f98SAdrian Hunter
152576099f98SAdrian Hunter		buf_sz = tot + 16
152676099f98SAdrian Hunter		buf = create_string_buffer(tot + 16)
152776099f98SAdrian Hunter		f.seek(sym_start + off)
152876099f98SAdrian Hunter		buf.value = f.read(buf_sz)
152976099f98SAdrian Hunter		buf_ptr = addressof(buf)
153076099f98SAdrian Hunter		i = 0
153176099f98SAdrian Hunter		while tot > 0:
153276099f98SAdrian Hunter			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
153376099f98SAdrian Hunter			if cnt:
153476099f98SAdrian Hunter				byte_str = tohex(ip).rjust(16)
153576099f98SAdrian Hunter				for k in xrange(cnt):
153676099f98SAdrian Hunter					byte_str += " %02x" % ord(buf[i])
153776099f98SAdrian Hunter					i += 1
153876099f98SAdrian Hunter				while k < 15:
153976099f98SAdrian Hunter					byte_str += "   "
154076099f98SAdrian Hunter					k += 1
1541530e22fdSAdrian Hunter				self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
154276099f98SAdrian Hunter				self.child_count += 1
154376099f98SAdrian Hunter			else:
154476099f98SAdrian Hunter				return
154576099f98SAdrian Hunter			buf_ptr += cnt
154676099f98SAdrian Hunter			tot -= cnt
154776099f98SAdrian Hunter			buf_sz -= cnt
154876099f98SAdrian Hunter			ip += cnt
154976099f98SAdrian Hunter
155076099f98SAdrian Hunter	def childCount(self):
155176099f98SAdrian Hunter		if not self.query_done:
155276099f98SAdrian Hunter			self.Select()
155376099f98SAdrian Hunter			if not self.child_count:
155476099f98SAdrian Hunter				return -1
155576099f98SAdrian Hunter		return self.child_count
155676099f98SAdrian Hunter
155776099f98SAdrian Hunter	def hasChildren(self):
155876099f98SAdrian Hunter		if not self.query_done:
155976099f98SAdrian Hunter			return True
156076099f98SAdrian Hunter		return self.child_count > 0
156176099f98SAdrian Hunter
156276099f98SAdrian Hunter	def getData(self, column):
156376099f98SAdrian Hunter		return self.data[column]
156476099f98SAdrian Hunter
156576099f98SAdrian Hunter# Brance data model root item
156676099f98SAdrian Hunter
156776099f98SAdrian Hunterclass BranchRootItem():
156876099f98SAdrian Hunter
156976099f98SAdrian Hunter	def __init__(self):
157076099f98SAdrian Hunter		self.child_count = 0
157176099f98SAdrian Hunter		self.child_items = []
157276099f98SAdrian Hunter		self.level = 0
157376099f98SAdrian Hunter
157476099f98SAdrian Hunter	def getChildItem(self, row):
157576099f98SAdrian Hunter		return self.child_items[row]
157676099f98SAdrian Hunter
157776099f98SAdrian Hunter	def getParentItem(self):
157876099f98SAdrian Hunter		return None
157976099f98SAdrian Hunter
158076099f98SAdrian Hunter	def getRow(self):
158176099f98SAdrian Hunter		return 0
158276099f98SAdrian Hunter
158376099f98SAdrian Hunter	def childCount(self):
158476099f98SAdrian Hunter		return self.child_count
158576099f98SAdrian Hunter
158676099f98SAdrian Hunter	def hasChildren(self):
158776099f98SAdrian Hunter		return self.child_count > 0
158876099f98SAdrian Hunter
158976099f98SAdrian Hunter	def getData(self, column):
159076099f98SAdrian Hunter		return ""
159176099f98SAdrian Hunter
1592530e22fdSAdrian Hunter# Calculate instructions per cycle
1593530e22fdSAdrian Hunter
1594530e22fdSAdrian Hunterdef CalcIPC(cyc_cnt, insn_cnt):
1595530e22fdSAdrian Hunter	if cyc_cnt and insn_cnt:
1596530e22fdSAdrian Hunter		ipc = Decimal(float(insn_cnt) / cyc_cnt)
1597530e22fdSAdrian Hunter		ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
1598530e22fdSAdrian Hunter	else:
1599530e22fdSAdrian Hunter		ipc = "0"
1600530e22fdSAdrian Hunter	return ipc
1601530e22fdSAdrian Hunter
160276099f98SAdrian Hunter# Branch data preparation
160376099f98SAdrian Hunter
1604530e22fdSAdrian Hunterdef BranchDataPrepBr(query, data):
1605530e22fdSAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1606530e22fdSAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
1607530e22fdSAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1608530e22fdSAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
1609530e22fdSAdrian Hunter
1610530e22fdSAdrian Hunterdef BranchDataPrepIPC(query, data):
1611530e22fdSAdrian Hunter	insn_cnt = query.value(16)
1612530e22fdSAdrian Hunter	cyc_cnt = query.value(17)
1613530e22fdSAdrian Hunter	ipc = CalcIPC(cyc_cnt, insn_cnt)
1614530e22fdSAdrian Hunter	data.append(insn_cnt)
1615530e22fdSAdrian Hunter	data.append(cyc_cnt)
1616530e22fdSAdrian Hunter	data.append(ipc)
1617530e22fdSAdrian Hunter
161876099f98SAdrian Hunterdef BranchDataPrep(query):
161976099f98SAdrian Hunter	data = []
162076099f98SAdrian Hunter	for i in xrange(0, 8):
162176099f98SAdrian Hunter		data.append(query.value(i))
1622530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
162376099f98SAdrian Hunter	return data
162476099f98SAdrian Hunter
16258453c936SAdrian Hunterdef BranchDataPrepWA(query):
16268453c936SAdrian Hunter	data = []
16278453c936SAdrian Hunter	data.append(query.value(0))
16288453c936SAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
16298453c936SAdrian Hunter	data.append("{:>19}".format(query.value(1)))
16308453c936SAdrian Hunter	for i in xrange(2, 8):
16318453c936SAdrian Hunter		data.append(query.value(i))
1632530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
1633530e22fdSAdrian Hunter	return data
1634530e22fdSAdrian Hunter
1635530e22fdSAdrian Hunterdef BranchDataWithIPCPrep(query):
1636530e22fdSAdrian Hunter	data = []
1637530e22fdSAdrian Hunter	for i in xrange(0, 8):
1638530e22fdSAdrian Hunter		data.append(query.value(i))
1639530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
1640530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
1641530e22fdSAdrian Hunter	return data
1642530e22fdSAdrian Hunter
1643530e22fdSAdrian Hunterdef BranchDataWithIPCPrepWA(query):
1644530e22fdSAdrian Hunter	data = []
1645530e22fdSAdrian Hunter	data.append(query.value(0))
1646530e22fdSAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1647530e22fdSAdrian Hunter	data.append("{:>19}".format(query.value(1)))
1648530e22fdSAdrian Hunter	for i in xrange(2, 8):
1649530e22fdSAdrian Hunter		data.append(query.value(i))
1650530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
1651530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
16528453c936SAdrian Hunter	return data
16538453c936SAdrian Hunter
165476099f98SAdrian Hunter# Branch data model
165576099f98SAdrian Hunter
165676099f98SAdrian Hunterclass BranchModel(TreeModel):
165776099f98SAdrian Hunter
165876099f98SAdrian Hunter	progress = Signal(object)
165976099f98SAdrian Hunter
166076099f98SAdrian Hunter	def __init__(self, glb, event_id, where_clause, parent=None):
16614a0979d4SAdrian Hunter		super(BranchModel, self).__init__(glb, None, parent)
166276099f98SAdrian Hunter		self.event_id = event_id
166376099f98SAdrian Hunter		self.more = True
166476099f98SAdrian Hunter		self.populated = 0
1665530e22fdSAdrian Hunter		self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
1666530e22fdSAdrian Hunter		if self.have_ipc:
1667530e22fdSAdrian Hunter			select_ipc = ", insn_count, cyc_count"
1668530e22fdSAdrian Hunter			prep_fn = BranchDataWithIPCPrep
1669530e22fdSAdrian Hunter			prep_wa_fn = BranchDataWithIPCPrepWA
1670530e22fdSAdrian Hunter		else:
1671530e22fdSAdrian Hunter			select_ipc = ""
1672530e22fdSAdrian Hunter			prep_fn = BranchDataPrep
1673530e22fdSAdrian Hunter			prep_wa_fn = BranchDataPrepWA
167476099f98SAdrian Hunter		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
167576099f98SAdrian Hunter			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
167676099f98SAdrian Hunter			" ip, symbols.name, sym_offset, dsos.short_name,"
167776099f98SAdrian Hunter			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1678530e22fdSAdrian Hunter			+ select_ipc +
167976099f98SAdrian Hunter			" FROM samples"
168076099f98SAdrian Hunter			" INNER JOIN comms ON comm_id = comms.id"
168176099f98SAdrian Hunter			" INNER JOIN threads ON thread_id = threads.id"
168276099f98SAdrian Hunter			" INNER JOIN branch_types ON branch_type = branch_types.id"
168376099f98SAdrian Hunter			" INNER JOIN symbols ON symbol_id = symbols.id"
168476099f98SAdrian Hunter			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
168576099f98SAdrian Hunter			" INNER JOIN dsos ON samples.dso_id = dsos.id"
168676099f98SAdrian Hunter			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
168776099f98SAdrian Hunter			" WHERE samples.id > $$last_id$$" + where_clause +
168876099f98SAdrian Hunter			" AND evsel_id = " + str(self.event_id) +
168976099f98SAdrian Hunter			" ORDER BY samples.id"
169076099f98SAdrian Hunter			" LIMIT " + str(glb_chunk_sz))
16918453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
1692530e22fdSAdrian Hunter			prep = prep_fn
16938453c936SAdrian Hunter		else:
1694530e22fdSAdrian Hunter			prep = prep_wa_fn
16958453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
169676099f98SAdrian Hunter		self.fetcher.done.connect(self.Update)
169776099f98SAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
169876099f98SAdrian Hunter
1699a448ba23SAdrian Hunter	def GetRoot(self):
1700a448ba23SAdrian Hunter		return BranchRootItem()
1701a448ba23SAdrian Hunter
170276099f98SAdrian Hunter	def columnCount(self, parent=None):
1703530e22fdSAdrian Hunter		if self.have_ipc:
1704530e22fdSAdrian Hunter			return 11
1705530e22fdSAdrian Hunter		else:
170676099f98SAdrian Hunter			return 8
170776099f98SAdrian Hunter
170876099f98SAdrian Hunter	def columnHeader(self, column):
1709530e22fdSAdrian Hunter		if self.have_ipc:
1710530e22fdSAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
1711530e22fdSAdrian Hunter		else:
171276099f98SAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
171376099f98SAdrian Hunter
171476099f98SAdrian Hunter	def columnFont(self, column):
1715530e22fdSAdrian Hunter		if self.have_ipc:
1716530e22fdSAdrian Hunter			br_col = 10
1717530e22fdSAdrian Hunter		else:
1718530e22fdSAdrian Hunter			br_col = 7
1719530e22fdSAdrian Hunter		if column != br_col:
172076099f98SAdrian Hunter			return None
172176099f98SAdrian Hunter		return QFont("Monospace")
172276099f98SAdrian Hunter
172376099f98SAdrian Hunter	def DisplayData(self, item, index):
172476099f98SAdrian Hunter		if item.level == 1:
172576099f98SAdrian Hunter			self.FetchIfNeeded(item.row)
172676099f98SAdrian Hunter		return item.getData(index.column())
172776099f98SAdrian Hunter
172876099f98SAdrian Hunter	def AddSample(self, data):
172976099f98SAdrian Hunter		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
173076099f98SAdrian Hunter		self.root.child_items.append(child)
173176099f98SAdrian Hunter		self.populated += 1
173276099f98SAdrian Hunter
173376099f98SAdrian Hunter	def Update(self, fetched):
173476099f98SAdrian Hunter		if not fetched:
173576099f98SAdrian Hunter			self.more = False
173676099f98SAdrian Hunter			self.progress.emit(0)
173776099f98SAdrian Hunter		child_count = self.root.child_count
173876099f98SAdrian Hunter		count = self.populated - child_count
173976099f98SAdrian Hunter		if count > 0:
174076099f98SAdrian Hunter			parent = QModelIndex()
174176099f98SAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
174276099f98SAdrian Hunter			self.insertRows(child_count, count, parent)
174376099f98SAdrian Hunter			self.root.child_count += count
174476099f98SAdrian Hunter			self.endInsertRows()
174576099f98SAdrian Hunter			self.progress.emit(self.root.child_count)
174676099f98SAdrian Hunter
174776099f98SAdrian Hunter	def FetchMoreRecords(self, count):
174876099f98SAdrian Hunter		current = self.root.child_count
174976099f98SAdrian Hunter		if self.more:
175076099f98SAdrian Hunter			self.fetcher.Fetch(count)
175176099f98SAdrian Hunter		else:
175276099f98SAdrian Hunter			self.progress.emit(0)
175376099f98SAdrian Hunter		return current
175476099f98SAdrian Hunter
175576099f98SAdrian Hunter	def HasMoreRecords(self):
175676099f98SAdrian Hunter		return self.more
175776099f98SAdrian Hunter
17580bf0947aSAdrian Hunter# Report Variables
17590bf0947aSAdrian Hunter
17600bf0947aSAdrian Hunterclass ReportVars():
17610bf0947aSAdrian Hunter
1762cd358012SAdrian Hunter	def __init__(self, name = "", where_clause = "", limit = ""):
1763947cc38dSAdrian Hunter		self.name = name
17640bf0947aSAdrian Hunter		self.where_clause = where_clause
1765cd358012SAdrian Hunter		self.limit = limit
17660bf0947aSAdrian Hunter
17670bf0947aSAdrian Hunter	def UniqueId(self):
1768cd358012SAdrian Hunter		return str(self.where_clause + ";" + self.limit)
17690bf0947aSAdrian Hunter
177076099f98SAdrian Hunter# Branch window
177176099f98SAdrian Hunter
177276099f98SAdrian Hunterclass BranchWindow(QMdiSubWindow):
177376099f98SAdrian Hunter
1774947cc38dSAdrian Hunter	def __init__(self, glb, event_id, report_vars, parent=None):
177576099f98SAdrian Hunter		super(BranchWindow, self).__init__(parent)
177676099f98SAdrian Hunter
17770bf0947aSAdrian Hunter		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
177876099f98SAdrian Hunter
17790bf0947aSAdrian Hunter		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
178076099f98SAdrian Hunter
178176099f98SAdrian Hunter		self.view = QTreeView()
178276099f98SAdrian Hunter		self.view.setUniformRowHeights(True)
178396c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
178496c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
178576099f98SAdrian Hunter		self.view.setModel(self.model)
178676099f98SAdrian Hunter
178776099f98SAdrian Hunter		self.ResizeColumnsToContents()
178876099f98SAdrian Hunter
17899bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
17909bc4e4bfSAdrian Hunter
179176099f98SAdrian Hunter		self.find_bar = FindBar(self, self, True)
179276099f98SAdrian Hunter
179376099f98SAdrian Hunter		self.finder = ChildDataItemFinder(self.model.root)
179476099f98SAdrian Hunter
179576099f98SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
179676099f98SAdrian Hunter
179776099f98SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
179876099f98SAdrian Hunter
179976099f98SAdrian Hunter		self.setWidget(self.vbox.Widget())
180076099f98SAdrian Hunter
1801947cc38dSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
180276099f98SAdrian Hunter
180376099f98SAdrian Hunter	def ResizeColumnToContents(self, column, n):
180476099f98SAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
180576099f98SAdrian Hunter		# so implement a crude alternative
180676099f98SAdrian Hunter		mm = "MM" if column else "MMMM"
180776099f98SAdrian Hunter		font = self.view.font()
180876099f98SAdrian Hunter		metrics = QFontMetrics(font)
180976099f98SAdrian Hunter		max = 0
181076099f98SAdrian Hunter		for row in xrange(n):
181176099f98SAdrian Hunter			val = self.model.root.child_items[row].data[column]
181276099f98SAdrian Hunter			len = metrics.width(str(val) + mm)
181376099f98SAdrian Hunter			max = len if len > max else max
181476099f98SAdrian Hunter		val = self.model.columnHeader(column)
181576099f98SAdrian Hunter		len = metrics.width(str(val) + mm)
181676099f98SAdrian Hunter		max = len if len > max else max
181776099f98SAdrian Hunter		self.view.setColumnWidth(column, max)
181876099f98SAdrian Hunter
181976099f98SAdrian Hunter	def ResizeColumnsToContents(self):
182076099f98SAdrian Hunter		n = min(self.model.root.child_count, 100)
182176099f98SAdrian Hunter		if n < 1:
182276099f98SAdrian Hunter			# No data yet, so connect a signal to notify when there is
182376099f98SAdrian Hunter			self.model.rowsInserted.connect(self.UpdateColumnWidths)
182476099f98SAdrian Hunter			return
182576099f98SAdrian Hunter		columns = self.model.columnCount()
182676099f98SAdrian Hunter		for i in xrange(columns):
182776099f98SAdrian Hunter			self.ResizeColumnToContents(i, n)
182876099f98SAdrian Hunter
182976099f98SAdrian Hunter	def UpdateColumnWidths(self, *x):
183076099f98SAdrian Hunter		# This only needs to be done once, so disconnect the signal now
183176099f98SAdrian Hunter		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
183276099f98SAdrian Hunter		self.ResizeColumnsToContents()
183376099f98SAdrian Hunter
183476099f98SAdrian Hunter	def Find(self, value, direction, pattern, context):
183576099f98SAdrian Hunter		self.view.setFocus()
183676099f98SAdrian Hunter		self.find_bar.Busy()
183776099f98SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
183876099f98SAdrian Hunter
183976099f98SAdrian Hunter	def FindDone(self, row):
184076099f98SAdrian Hunter		self.find_bar.Idle()
184176099f98SAdrian Hunter		if row >= 0:
184276099f98SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
184376099f98SAdrian Hunter		else:
184476099f98SAdrian Hunter			self.find_bar.NotFound()
184576099f98SAdrian Hunter
18461c3ca1b3SAdrian Hunter# Line edit data item
18471c3ca1b3SAdrian Hunter
18481c3ca1b3SAdrian Hunterclass LineEditDataItem(object):
18491c3ca1b3SAdrian Hunter
1850cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
18511c3ca1b3SAdrian Hunter		self.glb = glb
18521c3ca1b3SAdrian Hunter		self.label = label
18531c3ca1b3SAdrian Hunter		self.placeholder_text = placeholder_text
18541c3ca1b3SAdrian Hunter		self.parent = parent
18551c3ca1b3SAdrian Hunter		self.id = id
18561c3ca1b3SAdrian Hunter
1857cd358012SAdrian Hunter		self.value = default
18581c3ca1b3SAdrian Hunter
1859cd358012SAdrian Hunter		self.widget = QLineEdit(default)
18601c3ca1b3SAdrian Hunter		self.widget.editingFinished.connect(self.Validate)
18611c3ca1b3SAdrian Hunter		self.widget.textChanged.connect(self.Invalidate)
18621c3ca1b3SAdrian Hunter		self.red = False
18631c3ca1b3SAdrian Hunter		self.error = ""
18641c3ca1b3SAdrian Hunter		self.validated = True
18651c3ca1b3SAdrian Hunter
18661c3ca1b3SAdrian Hunter		if placeholder_text:
18671c3ca1b3SAdrian Hunter			self.widget.setPlaceholderText(placeholder_text)
18681c3ca1b3SAdrian Hunter
18691c3ca1b3SAdrian Hunter	def TurnTextRed(self):
18701c3ca1b3SAdrian Hunter		if not self.red:
18711c3ca1b3SAdrian Hunter			palette = QPalette()
18721c3ca1b3SAdrian Hunter			palette.setColor(QPalette.Text,Qt.red)
18731c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
18741c3ca1b3SAdrian Hunter			self.red = True
18751c3ca1b3SAdrian Hunter
18761c3ca1b3SAdrian Hunter	def TurnTextNormal(self):
18771c3ca1b3SAdrian Hunter		if self.red:
18781c3ca1b3SAdrian Hunter			palette = QPalette()
18791c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
18801c3ca1b3SAdrian Hunter			self.red = False
18811c3ca1b3SAdrian Hunter
18821c3ca1b3SAdrian Hunter	def InvalidValue(self, value):
18831c3ca1b3SAdrian Hunter		self.value = ""
18841c3ca1b3SAdrian Hunter		self.TurnTextRed()
18851c3ca1b3SAdrian Hunter		self.error = self.label + " invalid value '" + value + "'"
18861c3ca1b3SAdrian Hunter		self.parent.ShowMessage(self.error)
18871c3ca1b3SAdrian Hunter
18881c3ca1b3SAdrian Hunter	def Invalidate(self):
18891c3ca1b3SAdrian Hunter		self.validated = False
18901c3ca1b3SAdrian Hunter
18911c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
18921c3ca1b3SAdrian Hunter		self.value = input_string.strip()
18931c3ca1b3SAdrian Hunter
18941c3ca1b3SAdrian Hunter	def Validate(self):
18951c3ca1b3SAdrian Hunter		self.validated = True
18961c3ca1b3SAdrian Hunter		self.error = ""
18971c3ca1b3SAdrian Hunter		self.TurnTextNormal()
18981c3ca1b3SAdrian Hunter		self.parent.ClearMessage()
18991c3ca1b3SAdrian Hunter		input_string = self.widget.text()
19001c3ca1b3SAdrian Hunter		if not len(input_string.strip()):
19011c3ca1b3SAdrian Hunter			self.value = ""
19021c3ca1b3SAdrian Hunter			return
19031c3ca1b3SAdrian Hunter		self.DoValidate(input_string)
19041c3ca1b3SAdrian Hunter
19051c3ca1b3SAdrian Hunter	def IsValid(self):
19061c3ca1b3SAdrian Hunter		if not self.validated:
19071c3ca1b3SAdrian Hunter			self.Validate()
19081c3ca1b3SAdrian Hunter		if len(self.error):
19091c3ca1b3SAdrian Hunter			self.parent.ShowMessage(self.error)
19101c3ca1b3SAdrian Hunter			return False
19111c3ca1b3SAdrian Hunter		return True
19121c3ca1b3SAdrian Hunter
19131c3ca1b3SAdrian Hunter	def IsNumber(self, value):
19141c3ca1b3SAdrian Hunter		try:
19151c3ca1b3SAdrian Hunter			x = int(value)
19161c3ca1b3SAdrian Hunter		except:
19171c3ca1b3SAdrian Hunter			x = 0
19181c3ca1b3SAdrian Hunter		return str(x) == value
19191c3ca1b3SAdrian Hunter
19201c3ca1b3SAdrian Hunter# Non-negative integer ranges dialog data item
19211c3ca1b3SAdrian Hunter
19221c3ca1b3SAdrian Hunterclass NonNegativeIntegerRangesDataItem(LineEditDataItem):
19231c3ca1b3SAdrian Hunter
19241c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
19251c3ca1b3SAdrian Hunter		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
19261c3ca1b3SAdrian Hunter
19271c3ca1b3SAdrian Hunter		self.column_name = column_name
19281c3ca1b3SAdrian Hunter
19291c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
19301c3ca1b3SAdrian Hunter		singles = []
19311c3ca1b3SAdrian Hunter		ranges = []
19321c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
19331c3ca1b3SAdrian Hunter			if "-" in value:
19341c3ca1b3SAdrian Hunter				vrange = value.split("-")
19351c3ca1b3SAdrian Hunter				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
19361c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
19371c3ca1b3SAdrian Hunter				ranges.append(vrange)
19381c3ca1b3SAdrian Hunter			else:
19391c3ca1b3SAdrian Hunter				if not self.IsNumber(value):
19401c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
19411c3ca1b3SAdrian Hunter				singles.append(value)
19421c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
19431c3ca1b3SAdrian Hunter		if len(singles):
19441c3ca1b3SAdrian Hunter			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
19451c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
19461c3ca1b3SAdrian Hunter
1947cd358012SAdrian Hunter# Positive integer dialog data item
1948cd358012SAdrian Hunter
1949cd358012SAdrian Hunterclass PositiveIntegerDataItem(LineEditDataItem):
1950cd358012SAdrian Hunter
1951cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1952cd358012SAdrian Hunter		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1953cd358012SAdrian Hunter
1954cd358012SAdrian Hunter	def DoValidate(self, input_string):
1955cd358012SAdrian Hunter		if not self.IsNumber(input_string.strip()):
1956cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1957cd358012SAdrian Hunter		value = int(input_string.strip())
1958cd358012SAdrian Hunter		if value <= 0:
1959cd358012SAdrian Hunter			return self.InvalidValue(input_string)
1960cd358012SAdrian Hunter		self.value = str(value)
1961cd358012SAdrian Hunter
19621c3ca1b3SAdrian Hunter# Dialog data item converted and validated using a SQL table
19631c3ca1b3SAdrian Hunter
19641c3ca1b3SAdrian Hunterclass SQLTableDataItem(LineEditDataItem):
19651c3ca1b3SAdrian Hunter
19661c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
19671c3ca1b3SAdrian Hunter		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
19681c3ca1b3SAdrian Hunter
19691c3ca1b3SAdrian Hunter		self.table_name = table_name
19701c3ca1b3SAdrian Hunter		self.match_column = match_column
19711c3ca1b3SAdrian Hunter		self.column_name1 = column_name1
19721c3ca1b3SAdrian Hunter		self.column_name2 = column_name2
19731c3ca1b3SAdrian Hunter
19741c3ca1b3SAdrian Hunter	def ValueToIds(self, value):
19751c3ca1b3SAdrian Hunter		ids = []
19761c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
19771c3ca1b3SAdrian Hunter		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
19781c3ca1b3SAdrian Hunter		ret = query.exec_(stmt)
19791c3ca1b3SAdrian Hunter		if ret:
19801c3ca1b3SAdrian Hunter			while query.next():
19811c3ca1b3SAdrian Hunter				ids.append(str(query.value(0)))
19821c3ca1b3SAdrian Hunter		return ids
19831c3ca1b3SAdrian Hunter
19841c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
19851c3ca1b3SAdrian Hunter		all_ids = []
19861c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
19871c3ca1b3SAdrian Hunter			ids = self.ValueToIds(value)
19881c3ca1b3SAdrian Hunter			if len(ids):
19891c3ca1b3SAdrian Hunter				all_ids.extend(ids)
19901c3ca1b3SAdrian Hunter			else:
19911c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
19921c3ca1b3SAdrian Hunter		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
19931c3ca1b3SAdrian Hunter		if self.column_name2:
19941c3ca1b3SAdrian Hunter			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
19951c3ca1b3SAdrian Hunter
19961c3ca1b3SAdrian Hunter# Sample time ranges dialog data item converted and validated using 'samples' SQL table
19971c3ca1b3SAdrian Hunter
19981c3ca1b3SAdrian Hunterclass SampleTimeRangesDataItem(LineEditDataItem):
19991c3ca1b3SAdrian Hunter
20001c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
20011c3ca1b3SAdrian Hunter		self.column_name = column_name
20021c3ca1b3SAdrian Hunter
20031c3ca1b3SAdrian Hunter		self.last_id = 0
20041c3ca1b3SAdrian Hunter		self.first_time = 0
20051c3ca1b3SAdrian Hunter		self.last_time = 2 ** 64
20061c3ca1b3SAdrian Hunter
20071c3ca1b3SAdrian Hunter		query = QSqlQuery(glb.db)
20081c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
20091c3ca1b3SAdrian Hunter		if query.next():
20101c3ca1b3SAdrian Hunter			self.last_id = int(query.value(0))
20111c3ca1b3SAdrian Hunter			self.last_time = int(query.value(1))
20121c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
20131c3ca1b3SAdrian Hunter		if query.next():
20141c3ca1b3SAdrian Hunter			self.first_time = int(query.value(0))
20151c3ca1b3SAdrian Hunter		if placeholder_text:
20161c3ca1b3SAdrian Hunter			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
20171c3ca1b3SAdrian Hunter
20181c3ca1b3SAdrian Hunter		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
20191c3ca1b3SAdrian Hunter
20201c3ca1b3SAdrian Hunter	def IdBetween(self, query, lower_id, higher_id, order):
20211c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
20221c3ca1b3SAdrian Hunter		if query.next():
20231c3ca1b3SAdrian Hunter			return True, int(query.value(0))
20241c3ca1b3SAdrian Hunter		else:
20251c3ca1b3SAdrian Hunter			return False, 0
20261c3ca1b3SAdrian Hunter
20271c3ca1b3SAdrian Hunter	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
20281c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
20291c3ca1b3SAdrian Hunter		while True:
20301c3ca1b3SAdrian Hunter			next_id = int((lower_id + higher_id) / 2)
20311c3ca1b3SAdrian Hunter			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
20321c3ca1b3SAdrian Hunter			if not query.next():
20331c3ca1b3SAdrian Hunter				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
20341c3ca1b3SAdrian Hunter				if not ok:
20351c3ca1b3SAdrian Hunter					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
20361c3ca1b3SAdrian Hunter					if not ok:
20371c3ca1b3SAdrian Hunter						return str(higher_id)
20381c3ca1b3SAdrian Hunter				next_id = dbid
20391c3ca1b3SAdrian Hunter				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
20401c3ca1b3SAdrian Hunter			next_time = int(query.value(0))
20411c3ca1b3SAdrian Hunter			if get_floor:
20421c3ca1b3SAdrian Hunter				if target_time > next_time:
20431c3ca1b3SAdrian Hunter					lower_id = next_id
20441c3ca1b3SAdrian Hunter				else:
20451c3ca1b3SAdrian Hunter					higher_id = next_id
20461c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
20471c3ca1b3SAdrian Hunter					return str(higher_id)
20481c3ca1b3SAdrian Hunter			else:
20491c3ca1b3SAdrian Hunter				if target_time >= next_time:
20501c3ca1b3SAdrian Hunter					lower_id = next_id
20511c3ca1b3SAdrian Hunter				else:
20521c3ca1b3SAdrian Hunter					higher_id = next_id
20531c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
20541c3ca1b3SAdrian Hunter					return str(lower_id)
20551c3ca1b3SAdrian Hunter
20561c3ca1b3SAdrian Hunter	def ConvertRelativeTime(self, val):
20571c3ca1b3SAdrian Hunter		mult = 1
20581c3ca1b3SAdrian Hunter		suffix = val[-2:]
20591c3ca1b3SAdrian Hunter		if suffix == "ms":
20601c3ca1b3SAdrian Hunter			mult = 1000000
20611c3ca1b3SAdrian Hunter		elif suffix == "us":
20621c3ca1b3SAdrian Hunter			mult = 1000
20631c3ca1b3SAdrian Hunter		elif suffix == "ns":
20641c3ca1b3SAdrian Hunter			mult = 1
20651c3ca1b3SAdrian Hunter		else:
20661c3ca1b3SAdrian Hunter			return val
20671c3ca1b3SAdrian Hunter		val = val[:-2].strip()
20681c3ca1b3SAdrian Hunter		if not self.IsNumber(val):
20691c3ca1b3SAdrian Hunter			return val
20701c3ca1b3SAdrian Hunter		val = int(val) * mult
20711c3ca1b3SAdrian Hunter		if val >= 0:
20721c3ca1b3SAdrian Hunter			val += self.first_time
20731c3ca1b3SAdrian Hunter		else:
20741c3ca1b3SAdrian Hunter			val += self.last_time
20751c3ca1b3SAdrian Hunter		return str(val)
20761c3ca1b3SAdrian Hunter
20771c3ca1b3SAdrian Hunter	def ConvertTimeRange(self, vrange):
20781c3ca1b3SAdrian Hunter		if vrange[0] == "":
20791c3ca1b3SAdrian Hunter			vrange[0] = str(self.first_time)
20801c3ca1b3SAdrian Hunter		if vrange[1] == "":
20811c3ca1b3SAdrian Hunter			vrange[1] = str(self.last_time)
20821c3ca1b3SAdrian Hunter		vrange[0] = self.ConvertRelativeTime(vrange[0])
20831c3ca1b3SAdrian Hunter		vrange[1] = self.ConvertRelativeTime(vrange[1])
20841c3ca1b3SAdrian Hunter		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
20851c3ca1b3SAdrian Hunter			return False
20861c3ca1b3SAdrian Hunter		beg_range = max(int(vrange[0]), self.first_time)
20871c3ca1b3SAdrian Hunter		end_range = min(int(vrange[1]), self.last_time)
20881c3ca1b3SAdrian Hunter		if beg_range > self.last_time or end_range < self.first_time:
20891c3ca1b3SAdrian Hunter			return False
20901c3ca1b3SAdrian Hunter		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
20911c3ca1b3SAdrian Hunter		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
20921c3ca1b3SAdrian Hunter		return True
20931c3ca1b3SAdrian Hunter
20941c3ca1b3SAdrian Hunter	def AddTimeRange(self, value, ranges):
20951c3ca1b3SAdrian Hunter		n = value.count("-")
20961c3ca1b3SAdrian Hunter		if n == 1:
20971c3ca1b3SAdrian Hunter			pass
20981c3ca1b3SAdrian Hunter		elif n == 2:
20991c3ca1b3SAdrian Hunter			if value.split("-")[1].strip() == "":
21001c3ca1b3SAdrian Hunter				n = 1
21011c3ca1b3SAdrian Hunter		elif n == 3:
21021c3ca1b3SAdrian Hunter			n = 2
21031c3ca1b3SAdrian Hunter		else:
21041c3ca1b3SAdrian Hunter			return False
21051c3ca1b3SAdrian Hunter		pos = findnth(value, "-", n)
21061c3ca1b3SAdrian Hunter		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
21071c3ca1b3SAdrian Hunter		if self.ConvertTimeRange(vrange):
21081c3ca1b3SAdrian Hunter			ranges.append(vrange)
21091c3ca1b3SAdrian Hunter			return True
21101c3ca1b3SAdrian Hunter		return False
21111c3ca1b3SAdrian Hunter
21121c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
21131c3ca1b3SAdrian Hunter		ranges = []
21141c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
21151c3ca1b3SAdrian Hunter			if not self.AddTimeRange(value, ranges):
21161c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
21171c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
21181c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
21191c3ca1b3SAdrian Hunter
21200924cd68SAdrian Hunter# Report Dialog Base
2121210cf1f9SAdrian Hunter
21220924cd68SAdrian Hunterclass ReportDialogBase(QDialog):
2123210cf1f9SAdrian Hunter
21240924cd68SAdrian Hunter	def __init__(self, glb, title, items, partial, parent=None):
21250924cd68SAdrian Hunter		super(ReportDialogBase, self).__init__(parent)
2126210cf1f9SAdrian Hunter
2127210cf1f9SAdrian Hunter		self.glb = glb
2128210cf1f9SAdrian Hunter
21290bf0947aSAdrian Hunter		self.report_vars = ReportVars()
2130210cf1f9SAdrian Hunter
21310924cd68SAdrian Hunter		self.setWindowTitle(title)
2132210cf1f9SAdrian Hunter		self.setMinimumWidth(600)
2133210cf1f9SAdrian Hunter
21341c3ca1b3SAdrian Hunter		self.data_items = [x(glb, self) for x in items]
2135210cf1f9SAdrian Hunter
21360924cd68SAdrian Hunter		self.partial = partial
21370924cd68SAdrian Hunter
2138210cf1f9SAdrian Hunter		self.grid = QGridLayout()
2139210cf1f9SAdrian Hunter
2140210cf1f9SAdrian Hunter		for row in xrange(len(self.data_items)):
2141210cf1f9SAdrian Hunter			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2142210cf1f9SAdrian Hunter			self.grid.addWidget(self.data_items[row].widget, row, 1)
2143210cf1f9SAdrian Hunter
2144210cf1f9SAdrian Hunter		self.status = QLabel()
2145210cf1f9SAdrian Hunter
2146210cf1f9SAdrian Hunter		self.ok_button = QPushButton("Ok", self)
2147210cf1f9SAdrian Hunter		self.ok_button.setDefault(True)
2148210cf1f9SAdrian Hunter		self.ok_button.released.connect(self.Ok)
2149210cf1f9SAdrian Hunter		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2150210cf1f9SAdrian Hunter
2151210cf1f9SAdrian Hunter		self.cancel_button = QPushButton("Cancel", self)
2152210cf1f9SAdrian Hunter		self.cancel_button.released.connect(self.reject)
2153210cf1f9SAdrian Hunter		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2154210cf1f9SAdrian Hunter
2155210cf1f9SAdrian Hunter		self.hbox = QHBoxLayout()
2156210cf1f9SAdrian Hunter		#self.hbox.addStretch()
2157210cf1f9SAdrian Hunter		self.hbox.addWidget(self.status)
2158210cf1f9SAdrian Hunter		self.hbox.addWidget(self.ok_button)
2159210cf1f9SAdrian Hunter		self.hbox.addWidget(self.cancel_button)
2160210cf1f9SAdrian Hunter
2161210cf1f9SAdrian Hunter		self.vbox = QVBoxLayout()
2162210cf1f9SAdrian Hunter		self.vbox.addLayout(self.grid)
2163210cf1f9SAdrian Hunter		self.vbox.addLayout(self.hbox)
2164210cf1f9SAdrian Hunter
2165210cf1f9SAdrian Hunter		self.setLayout(self.vbox);
2166210cf1f9SAdrian Hunter
2167210cf1f9SAdrian Hunter	def Ok(self):
21680bf0947aSAdrian Hunter		vars = self.report_vars
21691c3ca1b3SAdrian Hunter		for d in self.data_items:
21701c3ca1b3SAdrian Hunter			if d.id == "REPORTNAME":
21711c3ca1b3SAdrian Hunter				vars.name = d.value
2172947cc38dSAdrian Hunter		if not vars.name:
2173210cf1f9SAdrian Hunter			self.ShowMessage("Report name is required")
2174210cf1f9SAdrian Hunter			return
2175210cf1f9SAdrian Hunter		for d in self.data_items:
2176210cf1f9SAdrian Hunter			if not d.IsValid():
2177210cf1f9SAdrian Hunter				return
2178210cf1f9SAdrian Hunter		for d in self.data_items[1:]:
2179cd358012SAdrian Hunter			if d.id == "LIMIT":
2180cd358012SAdrian Hunter				vars.limit = d.value
2181cd358012SAdrian Hunter			elif len(d.value):
21820bf0947aSAdrian Hunter				if len(vars.where_clause):
21830bf0947aSAdrian Hunter					vars.where_clause += " AND "
21840bf0947aSAdrian Hunter				vars.where_clause += d.value
21850bf0947aSAdrian Hunter		if len(vars.where_clause):
21860924cd68SAdrian Hunter			if self.partial:
21870bf0947aSAdrian Hunter				vars.where_clause = " AND ( " + vars.where_clause + " ) "
2188210cf1f9SAdrian Hunter			else:
21890bf0947aSAdrian Hunter				vars.where_clause = " WHERE " + vars.where_clause + " "
2190210cf1f9SAdrian Hunter		self.accept()
2191210cf1f9SAdrian Hunter
2192210cf1f9SAdrian Hunter	def ShowMessage(self, msg):
2193210cf1f9SAdrian Hunter		self.status.setText("<font color=#FF0000>" + msg)
2194210cf1f9SAdrian Hunter
2195210cf1f9SAdrian Hunter	def ClearMessage(self):
2196210cf1f9SAdrian Hunter		self.status.setText("")
2197210cf1f9SAdrian Hunter
21980924cd68SAdrian Hunter# Selected branch report creation dialog
21990924cd68SAdrian Hunter
22000924cd68SAdrian Hunterclass SelectedBranchDialog(ReportDialogBase):
22010924cd68SAdrian Hunter
22020924cd68SAdrian Hunter	def __init__(self, glb, parent=None):
22030924cd68SAdrian Hunter		title = "Selected Branches"
22041c3ca1b3SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
22051c3ca1b3SAdrian Hunter			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
22061c3ca1b3SAdrian Hunter			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
22071c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
22081c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
22091c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
22101c3ca1b3SAdrian 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),
22111c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
22121c3ca1b3SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
22130924cd68SAdrian Hunter		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
22140924cd68SAdrian Hunter
221576099f98SAdrian Hunter# Event list
221676099f98SAdrian Hunter
221776099f98SAdrian Hunterdef GetEventList(db):
221876099f98SAdrian Hunter	events = []
221976099f98SAdrian Hunter	query = QSqlQuery(db)
222076099f98SAdrian Hunter	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
222176099f98SAdrian Hunter	while query.next():
222276099f98SAdrian Hunter		events.append(query.value(0))
222376099f98SAdrian Hunter	return events
222476099f98SAdrian Hunter
2225655cb952SAdrian Hunter# Is a table selectable
2226655cb952SAdrian Hunter
2227530e22fdSAdrian Hunterdef IsSelectable(db, table, sql = "", columns = "*"):
2228655cb952SAdrian Hunter	query = QSqlQuery(db)
2229655cb952SAdrian Hunter	try:
2230530e22fdSAdrian Hunter		QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
2231655cb952SAdrian Hunter	except:
2232655cb952SAdrian Hunter		return False
2233655cb952SAdrian Hunter	return True
2234655cb952SAdrian Hunter
22358392b74bSAdrian Hunter# SQL table data model item
22368392b74bSAdrian Hunter
22378392b74bSAdrian Hunterclass SQLTableItem():
22388392b74bSAdrian Hunter
22398392b74bSAdrian Hunter	def __init__(self, row, data):
22408392b74bSAdrian Hunter		self.row = row
22418392b74bSAdrian Hunter		self.data = data
22428392b74bSAdrian Hunter
22438392b74bSAdrian Hunter	def getData(self, column):
22448392b74bSAdrian Hunter		return self.data[column]
22458392b74bSAdrian Hunter
22468392b74bSAdrian Hunter# SQL table data model
22478392b74bSAdrian Hunter
22488392b74bSAdrian Hunterclass SQLTableModel(TableModel):
22498392b74bSAdrian Hunter
22508392b74bSAdrian Hunter	progress = Signal(object)
22518392b74bSAdrian Hunter
22528c90fef9SAdrian Hunter	def __init__(self, glb, sql, column_headers, parent=None):
22538392b74bSAdrian Hunter		super(SQLTableModel, self).__init__(parent)
22548392b74bSAdrian Hunter		self.glb = glb
22558392b74bSAdrian Hunter		self.more = True
22568392b74bSAdrian Hunter		self.populated = 0
22578c90fef9SAdrian Hunter		self.column_headers = column_headers
22588453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
22598392b74bSAdrian Hunter		self.fetcher.done.connect(self.Update)
22608392b74bSAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
22618392b74bSAdrian Hunter
22628392b74bSAdrian Hunter	def DisplayData(self, item, index):
22638392b74bSAdrian Hunter		self.FetchIfNeeded(item.row)
22648392b74bSAdrian Hunter		return item.getData(index.column())
22658392b74bSAdrian Hunter
22668392b74bSAdrian Hunter	def AddSample(self, data):
22678392b74bSAdrian Hunter		child = SQLTableItem(self.populated, data)
22688392b74bSAdrian Hunter		self.child_items.append(child)
22698392b74bSAdrian Hunter		self.populated += 1
22708392b74bSAdrian Hunter
22718392b74bSAdrian Hunter	def Update(self, fetched):
22728392b74bSAdrian Hunter		if not fetched:
22738392b74bSAdrian Hunter			self.more = False
22748392b74bSAdrian Hunter			self.progress.emit(0)
22758392b74bSAdrian Hunter		child_count = self.child_count
22768392b74bSAdrian Hunter		count = self.populated - child_count
22778392b74bSAdrian Hunter		if count > 0:
22788392b74bSAdrian Hunter			parent = QModelIndex()
22798392b74bSAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
22808392b74bSAdrian Hunter			self.insertRows(child_count, count, parent)
22818392b74bSAdrian Hunter			self.child_count += count
22828392b74bSAdrian Hunter			self.endInsertRows()
22838392b74bSAdrian Hunter			self.progress.emit(self.child_count)
22848392b74bSAdrian Hunter
22858392b74bSAdrian Hunter	def FetchMoreRecords(self, count):
22868392b74bSAdrian Hunter		current = self.child_count
22878392b74bSAdrian Hunter		if self.more:
22888392b74bSAdrian Hunter			self.fetcher.Fetch(count)
22898392b74bSAdrian Hunter		else:
22908392b74bSAdrian Hunter			self.progress.emit(0)
22918392b74bSAdrian Hunter		return current
22928392b74bSAdrian Hunter
22938392b74bSAdrian Hunter	def HasMoreRecords(self):
22948392b74bSAdrian Hunter		return self.more
22958392b74bSAdrian Hunter
22968c90fef9SAdrian Hunter	def columnCount(self, parent=None):
22978c90fef9SAdrian Hunter		return len(self.column_headers)
22988c90fef9SAdrian Hunter
22998c90fef9SAdrian Hunter	def columnHeader(self, column):
23008c90fef9SAdrian Hunter		return self.column_headers[column]
23018c90fef9SAdrian Hunter
23028453c936SAdrian Hunter	def SQLTableDataPrep(self, query, count):
23038453c936SAdrian Hunter		data = []
23048453c936SAdrian Hunter		for i in xrange(count):
23058453c936SAdrian Hunter			data.append(query.value(i))
23068453c936SAdrian Hunter		return data
23078453c936SAdrian Hunter
23088392b74bSAdrian Hunter# SQL automatic table data model
23098392b74bSAdrian Hunter
23108392b74bSAdrian Hunterclass SQLAutoTableModel(SQLTableModel):
23118392b74bSAdrian Hunter
23128392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
23138392b74bSAdrian Hunter		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
23148392b74bSAdrian Hunter		if table_name == "comm_threads_view":
23158392b74bSAdrian Hunter			# For now, comm_threads_view has no id column
23168392b74bSAdrian Hunter			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
23178c90fef9SAdrian Hunter		column_headers = []
23188392b74bSAdrian Hunter		query = QSqlQuery(glb.db)
23198392b74bSAdrian Hunter		if glb.dbref.is_sqlite3:
23208392b74bSAdrian Hunter			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
23218392b74bSAdrian Hunter			while query.next():
23228c90fef9SAdrian Hunter				column_headers.append(query.value(1))
23238392b74bSAdrian Hunter			if table_name == "sqlite_master":
23248392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
23258392b74bSAdrian Hunter		else:
23268392b74bSAdrian Hunter			if table_name[:19] == "information_schema.":
23278392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
23288392b74bSAdrian Hunter				select_table_name = table_name[19:]
23298392b74bSAdrian Hunter				schema = "information_schema"
23308392b74bSAdrian Hunter			else:
23318392b74bSAdrian Hunter				select_table_name = table_name
23328392b74bSAdrian Hunter				schema = "public"
23338392b74bSAdrian Hunter			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
23348392b74bSAdrian Hunter			while query.next():
23358c90fef9SAdrian Hunter				column_headers.append(query.value(0))
23368453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
23378453c936SAdrian Hunter			if table_name == "samples_view":
23388453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_view_DataPrep
23398453c936SAdrian Hunter			if table_name == "samples":
23408453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_DataPrep
23418c90fef9SAdrian Hunter		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
23428392b74bSAdrian Hunter
23438453c936SAdrian Hunter	def samples_view_DataPrep(self, query, count):
23448453c936SAdrian Hunter		data = []
23458453c936SAdrian Hunter		data.append(query.value(0))
23468453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
23478453c936SAdrian Hunter		data.append("{:>19}".format(query.value(1)))
23488453c936SAdrian Hunter		for i in xrange(2, count):
23498453c936SAdrian Hunter			data.append(query.value(i))
23508453c936SAdrian Hunter		return data
23518453c936SAdrian Hunter
23528453c936SAdrian Hunter	def samples_DataPrep(self, query, count):
23538453c936SAdrian Hunter		data = []
23548453c936SAdrian Hunter		for i in xrange(9):
23558453c936SAdrian Hunter			data.append(query.value(i))
23568453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
23578453c936SAdrian Hunter		data.append("{:>19}".format(query.value(9)))
23588453c936SAdrian Hunter		for i in xrange(10, count):
23598453c936SAdrian Hunter			data.append(query.value(i))
23608453c936SAdrian Hunter		return data
23618453c936SAdrian Hunter
23628392b74bSAdrian Hunter# Base class for custom ResizeColumnsToContents
23638392b74bSAdrian Hunter
23648392b74bSAdrian Hunterclass ResizeColumnsToContentsBase(QObject):
23658392b74bSAdrian Hunter
23668392b74bSAdrian Hunter	def __init__(self, parent=None):
23678392b74bSAdrian Hunter		super(ResizeColumnsToContentsBase, self).__init__(parent)
23688392b74bSAdrian Hunter
23698392b74bSAdrian Hunter	def ResizeColumnToContents(self, column, n):
23708392b74bSAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
23718392b74bSAdrian Hunter		# so implement a crude alternative
23728392b74bSAdrian Hunter		font = self.view.font()
23738392b74bSAdrian Hunter		metrics = QFontMetrics(font)
23748392b74bSAdrian Hunter		max = 0
23758392b74bSAdrian Hunter		for row in xrange(n):
23768392b74bSAdrian Hunter			val = self.data_model.child_items[row].data[column]
23778392b74bSAdrian Hunter			len = metrics.width(str(val) + "MM")
23788392b74bSAdrian Hunter			max = len if len > max else max
23798392b74bSAdrian Hunter		val = self.data_model.columnHeader(column)
23808392b74bSAdrian Hunter		len = metrics.width(str(val) + "MM")
23818392b74bSAdrian Hunter		max = len if len > max else max
23828392b74bSAdrian Hunter		self.view.setColumnWidth(column, max)
23838392b74bSAdrian Hunter
23848392b74bSAdrian Hunter	def ResizeColumnsToContents(self):
23858392b74bSAdrian Hunter		n = min(self.data_model.child_count, 100)
23868392b74bSAdrian Hunter		if n < 1:
23878392b74bSAdrian Hunter			# No data yet, so connect a signal to notify when there is
23888392b74bSAdrian Hunter			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
23898392b74bSAdrian Hunter			return
23908392b74bSAdrian Hunter		columns = self.data_model.columnCount()
23918392b74bSAdrian Hunter		for i in xrange(columns):
23928392b74bSAdrian Hunter			self.ResizeColumnToContents(i, n)
23938392b74bSAdrian Hunter
23948392b74bSAdrian Hunter	def UpdateColumnWidths(self, *x):
23958392b74bSAdrian Hunter		# This only needs to be done once, so disconnect the signal now
23968392b74bSAdrian Hunter		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
23978392b74bSAdrian Hunter		self.ResizeColumnsToContents()
23988392b74bSAdrian Hunter
239996c43b9aSAdrian Hunter# Convert value to CSV
240096c43b9aSAdrian Hunter
240196c43b9aSAdrian Hunterdef ToCSValue(val):
240296c43b9aSAdrian Hunter	if '"' in val:
240396c43b9aSAdrian Hunter		val = val.replace('"', '""')
240496c43b9aSAdrian Hunter	if "," in val or '"' in val:
240596c43b9aSAdrian Hunter		val = '"' + val + '"'
240696c43b9aSAdrian Hunter	return val
240796c43b9aSAdrian Hunter
240896c43b9aSAdrian Hunter# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
240996c43b9aSAdrian Hunter
241096c43b9aSAdrian Hunterglb_max_cols = 1000
241196c43b9aSAdrian Hunter
241296c43b9aSAdrian Hunterdef RowColumnKey(a):
241396c43b9aSAdrian Hunter	return a.row() * glb_max_cols + a.column()
241496c43b9aSAdrian Hunter
241596c43b9aSAdrian Hunter# Copy selected table cells to clipboard
241696c43b9aSAdrian Hunter
241796c43b9aSAdrian Hunterdef CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
241896c43b9aSAdrian Hunter	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
241996c43b9aSAdrian Hunter	idx_cnt = len(indexes)
242096c43b9aSAdrian Hunter	if not idx_cnt:
242196c43b9aSAdrian Hunter		return
242296c43b9aSAdrian Hunter	if idx_cnt == 1:
242396c43b9aSAdrian Hunter		with_hdr=False
242496c43b9aSAdrian Hunter	min_row = indexes[0].row()
242596c43b9aSAdrian Hunter	max_row = indexes[0].row()
242696c43b9aSAdrian Hunter	min_col = indexes[0].column()
242796c43b9aSAdrian Hunter	max_col = indexes[0].column()
242896c43b9aSAdrian Hunter	for i in indexes:
242996c43b9aSAdrian Hunter		min_row = min(min_row, i.row())
243096c43b9aSAdrian Hunter		max_row = max(max_row, i.row())
243196c43b9aSAdrian Hunter		min_col = min(min_col, i.column())
243296c43b9aSAdrian Hunter		max_col = max(max_col, i.column())
243396c43b9aSAdrian Hunter	if max_col > glb_max_cols:
243496c43b9aSAdrian Hunter		raise RuntimeError("glb_max_cols is too low")
243596c43b9aSAdrian Hunter	max_width = [0] * (1 + max_col - min_col)
243696c43b9aSAdrian Hunter	for i in indexes:
243796c43b9aSAdrian Hunter		c = i.column() - min_col
243896c43b9aSAdrian Hunter		max_width[c] = max(max_width[c], len(str(i.data())))
243996c43b9aSAdrian Hunter	text = ""
244096c43b9aSAdrian Hunter	pad = ""
244196c43b9aSAdrian Hunter	sep = ""
244296c43b9aSAdrian Hunter	if with_hdr:
244396c43b9aSAdrian Hunter		model = indexes[0].model()
244496c43b9aSAdrian Hunter		for col in range(min_col, max_col + 1):
244596c43b9aSAdrian Hunter			val = model.headerData(col, Qt.Horizontal)
244696c43b9aSAdrian Hunter			if as_csv:
244796c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
244896c43b9aSAdrian Hunter				sep = ","
244996c43b9aSAdrian Hunter			else:
245096c43b9aSAdrian Hunter				c = col - min_col
245196c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
245296c43b9aSAdrian Hunter				width = max_width[c]
245396c43b9aSAdrian Hunter				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
245496c43b9aSAdrian Hunter				if align & Qt.AlignRight:
245596c43b9aSAdrian Hunter					val = val.rjust(width)
245696c43b9aSAdrian Hunter				text += pad + sep + val
245796c43b9aSAdrian Hunter				pad = " " * (width - len(val))
245896c43b9aSAdrian Hunter				sep = "  "
245996c43b9aSAdrian Hunter		text += "\n"
246096c43b9aSAdrian Hunter		pad = ""
246196c43b9aSAdrian Hunter		sep = ""
246296c43b9aSAdrian Hunter	last_row = min_row
246396c43b9aSAdrian Hunter	for i in indexes:
246496c43b9aSAdrian Hunter		if i.row() > last_row:
246596c43b9aSAdrian Hunter			last_row = i.row()
246696c43b9aSAdrian Hunter			text += "\n"
246796c43b9aSAdrian Hunter			pad = ""
246896c43b9aSAdrian Hunter			sep = ""
246996c43b9aSAdrian Hunter		if as_csv:
247096c43b9aSAdrian Hunter			text += sep + ToCSValue(str(i.data()))
247196c43b9aSAdrian Hunter			sep = ","
247296c43b9aSAdrian Hunter		else:
247396c43b9aSAdrian Hunter			width = max_width[i.column() - min_col]
247496c43b9aSAdrian Hunter			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
247596c43b9aSAdrian Hunter				val = str(i.data()).rjust(width)
247696c43b9aSAdrian Hunter			else:
247796c43b9aSAdrian Hunter				val = str(i.data())
247896c43b9aSAdrian Hunter			text += pad + sep + val
247996c43b9aSAdrian Hunter			pad = " " * (width - len(val))
248096c43b9aSAdrian Hunter			sep = "  "
248196c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
248296c43b9aSAdrian Hunter
248396c43b9aSAdrian Hunterdef CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
248496c43b9aSAdrian Hunter	indexes = view.selectedIndexes()
248596c43b9aSAdrian Hunter	if not len(indexes):
248696c43b9aSAdrian Hunter		return
248796c43b9aSAdrian Hunter
248896c43b9aSAdrian Hunter	selection = view.selectionModel()
248996c43b9aSAdrian Hunter
249096c43b9aSAdrian Hunter	first = None
249196c43b9aSAdrian Hunter	for i in indexes:
249296c43b9aSAdrian Hunter		above = view.indexAbove(i)
249396c43b9aSAdrian Hunter		if not selection.isSelected(above):
249496c43b9aSAdrian Hunter			first = i
249596c43b9aSAdrian Hunter			break
249696c43b9aSAdrian Hunter
249796c43b9aSAdrian Hunter	if first is None:
249896c43b9aSAdrian Hunter		raise RuntimeError("CopyTreeCellsToClipboard internal error")
249996c43b9aSAdrian Hunter
250096c43b9aSAdrian Hunter	model = first.model()
250196c43b9aSAdrian Hunter	row_cnt = 0
250296c43b9aSAdrian Hunter	col_cnt = model.columnCount(first)
250396c43b9aSAdrian Hunter	max_width = [0] * col_cnt
250496c43b9aSAdrian Hunter
250596c43b9aSAdrian Hunter	indent_sz = 2
250696c43b9aSAdrian Hunter	indent_str = " " * indent_sz
250796c43b9aSAdrian Hunter
250896c43b9aSAdrian Hunter	expanded_mark_sz = 2
250996c43b9aSAdrian Hunter	if sys.version_info[0] == 3:
251096c43b9aSAdrian Hunter		expanded_mark = "\u25BC "
251196c43b9aSAdrian Hunter		not_expanded_mark = "\u25B6 "
251296c43b9aSAdrian Hunter	else:
251396c43b9aSAdrian Hunter		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
251496c43b9aSAdrian Hunter		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
251596c43b9aSAdrian Hunter	leaf_mark = "  "
251696c43b9aSAdrian Hunter
251796c43b9aSAdrian Hunter	if not as_csv:
251896c43b9aSAdrian Hunter		pos = first
251996c43b9aSAdrian Hunter		while True:
252096c43b9aSAdrian Hunter			row_cnt += 1
252196c43b9aSAdrian Hunter			row = pos.row()
252296c43b9aSAdrian Hunter			for c in range(col_cnt):
252396c43b9aSAdrian Hunter				i = pos.sibling(row, c)
252496c43b9aSAdrian Hunter				if c:
252596c43b9aSAdrian Hunter					n = len(str(i.data()))
252696c43b9aSAdrian Hunter				else:
252796c43b9aSAdrian Hunter					n = len(str(i.data()).strip())
252896c43b9aSAdrian Hunter					n += (i.internalPointer().level - 1) * indent_sz
252996c43b9aSAdrian Hunter					n += expanded_mark_sz
253096c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], n)
253196c43b9aSAdrian Hunter			pos = view.indexBelow(pos)
253296c43b9aSAdrian Hunter			if not selection.isSelected(pos):
253396c43b9aSAdrian Hunter				break
253496c43b9aSAdrian Hunter
253596c43b9aSAdrian Hunter	text = ""
253696c43b9aSAdrian Hunter	pad = ""
253796c43b9aSAdrian Hunter	sep = ""
253896c43b9aSAdrian Hunter	if with_hdr:
253996c43b9aSAdrian Hunter		for c in range(col_cnt):
254096c43b9aSAdrian Hunter			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
254196c43b9aSAdrian Hunter			if as_csv:
254296c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
254396c43b9aSAdrian Hunter				sep = ","
254496c43b9aSAdrian Hunter			else:
254596c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
254696c43b9aSAdrian Hunter				width = max_width[c]
254796c43b9aSAdrian Hunter				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
254896c43b9aSAdrian Hunter				if align & Qt.AlignRight:
254996c43b9aSAdrian Hunter					val = val.rjust(width)
255096c43b9aSAdrian Hunter				text += pad + sep + val
255196c43b9aSAdrian Hunter				pad = " " * (width - len(val))
255296c43b9aSAdrian Hunter				sep = "   "
255396c43b9aSAdrian Hunter		text += "\n"
255496c43b9aSAdrian Hunter		pad = ""
255596c43b9aSAdrian Hunter		sep = ""
255696c43b9aSAdrian Hunter
255796c43b9aSAdrian Hunter	pos = first
255896c43b9aSAdrian Hunter	while True:
255996c43b9aSAdrian Hunter		row = pos.row()
256096c43b9aSAdrian Hunter		for c in range(col_cnt):
256196c43b9aSAdrian Hunter			i = pos.sibling(row, c)
256296c43b9aSAdrian Hunter			val = str(i.data())
256396c43b9aSAdrian Hunter			if not c:
256496c43b9aSAdrian Hunter				if model.hasChildren(i):
256596c43b9aSAdrian Hunter					if view.isExpanded(i):
256696c43b9aSAdrian Hunter						mark = expanded_mark
256796c43b9aSAdrian Hunter					else:
256896c43b9aSAdrian Hunter						mark = not_expanded_mark
256996c43b9aSAdrian Hunter				else:
257096c43b9aSAdrian Hunter					mark = leaf_mark
257196c43b9aSAdrian Hunter				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
257296c43b9aSAdrian Hunter			if as_csv:
257396c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
257496c43b9aSAdrian Hunter				sep = ","
257596c43b9aSAdrian Hunter			else:
257696c43b9aSAdrian Hunter				width = max_width[c]
257796c43b9aSAdrian Hunter				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
257896c43b9aSAdrian Hunter					val = val.rjust(width)
257996c43b9aSAdrian Hunter				text += pad + sep + val
258096c43b9aSAdrian Hunter				pad = " " * (width - len(val))
258196c43b9aSAdrian Hunter				sep = "   "
258296c43b9aSAdrian Hunter		pos = view.indexBelow(pos)
258396c43b9aSAdrian Hunter		if not selection.isSelected(pos):
258496c43b9aSAdrian Hunter			break
258596c43b9aSAdrian Hunter		text = text.rstrip() + "\n"
258696c43b9aSAdrian Hunter		pad = ""
258796c43b9aSAdrian Hunter		sep = ""
258896c43b9aSAdrian Hunter
258996c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
259096c43b9aSAdrian Hunter
259196c43b9aSAdrian Hunterdef CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
259296c43b9aSAdrian Hunter	view.CopyCellsToClipboard(view, as_csv, with_hdr)
259396c43b9aSAdrian Hunter
259496c43b9aSAdrian Hunterdef CopyCellsToClipboardHdr(view):
259596c43b9aSAdrian Hunter	CopyCellsToClipboard(view, False, True)
259696c43b9aSAdrian Hunter
259796c43b9aSAdrian Hunterdef CopyCellsToClipboardCSV(view):
259896c43b9aSAdrian Hunter	CopyCellsToClipboard(view, True, True)
259996c43b9aSAdrian Hunter
26009bc4e4bfSAdrian Hunter# Context menu
26019bc4e4bfSAdrian Hunter
26029bc4e4bfSAdrian Hunterclass ContextMenu(object):
26039bc4e4bfSAdrian Hunter
26049bc4e4bfSAdrian Hunter	def __init__(self, view):
26059bc4e4bfSAdrian Hunter		self.view = view
26069bc4e4bfSAdrian Hunter		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
26079bc4e4bfSAdrian Hunter		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
26089bc4e4bfSAdrian Hunter
26099bc4e4bfSAdrian Hunter	def ShowContextMenu(self, pos):
26109bc4e4bfSAdrian Hunter		menu = QMenu(self.view)
26119bc4e4bfSAdrian Hunter		self.AddActions(menu)
26129bc4e4bfSAdrian Hunter		menu.exec_(self.view.mapToGlobal(pos))
26139bc4e4bfSAdrian Hunter
26149bc4e4bfSAdrian Hunter	def AddCopy(self, menu):
26159bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
26169bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
26179bc4e4bfSAdrian Hunter
26189bc4e4bfSAdrian Hunter	def AddActions(self, menu):
26199bc4e4bfSAdrian Hunter		self.AddCopy(menu)
26209bc4e4bfSAdrian Hunter
26219bc4e4bfSAdrian Hunterclass TreeContextMenu(ContextMenu):
26229bc4e4bfSAdrian Hunter
26239bc4e4bfSAdrian Hunter	def __init__(self, view):
26249bc4e4bfSAdrian Hunter		super(TreeContextMenu, self).__init__(view)
26259bc4e4bfSAdrian Hunter
26269bc4e4bfSAdrian Hunter	def AddActions(self, menu):
26279bc4e4bfSAdrian Hunter		i = self.view.currentIndex()
26289bc4e4bfSAdrian Hunter		text = str(i.data()).strip()
26299bc4e4bfSAdrian Hunter		if len(text):
26309bc4e4bfSAdrian Hunter			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
26319bc4e4bfSAdrian Hunter		self.AddCopy(menu)
26329bc4e4bfSAdrian Hunter
26338392b74bSAdrian Hunter# Table window
26348392b74bSAdrian Hunter
26358392b74bSAdrian Hunterclass TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
26368392b74bSAdrian Hunter
26378392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
26388392b74bSAdrian Hunter		super(TableWindow, self).__init__(parent)
26398392b74bSAdrian Hunter
26408392b74bSAdrian Hunter		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
26418392b74bSAdrian Hunter
26428392b74bSAdrian Hunter		self.model = QSortFilterProxyModel()
26438392b74bSAdrian Hunter		self.model.setSourceModel(self.data_model)
26448392b74bSAdrian Hunter
26458392b74bSAdrian Hunter		self.view = QTableView()
26468392b74bSAdrian Hunter		self.view.setModel(self.model)
26478392b74bSAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
26488392b74bSAdrian Hunter		self.view.verticalHeader().setVisible(False)
26498392b74bSAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
26508392b74bSAdrian Hunter		self.view.setSortingEnabled(True)
265196c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
265296c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
26538392b74bSAdrian Hunter
26548392b74bSAdrian Hunter		self.ResizeColumnsToContents()
26558392b74bSAdrian Hunter
26569bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
26579bc4e4bfSAdrian Hunter
26588392b74bSAdrian Hunter		self.find_bar = FindBar(self, self, True)
26598392b74bSAdrian Hunter
26608392b74bSAdrian Hunter		self.finder = ChildDataItemFinder(self.data_model)
26618392b74bSAdrian Hunter
26628392b74bSAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
26638392b74bSAdrian Hunter
26648392b74bSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
26658392b74bSAdrian Hunter
26668392b74bSAdrian Hunter		self.setWidget(self.vbox.Widget())
26678392b74bSAdrian Hunter
26688392b74bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
26698392b74bSAdrian Hunter
26708392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context):
26718392b74bSAdrian Hunter		self.view.setFocus()
26728392b74bSAdrian Hunter		self.find_bar.Busy()
26738392b74bSAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
26748392b74bSAdrian Hunter
26758392b74bSAdrian Hunter	def FindDone(self, row):
26768392b74bSAdrian Hunter		self.find_bar.Idle()
26778392b74bSAdrian Hunter		if row >= 0:
267835fa1ceeSAdrian Hunter			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
26798392b74bSAdrian Hunter		else:
26808392b74bSAdrian Hunter			self.find_bar.NotFound()
26818392b74bSAdrian Hunter
26828392b74bSAdrian Hunter# Table list
26838392b74bSAdrian Hunter
26848392b74bSAdrian Hunterdef GetTableList(glb):
26858392b74bSAdrian Hunter	tables = []
26868392b74bSAdrian Hunter	query = QSqlQuery(glb.db)
26878392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
26888392b74bSAdrian Hunter		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
26898392b74bSAdrian Hunter	else:
26908392b74bSAdrian 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")
26918392b74bSAdrian Hunter	while query.next():
26928392b74bSAdrian Hunter		tables.append(query.value(0))
26938392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
26948392b74bSAdrian Hunter		tables.append("sqlite_master")
26958392b74bSAdrian Hunter	else:
26968392b74bSAdrian Hunter		tables.append("information_schema.tables")
26978392b74bSAdrian Hunter		tables.append("information_schema.views")
26988392b74bSAdrian Hunter		tables.append("information_schema.columns")
26998392b74bSAdrian Hunter	return tables
27008392b74bSAdrian Hunter
2701cd358012SAdrian Hunter# Top Calls data model
2702cd358012SAdrian Hunter
2703cd358012SAdrian Hunterclass TopCallsModel(SQLTableModel):
2704cd358012SAdrian Hunter
2705cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2706cd358012SAdrian Hunter		text = ""
2707cd358012SAdrian Hunter		if not glb.dbref.is_sqlite3:
2708cd358012SAdrian Hunter			text = "::text"
2709cd358012SAdrian Hunter		limit = ""
2710cd358012SAdrian Hunter		if len(report_vars.limit):
2711cd358012SAdrian Hunter			limit = " LIMIT " + report_vars.limit
2712cd358012SAdrian Hunter		sql = ("SELECT comm, pid, tid, name,"
2713cd358012SAdrian Hunter			" CASE"
2714cd358012SAdrian Hunter			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2715cd358012SAdrian Hunter			" ELSE short_name"
2716cd358012SAdrian Hunter			" END AS dso,"
2717cd358012SAdrian Hunter			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2718cd358012SAdrian Hunter			" CASE"
2719cd358012SAdrian Hunter			" WHEN (calls.flags = 1) THEN 'no call'" + text +
2720cd358012SAdrian Hunter			" WHEN (calls.flags = 2) THEN 'no return'" + text +
2721cd358012SAdrian Hunter			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2722cd358012SAdrian Hunter			" ELSE ''" + text +
2723cd358012SAdrian Hunter			" END AS flags"
2724cd358012SAdrian Hunter			" FROM calls"
2725cd358012SAdrian Hunter			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2726cd358012SAdrian Hunter			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2727cd358012SAdrian Hunter			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
2728cd358012SAdrian Hunter			" INNER JOIN comms ON calls.comm_id = comms.id"
2729cd358012SAdrian Hunter			" INNER JOIN threads ON calls.thread_id = threads.id" +
2730cd358012SAdrian Hunter			report_vars.where_clause +
2731cd358012SAdrian Hunter			" ORDER BY elapsed_time DESC" +
2732cd358012SAdrian Hunter			limit
2733cd358012SAdrian Hunter			)
2734cd358012SAdrian Hunter		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2735cd358012SAdrian Hunter		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2736cd358012SAdrian Hunter		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2737cd358012SAdrian Hunter
2738cd358012SAdrian Hunter	def columnAlignment(self, column):
2739cd358012SAdrian Hunter		return self.alignment[column]
2740cd358012SAdrian Hunter
2741cd358012SAdrian Hunter# Top Calls report creation dialog
2742cd358012SAdrian Hunter
2743cd358012SAdrian Hunterclass TopCallsDialog(ReportDialogBase):
2744cd358012SAdrian Hunter
2745cd358012SAdrian Hunter	def __init__(self, glb, parent=None):
2746cd358012SAdrian Hunter		title = "Top Calls by Elapsed Time"
2747cd358012SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2748cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2749cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2750cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2751cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2752cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2753cd358012SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2754cd358012SAdrian Hunter			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2755cd358012SAdrian Hunter		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2756cd358012SAdrian Hunter
2757cd358012SAdrian Hunter# Top Calls window
2758cd358012SAdrian Hunter
2759cd358012SAdrian Hunterclass TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2760cd358012SAdrian Hunter
2761cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
2762cd358012SAdrian Hunter		super(TopCallsWindow, self).__init__(parent)
2763cd358012SAdrian Hunter
2764cd358012SAdrian Hunter		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2765cd358012SAdrian Hunter		self.model = self.data_model
2766cd358012SAdrian Hunter
2767cd358012SAdrian Hunter		self.view = QTableView()
2768cd358012SAdrian Hunter		self.view.setModel(self.model)
2769cd358012SAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2770cd358012SAdrian Hunter		self.view.verticalHeader().setVisible(False)
277196c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
277296c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2773cd358012SAdrian Hunter
27749bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
27759bc4e4bfSAdrian Hunter
2776cd358012SAdrian Hunter		self.ResizeColumnsToContents()
2777cd358012SAdrian Hunter
2778cd358012SAdrian Hunter		self.find_bar = FindBar(self, self, True)
2779cd358012SAdrian Hunter
2780cd358012SAdrian Hunter		self.finder = ChildDataItemFinder(self.model)
2781cd358012SAdrian Hunter
2782cd358012SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2783cd358012SAdrian Hunter
2784cd358012SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2785cd358012SAdrian Hunter
2786cd358012SAdrian Hunter		self.setWidget(self.vbox.Widget())
2787cd358012SAdrian Hunter
2788cd358012SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2789cd358012SAdrian Hunter
2790cd358012SAdrian Hunter	def Find(self, value, direction, pattern, context):
2791cd358012SAdrian Hunter		self.view.setFocus()
2792cd358012SAdrian Hunter		self.find_bar.Busy()
2793cd358012SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
2794cd358012SAdrian Hunter
2795cd358012SAdrian Hunter	def FindDone(self, row):
2796cd358012SAdrian Hunter		self.find_bar.Idle()
2797cd358012SAdrian Hunter		if row >= 0:
2798cd358012SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2799cd358012SAdrian Hunter		else:
2800cd358012SAdrian Hunter			self.find_bar.NotFound()
2801cd358012SAdrian Hunter
28021beb5c7bSAdrian Hunter# Action Definition
28031beb5c7bSAdrian Hunter
28041beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None):
28051beb5c7bSAdrian Hunter	action = QAction(label, parent)
28061beb5c7bSAdrian Hunter	if shortcut != None:
28071beb5c7bSAdrian Hunter		action.setShortcuts(shortcut)
28081beb5c7bSAdrian Hunter	action.setStatusTip(tip)
28091beb5c7bSAdrian Hunter	action.triggered.connect(callback)
28101beb5c7bSAdrian Hunter	return action
28111beb5c7bSAdrian Hunter
28121beb5c7bSAdrian Hunter# Typical application actions
28131beb5c7bSAdrian Hunter
28141beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None):
28151beb5c7bSAdrian Hunter	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
28161beb5c7bSAdrian Hunter
28171beb5c7bSAdrian Hunter# Typical MDI actions
28181beb5c7bSAdrian Hunter
28191beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area):
28201beb5c7bSAdrian Hunter	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
28211beb5c7bSAdrian Hunter
28221beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area):
28231beb5c7bSAdrian Hunter	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
28241beb5c7bSAdrian Hunter
28251beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area):
28261beb5c7bSAdrian Hunter	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
28271beb5c7bSAdrian Hunter
28281beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area):
28291beb5c7bSAdrian Hunter	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
28301beb5c7bSAdrian Hunter
28311beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area):
28321beb5c7bSAdrian Hunter	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
28331beb5c7bSAdrian Hunter
28341beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area):
28351beb5c7bSAdrian Hunter	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
28361beb5c7bSAdrian Hunter
28371beb5c7bSAdrian Hunter# Typical MDI window menu
28381beb5c7bSAdrian Hunter
28391beb5c7bSAdrian Hunterclass WindowMenu():
28401beb5c7bSAdrian Hunter
28411beb5c7bSAdrian Hunter	def __init__(self, mdi_area, menu):
28421beb5c7bSAdrian Hunter		self.mdi_area = mdi_area
28431beb5c7bSAdrian Hunter		self.window_menu = menu.addMenu("&Windows")
28441beb5c7bSAdrian Hunter		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
28451beb5c7bSAdrian Hunter		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
28461beb5c7bSAdrian Hunter		self.tile_windows = CreateTileWindowsAction(mdi_area)
28471beb5c7bSAdrian Hunter		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
28481beb5c7bSAdrian Hunter		self.next_window = CreateNextWindowAction(mdi_area)
28491beb5c7bSAdrian Hunter		self.previous_window = CreatePreviousWindowAction(mdi_area)
28501beb5c7bSAdrian Hunter		self.window_menu.aboutToShow.connect(self.Update)
28511beb5c7bSAdrian Hunter
28521beb5c7bSAdrian Hunter	def Update(self):
28531beb5c7bSAdrian Hunter		self.window_menu.clear()
28541beb5c7bSAdrian Hunter		sub_window_count = len(self.mdi_area.subWindowList())
28551beb5c7bSAdrian Hunter		have_sub_windows = sub_window_count != 0
28561beb5c7bSAdrian Hunter		self.close_active_window.setEnabled(have_sub_windows)
28571beb5c7bSAdrian Hunter		self.close_all_windows.setEnabled(have_sub_windows)
28581beb5c7bSAdrian Hunter		self.tile_windows.setEnabled(have_sub_windows)
28591beb5c7bSAdrian Hunter		self.cascade_windows.setEnabled(have_sub_windows)
28601beb5c7bSAdrian Hunter		self.next_window.setEnabled(have_sub_windows)
28611beb5c7bSAdrian Hunter		self.previous_window.setEnabled(have_sub_windows)
28621beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_active_window)
28631beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_all_windows)
28641beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
28651beb5c7bSAdrian Hunter		self.window_menu.addAction(self.tile_windows)
28661beb5c7bSAdrian Hunter		self.window_menu.addAction(self.cascade_windows)
28671beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
28681beb5c7bSAdrian Hunter		self.window_menu.addAction(self.next_window)
28691beb5c7bSAdrian Hunter		self.window_menu.addAction(self.previous_window)
28701beb5c7bSAdrian Hunter		if sub_window_count == 0:
28711beb5c7bSAdrian Hunter			return
28721beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
28731beb5c7bSAdrian Hunter		nr = 1
28741beb5c7bSAdrian Hunter		for sub_window in self.mdi_area.subWindowList():
28751beb5c7bSAdrian Hunter			label = str(nr) + " " + sub_window.name
28761beb5c7bSAdrian Hunter			if nr < 10:
28771beb5c7bSAdrian Hunter				label = "&" + label
28781beb5c7bSAdrian Hunter			action = self.window_menu.addAction(label)
28791beb5c7bSAdrian Hunter			action.setCheckable(True)
28801beb5c7bSAdrian Hunter			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2881df8ea22aSAdrian Hunter			action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
28821beb5c7bSAdrian Hunter			self.window_menu.addAction(action)
28831beb5c7bSAdrian Hunter			nr += 1
28841beb5c7bSAdrian Hunter
28851beb5c7bSAdrian Hunter	def setActiveSubWindow(self, nr):
28861beb5c7bSAdrian Hunter		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
28871beb5c7bSAdrian Hunter
288865b24292SAdrian Hunter# Help text
288965b24292SAdrian Hunter
289065b24292SAdrian Hunterglb_help_text = """
289165b24292SAdrian Hunter<h1>Contents</h1>
289265b24292SAdrian Hunter<style>
289365b24292SAdrian Hunterp.c1 {
289465b24292SAdrian Hunter    text-indent: 40px;
289565b24292SAdrian Hunter}
289665b24292SAdrian Hunterp.c2 {
289765b24292SAdrian Hunter    text-indent: 80px;
289865b24292SAdrian Hunter}
289965b24292SAdrian Hunter}
290065b24292SAdrian Hunter</style>
290165b24292SAdrian Hunter<p class=c1><a href=#reports>1. Reports</a></p>
290265b24292SAdrian Hunter<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2903ae8b887cSAdrian Hunter<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2904ae8b887cSAdrian Hunter<p class=c2><a href=#allbranches>1.3 All branches</a></p>
2905ae8b887cSAdrian Hunter<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2906ae8b887cSAdrian Hunter<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
290765b24292SAdrian Hunter<p class=c1><a href=#tables>2. Tables</a></p>
290865b24292SAdrian Hunter<h1 id=reports>1. Reports</h1>
290965b24292SAdrian Hunter<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
291065b24292SAdrian HunterThe result is a GUI window with a tree representing a context-sensitive
291165b24292SAdrian Huntercall-graph. Expanding a couple of levels of the tree and adjusting column
291265b24292SAdrian Hunterwidths to suit will display something like:
291365b24292SAdrian Hunter<pre>
291465b24292SAdrian Hunter                                         Call Graph: pt_example
291565b24292SAdrian HunterCall Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
291665b24292SAdrian Hunterv- ls
291765b24292SAdrian Hunter    v- 2638:2638
291865b24292SAdrian Hunter        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
291965b24292SAdrian Hunter          |- unknown               unknown       1        13198     0.1              1              0.0
292065b24292SAdrian Hunter          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
292165b24292SAdrian Hunter          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
292265b24292SAdrian Hunter          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
292365b24292SAdrian Hunter             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
292465b24292SAdrian Hunter             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
292565b24292SAdrian Hunter             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
292665b24292SAdrian Hunter             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
292765b24292SAdrian Hunter             v- main               ls            1      8182043    99.6         180254             99.9
292865b24292SAdrian Hunter</pre>
292965b24292SAdrian Hunter<h3>Points to note:</h3>
293065b24292SAdrian Hunter<ul>
293165b24292SAdrian Hunter<li>The top level is a command name (comm)</li>
293265b24292SAdrian Hunter<li>The next level is a thread (pid:tid)</li>
293365b24292SAdrian Hunter<li>Subsequent levels are functions</li>
293465b24292SAdrian Hunter<li>'Count' is the number of calls</li>
293565b24292SAdrian Hunter<li>'Time' is the elapsed time until the function returns</li>
293665b24292SAdrian Hunter<li>Percentages are relative to the level above</li>
293765b24292SAdrian Hunter<li>'Branch Count' is the total number of branches for that function and all functions that it calls
293865b24292SAdrian Hunter</ul>
293965b24292SAdrian Hunter<h3>Find</h3>
294065b24292SAdrian HunterCtrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
294165b24292SAdrian HunterThe pattern matching symbols are ? for any character and * for zero or more characters.
2942ae8b887cSAdrian Hunter<h2 id=calltree>1.2 Call Tree</h2>
2943ae8b887cSAdrian HunterThe Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2944ae8b887cSAdrian HunterAlso the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2945ae8b887cSAdrian Hunter<h2 id=allbranches>1.3 All branches</h2>
294665b24292SAdrian HunterThe All branches report displays all branches in chronological order.
294765b24292SAdrian HunterNot all data is fetched immediately. More records can be fetched using the Fetch bar provided.
294865b24292SAdrian Hunter<h3>Disassembly</h3>
294965b24292SAdrian HunterOpen a branch to display disassembly. This only works if:
295065b24292SAdrian Hunter<ol>
295165b24292SAdrian Hunter<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
295265b24292SAdrian Hunter<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
295365b24292SAdrian HunterThe default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
295465b24292SAdrian HunterOne exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
295565b24292SAdrian Hunteror alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
295665b24292SAdrian Hunter</ol>
295765b24292SAdrian Hunter<h4 id=xed>Intel XED Setup</h4>
295865b24292SAdrian HunterTo use Intel XED, libxed.so must be present.  To build and install libxed.so:
295965b24292SAdrian Hunter<pre>
296065b24292SAdrian Huntergit clone https://github.com/intelxed/mbuild.git mbuild
296165b24292SAdrian Huntergit clone https://github.com/intelxed/xed
296265b24292SAdrian Huntercd xed
296365b24292SAdrian Hunter./mfile.py --share
296465b24292SAdrian Huntersudo ./mfile.py --prefix=/usr/local install
296565b24292SAdrian Huntersudo ldconfig
296665b24292SAdrian Hunter</pre>
2967530e22fdSAdrian Hunter<h3>Instructions per Cycle (IPC)</h3>
2968530e22fdSAdrian HunterIf available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
2969530e22fdSAdrian Hunter<p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
2970530e22fdSAdrian HunterDue to the granularity of timing information, the number of cycles for some code blocks will not be known.
2971530e22fdSAdrian HunterIn that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
2972530e22fdSAdrian Huntersince the previous displayed 'IPC'.
297365b24292SAdrian Hunter<h3>Find</h3>
297465b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
297565b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
297665b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
2977ae8b887cSAdrian Hunter<h2 id=selectedbranches>1.4 Selected branches</h2>
297865b24292SAdrian HunterThis is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
297965b24292SAdrian Hunterby various selection criteria. A dialog box displays available criteria which are AND'ed together.
2980ae8b887cSAdrian Hunter<h3>1.4.1 Time ranges</h3>
298165b24292SAdrian HunterThe time ranges hint text shows the total time range. Relative time ranges can also be entered in
298265b24292SAdrian Hunterms, us or ns. Also, negative values are relative to the end of trace.  Examples:
298365b24292SAdrian Hunter<pre>
298465b24292SAdrian Hunter	81073085947329-81073085958238	From 81073085947329 to 81073085958238
298565b24292SAdrian Hunter	100us-200us		From 100us to 200us
298665b24292SAdrian Hunter	10ms-			From 10ms to the end
298765b24292SAdrian Hunter	-100ns			The first 100ns
298865b24292SAdrian Hunter	-10ms-			The last 10ms
298965b24292SAdrian Hunter</pre>
299065b24292SAdrian HunterN.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2991ae8b887cSAdrian Hunter<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2992cd358012SAdrian 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.
2993cd358012SAdrian HunterThe data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2994cd358012SAdrian HunterIf not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
299565b24292SAdrian Hunter<h1 id=tables>2. Tables</h1>
299665b24292SAdrian HunterThe Tables menu shows all tables and views in the database. Most tables have an associated view
299765b24292SAdrian Hunterwhich displays the information in a more friendly way. Not all data for large tables is fetched
299865b24292SAdrian Hunterimmediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
299965b24292SAdrian Hunterbut that can be slow for large tables.
300065b24292SAdrian Hunter<p>There are also tables of database meta-information.
300165b24292SAdrian HunterFor SQLite3 databases, the sqlite_master table is included.
300265b24292SAdrian HunterFor PostgreSQL databases, information_schema.tables/views/columns are included.
300365b24292SAdrian Hunter<h3>Find</h3>
300465b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
300565b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
300665b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
300735fa1ceeSAdrian Hunter<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
300835fa1ceeSAdrian Hunterwill go to the next/previous result in id order, instead of display order.
300965b24292SAdrian Hunter"""
301065b24292SAdrian Hunter
301165b24292SAdrian Hunter# Help window
301265b24292SAdrian Hunter
301365b24292SAdrian Hunterclass HelpWindow(QMdiSubWindow):
301465b24292SAdrian Hunter
301565b24292SAdrian Hunter	def __init__(self, glb, parent=None):
301665b24292SAdrian Hunter		super(HelpWindow, self).__init__(parent)
301765b24292SAdrian Hunter
301865b24292SAdrian Hunter		self.text = QTextBrowser()
301965b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
302065b24292SAdrian Hunter		self.text.setReadOnly(True)
302165b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
302265b24292SAdrian Hunter
302365b24292SAdrian Hunter		self.setWidget(self.text)
302465b24292SAdrian Hunter
302565b24292SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
302665b24292SAdrian Hunter
302765b24292SAdrian Hunter# Main window that only displays the help text
302865b24292SAdrian Hunter
302965b24292SAdrian Hunterclass HelpOnlyWindow(QMainWindow):
303065b24292SAdrian Hunter
303165b24292SAdrian Hunter	def __init__(self, parent=None):
303265b24292SAdrian Hunter		super(HelpOnlyWindow, self).__init__(parent)
303365b24292SAdrian Hunter
303465b24292SAdrian Hunter		self.setMinimumSize(200, 100)
303565b24292SAdrian Hunter		self.resize(800, 600)
303665b24292SAdrian Hunter		self.setWindowTitle("Exported SQL Viewer Help")
303765b24292SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
303865b24292SAdrian Hunter
303965b24292SAdrian Hunter		self.text = QTextBrowser()
304065b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
304165b24292SAdrian Hunter		self.text.setReadOnly(True)
304265b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
304365b24292SAdrian Hunter
304465b24292SAdrian Hunter		self.setCentralWidget(self.text)
304565b24292SAdrian Hunter
3046b62d18abSAdrian Hunter# PostqreSQL server version
3047b62d18abSAdrian Hunter
3048b62d18abSAdrian Hunterdef PostqreSQLServerVersion(db):
3049b62d18abSAdrian Hunter	query = QSqlQuery(db)
3050b62d18abSAdrian Hunter	QueryExec(query, "SELECT VERSION()")
3051b62d18abSAdrian Hunter	if query.next():
3052b62d18abSAdrian Hunter		v_str = query.value(0)
3053b62d18abSAdrian Hunter		v_list = v_str.strip().split(" ")
3054b62d18abSAdrian Hunter		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
3055b62d18abSAdrian Hunter			return v_list[1]
3056b62d18abSAdrian Hunter		return v_str
3057b62d18abSAdrian Hunter	return "Unknown"
3058b62d18abSAdrian Hunter
3059b62d18abSAdrian Hunter# SQLite version
3060b62d18abSAdrian Hunter
3061b62d18abSAdrian Hunterdef SQLiteVersion(db):
3062b62d18abSAdrian Hunter	query = QSqlQuery(db)
3063b62d18abSAdrian Hunter	QueryExec(query, "SELECT sqlite_version()")
3064b62d18abSAdrian Hunter	if query.next():
3065b62d18abSAdrian Hunter		return query.value(0)
3066b62d18abSAdrian Hunter	return "Unknown"
3067b62d18abSAdrian Hunter
3068b62d18abSAdrian Hunter# About dialog
3069b62d18abSAdrian Hunter
3070b62d18abSAdrian Hunterclass AboutDialog(QDialog):
3071b62d18abSAdrian Hunter
3072b62d18abSAdrian Hunter	def __init__(self, glb, parent=None):
3073b62d18abSAdrian Hunter		super(AboutDialog, self).__init__(parent)
3074b62d18abSAdrian Hunter
3075b62d18abSAdrian Hunter		self.setWindowTitle("About Exported SQL Viewer")
3076b62d18abSAdrian Hunter		self.setMinimumWidth(300)
3077b62d18abSAdrian Hunter
3078b62d18abSAdrian Hunter		pyside_version = "1" if pyside_version_1 else "2"
3079b62d18abSAdrian Hunter
3080b62d18abSAdrian Hunter		text = "<pre>"
3081b62d18abSAdrian Hunter		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
3082b62d18abSAdrian Hunter		text += "PySide version:     " + pyside_version + "\n"
3083b62d18abSAdrian Hunter		text += "Qt version:         " + qVersion() + "\n"
3084b62d18abSAdrian Hunter		if glb.dbref.is_sqlite3:
3085b62d18abSAdrian Hunter			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
3086b62d18abSAdrian Hunter		else:
3087b62d18abSAdrian Hunter			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
3088b62d18abSAdrian Hunter		text += "</pre>"
3089b62d18abSAdrian Hunter
3090b62d18abSAdrian Hunter		self.text = QTextBrowser()
3091b62d18abSAdrian Hunter		self.text.setHtml(text)
3092b62d18abSAdrian Hunter		self.text.setReadOnly(True)
3093b62d18abSAdrian Hunter		self.text.setOpenExternalLinks(True)
3094b62d18abSAdrian Hunter
3095b62d18abSAdrian Hunter		self.vbox = QVBoxLayout()
3096b62d18abSAdrian Hunter		self.vbox.addWidget(self.text)
3097b62d18abSAdrian Hunter
3098b62d18abSAdrian Hunter		self.setLayout(self.vbox);
3099b62d18abSAdrian Hunter
310082f68e28SAdrian Hunter# Font resize
310182f68e28SAdrian Hunter
310282f68e28SAdrian Hunterdef ResizeFont(widget, diff):
310382f68e28SAdrian Hunter	font = widget.font()
310482f68e28SAdrian Hunter	sz = font.pointSize()
310582f68e28SAdrian Hunter	font.setPointSize(sz + diff)
310682f68e28SAdrian Hunter	widget.setFont(font)
310782f68e28SAdrian Hunter
310882f68e28SAdrian Hunterdef ShrinkFont(widget):
310982f68e28SAdrian Hunter	ResizeFont(widget, -1)
311082f68e28SAdrian Hunter
311182f68e28SAdrian Hunterdef EnlargeFont(widget):
311282f68e28SAdrian Hunter	ResizeFont(widget, 1)
311382f68e28SAdrian Hunter
31141beb5c7bSAdrian Hunter# Unique name for sub-windows
31151beb5c7bSAdrian Hunter
31161beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr):
31171beb5c7bSAdrian Hunter	if nr > 1:
31181beb5c7bSAdrian Hunter		name += " <" + str(nr) + ">"
31191beb5c7bSAdrian Hunter	return name
31201beb5c7bSAdrian Hunter
31211beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name):
31221beb5c7bSAdrian Hunter	nr = 1
31231beb5c7bSAdrian Hunter	while True:
31241beb5c7bSAdrian Hunter		unique_name = NumberedWindowName(name, nr)
31251beb5c7bSAdrian Hunter		ok = True
31261beb5c7bSAdrian Hunter		for sub_window in mdi_area.subWindowList():
31271beb5c7bSAdrian Hunter			if sub_window.name == unique_name:
31281beb5c7bSAdrian Hunter				ok = False
31291beb5c7bSAdrian Hunter				break
31301beb5c7bSAdrian Hunter		if ok:
31311beb5c7bSAdrian Hunter			return unique_name
31321beb5c7bSAdrian Hunter		nr += 1
31331beb5c7bSAdrian Hunter
31341beb5c7bSAdrian Hunter# Add a sub-window
31351beb5c7bSAdrian Hunter
31361beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name):
31371beb5c7bSAdrian Hunter	unique_name = UniqueSubWindowName(mdi_area, name)
31381beb5c7bSAdrian Hunter	sub_window.setMinimumSize(200, 100)
31391beb5c7bSAdrian Hunter	sub_window.resize(800, 600)
31401beb5c7bSAdrian Hunter	sub_window.setWindowTitle(unique_name)
31411beb5c7bSAdrian Hunter	sub_window.setAttribute(Qt.WA_DeleteOnClose)
31421beb5c7bSAdrian Hunter	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
31431beb5c7bSAdrian Hunter	sub_window.name = unique_name
31441beb5c7bSAdrian Hunter	mdi_area.addSubWindow(sub_window)
31451beb5c7bSAdrian Hunter	sub_window.show()
31461beb5c7bSAdrian Hunter
3147031c2a00SAdrian Hunter# Main window
3148031c2a00SAdrian Hunter
3149031c2a00SAdrian Hunterclass MainWindow(QMainWindow):
3150031c2a00SAdrian Hunter
3151031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
3152031c2a00SAdrian Hunter		super(MainWindow, self).__init__(parent)
3153031c2a00SAdrian Hunter
3154031c2a00SAdrian Hunter		self.glb = glb
3155031c2a00SAdrian Hunter
31561beb5c7bSAdrian Hunter		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3157031c2a00SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3158031c2a00SAdrian Hunter		self.setMinimumSize(200, 100)
3159031c2a00SAdrian Hunter
31601beb5c7bSAdrian Hunter		self.mdi_area = QMdiArea()
31611beb5c7bSAdrian Hunter		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
31621beb5c7bSAdrian Hunter		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3163031c2a00SAdrian Hunter
31641beb5c7bSAdrian Hunter		self.setCentralWidget(self.mdi_area)
3165031c2a00SAdrian Hunter
31661beb5c7bSAdrian Hunter		menu = self.menuBar()
3167031c2a00SAdrian Hunter
31681beb5c7bSAdrian Hunter		file_menu = menu.addMenu("&File")
31691beb5c7bSAdrian Hunter		file_menu.addAction(CreateExitAction(glb.app, self))
31701beb5c7bSAdrian Hunter
3171ebd70c7dSAdrian Hunter		edit_menu = menu.addMenu("&Edit")
317296c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
317396c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3174ebd70c7dSAdrian Hunter		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
31758392b74bSAdrian Hunter		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
317682f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
317782f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3178ebd70c7dSAdrian Hunter
31791beb5c7bSAdrian Hunter		reports_menu = menu.addMenu("&Reports")
3180655cb952SAdrian Hunter		if IsSelectable(glb.db, "calls"):
31811beb5c7bSAdrian Hunter			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
31821beb5c7bSAdrian Hunter
3183ae8b887cSAdrian Hunter		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3184ae8b887cSAdrian Hunter			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3185ae8b887cSAdrian Hunter
318676099f98SAdrian Hunter		self.EventMenu(GetEventList(glb.db), reports_menu)
318776099f98SAdrian Hunter
3188cd358012SAdrian Hunter		if IsSelectable(glb.db, "calls"):
3189cd358012SAdrian Hunter			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3190cd358012SAdrian Hunter
31918392b74bSAdrian Hunter		self.TableMenu(GetTableList(glb), menu)
31928392b74bSAdrian Hunter
31931beb5c7bSAdrian Hunter		self.window_menu = WindowMenu(self.mdi_area, menu)
31941beb5c7bSAdrian Hunter
319565b24292SAdrian Hunter		help_menu = menu.addMenu("&Help")
319665b24292SAdrian Hunter		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3197b62d18abSAdrian Hunter		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
319865b24292SAdrian Hunter
31994b208453SAdrian Hunter	def Try(self, fn):
32004b208453SAdrian Hunter		win = self.mdi_area.activeSubWindow()
32014b208453SAdrian Hunter		if win:
32024b208453SAdrian Hunter			try:
32034b208453SAdrian Hunter				fn(win.view)
32044b208453SAdrian Hunter			except:
32054b208453SAdrian Hunter				pass
32064b208453SAdrian Hunter
320796c43b9aSAdrian Hunter	def CopyToClipboard(self):
320896c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardHdr)
320996c43b9aSAdrian Hunter
321096c43b9aSAdrian Hunter	def CopyToClipboardCSV(self):
321196c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardCSV)
321296c43b9aSAdrian Hunter
3213ebd70c7dSAdrian Hunter	def Find(self):
3214ebd70c7dSAdrian Hunter		win = self.mdi_area.activeSubWindow()
3215ebd70c7dSAdrian Hunter		if win:
3216ebd70c7dSAdrian Hunter			try:
3217ebd70c7dSAdrian Hunter				win.find_bar.Activate()
3218ebd70c7dSAdrian Hunter			except:
3219ebd70c7dSAdrian Hunter				pass
3220ebd70c7dSAdrian Hunter
32218392b74bSAdrian Hunter	def FetchMoreRecords(self):
32228392b74bSAdrian Hunter		win = self.mdi_area.activeSubWindow()
32238392b74bSAdrian Hunter		if win:
32248392b74bSAdrian Hunter			try:
32258392b74bSAdrian Hunter				win.fetch_bar.Activate()
32268392b74bSAdrian Hunter			except:
32278392b74bSAdrian Hunter				pass
32288392b74bSAdrian Hunter
322982f68e28SAdrian Hunter	def ShrinkFont(self):
32304b208453SAdrian Hunter		self.Try(ShrinkFont)
323182f68e28SAdrian Hunter
323282f68e28SAdrian Hunter	def EnlargeFont(self):
32334b208453SAdrian Hunter		self.Try(EnlargeFont)
323482f68e28SAdrian Hunter
323576099f98SAdrian Hunter	def EventMenu(self, events, reports_menu):
323676099f98SAdrian Hunter		branches_events = 0
323776099f98SAdrian Hunter		for event in events:
323876099f98SAdrian Hunter			event = event.split(":")[0]
323976099f98SAdrian Hunter			if event == "branches":
324076099f98SAdrian Hunter				branches_events += 1
324176099f98SAdrian Hunter		dbid = 0
324276099f98SAdrian Hunter		for event in events:
324376099f98SAdrian Hunter			dbid += 1
324476099f98SAdrian Hunter			event = event.split(":")[0]
324576099f98SAdrian Hunter			if event == "branches":
324676099f98SAdrian Hunter				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3247df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
3248210cf1f9SAdrian Hunter				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3249df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
325076099f98SAdrian Hunter
32518392b74bSAdrian Hunter	def TableMenu(self, tables, menu):
32528392b74bSAdrian Hunter		table_menu = menu.addMenu("&Tables")
32538392b74bSAdrian Hunter		for table in tables:
3254df8ea22aSAdrian Hunter			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
32558392b74bSAdrian Hunter
32561beb5c7bSAdrian Hunter	def NewCallGraph(self):
32571beb5c7bSAdrian Hunter		CallGraphWindow(self.glb, self)
3258031c2a00SAdrian Hunter
3259ae8b887cSAdrian Hunter	def NewCallTree(self):
3260ae8b887cSAdrian Hunter		CallTreeWindow(self.glb, self)
3261ae8b887cSAdrian Hunter
3262cd358012SAdrian Hunter	def NewTopCalls(self):
3263cd358012SAdrian Hunter		dialog = TopCallsDialog(self.glb, self)
3264cd358012SAdrian Hunter		ret = dialog.exec_()
3265cd358012SAdrian Hunter		if ret:
3266cd358012SAdrian Hunter			TopCallsWindow(self.glb, dialog.report_vars, self)
3267cd358012SAdrian Hunter
326876099f98SAdrian Hunter	def NewBranchView(self, event_id):
3269947cc38dSAdrian Hunter		BranchWindow(self.glb, event_id, ReportVars(), self)
327076099f98SAdrian Hunter
3271210cf1f9SAdrian Hunter	def NewSelectedBranchView(self, event_id):
3272210cf1f9SAdrian Hunter		dialog = SelectedBranchDialog(self.glb, self)
3273210cf1f9SAdrian Hunter		ret = dialog.exec_()
3274210cf1f9SAdrian Hunter		if ret:
3275947cc38dSAdrian Hunter			BranchWindow(self.glb, event_id, dialog.report_vars, self)
3276210cf1f9SAdrian Hunter
32778392b74bSAdrian Hunter	def NewTableView(self, table_name):
32788392b74bSAdrian Hunter		TableWindow(self.glb, table_name, self)
32798392b74bSAdrian Hunter
328065b24292SAdrian Hunter	def Help(self):
328165b24292SAdrian Hunter		HelpWindow(self.glb, self)
328265b24292SAdrian Hunter
3283b62d18abSAdrian Hunter	def About(self):
3284b62d18abSAdrian Hunter		dialog = AboutDialog(self.glb, self)
3285b62d18abSAdrian Hunter		dialog.exec_()
3286b62d18abSAdrian Hunter
328776099f98SAdrian Hunter# XED Disassembler
328876099f98SAdrian Hunter
328976099f98SAdrian Hunterclass xed_state_t(Structure):
329076099f98SAdrian Hunter
329176099f98SAdrian Hunter	_fields_ = [
329276099f98SAdrian Hunter		("mode", c_int),
329376099f98SAdrian Hunter		("width", c_int)
329476099f98SAdrian Hunter	]
329576099f98SAdrian Hunter
329676099f98SAdrian Hunterclass XEDInstruction():
329776099f98SAdrian Hunter
329876099f98SAdrian Hunter	def __init__(self, libxed):
329976099f98SAdrian Hunter		# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
330076099f98SAdrian Hunter		xedd_t = c_byte * 512
330176099f98SAdrian Hunter		self.xedd = xedd_t()
330276099f98SAdrian Hunter		self.xedp = addressof(self.xedd)
330376099f98SAdrian Hunter		libxed.xed_decoded_inst_zero(self.xedp)
330476099f98SAdrian Hunter		self.state = xed_state_t()
330576099f98SAdrian Hunter		self.statep = addressof(self.state)
330676099f98SAdrian Hunter		# Buffer for disassembled instruction text
330776099f98SAdrian Hunter		self.buffer = create_string_buffer(256)
330876099f98SAdrian Hunter		self.bufferp = addressof(self.buffer)
330976099f98SAdrian Hunter
331076099f98SAdrian Hunterclass LibXED():
331176099f98SAdrian Hunter
331276099f98SAdrian Hunter	def __init__(self):
33135ed4419dSAdrian Hunter		try:
331476099f98SAdrian Hunter			self.libxed = CDLL("libxed.so")
33155ed4419dSAdrian Hunter		except:
33165ed4419dSAdrian Hunter			self.libxed = None
33175ed4419dSAdrian Hunter		if not self.libxed:
33185ed4419dSAdrian Hunter			self.libxed = CDLL("/usr/local/lib/libxed.so")
331976099f98SAdrian Hunter
332076099f98SAdrian Hunter		self.xed_tables_init = self.libxed.xed_tables_init
332176099f98SAdrian Hunter		self.xed_tables_init.restype = None
332276099f98SAdrian Hunter		self.xed_tables_init.argtypes = []
332376099f98SAdrian Hunter
332476099f98SAdrian Hunter		self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
332576099f98SAdrian Hunter		self.xed_decoded_inst_zero.restype = None
332676099f98SAdrian Hunter		self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
332776099f98SAdrian Hunter
332876099f98SAdrian Hunter		self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
332976099f98SAdrian Hunter		self.xed_operand_values_set_mode.restype = None
333076099f98SAdrian Hunter		self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
333176099f98SAdrian Hunter
333276099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
333376099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.restype = None
333476099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
333576099f98SAdrian Hunter
333676099f98SAdrian Hunter		self.xed_decode = self.libxed.xed_decode
333776099f98SAdrian Hunter		self.xed_decode.restype = c_int
333876099f98SAdrian Hunter		self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
333976099f98SAdrian Hunter
334076099f98SAdrian Hunter		self.xed_format_context = self.libxed.xed_format_context
334176099f98SAdrian Hunter		self.xed_format_context.restype = c_uint
334276099f98SAdrian Hunter		self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
334376099f98SAdrian Hunter
334476099f98SAdrian Hunter		self.xed_tables_init()
334576099f98SAdrian Hunter
334676099f98SAdrian Hunter	def Instruction(self):
334776099f98SAdrian Hunter		return XEDInstruction(self)
334876099f98SAdrian Hunter
334976099f98SAdrian Hunter	def SetMode(self, inst, mode):
335076099f98SAdrian Hunter		if mode:
335176099f98SAdrian Hunter			inst.state.mode = 4 # 32-bit
335276099f98SAdrian Hunter			inst.state.width = 4 # 4 bytes
335376099f98SAdrian Hunter		else:
335476099f98SAdrian Hunter			inst.state.mode = 1 # 64-bit
335576099f98SAdrian Hunter			inst.state.width = 8 # 8 bytes
335676099f98SAdrian Hunter		self.xed_operand_values_set_mode(inst.xedp, inst.statep)
335776099f98SAdrian Hunter
335876099f98SAdrian Hunter	def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
335976099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode(inst.xedp)
336076099f98SAdrian Hunter		err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
336176099f98SAdrian Hunter		if err:
336276099f98SAdrian Hunter			return 0, ""
336376099f98SAdrian Hunter		# Use AT&T mode (2), alternative is Intel (3)
336476099f98SAdrian Hunter		ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
336576099f98SAdrian Hunter		if not ok:
336676099f98SAdrian Hunter			return 0, ""
3367606bd60aSAdrian Hunter		if sys.version_info[0] == 2:
3368606bd60aSAdrian Hunter			result = inst.buffer.value
3369606bd60aSAdrian Hunter		else:
3370606bd60aSAdrian Hunter			result = inst.buffer.value.decode()
337176099f98SAdrian Hunter		# Return instruction length and the disassembled instruction text
337276099f98SAdrian Hunter		# For now, assume the length is in byte 166
3373606bd60aSAdrian Hunter		return inst.xedd[166], result
337476099f98SAdrian Hunter
337576099f98SAdrian Hunterdef TryOpen(file_name):
337676099f98SAdrian Hunter	try:
337776099f98SAdrian Hunter		return open(file_name, "rb")
337876099f98SAdrian Hunter	except:
337976099f98SAdrian Hunter		return None
338076099f98SAdrian Hunter
338176099f98SAdrian Hunterdef Is64Bit(f):
338276099f98SAdrian Hunter	result = sizeof(c_void_p)
338376099f98SAdrian Hunter	# ELF support only
338476099f98SAdrian Hunter	pos = f.tell()
338576099f98SAdrian Hunter	f.seek(0)
338676099f98SAdrian Hunter	header = f.read(7)
338776099f98SAdrian Hunter	f.seek(pos)
338876099f98SAdrian Hunter	magic = header[0:4]
3389606bd60aSAdrian Hunter	if sys.version_info[0] == 2:
339076099f98SAdrian Hunter		eclass = ord(header[4])
339176099f98SAdrian Hunter		encoding = ord(header[5])
339276099f98SAdrian Hunter		version = ord(header[6])
3393606bd60aSAdrian Hunter	else:
3394606bd60aSAdrian Hunter		eclass = header[4]
3395606bd60aSAdrian Hunter		encoding = header[5]
3396606bd60aSAdrian Hunter		version = header[6]
339776099f98SAdrian Hunter	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
339876099f98SAdrian Hunter		result = True if eclass == 2 else False
339976099f98SAdrian Hunter	return result
340076099f98SAdrian Hunter
3401031c2a00SAdrian Hunter# Global data
3402031c2a00SAdrian Hunter
3403031c2a00SAdrian Hunterclass Glb():
3404031c2a00SAdrian Hunter
3405031c2a00SAdrian Hunter	def __init__(self, dbref, db, dbname):
3406031c2a00SAdrian Hunter		self.dbref = dbref
3407031c2a00SAdrian Hunter		self.db = db
3408031c2a00SAdrian Hunter		self.dbname = dbname
340976099f98SAdrian Hunter		self.home_dir = os.path.expanduser("~")
341076099f98SAdrian Hunter		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
341176099f98SAdrian Hunter		if self.buildid_dir:
341276099f98SAdrian Hunter			self.buildid_dir += "/.build-id/"
341376099f98SAdrian Hunter		else:
341476099f98SAdrian Hunter			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3415031c2a00SAdrian Hunter		self.app = None
3416031c2a00SAdrian Hunter		self.mainwindow = None
34178392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit = weakref.WeakSet()
341876099f98SAdrian Hunter		try:
341976099f98SAdrian Hunter			self.disassembler = LibXED()
342076099f98SAdrian Hunter			self.have_disassembler = True
342176099f98SAdrian Hunter		except:
342276099f98SAdrian Hunter			self.have_disassembler = False
342376099f98SAdrian Hunter
342476099f98SAdrian Hunter	def FileFromBuildId(self, build_id):
342576099f98SAdrian Hunter		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
342676099f98SAdrian Hunter		return TryOpen(file_name)
342776099f98SAdrian Hunter
342876099f98SAdrian Hunter	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
342976099f98SAdrian Hunter		# Assume current machine i.e. no support for virtualization
343076099f98SAdrian Hunter		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
343176099f98SAdrian Hunter			file_name = os.getenv("PERF_KCORE")
343276099f98SAdrian Hunter			f = TryOpen(file_name) if file_name else None
343376099f98SAdrian Hunter			if f:
343476099f98SAdrian Hunter				return f
343576099f98SAdrian Hunter			# For now, no special handling if long_name is /proc/kcore
343676099f98SAdrian Hunter			f = TryOpen(long_name)
343776099f98SAdrian Hunter			if f:
343876099f98SAdrian Hunter				return f
343976099f98SAdrian Hunter		f = self.FileFromBuildId(build_id)
344076099f98SAdrian Hunter		if f:
344176099f98SAdrian Hunter			return f
344276099f98SAdrian Hunter		return None
34438392b74bSAdrian Hunter
34448392b74bSAdrian Hunter	def AddInstanceToShutdownOnExit(self, instance):
34458392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit.add(instance)
34468392b74bSAdrian Hunter
34478392b74bSAdrian Hunter	# Shutdown any background processes or threads
34488392b74bSAdrian Hunter	def ShutdownInstances(self):
34498392b74bSAdrian Hunter		for x in self.instances_to_shutdown_on_exit:
34508392b74bSAdrian Hunter			try:
34518392b74bSAdrian Hunter				x.Shutdown()
34528392b74bSAdrian Hunter			except:
34538392b74bSAdrian Hunter				pass
3454031c2a00SAdrian Hunter
3455031c2a00SAdrian Hunter# Database reference
3456031c2a00SAdrian Hunter
3457031c2a00SAdrian Hunterclass DBRef():
3458031c2a00SAdrian Hunter
3459031c2a00SAdrian Hunter	def __init__(self, is_sqlite3, dbname):
3460031c2a00SAdrian Hunter		self.is_sqlite3 = is_sqlite3
3461031c2a00SAdrian Hunter		self.dbname = dbname
3462031c2a00SAdrian Hunter
3463031c2a00SAdrian Hunter	def Open(self, connection_name):
3464031c2a00SAdrian Hunter		dbname = self.dbname
3465031c2a00SAdrian Hunter		if self.is_sqlite3:
3466031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3467031c2a00SAdrian Hunter		else:
3468031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3469031c2a00SAdrian Hunter			opts = dbname.split()
3470031c2a00SAdrian Hunter			for opt in opts:
3471031c2a00SAdrian Hunter				if "=" in opt:
3472031c2a00SAdrian Hunter					opt = opt.split("=")
3473031c2a00SAdrian Hunter					if opt[0] == "hostname":
3474031c2a00SAdrian Hunter						db.setHostName(opt[1])
3475031c2a00SAdrian Hunter					elif opt[0] == "port":
3476031c2a00SAdrian Hunter						db.setPort(int(opt[1]))
3477031c2a00SAdrian Hunter					elif opt[0] == "username":
3478031c2a00SAdrian Hunter						db.setUserName(opt[1])
3479031c2a00SAdrian Hunter					elif opt[0] == "password":
3480031c2a00SAdrian Hunter						db.setPassword(opt[1])
3481031c2a00SAdrian Hunter					elif opt[0] == "dbname":
3482031c2a00SAdrian Hunter						dbname = opt[1]
3483031c2a00SAdrian Hunter				else:
3484031c2a00SAdrian Hunter					dbname = opt
3485031c2a00SAdrian Hunter
3486031c2a00SAdrian Hunter		db.setDatabaseName(dbname)
3487031c2a00SAdrian Hunter		if not db.open():
3488031c2a00SAdrian Hunter			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3489031c2a00SAdrian Hunter		return db, dbname
3490031c2a00SAdrian Hunter
3491031c2a00SAdrian Hunter# Main
3492031c2a00SAdrian Hunter
3493031c2a00SAdrian Hunterdef Main():
34941ed7f47fSAdrian Hunter	usage_str =	"exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
34951ed7f47fSAdrian Hunter			"   or: exported-sql-viewer.py --help-only"
34961ed7f47fSAdrian Hunter	ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
3497df8ea22aSAdrian Hunter	ap.add_argument("--pyside-version-1", action='store_true')
34981ed7f47fSAdrian Hunter	ap.add_argument("dbname", nargs="?")
34991ed7f47fSAdrian Hunter	ap.add_argument("--help-only", action='store_true')
35001ed7f47fSAdrian Hunter	args = ap.parse_args()
3501031c2a00SAdrian Hunter
35021ed7f47fSAdrian Hunter	if args.help_only:
350365b24292SAdrian Hunter		app = QApplication(sys.argv)
350465b24292SAdrian Hunter		mainwindow = HelpOnlyWindow()
350565b24292SAdrian Hunter		mainwindow.show()
350665b24292SAdrian Hunter		err = app.exec_()
350765b24292SAdrian Hunter		sys.exit(err)
3508031c2a00SAdrian Hunter
35091ed7f47fSAdrian Hunter	dbname = args.dbname
35101ed7f47fSAdrian Hunter	if dbname is None:
35111ed7f47fSAdrian Hunter		ap.print_usage()
35121ed7f47fSAdrian Hunter		print("Too few arguments")
35131ed7f47fSAdrian Hunter		sys.exit(1)
35141ed7f47fSAdrian Hunter
3515031c2a00SAdrian Hunter	is_sqlite3 = False
3516031c2a00SAdrian Hunter	try:
3517beda0e72STony Jones		f = open(dbname, "rb")
3518beda0e72STony Jones		if f.read(15) == b'SQLite format 3':
3519031c2a00SAdrian Hunter			is_sqlite3 = True
3520031c2a00SAdrian Hunter		f.close()
3521031c2a00SAdrian Hunter	except:
3522031c2a00SAdrian Hunter		pass
3523031c2a00SAdrian Hunter
3524031c2a00SAdrian Hunter	dbref = DBRef(is_sqlite3, dbname)
3525031c2a00SAdrian Hunter	db, dbname = dbref.Open("main")
3526031c2a00SAdrian Hunter	glb = Glb(dbref, db, dbname)
3527031c2a00SAdrian Hunter	app = QApplication(sys.argv)
3528031c2a00SAdrian Hunter	glb.app = app
3529031c2a00SAdrian Hunter	mainwindow = MainWindow(glb)
3530031c2a00SAdrian Hunter	glb.mainwindow = mainwindow
3531031c2a00SAdrian Hunter	mainwindow.show()
3532031c2a00SAdrian Hunter	err = app.exec_()
35338392b74bSAdrian Hunter	glb.ShutdownInstances()
3534031c2a00SAdrian Hunter	db.close()
3535031c2a00SAdrian Hunter	sys.exit(err)
3536031c2a00SAdrian Hunter
3537031c2a00SAdrian Hunterif __name__ == "__main__":
3538031c2a00SAdrian Hunter	Main()
3539