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
108*b3700f21SAdrian Hunterimport random
109*b3700f21SAdrian Hunterimport copy
110*b3700f21SAdrian Hunterimport math
111df8ea22aSAdrian Hunter
112df8ea22aSAdrian Hunterpyside_version_1 = True
113df8ea22aSAdrian Hunterif not "--pyside-version-1" in sys.argv:
114df8ea22aSAdrian Hunter	try:
115df8ea22aSAdrian Hunter		from PySide2.QtCore import *
116df8ea22aSAdrian Hunter		from PySide2.QtGui import *
117df8ea22aSAdrian Hunter		from PySide2.QtSql import *
118df8ea22aSAdrian Hunter		from PySide2.QtWidgets import *
119df8ea22aSAdrian Hunter		pyside_version_1 = False
120df8ea22aSAdrian Hunter	except:
121df8ea22aSAdrian Hunter		pass
122df8ea22aSAdrian Hunter
123df8ea22aSAdrian Hunterif pyside_version_1:
124031c2a00SAdrian Hunter	from PySide.QtCore import *
125031c2a00SAdrian Hunter	from PySide.QtGui import *
126031c2a00SAdrian Hunter	from PySide.QtSql import *
127df8ea22aSAdrian Hunter
128031c2a00SAdrian Hunterfrom decimal import *
1298392b74bSAdrian Hunterfrom ctypes import *
1308392b74bSAdrian Hunterfrom multiprocessing import Process, Array, Value, Event
131031c2a00SAdrian Hunter
132beda0e72STony Jones# xrange is range in Python3
133beda0e72STony Jonestry:
134beda0e72STony Jones	xrange
135beda0e72STony Jonesexcept NameError:
136beda0e72STony Jones	xrange = range
137beda0e72STony Jones
138beda0e72STony Jonesdef printerr(*args, **keyword_args):
139beda0e72STony Jones	print(*args, file=sys.stderr, **keyword_args)
140beda0e72STony Jones
141031c2a00SAdrian Hunter# Data formatting helpers
142031c2a00SAdrian Hunter
14376099f98SAdrian Hunterdef tohex(ip):
14476099f98SAdrian Hunter	if ip < 0:
14576099f98SAdrian Hunter		ip += 1 << 64
14676099f98SAdrian Hunter	return "%x" % ip
14776099f98SAdrian Hunter
14876099f98SAdrian Hunterdef offstr(offset):
14976099f98SAdrian Hunter	if offset:
15076099f98SAdrian Hunter		return "+0x%x" % offset
15176099f98SAdrian Hunter	return ""
15276099f98SAdrian Hunter
153031c2a00SAdrian Hunterdef dsoname(name):
154031c2a00SAdrian Hunter	if name == "[kernel.kallsyms]":
155031c2a00SAdrian Hunter		return "[kernel]"
156031c2a00SAdrian Hunter	return name
157031c2a00SAdrian Hunter
158210cf1f9SAdrian Hunterdef findnth(s, sub, n, offs=0):
159210cf1f9SAdrian Hunter	pos = s.find(sub)
160210cf1f9SAdrian Hunter	if pos < 0:
161210cf1f9SAdrian Hunter		return pos
162210cf1f9SAdrian Hunter	if n <= 1:
163210cf1f9SAdrian Hunter		return offs + pos
164210cf1f9SAdrian Hunter	return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
165210cf1f9SAdrian Hunter
166031c2a00SAdrian Hunter# Percent to one decimal place
167031c2a00SAdrian Hunter
168031c2a00SAdrian Hunterdef PercentToOneDP(n, d):
169031c2a00SAdrian Hunter	if not d:
170031c2a00SAdrian Hunter		return "0.0"
171031c2a00SAdrian Hunter	x = (n * Decimal(100)) / d
172031c2a00SAdrian Hunter	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
173031c2a00SAdrian Hunter
174031c2a00SAdrian Hunter# Helper for queries that must not fail
175031c2a00SAdrian Hunter
176031c2a00SAdrian Hunterdef QueryExec(query, stmt):
177031c2a00SAdrian Hunter	ret = query.exec_(stmt)
178031c2a00SAdrian Hunter	if not ret:
179031c2a00SAdrian Hunter		raise Exception("Query failed: " + query.lastError().text())
180031c2a00SAdrian Hunter
181ebd70c7dSAdrian Hunter# Background thread
182ebd70c7dSAdrian Hunter
183ebd70c7dSAdrian Hunterclass Thread(QThread):
184ebd70c7dSAdrian Hunter
185ebd70c7dSAdrian Hunter	done = Signal(object)
186ebd70c7dSAdrian Hunter
187ebd70c7dSAdrian Hunter	def __init__(self, task, param=None, parent=None):
188ebd70c7dSAdrian Hunter		super(Thread, self).__init__(parent)
189ebd70c7dSAdrian Hunter		self.task = task
190ebd70c7dSAdrian Hunter		self.param = param
191ebd70c7dSAdrian Hunter
192ebd70c7dSAdrian Hunter	def run(self):
193ebd70c7dSAdrian Hunter		while True:
194ebd70c7dSAdrian Hunter			if self.param is None:
195ebd70c7dSAdrian Hunter				done, result = self.task()
196ebd70c7dSAdrian Hunter			else:
197ebd70c7dSAdrian Hunter				done, result = self.task(self.param)
198ebd70c7dSAdrian Hunter			self.done.emit(result)
199ebd70c7dSAdrian Hunter			if done:
200ebd70c7dSAdrian Hunter				break
201ebd70c7dSAdrian Hunter
202031c2a00SAdrian Hunter# Tree data model
203031c2a00SAdrian Hunter
204031c2a00SAdrian Hunterclass TreeModel(QAbstractItemModel):
205031c2a00SAdrian Hunter
2064a0979d4SAdrian Hunter	def __init__(self, glb, params, parent=None):
207031c2a00SAdrian Hunter		super(TreeModel, self).__init__(parent)
208a448ba23SAdrian Hunter		self.glb = glb
2094a0979d4SAdrian Hunter		self.params = params
210a448ba23SAdrian Hunter		self.root = self.GetRoot()
211031c2a00SAdrian Hunter		self.last_row_read = 0
212031c2a00SAdrian Hunter
213031c2a00SAdrian Hunter	def Item(self, parent):
214031c2a00SAdrian Hunter		if parent.isValid():
215031c2a00SAdrian Hunter			return parent.internalPointer()
216031c2a00SAdrian Hunter		else:
217031c2a00SAdrian Hunter			return self.root
218031c2a00SAdrian Hunter
219031c2a00SAdrian Hunter	def rowCount(self, parent):
220031c2a00SAdrian Hunter		result = self.Item(parent).childCount()
221031c2a00SAdrian Hunter		if result < 0:
222031c2a00SAdrian Hunter			result = 0
223031c2a00SAdrian Hunter			self.dataChanged.emit(parent, parent)
224031c2a00SAdrian Hunter		return result
225031c2a00SAdrian Hunter
226031c2a00SAdrian Hunter	def hasChildren(self, parent):
227031c2a00SAdrian Hunter		return self.Item(parent).hasChildren()
228031c2a00SAdrian Hunter
229031c2a00SAdrian Hunter	def headerData(self, section, orientation, role):
230031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
231031c2a00SAdrian Hunter			return self.columnAlignment(section)
232031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
233031c2a00SAdrian Hunter			return None
234031c2a00SAdrian Hunter		if orientation != Qt.Horizontal:
235031c2a00SAdrian Hunter			return None
236031c2a00SAdrian Hunter		return self.columnHeader(section)
237031c2a00SAdrian Hunter
238031c2a00SAdrian Hunter	def parent(self, child):
239031c2a00SAdrian Hunter		child_item = child.internalPointer()
240031c2a00SAdrian Hunter		if child_item is self.root:
241031c2a00SAdrian Hunter			return QModelIndex()
242031c2a00SAdrian Hunter		parent_item = child_item.getParentItem()
243031c2a00SAdrian Hunter		return self.createIndex(parent_item.getRow(), 0, parent_item)
244031c2a00SAdrian Hunter
245031c2a00SAdrian Hunter	def index(self, row, column, parent):
246031c2a00SAdrian Hunter		child_item = self.Item(parent).getChildItem(row)
247031c2a00SAdrian Hunter		return self.createIndex(row, column, child_item)
248031c2a00SAdrian Hunter
249031c2a00SAdrian Hunter	def DisplayData(self, item, index):
250031c2a00SAdrian Hunter		return item.getData(index.column())
251031c2a00SAdrian Hunter
2528392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2538392b74bSAdrian Hunter		if row > self.last_row_read:
2548392b74bSAdrian Hunter			self.last_row_read = row
2558392b74bSAdrian Hunter			if row + 10 >= self.root.child_count:
2568392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2578392b74bSAdrian Hunter
2588392b74bSAdrian Hunter	def columnAlignment(self, column):
2598392b74bSAdrian Hunter		return Qt.AlignLeft
2608392b74bSAdrian Hunter
2618392b74bSAdrian Hunter	def columnFont(self, column):
2628392b74bSAdrian Hunter		return None
2638392b74bSAdrian Hunter
2648392b74bSAdrian Hunter	def data(self, index, role):
2658392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2668392b74bSAdrian Hunter			return self.columnAlignment(index.column())
2678392b74bSAdrian Hunter		if role == Qt.FontRole:
2688392b74bSAdrian Hunter			return self.columnFont(index.column())
2698392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2708392b74bSAdrian Hunter			return None
2718392b74bSAdrian Hunter		item = index.internalPointer()
2728392b74bSAdrian Hunter		return self.DisplayData(item, index)
2738392b74bSAdrian Hunter
2748392b74bSAdrian Hunter# Table data model
2758392b74bSAdrian Hunter
2768392b74bSAdrian Hunterclass TableModel(QAbstractTableModel):
2778392b74bSAdrian Hunter
2788392b74bSAdrian Hunter	def __init__(self, parent=None):
2798392b74bSAdrian Hunter		super(TableModel, self).__init__(parent)
2808392b74bSAdrian Hunter		self.child_count = 0
2818392b74bSAdrian Hunter		self.child_items = []
2828392b74bSAdrian Hunter		self.last_row_read = 0
2838392b74bSAdrian Hunter
2848392b74bSAdrian Hunter	def Item(self, parent):
2858392b74bSAdrian Hunter		if parent.isValid():
2868392b74bSAdrian Hunter			return parent.internalPointer()
2878392b74bSAdrian Hunter		else:
2888392b74bSAdrian Hunter			return self
2898392b74bSAdrian Hunter
2908392b74bSAdrian Hunter	def rowCount(self, parent):
2918392b74bSAdrian Hunter		return self.child_count
2928392b74bSAdrian Hunter
2938392b74bSAdrian Hunter	def headerData(self, section, orientation, role):
2948392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2958392b74bSAdrian Hunter			return self.columnAlignment(section)
2968392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2978392b74bSAdrian Hunter			return None
2988392b74bSAdrian Hunter		if orientation != Qt.Horizontal:
2998392b74bSAdrian Hunter			return None
3008392b74bSAdrian Hunter		return self.columnHeader(section)
3018392b74bSAdrian Hunter
3028392b74bSAdrian Hunter	def index(self, row, column, parent):
3038392b74bSAdrian Hunter		return self.createIndex(row, column, self.child_items[row])
3048392b74bSAdrian Hunter
3058392b74bSAdrian Hunter	def DisplayData(self, item, index):
3068392b74bSAdrian Hunter		return item.getData(index.column())
3078392b74bSAdrian Hunter
3088392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
3098392b74bSAdrian Hunter		if row > self.last_row_read:
3108392b74bSAdrian Hunter			self.last_row_read = row
3118392b74bSAdrian Hunter			if row + 10 >= self.child_count:
3128392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
3138392b74bSAdrian Hunter
314031c2a00SAdrian Hunter	def columnAlignment(self, column):
315031c2a00SAdrian Hunter		return Qt.AlignLeft
316031c2a00SAdrian Hunter
317031c2a00SAdrian Hunter	def columnFont(self, column):
318031c2a00SAdrian Hunter		return None
319031c2a00SAdrian Hunter
320031c2a00SAdrian Hunter	def data(self, index, role):
321031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
322031c2a00SAdrian Hunter			return self.columnAlignment(index.column())
323031c2a00SAdrian Hunter		if role == Qt.FontRole:
324031c2a00SAdrian Hunter			return self.columnFont(index.column())
325031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
326031c2a00SAdrian Hunter			return None
327031c2a00SAdrian Hunter		item = index.internalPointer()
328031c2a00SAdrian Hunter		return self.DisplayData(item, index)
329031c2a00SAdrian Hunter
3301beb5c7bSAdrian Hunter# Model cache
3311beb5c7bSAdrian Hunter
3321beb5c7bSAdrian Huntermodel_cache = weakref.WeakValueDictionary()
3331beb5c7bSAdrian Huntermodel_cache_lock = threading.Lock()
3341beb5c7bSAdrian Hunter
3351beb5c7bSAdrian Hunterdef LookupCreateModel(model_name, create_fn):
3361beb5c7bSAdrian Hunter	model_cache_lock.acquire()
3371beb5c7bSAdrian Hunter	try:
3381beb5c7bSAdrian Hunter		model = model_cache[model_name]
3391beb5c7bSAdrian Hunter	except:
3401beb5c7bSAdrian Hunter		model = None
3411beb5c7bSAdrian Hunter	if model is None:
3421beb5c7bSAdrian Hunter		model = create_fn()
3431beb5c7bSAdrian Hunter		model_cache[model_name] = model
3441beb5c7bSAdrian Hunter	model_cache_lock.release()
3451beb5c7bSAdrian Hunter	return model
3461beb5c7bSAdrian Hunter
347181ea40aSAdrian Hunterdef LookupModel(model_name):
348181ea40aSAdrian Hunter	model_cache_lock.acquire()
349181ea40aSAdrian Hunter	try:
350181ea40aSAdrian Hunter		model = model_cache[model_name]
351181ea40aSAdrian Hunter	except:
352181ea40aSAdrian Hunter		model = None
353181ea40aSAdrian Hunter	model_cache_lock.release()
354181ea40aSAdrian Hunter	return model
355181ea40aSAdrian Hunter
356ebd70c7dSAdrian Hunter# Find bar
357ebd70c7dSAdrian Hunter
358ebd70c7dSAdrian Hunterclass FindBar():
359ebd70c7dSAdrian Hunter
360ebd70c7dSAdrian Hunter	def __init__(self, parent, finder, is_reg_expr=False):
361ebd70c7dSAdrian Hunter		self.finder = finder
362ebd70c7dSAdrian Hunter		self.context = []
363ebd70c7dSAdrian Hunter		self.last_value = None
364ebd70c7dSAdrian Hunter		self.last_pattern = None
365ebd70c7dSAdrian Hunter
366ebd70c7dSAdrian Hunter		label = QLabel("Find:")
367ebd70c7dSAdrian Hunter		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
368ebd70c7dSAdrian Hunter
369ebd70c7dSAdrian Hunter		self.textbox = QComboBox()
370ebd70c7dSAdrian Hunter		self.textbox.setEditable(True)
371ebd70c7dSAdrian Hunter		self.textbox.currentIndexChanged.connect(self.ValueChanged)
372ebd70c7dSAdrian Hunter
373ebd70c7dSAdrian Hunter		self.progress = QProgressBar()
374ebd70c7dSAdrian Hunter		self.progress.setRange(0, 0)
375ebd70c7dSAdrian Hunter		self.progress.hide()
376ebd70c7dSAdrian Hunter
377ebd70c7dSAdrian Hunter		if is_reg_expr:
378ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Regular Expression")
379ebd70c7dSAdrian Hunter		else:
380ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Pattern")
381ebd70c7dSAdrian Hunter		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
382ebd70c7dSAdrian Hunter
383ebd70c7dSAdrian Hunter		self.next_button = QToolButton()
384ebd70c7dSAdrian Hunter		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
385ebd70c7dSAdrian Hunter		self.next_button.released.connect(lambda: self.NextPrev(1))
386ebd70c7dSAdrian Hunter
387ebd70c7dSAdrian Hunter		self.prev_button = QToolButton()
388ebd70c7dSAdrian Hunter		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
389ebd70c7dSAdrian Hunter		self.prev_button.released.connect(lambda: self.NextPrev(-1))
390ebd70c7dSAdrian Hunter
391ebd70c7dSAdrian Hunter		self.close_button = QToolButton()
392ebd70c7dSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
393ebd70c7dSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
394ebd70c7dSAdrian Hunter
395ebd70c7dSAdrian Hunter		self.hbox = QHBoxLayout()
396ebd70c7dSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
397ebd70c7dSAdrian Hunter
398ebd70c7dSAdrian Hunter		self.hbox.addWidget(label)
399ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.textbox)
400ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.progress)
401ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.pattern)
402ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.next_button)
403ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.prev_button)
404ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.close_button)
405ebd70c7dSAdrian Hunter
406ebd70c7dSAdrian Hunter		self.bar = QWidget()
40726688729SAdrian Hunter		self.bar.setLayout(self.hbox)
408ebd70c7dSAdrian Hunter		self.bar.hide()
409ebd70c7dSAdrian Hunter
410ebd70c7dSAdrian Hunter	def Widget(self):
411ebd70c7dSAdrian Hunter		return self.bar
412ebd70c7dSAdrian Hunter
413ebd70c7dSAdrian Hunter	def Activate(self):
414ebd70c7dSAdrian Hunter		self.bar.show()
41580b3fb64SAdrian Hunter		self.textbox.lineEdit().selectAll()
416ebd70c7dSAdrian Hunter		self.textbox.setFocus()
417ebd70c7dSAdrian Hunter
418ebd70c7dSAdrian Hunter	def Deactivate(self):
419ebd70c7dSAdrian Hunter		self.bar.hide()
420ebd70c7dSAdrian Hunter
421ebd70c7dSAdrian Hunter	def Busy(self):
422ebd70c7dSAdrian Hunter		self.textbox.setEnabled(False)
423ebd70c7dSAdrian Hunter		self.pattern.hide()
424ebd70c7dSAdrian Hunter		self.next_button.hide()
425ebd70c7dSAdrian Hunter		self.prev_button.hide()
426ebd70c7dSAdrian Hunter		self.progress.show()
427ebd70c7dSAdrian Hunter
428ebd70c7dSAdrian Hunter	def Idle(self):
429ebd70c7dSAdrian Hunter		self.textbox.setEnabled(True)
430ebd70c7dSAdrian Hunter		self.progress.hide()
431ebd70c7dSAdrian Hunter		self.pattern.show()
432ebd70c7dSAdrian Hunter		self.next_button.show()
433ebd70c7dSAdrian Hunter		self.prev_button.show()
434ebd70c7dSAdrian Hunter
435ebd70c7dSAdrian Hunter	def Find(self, direction):
436ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
437ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
438ebd70c7dSAdrian Hunter		self.last_value = value
439ebd70c7dSAdrian Hunter		self.last_pattern = pattern
440ebd70c7dSAdrian Hunter		self.finder.Find(value, direction, pattern, self.context)
441ebd70c7dSAdrian Hunter
442ebd70c7dSAdrian Hunter	def ValueChanged(self):
443ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
444ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
445ebd70c7dSAdrian Hunter		index = self.textbox.currentIndex()
446ebd70c7dSAdrian Hunter		data = self.textbox.itemData(index)
447ebd70c7dSAdrian Hunter		# Store the pattern in the combo box to keep it with the text value
448ebd70c7dSAdrian Hunter		if data == None:
449ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
450ebd70c7dSAdrian Hunter		else:
451ebd70c7dSAdrian Hunter			self.pattern.setChecked(data)
452ebd70c7dSAdrian Hunter		self.Find(0)
453ebd70c7dSAdrian Hunter
454ebd70c7dSAdrian Hunter	def NextPrev(self, direction):
455ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
456ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
457ebd70c7dSAdrian Hunter		if value != self.last_value:
458ebd70c7dSAdrian Hunter			index = self.textbox.findText(value)
459ebd70c7dSAdrian Hunter			# Allow for a button press before the value has been added to the combo box
460ebd70c7dSAdrian Hunter			if index < 0:
461ebd70c7dSAdrian Hunter				index = self.textbox.count()
462ebd70c7dSAdrian Hunter				self.textbox.addItem(value, pattern)
463ebd70c7dSAdrian Hunter				self.textbox.setCurrentIndex(index)
464ebd70c7dSAdrian Hunter				return
465ebd70c7dSAdrian Hunter			else:
466ebd70c7dSAdrian Hunter				self.textbox.setItemData(index, pattern)
467ebd70c7dSAdrian Hunter		elif pattern != self.last_pattern:
468ebd70c7dSAdrian Hunter			# Keep the pattern recorded in the combo box up to date
469ebd70c7dSAdrian Hunter			index = self.textbox.currentIndex()
470ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
471ebd70c7dSAdrian Hunter		self.Find(direction)
472ebd70c7dSAdrian Hunter
473ebd70c7dSAdrian Hunter	def NotFound(self):
474ebd70c7dSAdrian Hunter		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
475ebd70c7dSAdrian Hunter
476031c2a00SAdrian Hunter# Context-sensitive call graph data model item base
477031c2a00SAdrian Hunter
478031c2a00SAdrian Hunterclass CallGraphLevelItemBase(object):
479031c2a00SAdrian Hunter
4804a0979d4SAdrian Hunter	def __init__(self, glb, params, row, parent_item):
481031c2a00SAdrian Hunter		self.glb = glb
4824a0979d4SAdrian Hunter		self.params = params
483031c2a00SAdrian Hunter		self.row = row
484031c2a00SAdrian Hunter		self.parent_item = parent_item
48526688729SAdrian Hunter		self.query_done = False
486031c2a00SAdrian Hunter		self.child_count = 0
487031c2a00SAdrian Hunter		self.child_items = []
4883ac641f4SAdrian Hunter		if parent_item:
4893ac641f4SAdrian Hunter			self.level = parent_item.level + 1
4903ac641f4SAdrian Hunter		else:
4913ac641f4SAdrian Hunter			self.level = 0
492031c2a00SAdrian Hunter
493031c2a00SAdrian Hunter	def getChildItem(self, row):
494031c2a00SAdrian Hunter		return self.child_items[row]
495031c2a00SAdrian Hunter
496031c2a00SAdrian Hunter	def getParentItem(self):
497031c2a00SAdrian Hunter		return self.parent_item
498031c2a00SAdrian Hunter
499031c2a00SAdrian Hunter	def getRow(self):
500031c2a00SAdrian Hunter		return self.row
501031c2a00SAdrian Hunter
502031c2a00SAdrian Hunter	def childCount(self):
503031c2a00SAdrian Hunter		if not self.query_done:
504031c2a00SAdrian Hunter			self.Select()
505031c2a00SAdrian Hunter			if not self.child_count:
506031c2a00SAdrian Hunter				return -1
507031c2a00SAdrian Hunter		return self.child_count
508031c2a00SAdrian Hunter
509031c2a00SAdrian Hunter	def hasChildren(self):
510031c2a00SAdrian Hunter		if not self.query_done:
511031c2a00SAdrian Hunter			return True
512031c2a00SAdrian Hunter		return self.child_count > 0
513031c2a00SAdrian Hunter
514031c2a00SAdrian Hunter	def getData(self, column):
515031c2a00SAdrian Hunter		return self.data[column]
516031c2a00SAdrian Hunter
517031c2a00SAdrian Hunter# Context-sensitive call graph data model level 2+ item base
518031c2a00SAdrian Hunter
519031c2a00SAdrian Hunterclass CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
520031c2a00SAdrian Hunter
52138a846d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
5224a0979d4SAdrian Hunter		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
523031c2a00SAdrian Hunter		self.comm_id = comm_id
524031c2a00SAdrian Hunter		self.thread_id = thread_id
525031c2a00SAdrian Hunter		self.call_path_id = call_path_id
52638a846d4SAdrian Hunter		self.insn_cnt = insn_cnt
52738a846d4SAdrian Hunter		self.cyc_cnt = cyc_cnt
528031c2a00SAdrian Hunter		self.branch_count = branch_count
529031c2a00SAdrian Hunter		self.time = time
530031c2a00SAdrian Hunter
531031c2a00SAdrian Hunter	def Select(self):
53226688729SAdrian Hunter		self.query_done = True
533031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
53438a846d4SAdrian Hunter		if self.params.have_ipc:
53538a846d4SAdrian Hunter			ipc_str = ", SUM(insn_count), SUM(cyc_count)"
53638a846d4SAdrian Hunter		else:
53738a846d4SAdrian Hunter			ipc_str = ""
53838a846d4SAdrian Hunter		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
539031c2a00SAdrian Hunter					" FROM calls"
540031c2a00SAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
541031c2a00SAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
542031c2a00SAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
543031c2a00SAdrian Hunter					" WHERE parent_call_path_id = " + str(self.call_path_id) +
544031c2a00SAdrian Hunter					" AND comm_id = " + str(self.comm_id) +
545031c2a00SAdrian Hunter					" AND thread_id = " + str(self.thread_id) +
546031c2a00SAdrian Hunter					" GROUP BY call_path_id, name, short_name"
547031c2a00SAdrian Hunter					" ORDER BY call_path_id")
548031c2a00SAdrian Hunter		while query.next():
54938a846d4SAdrian Hunter			if self.params.have_ipc:
55038a846d4SAdrian Hunter				insn_cnt = int(query.value(5))
55138a846d4SAdrian Hunter				cyc_cnt = int(query.value(6))
55238a846d4SAdrian Hunter				branch_count = int(query.value(7))
55338a846d4SAdrian Hunter			else:
55438a846d4SAdrian Hunter				insn_cnt = 0
55538a846d4SAdrian Hunter				cyc_cnt = 0
55638a846d4SAdrian Hunter				branch_count = int(query.value(5))
55738a846d4SAdrian 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)
558031c2a00SAdrian Hunter			self.child_items.append(child_item)
559031c2a00SAdrian Hunter			self.child_count += 1
560031c2a00SAdrian Hunter
561031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item
562031c2a00SAdrian Hunter
563031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
564031c2a00SAdrian Hunter
56538a846d4SAdrian 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):
56638a846d4SAdrian Hunter		super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
567031c2a00SAdrian Hunter		dso = dsoname(dso)
56838a846d4SAdrian Hunter		if self.params.have_ipc:
56938a846d4SAdrian Hunter			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
57038a846d4SAdrian Hunter			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
57138a846d4SAdrian Hunter			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
57238a846d4SAdrian Hunter			ipc = CalcIPC(cyc_cnt, insn_cnt)
57338a846d4SAdrian 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 ]
57438a846d4SAdrian Hunter		else:
575031c2a00SAdrian Hunter			self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
576031c2a00SAdrian Hunter		self.dbid = call_path_id
577031c2a00SAdrian Hunter
578031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item
579031c2a00SAdrian Hunter
580031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
581031c2a00SAdrian Hunter
5824a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
58338a846d4SAdrian Hunter		super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
58438a846d4SAdrian Hunter		if self.params.have_ipc:
58538a846d4SAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
58638a846d4SAdrian Hunter		else:
587031c2a00SAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
588031c2a00SAdrian Hunter		self.dbid = thread_id
589031c2a00SAdrian Hunter
590031c2a00SAdrian Hunter	def Select(self):
591031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).Select()
592031c2a00SAdrian Hunter		for child_item in self.child_items:
593031c2a00SAdrian Hunter			self.time += child_item.time
59438a846d4SAdrian Hunter			self.insn_cnt += child_item.insn_cnt
59538a846d4SAdrian Hunter			self.cyc_cnt += child_item.cyc_cnt
596031c2a00SAdrian Hunter			self.branch_count += child_item.branch_count
597031c2a00SAdrian Hunter		for child_item in self.child_items:
598031c2a00SAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
59938a846d4SAdrian Hunter			if self.params.have_ipc:
60038a846d4SAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
60138a846d4SAdrian Hunter				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
60238a846d4SAdrian Hunter				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
60338a846d4SAdrian Hunter			else:
604031c2a00SAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
605031c2a00SAdrian Hunter
606031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item
607031c2a00SAdrian Hunter
608031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase):
609031c2a00SAdrian Hunter
6104a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, comm, parent_item):
6114a0979d4SAdrian Hunter		super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
61238a846d4SAdrian Hunter		if self.params.have_ipc:
61338a846d4SAdrian Hunter			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
61438a846d4SAdrian Hunter		else:
615031c2a00SAdrian Hunter			self.data = [comm, "", "", "", "", "", ""]
616031c2a00SAdrian Hunter		self.dbid = comm_id
617031c2a00SAdrian Hunter
618031c2a00SAdrian Hunter	def Select(self):
61926688729SAdrian Hunter		self.query_done = True
620031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
621031c2a00SAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
622031c2a00SAdrian Hunter					" FROM comm_threads"
623031c2a00SAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
624031c2a00SAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
625031c2a00SAdrian Hunter		while query.next():
6264a0979d4SAdrian Hunter			child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
627031c2a00SAdrian Hunter			self.child_items.append(child_item)
628031c2a00SAdrian Hunter			self.child_count += 1
629031c2a00SAdrian Hunter
630031c2a00SAdrian Hunter# Context-sensitive call graph data model root item
631031c2a00SAdrian Hunter
632031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase):
633031c2a00SAdrian Hunter
6344a0979d4SAdrian Hunter	def __init__(self, glb, params):
6354a0979d4SAdrian Hunter		super(CallGraphRootItem, self).__init__(glb, params, 0, None)
636031c2a00SAdrian Hunter		self.dbid = 0
63726688729SAdrian Hunter		self.query_done = True
63826c11206SAdrian Hunter		if_has_calls = ""
63926c11206SAdrian Hunter		if IsSelectable(glb.db, "comms", columns = "has_calls"):
64026c11206SAdrian Hunter			if_has_calls = " WHERE has_calls = TRUE"
641031c2a00SAdrian Hunter		query = QSqlQuery(glb.db)
64226c11206SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
643031c2a00SAdrian Hunter		while query.next():
644031c2a00SAdrian Hunter			if not query.value(0):
645031c2a00SAdrian Hunter				continue
6464a0979d4SAdrian Hunter			child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
647031c2a00SAdrian Hunter			self.child_items.append(child_item)
648031c2a00SAdrian Hunter			self.child_count += 1
649031c2a00SAdrian Hunter
6504a0979d4SAdrian Hunter# Call graph model parameters
6514a0979d4SAdrian Hunter
6524a0979d4SAdrian Hunterclass CallGraphModelParams():
6534a0979d4SAdrian Hunter
6544a0979d4SAdrian Hunter	def __init__(self, glb, parent=None):
6554a0979d4SAdrian Hunter		self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
6564a0979d4SAdrian Hunter
657254c0d82SAdrian Hunter# Context-sensitive call graph data model base
658031c2a00SAdrian Hunter
659254c0d82SAdrian Hunterclass CallGraphModelBase(TreeModel):
660031c2a00SAdrian Hunter
661031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
6624a0979d4SAdrian Hunter		super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
663031c2a00SAdrian Hunter
664ebd70c7dSAdrian Hunter	def FindSelect(self, value, pattern, query):
665ebd70c7dSAdrian Hunter		if pattern:
666ebd70c7dSAdrian Hunter			# postgresql and sqlite pattern patching differences:
667ebd70c7dSAdrian Hunter			#   postgresql LIKE is case sensitive but sqlite LIKE is not
668ebd70c7dSAdrian Hunter			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
669ebd70c7dSAdrian Hunter			#   postgresql supports ILIKE which is case insensitive
670ebd70c7dSAdrian Hunter			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
671ebd70c7dSAdrian Hunter			if not self.glb.dbref.is_sqlite3:
672ebd70c7dSAdrian Hunter				# Escape % and _
673ebd70c7dSAdrian Hunter				s = value.replace("%", "\%")
674ebd70c7dSAdrian Hunter				s = s.replace("_", "\_")
675ebd70c7dSAdrian Hunter				# Translate * and ? into SQL LIKE pattern characters % and _
676ebd70c7dSAdrian Hunter				trans = string.maketrans("*?", "%_")
677ebd70c7dSAdrian Hunter				match = " LIKE '" + str(s).translate(trans) + "'"
678ebd70c7dSAdrian Hunter			else:
679ebd70c7dSAdrian Hunter				match = " GLOB '" + str(value) + "'"
680ebd70c7dSAdrian Hunter		else:
681ebd70c7dSAdrian Hunter			match = " = '" + str(value) + "'"
682254c0d82SAdrian Hunter		self.DoFindSelect(query, match)
683ebd70c7dSAdrian Hunter
684ebd70c7dSAdrian Hunter	def Found(self, query, found):
685ebd70c7dSAdrian Hunter		if found:
686ebd70c7dSAdrian Hunter			return self.FindPath(query)
687ebd70c7dSAdrian Hunter		return []
688ebd70c7dSAdrian Hunter
689ebd70c7dSAdrian Hunter	def FindValue(self, value, pattern, query, last_value, last_pattern):
690ebd70c7dSAdrian Hunter		if last_value == value and pattern == last_pattern:
691ebd70c7dSAdrian Hunter			found = query.first()
692ebd70c7dSAdrian Hunter		else:
693ebd70c7dSAdrian Hunter			self.FindSelect(value, pattern, query)
694ebd70c7dSAdrian Hunter			found = query.next()
695ebd70c7dSAdrian Hunter		return self.Found(query, found)
696ebd70c7dSAdrian Hunter
697ebd70c7dSAdrian Hunter	def FindNext(self, query):
698ebd70c7dSAdrian Hunter		found = query.next()
699ebd70c7dSAdrian Hunter		if not found:
700ebd70c7dSAdrian Hunter			found = query.first()
701ebd70c7dSAdrian Hunter		return self.Found(query, found)
702ebd70c7dSAdrian Hunter
703ebd70c7dSAdrian Hunter	def FindPrev(self, query):
704ebd70c7dSAdrian Hunter		found = query.previous()
705ebd70c7dSAdrian Hunter		if not found:
706ebd70c7dSAdrian Hunter			found = query.last()
707ebd70c7dSAdrian Hunter		return self.Found(query, found)
708ebd70c7dSAdrian Hunter
709ebd70c7dSAdrian Hunter	def FindThread(self, c):
710ebd70c7dSAdrian Hunter		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
711ebd70c7dSAdrian Hunter			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
712ebd70c7dSAdrian Hunter		elif c.direction > 0:
713ebd70c7dSAdrian Hunter			ids = self.FindNext(c.query)
714ebd70c7dSAdrian Hunter		else:
715ebd70c7dSAdrian Hunter			ids = self.FindPrev(c.query)
716ebd70c7dSAdrian Hunter		return (True, ids)
717ebd70c7dSAdrian Hunter
718ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
719ebd70c7dSAdrian Hunter		class Context():
720ebd70c7dSAdrian Hunter			def __init__(self, *x):
721ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
722ebd70c7dSAdrian Hunter			def Update(self, *x):
723ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
724ebd70c7dSAdrian Hunter		if len(context):
725ebd70c7dSAdrian Hunter			context[0].Update(value, direction, pattern)
726ebd70c7dSAdrian Hunter		else:
727ebd70c7dSAdrian Hunter			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
728ebd70c7dSAdrian Hunter		# Use a thread so the UI is not blocked during the SELECT
729ebd70c7dSAdrian Hunter		thread = Thread(self.FindThread, context[0])
730ebd70c7dSAdrian Hunter		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
731ebd70c7dSAdrian Hunter		thread.start()
732ebd70c7dSAdrian Hunter
733ebd70c7dSAdrian Hunter	def FindDone(self, thread, callback, ids):
734ebd70c7dSAdrian Hunter		callback(ids)
735ebd70c7dSAdrian Hunter
736254c0d82SAdrian Hunter# Context-sensitive call graph data model
737254c0d82SAdrian Hunter
738254c0d82SAdrian Hunterclass CallGraphModel(CallGraphModelBase):
739254c0d82SAdrian Hunter
740254c0d82SAdrian Hunter	def __init__(self, glb, parent=None):
741254c0d82SAdrian Hunter		super(CallGraphModel, self).__init__(glb, parent)
742254c0d82SAdrian Hunter
743254c0d82SAdrian Hunter	def GetRoot(self):
7444a0979d4SAdrian Hunter		return CallGraphRootItem(self.glb, self.params)
745254c0d82SAdrian Hunter
746254c0d82SAdrian Hunter	def columnCount(self, parent=None):
74738a846d4SAdrian Hunter		if self.params.have_ipc:
74838a846d4SAdrian Hunter			return 12
74938a846d4SAdrian Hunter		else:
750254c0d82SAdrian Hunter			return 7
751254c0d82SAdrian Hunter
752254c0d82SAdrian Hunter	def columnHeader(self, column):
75338a846d4SAdrian Hunter		if self.params.have_ipc:
75438a846d4SAdrian Hunter			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
75538a846d4SAdrian Hunter		else:
756254c0d82SAdrian Hunter			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
757254c0d82SAdrian Hunter		return headers[column]
758254c0d82SAdrian Hunter
759254c0d82SAdrian Hunter	def columnAlignment(self, column):
76038a846d4SAdrian Hunter		if self.params.have_ipc:
76138a846d4SAdrian 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 ]
76238a846d4SAdrian Hunter		else:
763254c0d82SAdrian Hunter			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
764254c0d82SAdrian Hunter		return alignment[column]
765254c0d82SAdrian Hunter
766254c0d82SAdrian Hunter	def DoFindSelect(self, query, match):
767254c0d82SAdrian Hunter		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
768254c0d82SAdrian Hunter						" FROM calls"
769254c0d82SAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
770254c0d82SAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
771254c0d82SAdrian Hunter						" WHERE symbols.name" + match +
772254c0d82SAdrian Hunter						" GROUP BY comm_id, thread_id, call_path_id"
773254c0d82SAdrian Hunter						" ORDER BY comm_id, thread_id, call_path_id")
774254c0d82SAdrian Hunter
775254c0d82SAdrian Hunter	def FindPath(self, query):
776254c0d82SAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
777254c0d82SAdrian Hunter		# to open the tree at the right place.
778254c0d82SAdrian Hunter		ids = []
779254c0d82SAdrian Hunter		parent_id = query.value(0)
780254c0d82SAdrian Hunter		while parent_id:
781254c0d82SAdrian Hunter			ids.insert(0, parent_id)
782254c0d82SAdrian Hunter			q2 = QSqlQuery(self.glb.db)
783254c0d82SAdrian Hunter			QueryExec(q2, "SELECT parent_id"
784254c0d82SAdrian Hunter					" FROM call_paths"
785254c0d82SAdrian Hunter					" WHERE id = " + str(parent_id))
786254c0d82SAdrian Hunter			if not q2.next():
787254c0d82SAdrian Hunter				break
788254c0d82SAdrian Hunter			parent_id = q2.value(0)
789254c0d82SAdrian Hunter		# The call path root is not used
790254c0d82SAdrian Hunter		if ids[0] == 1:
791254c0d82SAdrian Hunter			del ids[0]
792254c0d82SAdrian Hunter		ids.insert(0, query.value(2))
793254c0d82SAdrian Hunter		ids.insert(0, query.value(1))
794254c0d82SAdrian Hunter		return ids
795254c0d82SAdrian Hunter
796ae8b887cSAdrian Hunter# Call tree data model level 2+ item base
797ae8b887cSAdrian Hunter
798ae8b887cSAdrian Hunterclass CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
799ae8b887cSAdrian Hunter
800da4264f5SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
8014a0979d4SAdrian Hunter		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
802ae8b887cSAdrian Hunter		self.comm_id = comm_id
803ae8b887cSAdrian Hunter		self.thread_id = thread_id
804ae8b887cSAdrian Hunter		self.calls_id = calls_id
805da4264f5SAdrian Hunter		self.call_time = call_time
806da4264f5SAdrian Hunter		self.time = time
807b3b66079SAdrian Hunter		self.insn_cnt = insn_cnt
808b3b66079SAdrian Hunter		self.cyc_cnt = cyc_cnt
809ae8b887cSAdrian Hunter		self.branch_count = branch_count
810ae8b887cSAdrian Hunter
811ae8b887cSAdrian Hunter	def Select(self):
81226688729SAdrian Hunter		self.query_done = True
813ae8b887cSAdrian Hunter		if self.calls_id == 0:
814ae8b887cSAdrian Hunter			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
815ae8b887cSAdrian Hunter		else:
816ae8b887cSAdrian Hunter			comm_thread = ""
817b3b66079SAdrian Hunter		if self.params.have_ipc:
818b3b66079SAdrian Hunter			ipc_str = ", insn_count, cyc_count"
819b3b66079SAdrian Hunter		else:
820b3b66079SAdrian Hunter			ipc_str = ""
821ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
822b3b66079SAdrian Hunter		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
823ae8b887cSAdrian Hunter					" FROM calls"
824ae8b887cSAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
825ae8b887cSAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
826ae8b887cSAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
827ae8b887cSAdrian Hunter					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
828ae8b887cSAdrian Hunter					" ORDER BY call_time, calls.id")
829ae8b887cSAdrian Hunter		while query.next():
830b3b66079SAdrian Hunter			if self.params.have_ipc:
831b3b66079SAdrian Hunter				insn_cnt = int(query.value(5))
832b3b66079SAdrian Hunter				cyc_cnt = int(query.value(6))
833b3b66079SAdrian Hunter				branch_count = int(query.value(7))
834b3b66079SAdrian Hunter			else:
835b3b66079SAdrian Hunter				insn_cnt = 0
836b3b66079SAdrian Hunter				cyc_cnt = 0
837b3b66079SAdrian Hunter				branch_count = int(query.value(5))
838b3b66079SAdrian 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)), insn_cnt, cyc_cnt, branch_count, self)
839ae8b887cSAdrian Hunter			self.child_items.append(child_item)
840ae8b887cSAdrian Hunter			self.child_count += 1
841ae8b887cSAdrian Hunter
842ae8b887cSAdrian Hunter# Call tree data model level three item
843ae8b887cSAdrian Hunter
844ae8b887cSAdrian Hunterclass CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
845ae8b887cSAdrian Hunter
846da4264f5SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
847da4264f5SAdrian Hunter		super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item)
848ae8b887cSAdrian Hunter		dso = dsoname(dso)
849b3b66079SAdrian Hunter		if self.params.have_ipc:
850b3b66079SAdrian Hunter			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
851b3b66079SAdrian Hunter			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
852b3b66079SAdrian Hunter			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
853b3b66079SAdrian Hunter			ipc = CalcIPC(cyc_cnt, insn_cnt)
854da4264f5SAdrian Hunter			self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
855b3b66079SAdrian Hunter		else:
856da4264f5SAdrian Hunter			self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
857ae8b887cSAdrian Hunter		self.dbid = calls_id
858ae8b887cSAdrian Hunter
859ae8b887cSAdrian Hunter# Call tree data model level two item
860ae8b887cSAdrian Hunter
861ae8b887cSAdrian Hunterclass CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
862ae8b887cSAdrian Hunter
8634a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
864da4264f5SAdrian Hunter		super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, 0, parent_item)
865b3b66079SAdrian Hunter		if self.params.have_ipc:
866b3b66079SAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
867b3b66079SAdrian Hunter		else:
868ae8b887cSAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
869ae8b887cSAdrian Hunter		self.dbid = thread_id
870ae8b887cSAdrian Hunter
871ae8b887cSAdrian Hunter	def Select(self):
872ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).Select()
873ae8b887cSAdrian Hunter		for child_item in self.child_items:
874ae8b887cSAdrian Hunter			self.time += child_item.time
875b3b66079SAdrian Hunter			self.insn_cnt += child_item.insn_cnt
876b3b66079SAdrian Hunter			self.cyc_cnt += child_item.cyc_cnt
877ae8b887cSAdrian Hunter			self.branch_count += child_item.branch_count
878ae8b887cSAdrian Hunter		for child_item in self.child_items:
879ae8b887cSAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
880b3b66079SAdrian Hunter			if self.params.have_ipc:
881b3b66079SAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
882b3b66079SAdrian Hunter				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
883b3b66079SAdrian Hunter				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
884b3b66079SAdrian Hunter			else:
885ae8b887cSAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
886ae8b887cSAdrian Hunter
887ae8b887cSAdrian Hunter# Call tree data model level one item
888ae8b887cSAdrian Hunter
889ae8b887cSAdrian Hunterclass CallTreeLevelOneItem(CallGraphLevelItemBase):
890ae8b887cSAdrian Hunter
8914a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, comm, parent_item):
8924a0979d4SAdrian Hunter		super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
893b3b66079SAdrian Hunter		if self.params.have_ipc:
894b3b66079SAdrian Hunter			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
895b3b66079SAdrian Hunter		else:
896ae8b887cSAdrian Hunter			self.data = [comm, "", "", "", "", "", ""]
897ae8b887cSAdrian Hunter		self.dbid = comm_id
898ae8b887cSAdrian Hunter
899ae8b887cSAdrian Hunter	def Select(self):
90026688729SAdrian Hunter		self.query_done = True
901ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
902ae8b887cSAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
903ae8b887cSAdrian Hunter					" FROM comm_threads"
904ae8b887cSAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
905ae8b887cSAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
906ae8b887cSAdrian Hunter		while query.next():
9074a0979d4SAdrian Hunter			child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
908ae8b887cSAdrian Hunter			self.child_items.append(child_item)
909ae8b887cSAdrian Hunter			self.child_count += 1
910ae8b887cSAdrian Hunter
911ae8b887cSAdrian Hunter# Call tree data model root item
912ae8b887cSAdrian Hunter
913ae8b887cSAdrian Hunterclass CallTreeRootItem(CallGraphLevelItemBase):
914ae8b887cSAdrian Hunter
9154a0979d4SAdrian Hunter	def __init__(self, glb, params):
9164a0979d4SAdrian Hunter		super(CallTreeRootItem, self).__init__(glb, params, 0, None)
917ae8b887cSAdrian Hunter		self.dbid = 0
91826688729SAdrian Hunter		self.query_done = True
91926c11206SAdrian Hunter		if_has_calls = ""
92026c11206SAdrian Hunter		if IsSelectable(glb.db, "comms", columns = "has_calls"):
92126c11206SAdrian Hunter			if_has_calls = " WHERE has_calls = TRUE"
922ae8b887cSAdrian Hunter		query = QSqlQuery(glb.db)
92326c11206SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
924ae8b887cSAdrian Hunter		while query.next():
925ae8b887cSAdrian Hunter			if not query.value(0):
926ae8b887cSAdrian Hunter				continue
9274a0979d4SAdrian Hunter			child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
928ae8b887cSAdrian Hunter			self.child_items.append(child_item)
929ae8b887cSAdrian Hunter			self.child_count += 1
930ae8b887cSAdrian Hunter
931ae8b887cSAdrian Hunter# Call Tree data model
932ae8b887cSAdrian Hunter
933ae8b887cSAdrian Hunterclass CallTreeModel(CallGraphModelBase):
934ae8b887cSAdrian Hunter
935ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
936ae8b887cSAdrian Hunter		super(CallTreeModel, self).__init__(glb, parent)
937ae8b887cSAdrian Hunter
938ae8b887cSAdrian Hunter	def GetRoot(self):
9394a0979d4SAdrian Hunter		return CallTreeRootItem(self.glb, self.params)
940ae8b887cSAdrian Hunter
941ae8b887cSAdrian Hunter	def columnCount(self, parent=None):
942b3b66079SAdrian Hunter		if self.params.have_ipc:
943b3b66079SAdrian Hunter			return 12
944b3b66079SAdrian Hunter		else:
945ae8b887cSAdrian Hunter			return 7
946ae8b887cSAdrian Hunter
947ae8b887cSAdrian Hunter	def columnHeader(self, column):
948b3b66079SAdrian Hunter		if self.params.have_ipc:
949b3b66079SAdrian Hunter			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
950b3b66079SAdrian Hunter		else:
951ae8b887cSAdrian Hunter			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
952ae8b887cSAdrian Hunter		return headers[column]
953ae8b887cSAdrian Hunter
954ae8b887cSAdrian Hunter	def columnAlignment(self, column):
955b3b66079SAdrian Hunter		if self.params.have_ipc:
956b3b66079SAdrian 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 ]
957b3b66079SAdrian Hunter		else:
958ae8b887cSAdrian Hunter			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
959ae8b887cSAdrian Hunter		return alignment[column]
960ae8b887cSAdrian Hunter
961ae8b887cSAdrian Hunter	def DoFindSelect(self, query, match):
962ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
963ae8b887cSAdrian Hunter						" FROM calls"
964ae8b887cSAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
965ae8b887cSAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
966ae8b887cSAdrian Hunter						" WHERE symbols.name" + match +
967ae8b887cSAdrian Hunter						" ORDER BY comm_id, thread_id, call_time, calls.id")
968ae8b887cSAdrian Hunter
969ae8b887cSAdrian Hunter	def FindPath(self, query):
970ae8b887cSAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
971ae8b887cSAdrian Hunter		# to open the tree at the right place.
972ae8b887cSAdrian Hunter		ids = []
973ae8b887cSAdrian Hunter		parent_id = query.value(0)
974ae8b887cSAdrian Hunter		while parent_id:
975ae8b887cSAdrian Hunter			ids.insert(0, parent_id)
976ae8b887cSAdrian Hunter			q2 = QSqlQuery(self.glb.db)
977ae8b887cSAdrian Hunter			QueryExec(q2, "SELECT parent_id"
978ae8b887cSAdrian Hunter					" FROM calls"
979ae8b887cSAdrian Hunter					" WHERE id = " + str(parent_id))
980ae8b887cSAdrian Hunter			if not q2.next():
981ae8b887cSAdrian Hunter				break
982ae8b887cSAdrian Hunter			parent_id = q2.value(0)
983ae8b887cSAdrian Hunter		ids.insert(0, query.value(2))
984ae8b887cSAdrian Hunter		ids.insert(0, query.value(1))
985ae8b887cSAdrian Hunter		return ids
986ae8b887cSAdrian Hunter
98742c303ffSAdrian Hunter# Vertical layout
98842c303ffSAdrian Hunter
98942c303ffSAdrian Hunterclass HBoxLayout(QHBoxLayout):
99042c303ffSAdrian Hunter
99142c303ffSAdrian Hunter	def __init__(self, *children):
99242c303ffSAdrian Hunter		super(HBoxLayout, self).__init__()
99342c303ffSAdrian Hunter
99442c303ffSAdrian Hunter		self.layout().setContentsMargins(0, 0, 0, 0)
99542c303ffSAdrian Hunter		for child in children:
99642c303ffSAdrian Hunter			if child.isWidgetType():
99742c303ffSAdrian Hunter				self.layout().addWidget(child)
99842c303ffSAdrian Hunter			else:
99942c303ffSAdrian Hunter				self.layout().addLayout(child)
100042c303ffSAdrian Hunter
100142c303ffSAdrian Hunter# Horizontal layout
100242c303ffSAdrian Hunter
100342c303ffSAdrian Hunterclass VBoxLayout(QVBoxLayout):
100442c303ffSAdrian Hunter
100542c303ffSAdrian Hunter	def __init__(self, *children):
100642c303ffSAdrian Hunter		super(VBoxLayout, self).__init__()
100742c303ffSAdrian Hunter
100842c303ffSAdrian Hunter		self.layout().setContentsMargins(0, 0, 0, 0)
100942c303ffSAdrian Hunter		for child in children:
101042c303ffSAdrian Hunter			if child.isWidgetType():
101142c303ffSAdrian Hunter				self.layout().addWidget(child)
101242c303ffSAdrian Hunter			else:
101342c303ffSAdrian Hunter				self.layout().addLayout(child)
101442c303ffSAdrian Hunter
101542c303ffSAdrian Hunter# Vertical layout widget
1016ebd70c7dSAdrian Hunter
1017ebd70c7dSAdrian Hunterclass VBox():
1018ebd70c7dSAdrian Hunter
101942c303ffSAdrian Hunter	def __init__(self, *children):
1020ebd70c7dSAdrian Hunter		self.vbox = QWidget()
102142c303ffSAdrian Hunter		self.vbox.setLayout(VBoxLayout(*children))
1022ebd70c7dSAdrian Hunter
1023ebd70c7dSAdrian Hunter	def Widget(self):
1024ebd70c7dSAdrian Hunter		return self.vbox
1025ebd70c7dSAdrian Hunter
1026a731cc4cSAdrian Hunter# Tree window base
10271beb5c7bSAdrian Hunter
1028a731cc4cSAdrian Hunterclass TreeWindowBase(QMdiSubWindow):
10291beb5c7bSAdrian Hunter
1030a731cc4cSAdrian Hunter	def __init__(self, parent=None):
1031a731cc4cSAdrian Hunter		super(TreeWindowBase, self).__init__(parent)
10321beb5c7bSAdrian Hunter
1033a731cc4cSAdrian Hunter		self.model = None
1034a731cc4cSAdrian Hunter		self.find_bar = None
10351beb5c7bSAdrian Hunter
1036be6e7471SAdrian Hunter		self.view = QTreeView()
103796c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
103896c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1039be6e7471SAdrian Hunter
10409bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
10419bc4e4bfSAdrian Hunter
1042ebd70c7dSAdrian Hunter	def DisplayFound(self, ids):
1043ebd70c7dSAdrian Hunter		if not len(ids):
1044ebd70c7dSAdrian Hunter			return False
1045ebd70c7dSAdrian Hunter		parent = QModelIndex()
1046ebd70c7dSAdrian Hunter		for dbid in ids:
1047ebd70c7dSAdrian Hunter			found = False
1048ebd70c7dSAdrian Hunter			n = self.model.rowCount(parent)
1049ebd70c7dSAdrian Hunter			for row in xrange(n):
1050ebd70c7dSAdrian Hunter				child = self.model.index(row, 0, parent)
1051ebd70c7dSAdrian Hunter				if child.internalPointer().dbid == dbid:
1052ebd70c7dSAdrian Hunter					found = True
1053ebd70c7dSAdrian Hunter					self.view.setCurrentIndex(child)
1054ebd70c7dSAdrian Hunter					parent = child
1055ebd70c7dSAdrian Hunter					break
1056ebd70c7dSAdrian Hunter			if not found:
1057ebd70c7dSAdrian Hunter				break
1058ebd70c7dSAdrian Hunter		return found
1059ebd70c7dSAdrian Hunter
1060ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context):
1061ebd70c7dSAdrian Hunter		self.view.setFocus()
1062ebd70c7dSAdrian Hunter		self.find_bar.Busy()
1063ebd70c7dSAdrian Hunter		self.model.Find(value, direction, pattern, context, self.FindDone)
1064ebd70c7dSAdrian Hunter
1065ebd70c7dSAdrian Hunter	def FindDone(self, ids):
1066ebd70c7dSAdrian Hunter		found = True
1067ebd70c7dSAdrian Hunter		if not self.DisplayFound(ids):
1068ebd70c7dSAdrian Hunter			found = False
1069ebd70c7dSAdrian Hunter		self.find_bar.Idle()
1070ebd70c7dSAdrian Hunter		if not found:
1071ebd70c7dSAdrian Hunter			self.find_bar.NotFound()
1072ebd70c7dSAdrian Hunter
1073a731cc4cSAdrian Hunter
1074a731cc4cSAdrian Hunter# Context-sensitive call graph window
1075a731cc4cSAdrian Hunter
1076a731cc4cSAdrian Hunterclass CallGraphWindow(TreeWindowBase):
1077a731cc4cSAdrian Hunter
1078a731cc4cSAdrian Hunter	def __init__(self, glb, parent=None):
1079a731cc4cSAdrian Hunter		super(CallGraphWindow, self).__init__(parent)
1080a731cc4cSAdrian Hunter
1081a731cc4cSAdrian Hunter		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
1082a731cc4cSAdrian Hunter
1083a731cc4cSAdrian Hunter		self.view.setModel(self.model)
1084a731cc4cSAdrian Hunter
1085a731cc4cSAdrian Hunter		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1086a731cc4cSAdrian Hunter			self.view.setColumnWidth(c, w)
1087a731cc4cSAdrian Hunter
1088a731cc4cSAdrian Hunter		self.find_bar = FindBar(self, self)
1089a731cc4cSAdrian Hunter
1090a731cc4cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
1091a731cc4cSAdrian Hunter
1092a731cc4cSAdrian Hunter		self.setWidget(self.vbox.Widget())
1093a731cc4cSAdrian Hunter
1094a731cc4cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
1095a731cc4cSAdrian Hunter
1096ae8b887cSAdrian Hunter# Call tree window
1097ae8b887cSAdrian Hunter
1098ae8b887cSAdrian Hunterclass CallTreeWindow(TreeWindowBase):
1099ae8b887cSAdrian Hunter
1100e69d5df7SAdrian Hunter	def __init__(self, glb, parent=None, thread_at_time=None):
1101ae8b887cSAdrian Hunter		super(CallTreeWindow, self).__init__(parent)
1102ae8b887cSAdrian Hunter
1103ae8b887cSAdrian Hunter		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
1104ae8b887cSAdrian Hunter
1105ae8b887cSAdrian Hunter		self.view.setModel(self.model)
1106ae8b887cSAdrian Hunter
1107ae8b887cSAdrian Hunter		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1108ae8b887cSAdrian Hunter			self.view.setColumnWidth(c, w)
1109ae8b887cSAdrian Hunter
1110ae8b887cSAdrian Hunter		self.find_bar = FindBar(self, self)
1111ae8b887cSAdrian Hunter
1112ae8b887cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
1113ae8b887cSAdrian Hunter
1114ae8b887cSAdrian Hunter		self.setWidget(self.vbox.Widget())
1115ae8b887cSAdrian Hunter
1116ae8b887cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
1117ae8b887cSAdrian Hunter
1118e69d5df7SAdrian Hunter		if thread_at_time:
1119e69d5df7SAdrian Hunter			self.DisplayThreadAtTime(*thread_at_time)
1120e69d5df7SAdrian Hunter
1121e69d5df7SAdrian Hunter	def DisplayThreadAtTime(self, comm_id, thread_id, time):
1122e69d5df7SAdrian Hunter		parent = QModelIndex()
1123e69d5df7SAdrian Hunter		for dbid in (comm_id, thread_id):
1124e69d5df7SAdrian Hunter			found = False
1125e69d5df7SAdrian Hunter			n = self.model.rowCount(parent)
1126e69d5df7SAdrian Hunter			for row in xrange(n):
1127e69d5df7SAdrian Hunter				child = self.model.index(row, 0, parent)
1128e69d5df7SAdrian Hunter				if child.internalPointer().dbid == dbid:
1129e69d5df7SAdrian Hunter					found = True
1130e69d5df7SAdrian Hunter					self.view.setCurrentIndex(child)
1131e69d5df7SAdrian Hunter					parent = child
1132e69d5df7SAdrian Hunter					break
1133e69d5df7SAdrian Hunter			if not found:
1134e69d5df7SAdrian Hunter				return
1135e69d5df7SAdrian Hunter		found = False
1136e69d5df7SAdrian Hunter		while True:
1137e69d5df7SAdrian Hunter			n = self.model.rowCount(parent)
1138e69d5df7SAdrian Hunter			if not n:
1139e69d5df7SAdrian Hunter				return
1140e69d5df7SAdrian Hunter			last_child = None
1141e69d5df7SAdrian Hunter			for row in xrange(n):
1142e69d5df7SAdrian Hunter				child = self.model.index(row, 0, parent)
1143e69d5df7SAdrian Hunter				child_call_time = child.internalPointer().call_time
1144e69d5df7SAdrian Hunter				if child_call_time < time:
1145e69d5df7SAdrian Hunter					last_child = child
1146e69d5df7SAdrian Hunter				elif child_call_time == time:
1147e69d5df7SAdrian Hunter					self.view.setCurrentIndex(child)
1148e69d5df7SAdrian Hunter					return
1149e69d5df7SAdrian Hunter				elif child_call_time > time:
1150e69d5df7SAdrian Hunter					break
1151e69d5df7SAdrian Hunter			if not last_child:
1152e69d5df7SAdrian Hunter				if not found:
1153e69d5df7SAdrian Hunter					child = self.model.index(0, 0, parent)
1154e69d5df7SAdrian Hunter					self.view.setCurrentIndex(child)
1155e69d5df7SAdrian Hunter				return
1156e69d5df7SAdrian Hunter			found = True
1157e69d5df7SAdrian Hunter			self.view.setCurrentIndex(last_child)
1158e69d5df7SAdrian Hunter			parent = last_child
1159e69d5df7SAdrian Hunter
1160*b3700f21SAdrian Hunter# ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name
1161*b3700f21SAdrian Hunter
1162*b3700f21SAdrian Hunterdef ExecComm(db, thread_id, time):
1163*b3700f21SAdrian Hunter	query = QSqlQuery(db)
1164*b3700f21SAdrian Hunter	QueryExec(query, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag"
1165*b3700f21SAdrian Hunter				" FROM comm_threads"
1166*b3700f21SAdrian Hunter				" INNER JOIN comms ON comms.id = comm_threads.comm_id"
1167*b3700f21SAdrian Hunter				" WHERE comm_threads.thread_id = " + str(thread_id) +
1168*b3700f21SAdrian Hunter				" ORDER BY comms.c_time, comms.id")
1169*b3700f21SAdrian Hunter	first = None
1170*b3700f21SAdrian Hunter	last = None
1171*b3700f21SAdrian Hunter	while query.next():
1172*b3700f21SAdrian Hunter		if first is None:
1173*b3700f21SAdrian Hunter			first = query.value(0)
1174*b3700f21SAdrian Hunter		if query.value(2) and Decimal(query.value(1)) <= Decimal(time):
1175*b3700f21SAdrian Hunter			last = query.value(0)
1176*b3700f21SAdrian Hunter	if not(last is None):
1177*b3700f21SAdrian Hunter		return last
1178*b3700f21SAdrian Hunter	return first
1179*b3700f21SAdrian Hunter
1180*b3700f21SAdrian Hunter# Container for (x, y) data
1181*b3700f21SAdrian Hunter
1182*b3700f21SAdrian Hunterclass XY():
1183*b3700f21SAdrian Hunter	def __init__(self, x=0, y=0):
1184*b3700f21SAdrian Hunter		self.x = x
1185*b3700f21SAdrian Hunter		self.y = y
1186*b3700f21SAdrian Hunter
1187*b3700f21SAdrian Hunter	def __str__(self):
1188*b3700f21SAdrian Hunter		return "XY({}, {})".format(str(self.x), str(self.y))
1189*b3700f21SAdrian Hunter
1190*b3700f21SAdrian Hunter# Container for sub-range data
1191*b3700f21SAdrian Hunter
1192*b3700f21SAdrian Hunterclass Subrange():
1193*b3700f21SAdrian Hunter	def __init__(self, lo=0, hi=0):
1194*b3700f21SAdrian Hunter		self.lo = lo
1195*b3700f21SAdrian Hunter		self.hi = hi
1196*b3700f21SAdrian Hunter
1197*b3700f21SAdrian Hunter	def __str__(self):
1198*b3700f21SAdrian Hunter		return "Subrange({}, {})".format(str(self.lo), str(self.hi))
1199*b3700f21SAdrian Hunter
1200*b3700f21SAdrian Hunter# Graph data region base class
1201*b3700f21SAdrian Hunter
1202*b3700f21SAdrian Hunterclass GraphDataRegion(object):
1203*b3700f21SAdrian Hunter
1204*b3700f21SAdrian Hunter	def __init__(self, key, title = "", ordinal = ""):
1205*b3700f21SAdrian Hunter		self.key = key
1206*b3700f21SAdrian Hunter		self.title = title
1207*b3700f21SAdrian Hunter		self.ordinal = ordinal
1208*b3700f21SAdrian Hunter
1209*b3700f21SAdrian Hunter# Function to sort GraphDataRegion
1210*b3700f21SAdrian Hunter
1211*b3700f21SAdrian Hunterdef GraphDataRegionOrdinal(data_region):
1212*b3700f21SAdrian Hunter	return data_region.ordinal
1213*b3700f21SAdrian Hunter
1214*b3700f21SAdrian Hunter# Attributes for a graph region
1215*b3700f21SAdrian Hunter
1216*b3700f21SAdrian Hunterclass GraphRegionAttribute():
1217*b3700f21SAdrian Hunter
1218*b3700f21SAdrian Hunter	def __init__(self, colour):
1219*b3700f21SAdrian Hunter		self.colour = colour
1220*b3700f21SAdrian Hunter
1221*b3700f21SAdrian Hunter# Switch graph data region represents a task
1222*b3700f21SAdrian Hunter
1223*b3700f21SAdrian Hunterclass SwitchGraphDataRegion(GraphDataRegion):
1224*b3700f21SAdrian Hunter
1225*b3700f21SAdrian Hunter	def __init__(self, key, exec_comm_id, pid, tid, comm, thread_id, comm_id):
1226*b3700f21SAdrian Hunter		super(SwitchGraphDataRegion, self).__init__(key)
1227*b3700f21SAdrian Hunter
1228*b3700f21SAdrian Hunter		self.title = str(pid) + " / " + str(tid) + " " + comm
1229*b3700f21SAdrian Hunter		# Order graph legend within exec comm by pid / tid / time
1230*b3700f21SAdrian Hunter		self.ordinal = str(pid).rjust(16) + str(exec_comm_id).rjust(8) + str(tid).rjust(16)
1231*b3700f21SAdrian Hunter		self.exec_comm_id = exec_comm_id
1232*b3700f21SAdrian Hunter		self.pid = pid
1233*b3700f21SAdrian Hunter		self.tid = tid
1234*b3700f21SAdrian Hunter		self.comm = comm
1235*b3700f21SAdrian Hunter		self.thread_id = thread_id
1236*b3700f21SAdrian Hunter		self.comm_id = comm_id
1237*b3700f21SAdrian Hunter
1238*b3700f21SAdrian Hunter# Graph data point
1239*b3700f21SAdrian Hunter
1240*b3700f21SAdrian Hunterclass GraphDataPoint():
1241*b3700f21SAdrian Hunter
1242*b3700f21SAdrian Hunter	def __init__(self, data, index, x, y, altx=None, alty=None, hregion=None, vregion=None):
1243*b3700f21SAdrian Hunter		self.data = data
1244*b3700f21SAdrian Hunter		self.index = index
1245*b3700f21SAdrian Hunter		self.x = x
1246*b3700f21SAdrian Hunter		self.y = y
1247*b3700f21SAdrian Hunter		self.altx = altx
1248*b3700f21SAdrian Hunter		self.alty = alty
1249*b3700f21SAdrian Hunter		self.hregion = hregion
1250*b3700f21SAdrian Hunter		self.vregion = vregion
1251*b3700f21SAdrian Hunter
1252*b3700f21SAdrian Hunter# Graph data (single graph) base class
1253*b3700f21SAdrian Hunter
1254*b3700f21SAdrian Hunterclass GraphData(object):
1255*b3700f21SAdrian Hunter
1256*b3700f21SAdrian Hunter	def __init__(self, collection, xbase=Decimal(0), ybase=Decimal(0)):
1257*b3700f21SAdrian Hunter		self.collection = collection
1258*b3700f21SAdrian Hunter		self.points = []
1259*b3700f21SAdrian Hunter		self.xbase = xbase
1260*b3700f21SAdrian Hunter		self.ybase = ybase
1261*b3700f21SAdrian Hunter		self.title = ""
1262*b3700f21SAdrian Hunter
1263*b3700f21SAdrian Hunter	def AddPoint(self, x, y, altx=None, alty=None, hregion=None, vregion=None):
1264*b3700f21SAdrian Hunter		index = len(self.points)
1265*b3700f21SAdrian Hunter
1266*b3700f21SAdrian Hunter		x = float(Decimal(x) - self.xbase)
1267*b3700f21SAdrian Hunter		y = float(Decimal(y) - self.ybase)
1268*b3700f21SAdrian Hunter
1269*b3700f21SAdrian Hunter		self.points.append(GraphDataPoint(self, index, x, y, altx, alty, hregion, vregion))
1270*b3700f21SAdrian Hunter
1271*b3700f21SAdrian Hunter	def XToData(self, x):
1272*b3700f21SAdrian Hunter		return Decimal(x) + self.xbase
1273*b3700f21SAdrian Hunter
1274*b3700f21SAdrian Hunter	def YToData(self, y):
1275*b3700f21SAdrian Hunter		return Decimal(y) + self.ybase
1276*b3700f21SAdrian Hunter
1277*b3700f21SAdrian Hunter# Switch graph data (for one CPU)
1278*b3700f21SAdrian Hunter
1279*b3700f21SAdrian Hunterclass SwitchGraphData(GraphData):
1280*b3700f21SAdrian Hunter
1281*b3700f21SAdrian Hunter	def __init__(self, db, collection, cpu, xbase):
1282*b3700f21SAdrian Hunter		super(SwitchGraphData, self).__init__(collection, xbase)
1283*b3700f21SAdrian Hunter
1284*b3700f21SAdrian Hunter		self.cpu = cpu
1285*b3700f21SAdrian Hunter		self.title = "CPU " + str(cpu)
1286*b3700f21SAdrian Hunter		self.SelectSwitches(db)
1287*b3700f21SAdrian Hunter
1288*b3700f21SAdrian Hunter	def SelectComms(self, db, thread_id, last_comm_id, start_time, end_time):
1289*b3700f21SAdrian Hunter		query = QSqlQuery(db)
1290*b3700f21SAdrian Hunter		QueryExec(query, "SELECT id, c_time"
1291*b3700f21SAdrian Hunter					" FROM comms"
1292*b3700f21SAdrian Hunter					" WHERE c_thread_id = " + str(thread_id) +
1293*b3700f21SAdrian Hunter					"   AND exec_flag = TRUE"
1294*b3700f21SAdrian Hunter					"   AND c_time >= " + str(start_time) +
1295*b3700f21SAdrian Hunter					"   AND c_time <= " + str(end_time) +
1296*b3700f21SAdrian Hunter					" ORDER BY c_time, id")
1297*b3700f21SAdrian Hunter		while query.next():
1298*b3700f21SAdrian Hunter			comm_id = query.value(0)
1299*b3700f21SAdrian Hunter			if comm_id == last_comm_id:
1300*b3700f21SAdrian Hunter				continue
1301*b3700f21SAdrian Hunter			time = query.value(1)
1302*b3700f21SAdrian Hunter			hregion = self.HRegion(db, thread_id, comm_id, time)
1303*b3700f21SAdrian Hunter			self.AddPoint(time, 1000, None, None, hregion)
1304*b3700f21SAdrian Hunter
1305*b3700f21SAdrian Hunter	def SelectSwitches(self, db):
1306*b3700f21SAdrian Hunter		last_time = None
1307*b3700f21SAdrian Hunter		last_comm_id = None
1308*b3700f21SAdrian Hunter		last_thread_id = None
1309*b3700f21SAdrian Hunter		query = QSqlQuery(db)
1310*b3700f21SAdrian Hunter		QueryExec(query, "SELECT time, thread_out_id, thread_in_id, comm_out_id, comm_in_id, flags"
1311*b3700f21SAdrian Hunter					" FROM context_switches"
1312*b3700f21SAdrian Hunter					" WHERE machine_id = " + str(self.collection.machine_id) +
1313*b3700f21SAdrian Hunter					"   AND cpu = " + str(self.cpu) +
1314*b3700f21SAdrian Hunter					" ORDER BY time, id")
1315*b3700f21SAdrian Hunter		while query.next():
1316*b3700f21SAdrian Hunter			flags = int(query.value(5))
1317*b3700f21SAdrian Hunter			if flags & 1:
1318*b3700f21SAdrian Hunter				# Schedule-out: detect and add exec's
1319*b3700f21SAdrian Hunter				if last_thread_id == query.value(1) and last_comm_id is not None and last_comm_id != query.value(3):
1320*b3700f21SAdrian Hunter					self.SelectComms(db, last_thread_id, last_comm_id, last_time, query.value(0))
1321*b3700f21SAdrian Hunter				continue
1322*b3700f21SAdrian Hunter			# Schedule-in: add data point
1323*b3700f21SAdrian Hunter			if len(self.points) == 0:
1324*b3700f21SAdrian Hunter				start_time = self.collection.glb.StartTime(self.collection.machine_id)
1325*b3700f21SAdrian Hunter				hregion = self.HRegion(db, query.value(1), query.value(3), start_time)
1326*b3700f21SAdrian Hunter				self.AddPoint(start_time, 1000, None, None, hregion)
1327*b3700f21SAdrian Hunter			time = query.value(0)
1328*b3700f21SAdrian Hunter			comm_id = query.value(4)
1329*b3700f21SAdrian Hunter			thread_id = query.value(2)
1330*b3700f21SAdrian Hunter			hregion = self.HRegion(db, thread_id, comm_id, time)
1331*b3700f21SAdrian Hunter			self.AddPoint(time, 1000, None, None, hregion)
1332*b3700f21SAdrian Hunter			last_time = time
1333*b3700f21SAdrian Hunter			last_comm_id = comm_id
1334*b3700f21SAdrian Hunter			last_thread_id = thread_id
1335*b3700f21SAdrian Hunter
1336*b3700f21SAdrian Hunter	def NewHRegion(self, db, key, thread_id, comm_id, time):
1337*b3700f21SAdrian Hunter		exec_comm_id = ExecComm(db, thread_id, time)
1338*b3700f21SAdrian Hunter		query = QSqlQuery(db)
1339*b3700f21SAdrian Hunter		QueryExec(query, "SELECT pid, tid FROM threads WHERE id = " + str(thread_id))
1340*b3700f21SAdrian Hunter		if query.next():
1341*b3700f21SAdrian Hunter			pid = query.value(0)
1342*b3700f21SAdrian Hunter			tid = query.value(1)
1343*b3700f21SAdrian Hunter		else:
1344*b3700f21SAdrian Hunter			pid = -1
1345*b3700f21SAdrian Hunter			tid = -1
1346*b3700f21SAdrian Hunter		query = QSqlQuery(db)
1347*b3700f21SAdrian Hunter		QueryExec(query, "SELECT comm FROM comms WHERE id = " + str(comm_id))
1348*b3700f21SAdrian Hunter		if query.next():
1349*b3700f21SAdrian Hunter			comm = query.value(0)
1350*b3700f21SAdrian Hunter		else:
1351*b3700f21SAdrian Hunter			comm = ""
1352*b3700f21SAdrian Hunter		return SwitchGraphDataRegion(key, exec_comm_id, pid, tid, comm, thread_id, comm_id)
1353*b3700f21SAdrian Hunter
1354*b3700f21SAdrian Hunter	def HRegion(self, db, thread_id, comm_id, time):
1355*b3700f21SAdrian Hunter		key = str(thread_id) + ":" + str(comm_id)
1356*b3700f21SAdrian Hunter		hregion = self.collection.LookupHRegion(key)
1357*b3700f21SAdrian Hunter		if hregion is None:
1358*b3700f21SAdrian Hunter			hregion = self.NewHRegion(db, key, thread_id, comm_id, time)
1359*b3700f21SAdrian Hunter			self.collection.AddHRegion(key, hregion)
1360*b3700f21SAdrian Hunter		return hregion
1361*b3700f21SAdrian Hunter
1362*b3700f21SAdrian Hunter# Graph data collection (multiple related graphs) base class
1363*b3700f21SAdrian Hunter
1364*b3700f21SAdrian Hunterclass GraphDataCollection(object):
1365*b3700f21SAdrian Hunter
1366*b3700f21SAdrian Hunter	def __init__(self, glb):
1367*b3700f21SAdrian Hunter		self.glb = glb
1368*b3700f21SAdrian Hunter		self.data = []
1369*b3700f21SAdrian Hunter		self.hregions = {}
1370*b3700f21SAdrian Hunter		self.xrangelo = None
1371*b3700f21SAdrian Hunter		self.xrangehi = None
1372*b3700f21SAdrian Hunter		self.yrangelo = None
1373*b3700f21SAdrian Hunter		self.yrangehi = None
1374*b3700f21SAdrian Hunter		self.dp = XY(0, 0)
1375*b3700f21SAdrian Hunter
1376*b3700f21SAdrian Hunter	def AddGraphData(self, data):
1377*b3700f21SAdrian Hunter		self.data.append(data)
1378*b3700f21SAdrian Hunter
1379*b3700f21SAdrian Hunter	def LookupHRegion(self, key):
1380*b3700f21SAdrian Hunter		if key in self.hregions:
1381*b3700f21SAdrian Hunter			return self.hregions[key]
1382*b3700f21SAdrian Hunter		return None
1383*b3700f21SAdrian Hunter
1384*b3700f21SAdrian Hunter	def AddHRegion(self, key, hregion):
1385*b3700f21SAdrian Hunter		self.hregions[key] = hregion
1386*b3700f21SAdrian Hunter
1387*b3700f21SAdrian Hunter# Switch graph data collection (SwitchGraphData for each CPU)
1388*b3700f21SAdrian Hunter
1389*b3700f21SAdrian Hunterclass SwitchGraphDataCollection(GraphDataCollection):
1390*b3700f21SAdrian Hunter
1391*b3700f21SAdrian Hunter	def __init__(self, glb, db, machine_id):
1392*b3700f21SAdrian Hunter		super(SwitchGraphDataCollection, self).__init__(glb)
1393*b3700f21SAdrian Hunter
1394*b3700f21SAdrian Hunter		self.machine_id = machine_id
1395*b3700f21SAdrian Hunter		self.cpus = self.SelectCPUs(db)
1396*b3700f21SAdrian Hunter
1397*b3700f21SAdrian Hunter		self.xrangelo = glb.StartTime(machine_id)
1398*b3700f21SAdrian Hunter		self.xrangehi = glb.FinishTime(machine_id)
1399*b3700f21SAdrian Hunter
1400*b3700f21SAdrian Hunter		self.yrangelo = Decimal(0)
1401*b3700f21SAdrian Hunter		self.yrangehi = Decimal(1000)
1402*b3700f21SAdrian Hunter
1403*b3700f21SAdrian Hunter		for cpu in self.cpus:
1404*b3700f21SAdrian Hunter			self.AddGraphData(SwitchGraphData(db, self, cpu, self.xrangelo))
1405*b3700f21SAdrian Hunter
1406*b3700f21SAdrian Hunter	def SelectCPUs(self, db):
1407*b3700f21SAdrian Hunter		cpus = []
1408*b3700f21SAdrian Hunter		query = QSqlQuery(db)
1409*b3700f21SAdrian Hunter		QueryExec(query, "SELECT DISTINCT cpu"
1410*b3700f21SAdrian Hunter					" FROM context_switches"
1411*b3700f21SAdrian Hunter					" WHERE machine_id = " + str(self.machine_id))
1412*b3700f21SAdrian Hunter		while query.next():
1413*b3700f21SAdrian Hunter			cpus.append(int(query.value(0)))
1414*b3700f21SAdrian Hunter		return sorted(cpus)
1415*b3700f21SAdrian Hunter
1416*b3700f21SAdrian Hunter# Switch graph data graphics item displays the graphed data
1417*b3700f21SAdrian Hunter
1418*b3700f21SAdrian Hunterclass SwitchGraphDataGraphicsItem(QGraphicsItem):
1419*b3700f21SAdrian Hunter
1420*b3700f21SAdrian Hunter	def __init__(self, data, graph_width, graph_height, attrs, event_handler, parent=None):
1421*b3700f21SAdrian Hunter		super(SwitchGraphDataGraphicsItem, self).__init__(parent)
1422*b3700f21SAdrian Hunter
1423*b3700f21SAdrian Hunter		self.data = data
1424*b3700f21SAdrian Hunter		self.graph_width = graph_width
1425*b3700f21SAdrian Hunter		self.graph_height = graph_height
1426*b3700f21SAdrian Hunter		self.attrs = attrs
1427*b3700f21SAdrian Hunter		self.event_handler = event_handler
1428*b3700f21SAdrian Hunter		self.setAcceptHoverEvents(True)
1429*b3700f21SAdrian Hunter
1430*b3700f21SAdrian Hunter	def boundingRect(self):
1431*b3700f21SAdrian Hunter		return QRectF(0, 0, self.graph_width, self.graph_height)
1432*b3700f21SAdrian Hunter
1433*b3700f21SAdrian Hunter	def PaintPoint(self, painter, last, x):
1434*b3700f21SAdrian Hunter		if not(last is None or last.hregion.pid == 0 or x < self.attrs.subrange.x.lo):
1435*b3700f21SAdrian Hunter			if last.x < self.attrs.subrange.x.lo:
1436*b3700f21SAdrian Hunter				x0 = self.attrs.subrange.x.lo
1437*b3700f21SAdrian Hunter			else:
1438*b3700f21SAdrian Hunter				x0 = last.x
1439*b3700f21SAdrian Hunter			if x > self.attrs.subrange.x.hi:
1440*b3700f21SAdrian Hunter				x1 = self.attrs.subrange.x.hi
1441*b3700f21SAdrian Hunter			else:
1442*b3700f21SAdrian Hunter				x1 = x - 1
1443*b3700f21SAdrian Hunter			x0 = self.attrs.XToPixel(x0)
1444*b3700f21SAdrian Hunter			x1 = self.attrs.XToPixel(x1)
1445*b3700f21SAdrian Hunter
1446*b3700f21SAdrian Hunter			y0 = self.attrs.YToPixel(last.y)
1447*b3700f21SAdrian Hunter
1448*b3700f21SAdrian Hunter			colour = self.attrs.region_attributes[last.hregion.key].colour
1449*b3700f21SAdrian Hunter
1450*b3700f21SAdrian Hunter			width = x1 - x0 + 1
1451*b3700f21SAdrian Hunter			if width < 2:
1452*b3700f21SAdrian Hunter				painter.setPen(colour)
1453*b3700f21SAdrian Hunter				painter.drawLine(x0, self.graph_height - y0, x0, self.graph_height)
1454*b3700f21SAdrian Hunter			else:
1455*b3700f21SAdrian Hunter				painter.fillRect(x0, self.graph_height - y0, width, self.graph_height - 1, colour)
1456*b3700f21SAdrian Hunter
1457*b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1458*b3700f21SAdrian Hunter		last = None
1459*b3700f21SAdrian Hunter		for point in self.data.points:
1460*b3700f21SAdrian Hunter			self.PaintPoint(painter, last, point.x)
1461*b3700f21SAdrian Hunter			if point.x > self.attrs.subrange.x.hi:
1462*b3700f21SAdrian Hunter				break;
1463*b3700f21SAdrian Hunter			last = point
1464*b3700f21SAdrian Hunter		self.PaintPoint(painter, last, self.attrs.subrange.x.hi + 1)
1465*b3700f21SAdrian Hunter
1466*b3700f21SAdrian Hunter	def BinarySearchPoint(self, target):
1467*b3700f21SAdrian Hunter		lower_pos = 0
1468*b3700f21SAdrian Hunter		higher_pos = len(self.data.points)
1469*b3700f21SAdrian Hunter		while True:
1470*b3700f21SAdrian Hunter			pos = int((lower_pos + higher_pos) / 2)
1471*b3700f21SAdrian Hunter			val = self.data.points[pos].x
1472*b3700f21SAdrian Hunter			if target >= val:
1473*b3700f21SAdrian Hunter				lower_pos = pos
1474*b3700f21SAdrian Hunter			else:
1475*b3700f21SAdrian Hunter				higher_pos = pos
1476*b3700f21SAdrian Hunter			if higher_pos <= lower_pos + 1:
1477*b3700f21SAdrian Hunter				return lower_pos
1478*b3700f21SAdrian Hunter
1479*b3700f21SAdrian Hunter	def XPixelToData(self, x):
1480*b3700f21SAdrian Hunter		x = self.attrs.PixelToX(x)
1481*b3700f21SAdrian Hunter		if x < self.data.points[0].x:
1482*b3700f21SAdrian Hunter			x = 0
1483*b3700f21SAdrian Hunter			pos = 0
1484*b3700f21SAdrian Hunter			low = True
1485*b3700f21SAdrian Hunter		else:
1486*b3700f21SAdrian Hunter			pos = self.BinarySearchPoint(x)
1487*b3700f21SAdrian Hunter			low = False
1488*b3700f21SAdrian Hunter		return (low, pos, self.data.XToData(x))
1489*b3700f21SAdrian Hunter
1490*b3700f21SAdrian Hunter	def EventToData(self, event):
1491*b3700f21SAdrian Hunter		no_data = (None,) * 4
1492*b3700f21SAdrian Hunter		if len(self.data.points) < 1:
1493*b3700f21SAdrian Hunter			return no_data
1494*b3700f21SAdrian Hunter		x = event.pos().x()
1495*b3700f21SAdrian Hunter		if x < 0:
1496*b3700f21SAdrian Hunter			return no_data
1497*b3700f21SAdrian Hunter		low0, pos0, time_from = self.XPixelToData(x)
1498*b3700f21SAdrian Hunter		low1, pos1, time_to = self.XPixelToData(x + 1)
1499*b3700f21SAdrian Hunter		hregions = set()
1500*b3700f21SAdrian Hunter		hregion_times = []
1501*b3700f21SAdrian Hunter		if not low1:
1502*b3700f21SAdrian Hunter			for i in xrange(pos0, pos1 + 1):
1503*b3700f21SAdrian Hunter				hregion = self.data.points[i].hregion
1504*b3700f21SAdrian Hunter				hregions.add(hregion)
1505*b3700f21SAdrian Hunter				if i == pos0:
1506*b3700f21SAdrian Hunter					time = time_from
1507*b3700f21SAdrian Hunter				else:
1508*b3700f21SAdrian Hunter					time = self.data.XToData(self.data.points[i].x)
1509*b3700f21SAdrian Hunter				hregion_times.append((hregion, time))
1510*b3700f21SAdrian Hunter		return (time_from, time_to, hregions, hregion_times)
1511*b3700f21SAdrian Hunter
1512*b3700f21SAdrian Hunter	def hoverMoveEvent(self, event):
1513*b3700f21SAdrian Hunter		time_from, time_to, hregions, hregion_times = self.EventToData(event)
1514*b3700f21SAdrian Hunter		if time_from is not None:
1515*b3700f21SAdrian Hunter			self.event_handler.PointEvent(self.data.cpu, time_from, time_to, hregions)
1516*b3700f21SAdrian Hunter
1517*b3700f21SAdrian Hunter	def hoverLeaveEvent(self, event):
1518*b3700f21SAdrian Hunter		self.event_handler.NoPointEvent()
1519*b3700f21SAdrian Hunter
1520*b3700f21SAdrian Hunter	def mousePressEvent(self, event):
1521*b3700f21SAdrian Hunter		if event.button() != Qt.RightButton:
1522*b3700f21SAdrian Hunter			super(SwitchGraphDataGraphicsItem, self).mousePressEvent(event)
1523*b3700f21SAdrian Hunter			return
1524*b3700f21SAdrian Hunter		time_from, time_to, hregions, hregion_times = self.EventToData(event)
1525*b3700f21SAdrian Hunter		if hregion_times:
1526*b3700f21SAdrian Hunter			self.event_handler.RightClickEvent(self.data.cpu, hregion_times, event.screenPos())
1527*b3700f21SAdrian Hunter
1528*b3700f21SAdrian Hunter# X-axis graphics item
1529*b3700f21SAdrian Hunter
1530*b3700f21SAdrian Hunterclass XAxisGraphicsItem(QGraphicsItem):
1531*b3700f21SAdrian Hunter
1532*b3700f21SAdrian Hunter	def __init__(self, width, parent=None):
1533*b3700f21SAdrian Hunter		super(XAxisGraphicsItem, self).__init__(parent)
1534*b3700f21SAdrian Hunter
1535*b3700f21SAdrian Hunter		self.width = width
1536*b3700f21SAdrian Hunter		self.max_mark_sz = 4
1537*b3700f21SAdrian Hunter		self.height = self.max_mark_sz + 1
1538*b3700f21SAdrian Hunter
1539*b3700f21SAdrian Hunter	def boundingRect(self):
1540*b3700f21SAdrian Hunter		return QRectF(0, 0, self.width, self.height)
1541*b3700f21SAdrian Hunter
1542*b3700f21SAdrian Hunter	def Step(self):
1543*b3700f21SAdrian Hunter		attrs = self.parentItem().attrs
1544*b3700f21SAdrian Hunter		subrange = attrs.subrange.x
1545*b3700f21SAdrian Hunter		t = subrange.hi - subrange.lo
1546*b3700f21SAdrian Hunter		s = (3.0 * t) / self.width
1547*b3700f21SAdrian Hunter		n = 1.0
1548*b3700f21SAdrian Hunter		while s > n:
1549*b3700f21SAdrian Hunter			n = n * 10.0
1550*b3700f21SAdrian Hunter		return n
1551*b3700f21SAdrian Hunter
1552*b3700f21SAdrian Hunter	def PaintMarks(self, painter, at_y, lo, hi, step, i):
1553*b3700f21SAdrian Hunter		attrs = self.parentItem().attrs
1554*b3700f21SAdrian Hunter		x = lo
1555*b3700f21SAdrian Hunter		while x <= hi:
1556*b3700f21SAdrian Hunter			xp = attrs.XToPixel(x)
1557*b3700f21SAdrian Hunter			if i % 10:
1558*b3700f21SAdrian Hunter				if i % 5:
1559*b3700f21SAdrian Hunter					sz = 1
1560*b3700f21SAdrian Hunter				else:
1561*b3700f21SAdrian Hunter					sz = 2
1562*b3700f21SAdrian Hunter			else:
1563*b3700f21SAdrian Hunter				sz = self.max_mark_sz
1564*b3700f21SAdrian Hunter				i = 0
1565*b3700f21SAdrian Hunter			painter.drawLine(xp, at_y, xp, at_y + sz)
1566*b3700f21SAdrian Hunter			x += step
1567*b3700f21SAdrian Hunter			i += 1
1568*b3700f21SAdrian Hunter
1569*b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1570*b3700f21SAdrian Hunter		# Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1
1571*b3700f21SAdrian Hunter		painter.drawLine(0, 0, self.width - 1, 0)
1572*b3700f21SAdrian Hunter		n = self.Step()
1573*b3700f21SAdrian Hunter		attrs = self.parentItem().attrs
1574*b3700f21SAdrian Hunter		subrange = attrs.subrange.x
1575*b3700f21SAdrian Hunter		if subrange.lo:
1576*b3700f21SAdrian Hunter			x_offset = n - (subrange.lo % n)
1577*b3700f21SAdrian Hunter		else:
1578*b3700f21SAdrian Hunter			x_offset = 0.0
1579*b3700f21SAdrian Hunter		x = subrange.lo + x_offset
1580*b3700f21SAdrian Hunter		i = (x / n) % 10
1581*b3700f21SAdrian Hunter		self.PaintMarks(painter, 0, x, subrange.hi, n, i)
1582*b3700f21SAdrian Hunter
1583*b3700f21SAdrian Hunter	def ScaleDimensions(self):
1584*b3700f21SAdrian Hunter		n = self.Step()
1585*b3700f21SAdrian Hunter		attrs = self.parentItem().attrs
1586*b3700f21SAdrian Hunter		lo = attrs.subrange.x.lo
1587*b3700f21SAdrian Hunter		hi = (n * 10.0) + lo
1588*b3700f21SAdrian Hunter		width = attrs.XToPixel(hi)
1589*b3700f21SAdrian Hunter		if width > 500:
1590*b3700f21SAdrian Hunter			width = 0
1591*b3700f21SAdrian Hunter		return (n, lo, hi, width)
1592*b3700f21SAdrian Hunter
1593*b3700f21SAdrian Hunter	def PaintScale(self, painter, at_x, at_y):
1594*b3700f21SAdrian Hunter		n, lo, hi, width = self.ScaleDimensions()
1595*b3700f21SAdrian Hunter		if not width:
1596*b3700f21SAdrian Hunter			return
1597*b3700f21SAdrian Hunter		painter.drawLine(at_x, at_y, at_x + width, at_y)
1598*b3700f21SAdrian Hunter		self.PaintMarks(painter, at_y, lo, hi, n, 0)
1599*b3700f21SAdrian Hunter
1600*b3700f21SAdrian Hunter	def ScaleWidth(self):
1601*b3700f21SAdrian Hunter		n, lo, hi, width = self.ScaleDimensions()
1602*b3700f21SAdrian Hunter		return width
1603*b3700f21SAdrian Hunter
1604*b3700f21SAdrian Hunter	def ScaleHeight(self):
1605*b3700f21SAdrian Hunter		return self.height
1606*b3700f21SAdrian Hunter
1607*b3700f21SAdrian Hunter	def ScaleUnit(self):
1608*b3700f21SAdrian Hunter		return self.Step() * 10
1609*b3700f21SAdrian Hunter
1610*b3700f21SAdrian Hunter# Scale graphics item base class
1611*b3700f21SAdrian Hunter
1612*b3700f21SAdrian Hunterclass ScaleGraphicsItem(QGraphicsItem):
1613*b3700f21SAdrian Hunter
1614*b3700f21SAdrian Hunter	def __init__(self, axis, parent=None):
1615*b3700f21SAdrian Hunter		super(ScaleGraphicsItem, self).__init__(parent)
1616*b3700f21SAdrian Hunter		self.axis = axis
1617*b3700f21SAdrian Hunter
1618*b3700f21SAdrian Hunter	def boundingRect(self):
1619*b3700f21SAdrian Hunter		scale_width = self.axis.ScaleWidth()
1620*b3700f21SAdrian Hunter		if not scale_width:
1621*b3700f21SAdrian Hunter			return QRectF()
1622*b3700f21SAdrian Hunter		return QRectF(0, 0, self.axis.ScaleWidth() + 100, self.axis.ScaleHeight())
1623*b3700f21SAdrian Hunter
1624*b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1625*b3700f21SAdrian Hunter		scale_width = self.axis.ScaleWidth()
1626*b3700f21SAdrian Hunter		if not scale_width:
1627*b3700f21SAdrian Hunter			return
1628*b3700f21SAdrian Hunter		self.axis.PaintScale(painter, 0, 5)
1629*b3700f21SAdrian Hunter		x = scale_width + 4
1630*b3700f21SAdrian Hunter		painter.drawText(QPointF(x, 10), self.Text())
1631*b3700f21SAdrian Hunter
1632*b3700f21SAdrian Hunter	def Unit(self):
1633*b3700f21SAdrian Hunter		return self.axis.ScaleUnit()
1634*b3700f21SAdrian Hunter
1635*b3700f21SAdrian Hunter	def Text(self):
1636*b3700f21SAdrian Hunter		return ""
1637*b3700f21SAdrian Hunter
1638*b3700f21SAdrian Hunter# Switch graph scale graphics item
1639*b3700f21SAdrian Hunter
1640*b3700f21SAdrian Hunterclass SwitchScaleGraphicsItem(ScaleGraphicsItem):
1641*b3700f21SAdrian Hunter
1642*b3700f21SAdrian Hunter	def __init__(self, axis, parent=None):
1643*b3700f21SAdrian Hunter		super(SwitchScaleGraphicsItem, self).__init__(axis, parent)
1644*b3700f21SAdrian Hunter
1645*b3700f21SAdrian Hunter	def Text(self):
1646*b3700f21SAdrian Hunter		unit = self.Unit()
1647*b3700f21SAdrian Hunter		if unit >= 1000000000:
1648*b3700f21SAdrian Hunter			unit = int(unit / 1000000000)
1649*b3700f21SAdrian Hunter			us = "s"
1650*b3700f21SAdrian Hunter		elif unit >= 1000000:
1651*b3700f21SAdrian Hunter			unit = int(unit / 1000000)
1652*b3700f21SAdrian Hunter			us = "ms"
1653*b3700f21SAdrian Hunter		elif unit >= 1000:
1654*b3700f21SAdrian Hunter			unit = int(unit / 1000)
1655*b3700f21SAdrian Hunter			us = "us"
1656*b3700f21SAdrian Hunter		else:
1657*b3700f21SAdrian Hunter			unit = int(unit)
1658*b3700f21SAdrian Hunter			us = "ns"
1659*b3700f21SAdrian Hunter		return " = " + str(unit) + " " + us
1660*b3700f21SAdrian Hunter
1661*b3700f21SAdrian Hunter# Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data
1662*b3700f21SAdrian Hunter
1663*b3700f21SAdrian Hunterclass SwitchGraphGraphicsItem(QGraphicsItem):
1664*b3700f21SAdrian Hunter
1665*b3700f21SAdrian Hunter	def __init__(self, collection, data, attrs, event_handler, first, parent=None):
1666*b3700f21SAdrian Hunter		super(SwitchGraphGraphicsItem, self).__init__(parent)
1667*b3700f21SAdrian Hunter		self.collection = collection
1668*b3700f21SAdrian Hunter		self.data = data
1669*b3700f21SAdrian Hunter		self.attrs = attrs
1670*b3700f21SAdrian Hunter		self.event_handler = event_handler
1671*b3700f21SAdrian Hunter
1672*b3700f21SAdrian Hunter		margin = 20
1673*b3700f21SAdrian Hunter		title_width = 50
1674*b3700f21SAdrian Hunter
1675*b3700f21SAdrian Hunter		self.title_graphics = QGraphicsSimpleTextItem(data.title, self)
1676*b3700f21SAdrian Hunter
1677*b3700f21SAdrian Hunter		self.title_graphics.setPos(margin, margin)
1678*b3700f21SAdrian Hunter		graph_width = attrs.XToPixel(attrs.subrange.x.hi) + 1
1679*b3700f21SAdrian Hunter		graph_height = attrs.YToPixel(attrs.subrange.y.hi) + 1
1680*b3700f21SAdrian Hunter
1681*b3700f21SAdrian Hunter		self.graph_origin_x = margin + title_width + margin
1682*b3700f21SAdrian Hunter		self.graph_origin_y = graph_height + margin
1683*b3700f21SAdrian Hunter
1684*b3700f21SAdrian Hunter		x_axis_size = 1
1685*b3700f21SAdrian Hunter		y_axis_size = 1
1686*b3700f21SAdrian Hunter		self.yline = QGraphicsLineItem(0, 0, 0, graph_height, self)
1687*b3700f21SAdrian Hunter
1688*b3700f21SAdrian Hunter		self.x_axis = XAxisGraphicsItem(graph_width, self)
1689*b3700f21SAdrian Hunter		self.x_axis.setPos(self.graph_origin_x, self.graph_origin_y + 1)
1690*b3700f21SAdrian Hunter
1691*b3700f21SAdrian Hunter		if first:
1692*b3700f21SAdrian Hunter			self.scale_item = SwitchScaleGraphicsItem(self.x_axis, self)
1693*b3700f21SAdrian Hunter			self.scale_item.setPos(self.graph_origin_x, self.graph_origin_y + 10)
1694*b3700f21SAdrian Hunter
1695*b3700f21SAdrian Hunter		self.yline.setPos(self.graph_origin_x - y_axis_size, self.graph_origin_y - graph_height)
1696*b3700f21SAdrian Hunter
1697*b3700f21SAdrian Hunter		self.axis_point = QGraphicsLineItem(0, 0, 0, 0, self)
1698*b3700f21SAdrian Hunter		self.axis_point.setPos(self.graph_origin_x - 1, self.graph_origin_y +1)
1699*b3700f21SAdrian Hunter
1700*b3700f21SAdrian Hunter		self.width = self.graph_origin_x + graph_width + margin
1701*b3700f21SAdrian Hunter		self.height = self.graph_origin_y + margin
1702*b3700f21SAdrian Hunter
1703*b3700f21SAdrian Hunter		self.graph = SwitchGraphDataGraphicsItem(data, graph_width, graph_height, attrs, event_handler, self)
1704*b3700f21SAdrian Hunter		self.graph.setPos(self.graph_origin_x, self.graph_origin_y - graph_height)
1705*b3700f21SAdrian Hunter
1706*b3700f21SAdrian Hunter		if parent and 'EnableRubberBand' in dir(parent):
1707*b3700f21SAdrian Hunter			parent.EnableRubberBand(self.graph_origin_x, self.graph_origin_x + graph_width - 1, self)
1708*b3700f21SAdrian Hunter
1709*b3700f21SAdrian Hunter	def boundingRect(self):
1710*b3700f21SAdrian Hunter		return QRectF(0, 0, self.width, self.height)
1711*b3700f21SAdrian Hunter
1712*b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1713*b3700f21SAdrian Hunter		pass
1714*b3700f21SAdrian Hunter
1715*b3700f21SAdrian Hunter	def RBXToPixel(self, x):
1716*b3700f21SAdrian Hunter		return self.attrs.PixelToX(x - self.graph_origin_x)
1717*b3700f21SAdrian Hunter
1718*b3700f21SAdrian Hunter	def RBXRangeToPixel(self, x0, x1):
1719*b3700f21SAdrian Hunter		return (self.RBXToPixel(x0), self.RBXToPixel(x1 + 1))
1720*b3700f21SAdrian Hunter
1721*b3700f21SAdrian Hunter	def RBPixelToTime(self, x):
1722*b3700f21SAdrian Hunter		if x < self.data.points[0].x:
1723*b3700f21SAdrian Hunter			return self.data.XToData(0)
1724*b3700f21SAdrian Hunter		return self.data.XToData(x)
1725*b3700f21SAdrian Hunter
1726*b3700f21SAdrian Hunter	def RBEventTimes(self, x0, x1):
1727*b3700f21SAdrian Hunter		x0, x1 = self.RBXRangeToPixel(x0, x1)
1728*b3700f21SAdrian Hunter		time_from = self.RBPixelToTime(x0)
1729*b3700f21SAdrian Hunter		time_to = self.RBPixelToTime(x1)
1730*b3700f21SAdrian Hunter		return (time_from, time_to)
1731*b3700f21SAdrian Hunter
1732*b3700f21SAdrian Hunter	def RBEvent(self, x0, x1):
1733*b3700f21SAdrian Hunter		time_from, time_to = self.RBEventTimes(x0, x1)
1734*b3700f21SAdrian Hunter		self.event_handler.RangeEvent(time_from, time_to)
1735*b3700f21SAdrian Hunter
1736*b3700f21SAdrian Hunter	def RBMoveEvent(self, x0, x1):
1737*b3700f21SAdrian Hunter		if x1 < x0:
1738*b3700f21SAdrian Hunter			x0, x1 = x1, x0
1739*b3700f21SAdrian Hunter		self.RBEvent(x0, x1)
1740*b3700f21SAdrian Hunter
1741*b3700f21SAdrian Hunter	def RBReleaseEvent(self, x0, x1, selection_state):
1742*b3700f21SAdrian Hunter		if x1 < x0:
1743*b3700f21SAdrian Hunter			x0, x1 = x1, x0
1744*b3700f21SAdrian Hunter		x0, x1 = self.RBXRangeToPixel(x0, x1)
1745*b3700f21SAdrian Hunter		self.event_handler.SelectEvent(x0, x1, selection_state)
1746*b3700f21SAdrian Hunter
1747*b3700f21SAdrian Hunter# Graphics item to draw a vertical bracket (used to highlight "forward" sub-range)
1748*b3700f21SAdrian Hunter
1749*b3700f21SAdrian Hunterclass VerticalBracketGraphicsItem(QGraphicsItem):
1750*b3700f21SAdrian Hunter
1751*b3700f21SAdrian Hunter	def __init__(self, parent=None):
1752*b3700f21SAdrian Hunter		super(VerticalBracketGraphicsItem, self).__init__(parent)
1753*b3700f21SAdrian Hunter
1754*b3700f21SAdrian Hunter		self.width = 0
1755*b3700f21SAdrian Hunter		self.height = 0
1756*b3700f21SAdrian Hunter		self.hide()
1757*b3700f21SAdrian Hunter
1758*b3700f21SAdrian Hunter	def SetSize(self, width, height):
1759*b3700f21SAdrian Hunter		self.width = width + 1
1760*b3700f21SAdrian Hunter		self.height = height + 1
1761*b3700f21SAdrian Hunter
1762*b3700f21SAdrian Hunter	def boundingRect(self):
1763*b3700f21SAdrian Hunter		return QRectF(0, 0, self.width, self.height)
1764*b3700f21SAdrian Hunter
1765*b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1766*b3700f21SAdrian Hunter		colour = QColor(255, 255, 0, 32)
1767*b3700f21SAdrian Hunter		painter.fillRect(0, 0, self.width, self.height, colour)
1768*b3700f21SAdrian Hunter		x1 = self.width - 1
1769*b3700f21SAdrian Hunter		y1 = self.height - 1
1770*b3700f21SAdrian Hunter		painter.drawLine(0, 0, x1, 0)
1771*b3700f21SAdrian Hunter		painter.drawLine(0, 0, 0, 3)
1772*b3700f21SAdrian Hunter		painter.drawLine(x1, 0, x1, 3)
1773*b3700f21SAdrian Hunter		painter.drawLine(0, y1, x1, y1)
1774*b3700f21SAdrian Hunter		painter.drawLine(0, y1, 0, y1 - 3)
1775*b3700f21SAdrian Hunter		painter.drawLine(x1, y1, x1, y1 - 3)
1776*b3700f21SAdrian Hunter
1777*b3700f21SAdrian Hunter# Graphics item to contain graphs arranged vertically
1778*b3700f21SAdrian Hunter
1779*b3700f21SAdrian Hunterclass VertcalGraphSetGraphicsItem(QGraphicsItem):
1780*b3700f21SAdrian Hunter
1781*b3700f21SAdrian Hunter	def __init__(self, collection, attrs, event_handler, child_class, parent=None):
1782*b3700f21SAdrian Hunter		super(VertcalGraphSetGraphicsItem, self).__init__(parent)
1783*b3700f21SAdrian Hunter
1784*b3700f21SAdrian Hunter		self.collection = collection
1785*b3700f21SAdrian Hunter
1786*b3700f21SAdrian Hunter		self.top = 10
1787*b3700f21SAdrian Hunter
1788*b3700f21SAdrian Hunter		self.width = 0
1789*b3700f21SAdrian Hunter		self.height = self.top
1790*b3700f21SAdrian Hunter
1791*b3700f21SAdrian Hunter		self.rubber_band = None
1792*b3700f21SAdrian Hunter		self.rb_enabled = False
1793*b3700f21SAdrian Hunter
1794*b3700f21SAdrian Hunter		first = True
1795*b3700f21SAdrian Hunter		for data in collection.data:
1796*b3700f21SAdrian Hunter			child = child_class(collection, data, attrs, event_handler, first, self)
1797*b3700f21SAdrian Hunter			child.setPos(0, self.height + 1)
1798*b3700f21SAdrian Hunter			rect = child.boundingRect()
1799*b3700f21SAdrian Hunter			if rect.right() > self.width:
1800*b3700f21SAdrian Hunter				self.width = rect.right()
1801*b3700f21SAdrian Hunter			self.height = self.height + rect.bottom() + 1
1802*b3700f21SAdrian Hunter			first = False
1803*b3700f21SAdrian Hunter
1804*b3700f21SAdrian Hunter		self.bracket = VerticalBracketGraphicsItem(self)
1805*b3700f21SAdrian Hunter
1806*b3700f21SAdrian Hunter	def EnableRubberBand(self, xlo, xhi, rb_event_handler):
1807*b3700f21SAdrian Hunter		if self.rb_enabled:
1808*b3700f21SAdrian Hunter			return
1809*b3700f21SAdrian Hunter		self.rb_enabled = True
1810*b3700f21SAdrian Hunter		self.rb_in_view = False
1811*b3700f21SAdrian Hunter		self.setAcceptedMouseButtons(Qt.LeftButton)
1812*b3700f21SAdrian Hunter		self.rb_xlo = xlo
1813*b3700f21SAdrian Hunter		self.rb_xhi = xhi
1814*b3700f21SAdrian Hunter		self.rb_event_handler = rb_event_handler
1815*b3700f21SAdrian Hunter		self.mousePressEvent = self.MousePressEvent
1816*b3700f21SAdrian Hunter		self.mouseMoveEvent = self.MouseMoveEvent
1817*b3700f21SAdrian Hunter		self.mouseReleaseEvent = self.MouseReleaseEvent
1818*b3700f21SAdrian Hunter
1819*b3700f21SAdrian Hunter	def boundingRect(self):
1820*b3700f21SAdrian Hunter		return QRectF(0, 0, self.width, self.height)
1821*b3700f21SAdrian Hunter
1822*b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1823*b3700f21SAdrian Hunter		pass
1824*b3700f21SAdrian Hunter
1825*b3700f21SAdrian Hunter	def RubberBandParent(self):
1826*b3700f21SAdrian Hunter		scene = self.scene()
1827*b3700f21SAdrian Hunter		view = scene.views()[0]
1828*b3700f21SAdrian Hunter		viewport = view.viewport()
1829*b3700f21SAdrian Hunter		return viewport
1830*b3700f21SAdrian Hunter
1831*b3700f21SAdrian Hunter	def RubberBandSetGeometry(self, rect):
1832*b3700f21SAdrian Hunter		scene_rectf = self.mapRectToScene(QRectF(rect))
1833*b3700f21SAdrian Hunter		scene = self.scene()
1834*b3700f21SAdrian Hunter		view = scene.views()[0]
1835*b3700f21SAdrian Hunter		poly = view.mapFromScene(scene_rectf)
1836*b3700f21SAdrian Hunter		self.rubber_band.setGeometry(poly.boundingRect())
1837*b3700f21SAdrian Hunter
1838*b3700f21SAdrian Hunter	def SetSelection(self, selection_state):
1839*b3700f21SAdrian Hunter		if self.rubber_band:
1840*b3700f21SAdrian Hunter			if selection_state:
1841*b3700f21SAdrian Hunter				self.RubberBandSetGeometry(selection_state)
1842*b3700f21SAdrian Hunter				self.rubber_band.show()
1843*b3700f21SAdrian Hunter			else:
1844*b3700f21SAdrian Hunter				self.rubber_band.hide()
1845*b3700f21SAdrian Hunter
1846*b3700f21SAdrian Hunter	def SetBracket(self, rect):
1847*b3700f21SAdrian Hunter		if rect:
1848*b3700f21SAdrian Hunter			x, y, width, height = rect.x(), rect.y(), rect.width(), rect.height()
1849*b3700f21SAdrian Hunter			self.bracket.setPos(x, y)
1850*b3700f21SAdrian Hunter			self.bracket.SetSize(width, height)
1851*b3700f21SAdrian Hunter			self.bracket.show()
1852*b3700f21SAdrian Hunter		else:
1853*b3700f21SAdrian Hunter			self.bracket.hide()
1854*b3700f21SAdrian Hunter
1855*b3700f21SAdrian Hunter	def RubberBandX(self, event):
1856*b3700f21SAdrian Hunter		x = event.pos().toPoint().x()
1857*b3700f21SAdrian Hunter		if x < self.rb_xlo:
1858*b3700f21SAdrian Hunter			x = self.rb_xlo
1859*b3700f21SAdrian Hunter		elif x > self.rb_xhi:
1860*b3700f21SAdrian Hunter			x = self.rb_xhi
1861*b3700f21SAdrian Hunter		else:
1862*b3700f21SAdrian Hunter			self.rb_in_view = True
1863*b3700f21SAdrian Hunter		return x
1864*b3700f21SAdrian Hunter
1865*b3700f21SAdrian Hunter	def RubberBandRect(self, x):
1866*b3700f21SAdrian Hunter		if self.rb_origin.x() <= x:
1867*b3700f21SAdrian Hunter			width = x - self.rb_origin.x()
1868*b3700f21SAdrian Hunter			rect = QRect(self.rb_origin, QSize(width, self.height))
1869*b3700f21SAdrian Hunter		else:
1870*b3700f21SAdrian Hunter			width = self.rb_origin.x() - x
1871*b3700f21SAdrian Hunter			top_left = QPoint(self.rb_origin.x() - width, self.rb_origin.y())
1872*b3700f21SAdrian Hunter			rect = QRect(top_left, QSize(width, self.height))
1873*b3700f21SAdrian Hunter		return rect
1874*b3700f21SAdrian Hunter
1875*b3700f21SAdrian Hunter	def MousePressEvent(self, event):
1876*b3700f21SAdrian Hunter		self.rb_in_view = False
1877*b3700f21SAdrian Hunter		x = self.RubberBandX(event)
1878*b3700f21SAdrian Hunter		self.rb_origin = QPoint(x, self.top)
1879*b3700f21SAdrian Hunter		if self.rubber_band is None:
1880*b3700f21SAdrian Hunter			self.rubber_band = QRubberBand(QRubberBand.Rectangle, self.RubberBandParent())
1881*b3700f21SAdrian Hunter		self.RubberBandSetGeometry(QRect(self.rb_origin, QSize(0, self.height)))
1882*b3700f21SAdrian Hunter		if self.rb_in_view:
1883*b3700f21SAdrian Hunter			self.rubber_band.show()
1884*b3700f21SAdrian Hunter			self.rb_event_handler.RBMoveEvent(x, x)
1885*b3700f21SAdrian Hunter		else:
1886*b3700f21SAdrian Hunter			self.rubber_band.hide()
1887*b3700f21SAdrian Hunter
1888*b3700f21SAdrian Hunter	def MouseMoveEvent(self, event):
1889*b3700f21SAdrian Hunter		x = self.RubberBandX(event)
1890*b3700f21SAdrian Hunter		rect = self.RubberBandRect(x)
1891*b3700f21SAdrian Hunter		self.RubberBandSetGeometry(rect)
1892*b3700f21SAdrian Hunter		if self.rb_in_view:
1893*b3700f21SAdrian Hunter			self.rubber_band.show()
1894*b3700f21SAdrian Hunter			self.rb_event_handler.RBMoveEvent(self.rb_origin.x(), x)
1895*b3700f21SAdrian Hunter
1896*b3700f21SAdrian Hunter	def MouseReleaseEvent(self, event):
1897*b3700f21SAdrian Hunter		x = self.RubberBandX(event)
1898*b3700f21SAdrian Hunter		if self.rb_in_view:
1899*b3700f21SAdrian Hunter			selection_state = self.RubberBandRect(x)
1900*b3700f21SAdrian Hunter		else:
1901*b3700f21SAdrian Hunter			selection_state = None
1902*b3700f21SAdrian Hunter		self.rb_event_handler.RBReleaseEvent(self.rb_origin.x(), x, selection_state)
1903*b3700f21SAdrian Hunter
1904*b3700f21SAdrian Hunter# Switch graph legend data model
1905*b3700f21SAdrian Hunter
1906*b3700f21SAdrian Hunterclass SwitchGraphLegendModel(QAbstractTableModel):
1907*b3700f21SAdrian Hunter
1908*b3700f21SAdrian Hunter	def __init__(self, collection, region_attributes, parent=None):
1909*b3700f21SAdrian Hunter		super(SwitchGraphLegendModel, self).__init__(parent)
1910*b3700f21SAdrian Hunter
1911*b3700f21SAdrian Hunter		self.region_attributes = region_attributes
1912*b3700f21SAdrian Hunter
1913*b3700f21SAdrian Hunter		self.child_items = sorted(collection.hregions.values(), key=GraphDataRegionOrdinal)
1914*b3700f21SAdrian Hunter		self.child_count = len(self.child_items)
1915*b3700f21SAdrian Hunter
1916*b3700f21SAdrian Hunter		self.highlight_set = set()
1917*b3700f21SAdrian Hunter
1918*b3700f21SAdrian Hunter		self.column_headers = ("pid", "tid", "comm")
1919*b3700f21SAdrian Hunter
1920*b3700f21SAdrian Hunter	def rowCount(self, parent):
1921*b3700f21SAdrian Hunter		return self.child_count
1922*b3700f21SAdrian Hunter
1923*b3700f21SAdrian Hunter	def headerData(self, section, orientation, role):
1924*b3700f21SAdrian Hunter		if role != Qt.DisplayRole:
1925*b3700f21SAdrian Hunter			return None
1926*b3700f21SAdrian Hunter		if orientation != Qt.Horizontal:
1927*b3700f21SAdrian Hunter			return None
1928*b3700f21SAdrian Hunter		return self.columnHeader(section)
1929*b3700f21SAdrian Hunter
1930*b3700f21SAdrian Hunter	def index(self, row, column, parent):
1931*b3700f21SAdrian Hunter		return self.createIndex(row, column, self.child_items[row])
1932*b3700f21SAdrian Hunter
1933*b3700f21SAdrian Hunter	def columnCount(self, parent=None):
1934*b3700f21SAdrian Hunter		return len(self.column_headers)
1935*b3700f21SAdrian Hunter
1936*b3700f21SAdrian Hunter	def columnHeader(self, column):
1937*b3700f21SAdrian Hunter		return self.column_headers[column]
1938*b3700f21SAdrian Hunter
1939*b3700f21SAdrian Hunter	def data(self, index, role):
1940*b3700f21SAdrian Hunter		if role == Qt.BackgroundRole:
1941*b3700f21SAdrian Hunter			child = self.child_items[index.row()]
1942*b3700f21SAdrian Hunter			if child in self.highlight_set:
1943*b3700f21SAdrian Hunter				return self.region_attributes[child.key].colour
1944*b3700f21SAdrian Hunter			return None
1945*b3700f21SAdrian Hunter		if role == Qt.ForegroundRole:
1946*b3700f21SAdrian Hunter			child = self.child_items[index.row()]
1947*b3700f21SAdrian Hunter			if child in self.highlight_set:
1948*b3700f21SAdrian Hunter				return QColor(255, 255, 255)
1949*b3700f21SAdrian Hunter			return self.region_attributes[child.key].colour
1950*b3700f21SAdrian Hunter		if role != Qt.DisplayRole:
1951*b3700f21SAdrian Hunter			return None
1952*b3700f21SAdrian Hunter		hregion = self.child_items[index.row()]
1953*b3700f21SAdrian Hunter		col = index.column()
1954*b3700f21SAdrian Hunter		if col == 0:
1955*b3700f21SAdrian Hunter			return hregion.pid
1956*b3700f21SAdrian Hunter		if col == 1:
1957*b3700f21SAdrian Hunter			return hregion.tid
1958*b3700f21SAdrian Hunter		if col == 2:
1959*b3700f21SAdrian Hunter			return hregion.comm
1960*b3700f21SAdrian Hunter		return None
1961*b3700f21SAdrian Hunter
1962*b3700f21SAdrian Hunter	def SetHighlight(self, row, set_highlight):
1963*b3700f21SAdrian Hunter		child = self.child_items[row]
1964*b3700f21SAdrian Hunter		top_left = self.createIndex(row, 0, child)
1965*b3700f21SAdrian Hunter		bottom_right = self.createIndex(row, len(self.column_headers) - 1, child)
1966*b3700f21SAdrian Hunter		self.dataChanged.emit(top_left, bottom_right)
1967*b3700f21SAdrian Hunter
1968*b3700f21SAdrian Hunter	def Highlight(self, highlight_set):
1969*b3700f21SAdrian Hunter		for row in xrange(self.child_count):
1970*b3700f21SAdrian Hunter			child = self.child_items[row]
1971*b3700f21SAdrian Hunter			if child in self.highlight_set:
1972*b3700f21SAdrian Hunter				if child not in highlight_set:
1973*b3700f21SAdrian Hunter					self.SetHighlight(row, False)
1974*b3700f21SAdrian Hunter			elif child in highlight_set:
1975*b3700f21SAdrian Hunter				self.SetHighlight(row, True)
1976*b3700f21SAdrian Hunter		self.highlight_set = highlight_set
1977*b3700f21SAdrian Hunter
1978*b3700f21SAdrian Hunter# Switch graph legend is a table
1979*b3700f21SAdrian Hunter
1980*b3700f21SAdrian Hunterclass SwitchGraphLegend(QWidget):
1981*b3700f21SAdrian Hunter
1982*b3700f21SAdrian Hunter	def __init__(self, collection, region_attributes, parent=None):
1983*b3700f21SAdrian Hunter		super(SwitchGraphLegend, self).__init__(parent)
1984*b3700f21SAdrian Hunter
1985*b3700f21SAdrian Hunter		self.data_model = SwitchGraphLegendModel(collection, region_attributes)
1986*b3700f21SAdrian Hunter
1987*b3700f21SAdrian Hunter		self.model = QSortFilterProxyModel()
1988*b3700f21SAdrian Hunter		self.model.setSourceModel(self.data_model)
1989*b3700f21SAdrian Hunter
1990*b3700f21SAdrian Hunter		self.view = QTableView()
1991*b3700f21SAdrian Hunter		self.view.setModel(self.model)
1992*b3700f21SAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
1993*b3700f21SAdrian Hunter		self.view.verticalHeader().setVisible(False)
1994*b3700f21SAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
1995*b3700f21SAdrian Hunter		self.view.setSortingEnabled(True)
1996*b3700f21SAdrian Hunter		self.view.resizeColumnsToContents()
1997*b3700f21SAdrian Hunter		self.view.resizeRowsToContents()
1998*b3700f21SAdrian Hunter
1999*b3700f21SAdrian Hunter		self.vbox = VBoxLayout(self.view)
2000*b3700f21SAdrian Hunter		self.setLayout(self.vbox)
2001*b3700f21SAdrian Hunter
2002*b3700f21SAdrian Hunter		sz1 = self.view.columnWidth(0) + self.view.columnWidth(1) + self.view.columnWidth(2) + 2
2003*b3700f21SAdrian Hunter		sz1 = sz1 + self.view.verticalScrollBar().sizeHint().width()
2004*b3700f21SAdrian Hunter		self.saved_size = sz1
2005*b3700f21SAdrian Hunter
2006*b3700f21SAdrian Hunter	def resizeEvent(self, event):
2007*b3700f21SAdrian Hunter		self.saved_size = self.size().width()
2008*b3700f21SAdrian Hunter		super(SwitchGraphLegend, self).resizeEvent(event)
2009*b3700f21SAdrian Hunter
2010*b3700f21SAdrian Hunter	def Highlight(self, highlight_set):
2011*b3700f21SAdrian Hunter		self.data_model.Highlight(highlight_set)
2012*b3700f21SAdrian Hunter		self.update()
2013*b3700f21SAdrian Hunter
2014*b3700f21SAdrian Hunter	def changeEvent(self, event):
2015*b3700f21SAdrian Hunter		if event.type() == QEvent.FontChange:
2016*b3700f21SAdrian Hunter			self.view.resizeRowsToContents()
2017*b3700f21SAdrian Hunter			self.view.resizeColumnsToContents()
2018*b3700f21SAdrian Hunter			# Need to resize rows again after column resize
2019*b3700f21SAdrian Hunter			self.view.resizeRowsToContents()
2020*b3700f21SAdrian Hunter		super(SwitchGraphLegend, self).changeEvent(event)
2021*b3700f21SAdrian Hunter
2022*b3700f21SAdrian Hunter# Random colour generation
2023*b3700f21SAdrian Hunter
2024*b3700f21SAdrian Hunterdef RGBColourTooLight(r, g, b):
2025*b3700f21SAdrian Hunter	if g > 230:
2026*b3700f21SAdrian Hunter		return True
2027*b3700f21SAdrian Hunter	if g <= 160:
2028*b3700f21SAdrian Hunter		return False
2029*b3700f21SAdrian Hunter	if r <= 180 and g <= 180:
2030*b3700f21SAdrian Hunter		return False
2031*b3700f21SAdrian Hunter	if r < 60:
2032*b3700f21SAdrian Hunter		return False
2033*b3700f21SAdrian Hunter	return True
2034*b3700f21SAdrian Hunter
2035*b3700f21SAdrian Hunterdef GenerateColours(x):
2036*b3700f21SAdrian Hunter	cs = [0]
2037*b3700f21SAdrian Hunter	for i in xrange(1, x):
2038*b3700f21SAdrian Hunter		cs.append(int((255.0 / i) + 0.5))
2039*b3700f21SAdrian Hunter	colours = []
2040*b3700f21SAdrian Hunter	for r in cs:
2041*b3700f21SAdrian Hunter		for g in cs:
2042*b3700f21SAdrian Hunter			for b in cs:
2043*b3700f21SAdrian Hunter				# Exclude black and colours that look too light against a white background
2044*b3700f21SAdrian Hunter				if (r, g, b) == (0, 0, 0) or RGBColourTooLight(r, g, b):
2045*b3700f21SAdrian Hunter					continue
2046*b3700f21SAdrian Hunter				colours.append(QColor(r, g, b))
2047*b3700f21SAdrian Hunter	return colours
2048*b3700f21SAdrian Hunter
2049*b3700f21SAdrian Hunterdef GenerateNColours(n):
2050*b3700f21SAdrian Hunter	for x in xrange(2, n + 2):
2051*b3700f21SAdrian Hunter		colours = GenerateColours(x)
2052*b3700f21SAdrian Hunter		if len(colours) >= n:
2053*b3700f21SAdrian Hunter			return colours
2054*b3700f21SAdrian Hunter	return []
2055*b3700f21SAdrian Hunter
2056*b3700f21SAdrian Hunterdef GenerateNRandomColours(n, seed):
2057*b3700f21SAdrian Hunter	colours = GenerateNColours(n)
2058*b3700f21SAdrian Hunter	random.seed(seed)
2059*b3700f21SAdrian Hunter	random.shuffle(colours)
2060*b3700f21SAdrian Hunter	return colours
2061*b3700f21SAdrian Hunter
2062*b3700f21SAdrian Hunter# Graph attributes, in particular the scale and subrange that change when zooming
2063*b3700f21SAdrian Hunter
2064*b3700f21SAdrian Hunterclass GraphAttributes():
2065*b3700f21SAdrian Hunter
2066*b3700f21SAdrian Hunter	def __init__(self, scale, subrange, region_attributes, dp):
2067*b3700f21SAdrian Hunter		self.scale = scale
2068*b3700f21SAdrian Hunter		self.subrange = subrange
2069*b3700f21SAdrian Hunter		self.region_attributes = region_attributes
2070*b3700f21SAdrian Hunter		# Rounding avoids errors due to finite floating point precision
2071*b3700f21SAdrian Hunter		self.dp = dp	# data decimal places
2072*b3700f21SAdrian Hunter		self.Update()
2073*b3700f21SAdrian Hunter
2074*b3700f21SAdrian Hunter	def XToPixel(self, x):
2075*b3700f21SAdrian Hunter		return int(round((x - self.subrange.x.lo) * self.scale.x, self.pdp.x))
2076*b3700f21SAdrian Hunter
2077*b3700f21SAdrian Hunter	def YToPixel(self, y):
2078*b3700f21SAdrian Hunter		return int(round((y - self.subrange.y.lo) * self.scale.y, self.pdp.y))
2079*b3700f21SAdrian Hunter
2080*b3700f21SAdrian Hunter	def PixelToXRounded(self, px):
2081*b3700f21SAdrian Hunter		return round((round(px, 0) / self.scale.x), self.dp.x) + self.subrange.x.lo
2082*b3700f21SAdrian Hunter
2083*b3700f21SAdrian Hunter	def PixelToYRounded(self, py):
2084*b3700f21SAdrian Hunter		return round((round(py, 0) / self.scale.y), self.dp.y) + self.subrange.y.lo
2085*b3700f21SAdrian Hunter
2086*b3700f21SAdrian Hunter	def PixelToX(self, px):
2087*b3700f21SAdrian Hunter		x = self.PixelToXRounded(px)
2088*b3700f21SAdrian Hunter		if self.pdp.x == 0:
2089*b3700f21SAdrian Hunter			rt = self.XToPixel(x)
2090*b3700f21SAdrian Hunter			if rt > px:
2091*b3700f21SAdrian Hunter				return x - 1
2092*b3700f21SAdrian Hunter		return x
2093*b3700f21SAdrian Hunter
2094*b3700f21SAdrian Hunter	def PixelToY(self, py):
2095*b3700f21SAdrian Hunter		y = self.PixelToYRounded(py)
2096*b3700f21SAdrian Hunter		if self.pdp.y == 0:
2097*b3700f21SAdrian Hunter			rt = self.YToPixel(y)
2098*b3700f21SAdrian Hunter			if rt > py:
2099*b3700f21SAdrian Hunter				return y - 1
2100*b3700f21SAdrian Hunter		return y
2101*b3700f21SAdrian Hunter
2102*b3700f21SAdrian Hunter	def ToPDP(self, dp, scale):
2103*b3700f21SAdrian Hunter		# Calculate pixel decimal places:
2104*b3700f21SAdrian Hunter		#    (10 ** dp) is the minimum delta in the data
2105*b3700f21SAdrian Hunter		#    scale it to get the minimum delta in pixels
2106*b3700f21SAdrian Hunter		#    log10 gives the number of decimals places negatively
2107*b3700f21SAdrian Hunter		#    subtrace 1 to divide by 10
2108*b3700f21SAdrian Hunter		#    round to the lower negative number
2109*b3700f21SAdrian Hunter		#    change the sign to get the number of decimals positively
2110*b3700f21SAdrian Hunter		x = math.log10((10 ** dp) * scale)
2111*b3700f21SAdrian Hunter		if x < 0:
2112*b3700f21SAdrian Hunter			x -= 1
2113*b3700f21SAdrian Hunter			x = -int(math.floor(x) - 0.1)
2114*b3700f21SAdrian Hunter		else:
2115*b3700f21SAdrian Hunter			x = 0
2116*b3700f21SAdrian Hunter		return x
2117*b3700f21SAdrian Hunter
2118*b3700f21SAdrian Hunter	def Update(self):
2119*b3700f21SAdrian Hunter		x = self.ToPDP(self.dp.x, self.scale.x)
2120*b3700f21SAdrian Hunter		y = self.ToPDP(self.dp.y, self.scale.y)
2121*b3700f21SAdrian Hunter		self.pdp = XY(x, y) # pixel decimal places
2122*b3700f21SAdrian Hunter
2123*b3700f21SAdrian Hunter# Switch graph splitter which divides the CPU graphs from the legend
2124*b3700f21SAdrian Hunter
2125*b3700f21SAdrian Hunterclass SwitchGraphSplitter(QSplitter):
2126*b3700f21SAdrian Hunter
2127*b3700f21SAdrian Hunter	def __init__(self, parent=None):
2128*b3700f21SAdrian Hunter		super(SwitchGraphSplitter, self).__init__(parent)
2129*b3700f21SAdrian Hunter
2130*b3700f21SAdrian Hunter		self.first_time = False
2131*b3700f21SAdrian Hunter
2132*b3700f21SAdrian Hunter	def resizeEvent(self, ev):
2133*b3700f21SAdrian Hunter		if self.first_time:
2134*b3700f21SAdrian Hunter			self.first_time = False
2135*b3700f21SAdrian Hunter			sz1 = self.widget(1).view.columnWidth(0) + self.widget(1).view.columnWidth(1) + self.widget(1).view.columnWidth(2) + 2
2136*b3700f21SAdrian Hunter			sz1 = sz1 + self.widget(1).view.verticalScrollBar().sizeHint().width()
2137*b3700f21SAdrian Hunter			sz0 = self.size().width() - self.handleWidth() - sz1
2138*b3700f21SAdrian Hunter			self.setSizes([sz0, sz1])
2139*b3700f21SAdrian Hunter		elif not(self.widget(1).saved_size is None):
2140*b3700f21SAdrian Hunter			sz1 = self.widget(1).saved_size
2141*b3700f21SAdrian Hunter			sz0 = self.size().width() - self.handleWidth() - sz1
2142*b3700f21SAdrian Hunter			self.setSizes([sz0, sz1])
2143*b3700f21SAdrian Hunter		super(SwitchGraphSplitter, self).resizeEvent(ev)
2144*b3700f21SAdrian Hunter
2145*b3700f21SAdrian Hunter# Graph widget base class
2146*b3700f21SAdrian Hunter
2147*b3700f21SAdrian Hunterclass GraphWidget(QWidget):
2148*b3700f21SAdrian Hunter
2149*b3700f21SAdrian Hunter	graph_title_changed = Signal(object)
2150*b3700f21SAdrian Hunter
2151*b3700f21SAdrian Hunter	def __init__(self, parent=None):
2152*b3700f21SAdrian Hunter		super(GraphWidget, self).__init__(parent)
2153*b3700f21SAdrian Hunter
2154*b3700f21SAdrian Hunter	def GraphTitleChanged(self, title):
2155*b3700f21SAdrian Hunter		self.graph_title_changed.emit(title)
2156*b3700f21SAdrian Hunter
2157*b3700f21SAdrian Hunter	def Title(self):
2158*b3700f21SAdrian Hunter		return ""
2159*b3700f21SAdrian Hunter
2160*b3700f21SAdrian Hunter# Display time in s, ms, us or ns
2161*b3700f21SAdrian Hunter
2162*b3700f21SAdrian Hunterdef ToTimeStr(val):
2163*b3700f21SAdrian Hunter	val = Decimal(val)
2164*b3700f21SAdrian Hunter	if val >= 1000000000:
2165*b3700f21SAdrian Hunter		return "{} s".format((val / 1000000000).quantize(Decimal("0.000000001")))
2166*b3700f21SAdrian Hunter	if val >= 1000000:
2167*b3700f21SAdrian Hunter		return "{} ms".format((val / 1000000).quantize(Decimal("0.000001")))
2168*b3700f21SAdrian Hunter	if val >= 1000:
2169*b3700f21SAdrian Hunter		return "{} us".format((val / 1000).quantize(Decimal("0.001")))
2170*b3700f21SAdrian Hunter	return "{} ns".format(val.quantize(Decimal("1")))
2171*b3700f21SAdrian Hunter
2172*b3700f21SAdrian Hunter# Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons
2173*b3700f21SAdrian Hunter
2174*b3700f21SAdrian Hunterclass SwitchGraphWidget(GraphWidget):
2175*b3700f21SAdrian Hunter
2176*b3700f21SAdrian Hunter	def __init__(self, glb, collection, parent=None):
2177*b3700f21SAdrian Hunter		super(SwitchGraphWidget, self).__init__(parent)
2178*b3700f21SAdrian Hunter
2179*b3700f21SAdrian Hunter		self.glb = glb
2180*b3700f21SAdrian Hunter		self.collection = collection
2181*b3700f21SAdrian Hunter
2182*b3700f21SAdrian Hunter		self.back_state = []
2183*b3700f21SAdrian Hunter		self.forward_state = []
2184*b3700f21SAdrian Hunter		self.selection_state = (None, None)
2185*b3700f21SAdrian Hunter		self.fwd_rect = None
2186*b3700f21SAdrian Hunter		self.start_time = self.glb.StartTime(collection.machine_id)
2187*b3700f21SAdrian Hunter
2188*b3700f21SAdrian Hunter		i = 0
2189*b3700f21SAdrian Hunter		hregions = collection.hregions.values()
2190*b3700f21SAdrian Hunter		colours = GenerateNRandomColours(len(hregions), 1013)
2191*b3700f21SAdrian Hunter		region_attributes = {}
2192*b3700f21SAdrian Hunter		for hregion in hregions:
2193*b3700f21SAdrian Hunter			if hregion.pid == 0 and hregion.tid == 0:
2194*b3700f21SAdrian Hunter				region_attributes[hregion.key] = GraphRegionAttribute(QColor(0, 0, 0))
2195*b3700f21SAdrian Hunter			else:
2196*b3700f21SAdrian Hunter				region_attributes[hregion.key] = GraphRegionAttribute(colours[i])
2197*b3700f21SAdrian Hunter				i = i + 1
2198*b3700f21SAdrian Hunter
2199*b3700f21SAdrian Hunter		# Default to entire range
2200*b3700f21SAdrian Hunter		xsubrange = Subrange(0.0, float(collection.xrangehi - collection.xrangelo) + 1.0)
2201*b3700f21SAdrian Hunter		ysubrange = Subrange(0.0, float(collection.yrangehi - collection.yrangelo) + 1.0)
2202*b3700f21SAdrian Hunter		subrange = XY(xsubrange, ysubrange)
2203*b3700f21SAdrian Hunter
2204*b3700f21SAdrian Hunter		scale = self.GetScaleForRange(subrange)
2205*b3700f21SAdrian Hunter
2206*b3700f21SAdrian Hunter		self.attrs = GraphAttributes(scale, subrange, region_attributes, collection.dp)
2207*b3700f21SAdrian Hunter
2208*b3700f21SAdrian Hunter		self.item = VertcalGraphSetGraphicsItem(collection, self.attrs, self, SwitchGraphGraphicsItem)
2209*b3700f21SAdrian Hunter
2210*b3700f21SAdrian Hunter		self.scene = QGraphicsScene()
2211*b3700f21SAdrian Hunter		self.scene.addItem(self.item)
2212*b3700f21SAdrian Hunter
2213*b3700f21SAdrian Hunter		self.view = QGraphicsView(self.scene)
2214*b3700f21SAdrian Hunter		self.view.centerOn(0, 0)
2215*b3700f21SAdrian Hunter		self.view.setAlignment(Qt.AlignLeft | Qt.AlignTop)
2216*b3700f21SAdrian Hunter
2217*b3700f21SAdrian Hunter		self.legend = SwitchGraphLegend(collection, region_attributes)
2218*b3700f21SAdrian Hunter
2219*b3700f21SAdrian Hunter		self.splitter = SwitchGraphSplitter()
2220*b3700f21SAdrian Hunter		self.splitter.addWidget(self.view)
2221*b3700f21SAdrian Hunter		self.splitter.addWidget(self.legend)
2222*b3700f21SAdrian Hunter
2223*b3700f21SAdrian Hunter		self.point_label = QLabel("")
2224*b3700f21SAdrian Hunter		self.point_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
2225*b3700f21SAdrian Hunter
2226*b3700f21SAdrian Hunter		self.back_button = QToolButton()
2227*b3700f21SAdrian Hunter		self.back_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowLeft))
2228*b3700f21SAdrian Hunter		self.back_button.setDisabled(True)
2229*b3700f21SAdrian Hunter		self.back_button.released.connect(lambda: self.Back())
2230*b3700f21SAdrian Hunter
2231*b3700f21SAdrian Hunter		self.forward_button = QToolButton()
2232*b3700f21SAdrian Hunter		self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowRight))
2233*b3700f21SAdrian Hunter		self.forward_button.setDisabled(True)
2234*b3700f21SAdrian Hunter		self.forward_button.released.connect(lambda: self.Forward())
2235*b3700f21SAdrian Hunter
2236*b3700f21SAdrian Hunter		self.zoom_button = QToolButton()
2237*b3700f21SAdrian Hunter		self.zoom_button.setText("Zoom")
2238*b3700f21SAdrian Hunter		self.zoom_button.setDisabled(True)
2239*b3700f21SAdrian Hunter		self.zoom_button.released.connect(lambda: self.Zoom())
2240*b3700f21SAdrian Hunter
2241*b3700f21SAdrian Hunter		self.hbox = HBoxLayout(self.back_button, self.forward_button, self.zoom_button, self.point_label)
2242*b3700f21SAdrian Hunter
2243*b3700f21SAdrian Hunter		self.vbox = VBoxLayout(self.splitter, self.hbox)
2244*b3700f21SAdrian Hunter
2245*b3700f21SAdrian Hunter		self.setLayout(self.vbox)
2246*b3700f21SAdrian Hunter
2247*b3700f21SAdrian Hunter	def GetScaleForRangeX(self, xsubrange):
2248*b3700f21SAdrian Hunter		# Default graph 1000 pixels wide
2249*b3700f21SAdrian Hunter		dflt = 1000.0
2250*b3700f21SAdrian Hunter		r = xsubrange.hi - xsubrange.lo
2251*b3700f21SAdrian Hunter		return dflt / r
2252*b3700f21SAdrian Hunter
2253*b3700f21SAdrian Hunter	def GetScaleForRangeY(self, ysubrange):
2254*b3700f21SAdrian Hunter		# Default graph 50 pixels high
2255*b3700f21SAdrian Hunter		dflt = 50.0
2256*b3700f21SAdrian Hunter		r = ysubrange.hi - ysubrange.lo
2257*b3700f21SAdrian Hunter		return dflt / r
2258*b3700f21SAdrian Hunter
2259*b3700f21SAdrian Hunter	def GetScaleForRange(self, subrange):
2260*b3700f21SAdrian Hunter		# Default graph 1000 pixels wide, 50 pixels high
2261*b3700f21SAdrian Hunter		xscale = self.GetScaleForRangeX(subrange.x)
2262*b3700f21SAdrian Hunter		yscale = self.GetScaleForRangeY(subrange.y)
2263*b3700f21SAdrian Hunter		return XY(xscale, yscale)
2264*b3700f21SAdrian Hunter
2265*b3700f21SAdrian Hunter	def PointEvent(self, cpu, time_from, time_to, hregions):
2266*b3700f21SAdrian Hunter		text = "CPU: " + str(cpu)
2267*b3700f21SAdrian Hunter		time_from = time_from.quantize(Decimal(1))
2268*b3700f21SAdrian Hunter		rel_time_from = time_from - self.glb.StartTime(self.collection.machine_id)
2269*b3700f21SAdrian Hunter		text = text + " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ")"
2270*b3700f21SAdrian Hunter		self.point_label.setText(text)
2271*b3700f21SAdrian Hunter		self.legend.Highlight(hregions)
2272*b3700f21SAdrian Hunter
2273*b3700f21SAdrian Hunter	def RightClickEvent(self, cpu, hregion_times, pos):
2274*b3700f21SAdrian Hunter		if not IsSelectable(self.glb.db, "calls", "WHERE parent_id >= 0"):
2275*b3700f21SAdrian Hunter			return
2276*b3700f21SAdrian Hunter		menu = QMenu(self.view)
2277*b3700f21SAdrian Hunter		for hregion, time in hregion_times:
2278*b3700f21SAdrian Hunter			thread_at_time = (hregion.exec_comm_id, hregion.thread_id, time)
2279*b3700f21SAdrian Hunter			menu_text = "Show Call Tree for {} {}:{} at {}".format(hregion.comm, hregion.pid, hregion.tid, time)
2280*b3700f21SAdrian Hunter			menu.addAction(CreateAction(menu_text, "Show Call Tree", lambda a=None, args=thread_at_time: self.RightClickSelect(args), self.view))
2281*b3700f21SAdrian Hunter		menu.exec_(pos)
2282*b3700f21SAdrian Hunter
2283*b3700f21SAdrian Hunter	def RightClickSelect(self, args):
2284*b3700f21SAdrian Hunter		CallTreeWindow(self.glb, self.glb.mainwindow, thread_at_time=args)
2285*b3700f21SAdrian Hunter
2286*b3700f21SAdrian Hunter	def NoPointEvent(self):
2287*b3700f21SAdrian Hunter		self.point_label.setText("")
2288*b3700f21SAdrian Hunter		self.legend.Highlight({})
2289*b3700f21SAdrian Hunter
2290*b3700f21SAdrian Hunter	def RangeEvent(self, time_from, time_to):
2291*b3700f21SAdrian Hunter		time_from = time_from.quantize(Decimal(1))
2292*b3700f21SAdrian Hunter		time_to = time_to.quantize(Decimal(1))
2293*b3700f21SAdrian Hunter		if time_to <= time_from:
2294*b3700f21SAdrian Hunter			self.point_label.setText("")
2295*b3700f21SAdrian Hunter			return
2296*b3700f21SAdrian Hunter		rel_time_from = time_from - self.start_time
2297*b3700f21SAdrian Hunter		rel_time_to = time_to - self.start_time
2298*b3700f21SAdrian Hunter		text = " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ") to: " + str(time_to) + " (+" + ToTimeStr(rel_time_to) + ")"
2299*b3700f21SAdrian Hunter		text = text + " duration: " + ToTimeStr(time_to - time_from)
2300*b3700f21SAdrian Hunter		self.point_label.setText(text)
2301*b3700f21SAdrian Hunter
2302*b3700f21SAdrian Hunter	def BackState(self):
2303*b3700f21SAdrian Hunter		return (self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect)
2304*b3700f21SAdrian Hunter
2305*b3700f21SAdrian Hunter	def PushBackState(self):
2306*b3700f21SAdrian Hunter		state = copy.deepcopy(self.BackState())
2307*b3700f21SAdrian Hunter		self.back_state.append(state)
2308*b3700f21SAdrian Hunter		self.back_button.setEnabled(True)
2309*b3700f21SAdrian Hunter
2310*b3700f21SAdrian Hunter	def PopBackState(self):
2311*b3700f21SAdrian Hunter		self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.back_state.pop()
2312*b3700f21SAdrian Hunter		self.attrs.Update()
2313*b3700f21SAdrian Hunter		if not self.back_state:
2314*b3700f21SAdrian Hunter			self.back_button.setDisabled(True)
2315*b3700f21SAdrian Hunter
2316*b3700f21SAdrian Hunter	def PushForwardState(self):
2317*b3700f21SAdrian Hunter		state = copy.deepcopy(self.BackState())
2318*b3700f21SAdrian Hunter		self.forward_state.append(state)
2319*b3700f21SAdrian Hunter		self.forward_button.setEnabled(True)
2320*b3700f21SAdrian Hunter
2321*b3700f21SAdrian Hunter	def PopForwardState(self):
2322*b3700f21SAdrian Hunter		self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.forward_state.pop()
2323*b3700f21SAdrian Hunter		self.attrs.Update()
2324*b3700f21SAdrian Hunter		if not self.forward_state:
2325*b3700f21SAdrian Hunter			self.forward_button.setDisabled(True)
2326*b3700f21SAdrian Hunter
2327*b3700f21SAdrian Hunter	def Title(self):
2328*b3700f21SAdrian Hunter		time_from = self.collection.xrangelo + Decimal(self.attrs.subrange.x.lo)
2329*b3700f21SAdrian Hunter		time_to = self.collection.xrangelo + Decimal(self.attrs.subrange.x.hi)
2330*b3700f21SAdrian Hunter		rel_time_from = time_from - self.start_time
2331*b3700f21SAdrian Hunter		rel_time_to = time_to - self.start_time
2332*b3700f21SAdrian Hunter		title = "+" + ToTimeStr(rel_time_from) + " to +" + ToTimeStr(rel_time_to)
2333*b3700f21SAdrian Hunter		title = title + " (" + ToTimeStr(time_to - time_from) + ")"
2334*b3700f21SAdrian Hunter		return title
2335*b3700f21SAdrian Hunter
2336*b3700f21SAdrian Hunter	def Update(self):
2337*b3700f21SAdrian Hunter		selected_subrange, selection_state = self.selection_state
2338*b3700f21SAdrian Hunter		self.item.SetSelection(selection_state)
2339*b3700f21SAdrian Hunter		self.item.SetBracket(self.fwd_rect)
2340*b3700f21SAdrian Hunter		self.zoom_button.setDisabled(selected_subrange is None)
2341*b3700f21SAdrian Hunter		self.GraphTitleChanged(self.Title())
2342*b3700f21SAdrian Hunter		self.item.update(self.item.boundingRect())
2343*b3700f21SAdrian Hunter
2344*b3700f21SAdrian Hunter	def Back(self):
2345*b3700f21SAdrian Hunter		if not self.back_state:
2346*b3700f21SAdrian Hunter			return
2347*b3700f21SAdrian Hunter		self.PushForwardState()
2348*b3700f21SAdrian Hunter		self.PopBackState()
2349*b3700f21SAdrian Hunter		self.Update()
2350*b3700f21SAdrian Hunter
2351*b3700f21SAdrian Hunter	def Forward(self):
2352*b3700f21SAdrian Hunter		if not self.forward_state:
2353*b3700f21SAdrian Hunter			return
2354*b3700f21SAdrian Hunter		self.PushBackState()
2355*b3700f21SAdrian Hunter		self.PopForwardState()
2356*b3700f21SAdrian Hunter		self.Update()
2357*b3700f21SAdrian Hunter
2358*b3700f21SAdrian Hunter	def SelectEvent(self, x0, x1, selection_state):
2359*b3700f21SAdrian Hunter		if selection_state is None:
2360*b3700f21SAdrian Hunter			selected_subrange = None
2361*b3700f21SAdrian Hunter		else:
2362*b3700f21SAdrian Hunter			if x1 - x0 < 1.0:
2363*b3700f21SAdrian Hunter				x1 += 1.0
2364*b3700f21SAdrian Hunter			selected_subrange = Subrange(x0, x1)
2365*b3700f21SAdrian Hunter		self.selection_state = (selected_subrange, selection_state)
2366*b3700f21SAdrian Hunter		self.zoom_button.setDisabled(selected_subrange is None)
2367*b3700f21SAdrian Hunter
2368*b3700f21SAdrian Hunter	def Zoom(self):
2369*b3700f21SAdrian Hunter		selected_subrange, selection_state = self.selection_state
2370*b3700f21SAdrian Hunter		if selected_subrange is None:
2371*b3700f21SAdrian Hunter			return
2372*b3700f21SAdrian Hunter		self.fwd_rect = selection_state
2373*b3700f21SAdrian Hunter		self.item.SetSelection(None)
2374*b3700f21SAdrian Hunter		self.PushBackState()
2375*b3700f21SAdrian Hunter		self.attrs.subrange.x = selected_subrange
2376*b3700f21SAdrian Hunter		self.forward_state = []
2377*b3700f21SAdrian Hunter		self.forward_button.setDisabled(True)
2378*b3700f21SAdrian Hunter		self.selection_state = (None, None)
2379*b3700f21SAdrian Hunter		self.fwd_rect = None
2380*b3700f21SAdrian Hunter		self.attrs.scale.x = self.GetScaleForRangeX(self.attrs.subrange.x)
2381*b3700f21SAdrian Hunter		self.attrs.Update()
2382*b3700f21SAdrian Hunter		self.Update()
2383*b3700f21SAdrian Hunter
2384*b3700f21SAdrian Hunter# Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting
2385*b3700f21SAdrian Hunter
2386*b3700f21SAdrian Hunterclass SlowInitClass():
2387*b3700f21SAdrian Hunter
2388*b3700f21SAdrian Hunter	def __init__(self, glb, title, init_fn):
2389*b3700f21SAdrian Hunter		self.init_fn = init_fn
2390*b3700f21SAdrian Hunter		self.done = False
2391*b3700f21SAdrian Hunter		self.result = None
2392*b3700f21SAdrian Hunter
2393*b3700f21SAdrian Hunter		self.msg_box = QMessageBox(glb.mainwindow)
2394*b3700f21SAdrian Hunter		self.msg_box.setText("Initializing " + title + ". Please wait.")
2395*b3700f21SAdrian Hunter		self.msg_box.setWindowTitle("Initializing " + title)
2396*b3700f21SAdrian Hunter		self.msg_box.setWindowIcon(glb.mainwindow.style().standardIcon(QStyle.SP_MessageBoxInformation))
2397*b3700f21SAdrian Hunter
2398*b3700f21SAdrian Hunter		self.init_thread = Thread(self.ThreadFn, glb)
2399*b3700f21SAdrian Hunter		self.init_thread.done.connect(lambda: self.Done(), Qt.QueuedConnection)
2400*b3700f21SAdrian Hunter
2401*b3700f21SAdrian Hunter		self.init_thread.start()
2402*b3700f21SAdrian Hunter
2403*b3700f21SAdrian Hunter	def Done(self):
2404*b3700f21SAdrian Hunter		self.msg_box.done(0)
2405*b3700f21SAdrian Hunter
2406*b3700f21SAdrian Hunter	def ThreadFn(self, glb):
2407*b3700f21SAdrian Hunter		conn_name = "SlowInitClass" + str(os.getpid())
2408*b3700f21SAdrian Hunter		db, dbname = glb.dbref.Open(conn_name)
2409*b3700f21SAdrian Hunter		self.result = self.init_fn(db)
2410*b3700f21SAdrian Hunter		self.done = True
2411*b3700f21SAdrian Hunter		return (True, 0)
2412*b3700f21SAdrian Hunter
2413*b3700f21SAdrian Hunter	def Result(self):
2414*b3700f21SAdrian Hunter		while not self.done:
2415*b3700f21SAdrian Hunter			self.msg_box.exec_()
2416*b3700f21SAdrian Hunter		self.init_thread.wait()
2417*b3700f21SAdrian Hunter		return self.result
2418*b3700f21SAdrian Hunter
2419*b3700f21SAdrian Hunterdef SlowInit(glb, title, init_fn):
2420*b3700f21SAdrian Hunter	init = SlowInitClass(glb, title, init_fn)
2421*b3700f21SAdrian Hunter	return init.Result()
2422*b3700f21SAdrian Hunter
2423*b3700f21SAdrian Hunter# Time chart by CPU window
2424*b3700f21SAdrian Hunter
2425*b3700f21SAdrian Hunterclass TimeChartByCPUWindow(QMdiSubWindow):
2426*b3700f21SAdrian Hunter
2427*b3700f21SAdrian Hunter	def __init__(self, glb, parent=None):
2428*b3700f21SAdrian Hunter		super(TimeChartByCPUWindow, self).__init__(parent)
2429*b3700f21SAdrian Hunter
2430*b3700f21SAdrian Hunter		self.glb = glb
2431*b3700f21SAdrian Hunter		self.machine_id = glb.HostMachineId()
2432*b3700f21SAdrian Hunter		self.collection_name = "SwitchGraphDataCollection " + str(self.machine_id)
2433*b3700f21SAdrian Hunter
2434*b3700f21SAdrian Hunter		collection = LookupModel(self.collection_name)
2435*b3700f21SAdrian Hunter		if collection is None:
2436*b3700f21SAdrian Hunter			collection = SlowInit(glb, "Time Chart", self.Init)
2437*b3700f21SAdrian Hunter
2438*b3700f21SAdrian Hunter		self.widget = SwitchGraphWidget(glb, collection, self)
2439*b3700f21SAdrian Hunter		self.view = self.widget
2440*b3700f21SAdrian Hunter
2441*b3700f21SAdrian Hunter		self.base_title = "Time Chart by CPU"
2442*b3700f21SAdrian Hunter		self.setWindowTitle(self.base_title + self.widget.Title())
2443*b3700f21SAdrian Hunter		self.widget.graph_title_changed.connect(self.GraphTitleChanged)
2444*b3700f21SAdrian Hunter
2445*b3700f21SAdrian Hunter		self.setWidget(self.widget)
2446*b3700f21SAdrian Hunter
2447*b3700f21SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, self.windowTitle())
2448*b3700f21SAdrian Hunter
2449*b3700f21SAdrian Hunter	def Init(self, db):
2450*b3700f21SAdrian Hunter		return LookupCreateModel(self.collection_name, lambda : SwitchGraphDataCollection(self.glb, db, self.machine_id))
2451*b3700f21SAdrian Hunter
2452*b3700f21SAdrian Hunter	def GraphTitleChanged(self, title):
2453*b3700f21SAdrian Hunter		self.setWindowTitle(self.base_title + " : " + title)
2454*b3700f21SAdrian Hunter
24558392b74bSAdrian Hunter# Child data item  finder
24568392b74bSAdrian Hunter
24578392b74bSAdrian Hunterclass ChildDataItemFinder():
24588392b74bSAdrian Hunter
24598392b74bSAdrian Hunter	def __init__(self, root):
24608392b74bSAdrian Hunter		self.root = root
24618392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
24628392b74bSAdrian Hunter		self.rows = []
24638392b74bSAdrian Hunter		self.pos = 0
24648392b74bSAdrian Hunter
24658392b74bSAdrian Hunter	def FindSelect(self):
24668392b74bSAdrian Hunter		self.rows = []
24678392b74bSAdrian Hunter		if self.pattern:
24688392b74bSAdrian Hunter			pattern = re.compile(self.value)
24698392b74bSAdrian Hunter			for child in self.root.child_items:
24708392b74bSAdrian Hunter				for column_data in child.data:
24718392b74bSAdrian Hunter					if re.search(pattern, str(column_data)) is not None:
24728392b74bSAdrian Hunter						self.rows.append(child.row)
24738392b74bSAdrian Hunter						break
24748392b74bSAdrian Hunter		else:
24758392b74bSAdrian Hunter			for child in self.root.child_items:
24768392b74bSAdrian Hunter				for column_data in child.data:
24778392b74bSAdrian Hunter					if self.value in str(column_data):
24788392b74bSAdrian Hunter						self.rows.append(child.row)
24798392b74bSAdrian Hunter						break
24808392b74bSAdrian Hunter
24818392b74bSAdrian Hunter	def FindValue(self):
24828392b74bSAdrian Hunter		self.pos = 0
24838392b74bSAdrian Hunter		if self.last_value != self.value or self.pattern != self.last_pattern:
24848392b74bSAdrian Hunter			self.FindSelect()
24858392b74bSAdrian Hunter		if not len(self.rows):
24868392b74bSAdrian Hunter			return -1
24878392b74bSAdrian Hunter		return self.rows[self.pos]
24888392b74bSAdrian Hunter
24898392b74bSAdrian Hunter	def FindThread(self):
24908392b74bSAdrian Hunter		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
24918392b74bSAdrian Hunter			row = self.FindValue()
24928392b74bSAdrian Hunter		elif len(self.rows):
24938392b74bSAdrian Hunter			if self.direction > 0:
24948392b74bSAdrian Hunter				self.pos += 1
24958392b74bSAdrian Hunter				if self.pos >= len(self.rows):
24968392b74bSAdrian Hunter					self.pos = 0
24978392b74bSAdrian Hunter			else:
24988392b74bSAdrian Hunter				self.pos -= 1
24998392b74bSAdrian Hunter				if self.pos < 0:
25008392b74bSAdrian Hunter					self.pos = len(self.rows) - 1
25018392b74bSAdrian Hunter			row = self.rows[self.pos]
25028392b74bSAdrian Hunter		else:
25038392b74bSAdrian Hunter			row = -1
25048392b74bSAdrian Hunter		return (True, row)
25058392b74bSAdrian Hunter
25068392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
25078392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
25088392b74bSAdrian Hunter		# Use a thread so the UI is not blocked
25098392b74bSAdrian Hunter		thread = Thread(self.FindThread)
25108392b74bSAdrian Hunter		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
25118392b74bSAdrian Hunter		thread.start()
25128392b74bSAdrian Hunter
25138392b74bSAdrian Hunter	def FindDone(self, thread, callback, row):
25148392b74bSAdrian Hunter		callback(row)
25158392b74bSAdrian Hunter
25168392b74bSAdrian Hunter# Number of database records to fetch in one go
25178392b74bSAdrian Hunter
25188392b74bSAdrian Hunterglb_chunk_sz = 10000
25198392b74bSAdrian Hunter
25208392b74bSAdrian Hunter# Background process for SQL data fetcher
25218392b74bSAdrian Hunter
25228392b74bSAdrian Hunterclass SQLFetcherProcess():
25238392b74bSAdrian Hunter
25248392b74bSAdrian Hunter	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
25258392b74bSAdrian Hunter		# Need a unique connection name
25268392b74bSAdrian Hunter		conn_name = "SQLFetcher" + str(os.getpid())
25278392b74bSAdrian Hunter		self.db, dbname = dbref.Open(conn_name)
25288392b74bSAdrian Hunter		self.sql = sql
25298392b74bSAdrian Hunter		self.buffer = buffer
25308392b74bSAdrian Hunter		self.head = head
25318392b74bSAdrian Hunter		self.tail = tail
25328392b74bSAdrian Hunter		self.fetch_count = fetch_count
25338392b74bSAdrian Hunter		self.fetching_done = fetching_done
25348392b74bSAdrian Hunter		self.process_target = process_target
25358392b74bSAdrian Hunter		self.wait_event = wait_event
25368392b74bSAdrian Hunter		self.fetched_event = fetched_event
25378392b74bSAdrian Hunter		self.prep = prep
25388392b74bSAdrian Hunter		self.query = QSqlQuery(self.db)
25398392b74bSAdrian Hunter		self.query_limit = 0 if "$$last_id$$" in sql else 2
25408392b74bSAdrian Hunter		self.last_id = -1
25418392b74bSAdrian Hunter		self.fetched = 0
25428392b74bSAdrian Hunter		self.more = True
25438392b74bSAdrian Hunter		self.local_head = self.head.value
25448392b74bSAdrian Hunter		self.local_tail = self.tail.value
25458392b74bSAdrian Hunter
25468392b74bSAdrian Hunter	def Select(self):
25478392b74bSAdrian Hunter		if self.query_limit:
25488392b74bSAdrian Hunter			if self.query_limit == 1:
25498392b74bSAdrian Hunter				return
25508392b74bSAdrian Hunter			self.query_limit -= 1
25518392b74bSAdrian Hunter		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
25528392b74bSAdrian Hunter		QueryExec(self.query, stmt)
25538392b74bSAdrian Hunter
25548392b74bSAdrian Hunter	def Next(self):
25558392b74bSAdrian Hunter		if not self.query.next():
25568392b74bSAdrian Hunter			self.Select()
25578392b74bSAdrian Hunter			if not self.query.next():
25588392b74bSAdrian Hunter				return None
25598392b74bSAdrian Hunter		self.last_id = self.query.value(0)
25608392b74bSAdrian Hunter		return self.prep(self.query)
25618392b74bSAdrian Hunter
25628392b74bSAdrian Hunter	def WaitForTarget(self):
25638392b74bSAdrian Hunter		while True:
25648392b74bSAdrian Hunter			self.wait_event.clear()
25658392b74bSAdrian Hunter			target = self.process_target.value
25668392b74bSAdrian Hunter			if target > self.fetched or target < 0:
25678392b74bSAdrian Hunter				break
25688392b74bSAdrian Hunter			self.wait_event.wait()
25698392b74bSAdrian Hunter		return target
25708392b74bSAdrian Hunter
25718392b74bSAdrian Hunter	def HasSpace(self, sz):
25728392b74bSAdrian Hunter		if self.local_tail <= self.local_head:
25738392b74bSAdrian Hunter			space = len(self.buffer) - self.local_head
25748392b74bSAdrian Hunter			if space > sz:
25758392b74bSAdrian Hunter				return True
25768392b74bSAdrian Hunter			if space >= glb_nsz:
25778392b74bSAdrian Hunter				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
2578beda0e72STony Jones				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
25798392b74bSAdrian Hunter				self.buffer[self.local_head : self.local_head + len(nd)] = nd
25808392b74bSAdrian Hunter			self.local_head = 0
25818392b74bSAdrian Hunter		if self.local_tail - self.local_head > sz:
25828392b74bSAdrian Hunter			return True
25838392b74bSAdrian Hunter		return False
25848392b74bSAdrian Hunter
25858392b74bSAdrian Hunter	def WaitForSpace(self, sz):
25868392b74bSAdrian Hunter		if self.HasSpace(sz):
25878392b74bSAdrian Hunter			return
25888392b74bSAdrian Hunter		while True:
25898392b74bSAdrian Hunter			self.wait_event.clear()
25908392b74bSAdrian Hunter			self.local_tail = self.tail.value
25918392b74bSAdrian Hunter			if self.HasSpace(sz):
25928392b74bSAdrian Hunter				return
25938392b74bSAdrian Hunter			self.wait_event.wait()
25948392b74bSAdrian Hunter
25958392b74bSAdrian Hunter	def AddToBuffer(self, obj):
2596beda0e72STony Jones		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
25978392b74bSAdrian Hunter		n = len(d)
2598beda0e72STony Jones		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
25998392b74bSAdrian Hunter		sz = n + glb_nsz
26008392b74bSAdrian Hunter		self.WaitForSpace(sz)
26018392b74bSAdrian Hunter		pos = self.local_head
26028392b74bSAdrian Hunter		self.buffer[pos : pos + len(nd)] = nd
26038392b74bSAdrian Hunter		self.buffer[pos + glb_nsz : pos + sz] = d
26048392b74bSAdrian Hunter		self.local_head += sz
26058392b74bSAdrian Hunter
26068392b74bSAdrian Hunter	def FetchBatch(self, batch_size):
26078392b74bSAdrian Hunter		fetched = 0
26088392b74bSAdrian Hunter		while batch_size > fetched:
26098392b74bSAdrian Hunter			obj = self.Next()
26108392b74bSAdrian Hunter			if obj is None:
26118392b74bSAdrian Hunter				self.more = False
26128392b74bSAdrian Hunter				break
26138392b74bSAdrian Hunter			self.AddToBuffer(obj)
26148392b74bSAdrian Hunter			fetched += 1
26158392b74bSAdrian Hunter		if fetched:
26168392b74bSAdrian Hunter			self.fetched += fetched
26178392b74bSAdrian Hunter			with self.fetch_count.get_lock():
26188392b74bSAdrian Hunter				self.fetch_count.value += fetched
26198392b74bSAdrian Hunter			self.head.value = self.local_head
26208392b74bSAdrian Hunter			self.fetched_event.set()
26218392b74bSAdrian Hunter
26228392b74bSAdrian Hunter	def Run(self):
26238392b74bSAdrian Hunter		while self.more:
26248392b74bSAdrian Hunter			target = self.WaitForTarget()
26258392b74bSAdrian Hunter			if target < 0:
26268392b74bSAdrian Hunter				break
26278392b74bSAdrian Hunter			batch_size = min(glb_chunk_sz, target - self.fetched)
26288392b74bSAdrian Hunter			self.FetchBatch(batch_size)
26298392b74bSAdrian Hunter		self.fetching_done.value = True
26308392b74bSAdrian Hunter		self.fetched_event.set()
26318392b74bSAdrian Hunter
26328392b74bSAdrian Hunterdef SQLFetcherFn(*x):
26338392b74bSAdrian Hunter	process = SQLFetcherProcess(*x)
26348392b74bSAdrian Hunter	process.Run()
26358392b74bSAdrian Hunter
26368392b74bSAdrian Hunter# SQL data fetcher
26378392b74bSAdrian Hunter
26388392b74bSAdrian Hunterclass SQLFetcher(QObject):
26398392b74bSAdrian Hunter
26408392b74bSAdrian Hunter	done = Signal(object)
26418392b74bSAdrian Hunter
26428392b74bSAdrian Hunter	def __init__(self, glb, sql, prep, process_data, parent=None):
26438392b74bSAdrian Hunter		super(SQLFetcher, self).__init__(parent)
26448392b74bSAdrian Hunter		self.process_data = process_data
26458392b74bSAdrian Hunter		self.more = True
26468392b74bSAdrian Hunter		self.target = 0
26478392b74bSAdrian Hunter		self.last_target = 0
26488392b74bSAdrian Hunter		self.fetched = 0
26498392b74bSAdrian Hunter		self.buffer_size = 16 * 1024 * 1024
26508392b74bSAdrian Hunter		self.buffer = Array(c_char, self.buffer_size, lock=False)
26518392b74bSAdrian Hunter		self.head = Value(c_longlong)
26528392b74bSAdrian Hunter		self.tail = Value(c_longlong)
26538392b74bSAdrian Hunter		self.local_tail = 0
26548392b74bSAdrian Hunter		self.fetch_count = Value(c_longlong)
26558392b74bSAdrian Hunter		self.fetching_done = Value(c_bool)
26568392b74bSAdrian Hunter		self.last_count = 0
26578392b74bSAdrian Hunter		self.process_target = Value(c_longlong)
26588392b74bSAdrian Hunter		self.wait_event = Event()
26598392b74bSAdrian Hunter		self.fetched_event = Event()
26608392b74bSAdrian Hunter		glb.AddInstanceToShutdownOnExit(self)
26618392b74bSAdrian 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))
26628392b74bSAdrian Hunter		self.process.start()
26638392b74bSAdrian Hunter		self.thread = Thread(self.Thread)
26648392b74bSAdrian Hunter		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
26658392b74bSAdrian Hunter		self.thread.start()
26668392b74bSAdrian Hunter
26678392b74bSAdrian Hunter	def Shutdown(self):
26688392b74bSAdrian Hunter		# Tell the thread and process to exit
26698392b74bSAdrian Hunter		self.process_target.value = -1
26708392b74bSAdrian Hunter		self.wait_event.set()
26718392b74bSAdrian Hunter		self.more = False
26728392b74bSAdrian Hunter		self.fetching_done.value = True
26738392b74bSAdrian Hunter		self.fetched_event.set()
26748392b74bSAdrian Hunter
26758392b74bSAdrian Hunter	def Thread(self):
26768392b74bSAdrian Hunter		if not self.more:
26778392b74bSAdrian Hunter			return True, 0
26788392b74bSAdrian Hunter		while True:
26798392b74bSAdrian Hunter			self.fetched_event.clear()
26808392b74bSAdrian Hunter			fetch_count = self.fetch_count.value
26818392b74bSAdrian Hunter			if fetch_count != self.last_count:
26828392b74bSAdrian Hunter				break
26838392b74bSAdrian Hunter			if self.fetching_done.value:
26848392b74bSAdrian Hunter				self.more = False
26858392b74bSAdrian Hunter				return True, 0
26868392b74bSAdrian Hunter			self.fetched_event.wait()
26878392b74bSAdrian Hunter		count = fetch_count - self.last_count
26888392b74bSAdrian Hunter		self.last_count = fetch_count
26898392b74bSAdrian Hunter		self.fetched += count
26908392b74bSAdrian Hunter		return False, count
26918392b74bSAdrian Hunter
26928392b74bSAdrian Hunter	def Fetch(self, nr):
26938392b74bSAdrian Hunter		if not self.more:
26948392b74bSAdrian Hunter			# -1 inidcates there are no more
26958392b74bSAdrian Hunter			return -1
26968392b74bSAdrian Hunter		result = self.fetched
26978392b74bSAdrian Hunter		extra = result + nr - self.target
26988392b74bSAdrian Hunter		if extra > 0:
26998392b74bSAdrian Hunter			self.target += extra
27008392b74bSAdrian Hunter			# process_target < 0 indicates shutting down
27018392b74bSAdrian Hunter			if self.process_target.value >= 0:
27028392b74bSAdrian Hunter				self.process_target.value = self.target
27038392b74bSAdrian Hunter			self.wait_event.set()
27048392b74bSAdrian Hunter		return result
27058392b74bSAdrian Hunter
27068392b74bSAdrian Hunter	def RemoveFromBuffer(self):
27078392b74bSAdrian Hunter		pos = self.local_tail
27088392b74bSAdrian Hunter		if len(self.buffer) - pos < glb_nsz:
27098392b74bSAdrian Hunter			pos = 0
2710beda0e72STony Jones		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
27118392b74bSAdrian Hunter		if n == 0:
27128392b74bSAdrian Hunter			pos = 0
2713beda0e72STony Jones			n = pickle.loads(self.buffer[0 : glb_nsz])
27148392b74bSAdrian Hunter		pos += glb_nsz
2715beda0e72STony Jones		obj = pickle.loads(self.buffer[pos : pos + n])
27168392b74bSAdrian Hunter		self.local_tail = pos + n
27178392b74bSAdrian Hunter		return obj
27188392b74bSAdrian Hunter
27198392b74bSAdrian Hunter	def ProcessData(self, count):
27208392b74bSAdrian Hunter		for i in xrange(count):
27218392b74bSAdrian Hunter			obj = self.RemoveFromBuffer()
27228392b74bSAdrian Hunter			self.process_data(obj)
27238392b74bSAdrian Hunter		self.tail.value = self.local_tail
27248392b74bSAdrian Hunter		self.wait_event.set()
27258392b74bSAdrian Hunter		self.done.emit(count)
27268392b74bSAdrian Hunter
27278392b74bSAdrian Hunter# Fetch more records bar
27288392b74bSAdrian Hunter
27298392b74bSAdrian Hunterclass FetchMoreRecordsBar():
27308392b74bSAdrian Hunter
27318392b74bSAdrian Hunter	def __init__(self, model, parent):
27328392b74bSAdrian Hunter		self.model = model
27338392b74bSAdrian Hunter
27348392b74bSAdrian Hunter		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
27358392b74bSAdrian Hunter		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
27368392b74bSAdrian Hunter
27378392b74bSAdrian Hunter		self.fetch_count = QSpinBox()
27388392b74bSAdrian Hunter		self.fetch_count.setRange(1, 1000000)
27398392b74bSAdrian Hunter		self.fetch_count.setValue(10)
27408392b74bSAdrian Hunter		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
27418392b74bSAdrian Hunter
27428392b74bSAdrian Hunter		self.fetch = QPushButton("Go!")
27438392b74bSAdrian Hunter		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
27448392b74bSAdrian Hunter		self.fetch.released.connect(self.FetchMoreRecords)
27458392b74bSAdrian Hunter
27468392b74bSAdrian Hunter		self.progress = QProgressBar()
27478392b74bSAdrian Hunter		self.progress.setRange(0, 100)
27488392b74bSAdrian Hunter		self.progress.hide()
27498392b74bSAdrian Hunter
27508392b74bSAdrian Hunter		self.done_label = QLabel("All records fetched")
27518392b74bSAdrian Hunter		self.done_label.hide()
27528392b74bSAdrian Hunter
27538392b74bSAdrian Hunter		self.spacer = QLabel("")
27548392b74bSAdrian Hunter
27558392b74bSAdrian Hunter		self.close_button = QToolButton()
27568392b74bSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
27578392b74bSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
27588392b74bSAdrian Hunter
27598392b74bSAdrian Hunter		self.hbox = QHBoxLayout()
27608392b74bSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
27618392b74bSAdrian Hunter
27628392b74bSAdrian Hunter		self.hbox.addWidget(self.label)
27638392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch_count)
27648392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch)
27658392b74bSAdrian Hunter		self.hbox.addWidget(self.spacer)
27668392b74bSAdrian Hunter		self.hbox.addWidget(self.progress)
27678392b74bSAdrian Hunter		self.hbox.addWidget(self.done_label)
27688392b74bSAdrian Hunter		self.hbox.addWidget(self.close_button)
27698392b74bSAdrian Hunter
27708392b74bSAdrian Hunter		self.bar = QWidget()
277126688729SAdrian Hunter		self.bar.setLayout(self.hbox)
27728392b74bSAdrian Hunter		self.bar.show()
27738392b74bSAdrian Hunter
27748392b74bSAdrian Hunter		self.in_progress = False
27758392b74bSAdrian Hunter		self.model.progress.connect(self.Progress)
27768392b74bSAdrian Hunter
27778392b74bSAdrian Hunter		self.done = False
27788392b74bSAdrian Hunter
27798392b74bSAdrian Hunter		if not model.HasMoreRecords():
27808392b74bSAdrian Hunter			self.Done()
27818392b74bSAdrian Hunter
27828392b74bSAdrian Hunter	def Widget(self):
27838392b74bSAdrian Hunter		return self.bar
27848392b74bSAdrian Hunter
27858392b74bSAdrian Hunter	def Activate(self):
27868392b74bSAdrian Hunter		self.bar.show()
27878392b74bSAdrian Hunter		self.fetch.setFocus()
27888392b74bSAdrian Hunter
27898392b74bSAdrian Hunter	def Deactivate(self):
27908392b74bSAdrian Hunter		self.bar.hide()
27918392b74bSAdrian Hunter
27928392b74bSAdrian Hunter	def Enable(self, enable):
27938392b74bSAdrian Hunter		self.fetch.setEnabled(enable)
27948392b74bSAdrian Hunter		self.fetch_count.setEnabled(enable)
27958392b74bSAdrian Hunter
27968392b74bSAdrian Hunter	def Busy(self):
27978392b74bSAdrian Hunter		self.Enable(False)
27988392b74bSAdrian Hunter		self.fetch.hide()
27998392b74bSAdrian Hunter		self.spacer.hide()
28008392b74bSAdrian Hunter		self.progress.show()
28018392b74bSAdrian Hunter
28028392b74bSAdrian Hunter	def Idle(self):
28038392b74bSAdrian Hunter		self.in_progress = False
28048392b74bSAdrian Hunter		self.Enable(True)
28058392b74bSAdrian Hunter		self.progress.hide()
28068392b74bSAdrian Hunter		self.fetch.show()
28078392b74bSAdrian Hunter		self.spacer.show()
28088392b74bSAdrian Hunter
28098392b74bSAdrian Hunter	def Target(self):
28108392b74bSAdrian Hunter		return self.fetch_count.value() * glb_chunk_sz
28118392b74bSAdrian Hunter
28128392b74bSAdrian Hunter	def Done(self):
28138392b74bSAdrian Hunter		self.done = True
28148392b74bSAdrian Hunter		self.Idle()
28158392b74bSAdrian Hunter		self.label.hide()
28168392b74bSAdrian Hunter		self.fetch_count.hide()
28178392b74bSAdrian Hunter		self.fetch.hide()
28188392b74bSAdrian Hunter		self.spacer.hide()
28198392b74bSAdrian Hunter		self.done_label.show()
28208392b74bSAdrian Hunter
28218392b74bSAdrian Hunter	def Progress(self, count):
28228392b74bSAdrian Hunter		if self.in_progress:
28238392b74bSAdrian Hunter			if count:
28248392b74bSAdrian Hunter				percent = ((count - self.start) * 100) / self.Target()
28258392b74bSAdrian Hunter				if percent >= 100:
28268392b74bSAdrian Hunter					self.Idle()
28278392b74bSAdrian Hunter				else:
28288392b74bSAdrian Hunter					self.progress.setValue(percent)
28298392b74bSAdrian Hunter		if not count:
28308392b74bSAdrian Hunter			# Count value of zero means no more records
28318392b74bSAdrian Hunter			self.Done()
28328392b74bSAdrian Hunter
28338392b74bSAdrian Hunter	def FetchMoreRecords(self):
28348392b74bSAdrian Hunter		if self.done:
28358392b74bSAdrian Hunter			return
28368392b74bSAdrian Hunter		self.progress.setValue(0)
28378392b74bSAdrian Hunter		self.Busy()
28388392b74bSAdrian Hunter		self.in_progress = True
28398392b74bSAdrian Hunter		self.start = self.model.FetchMoreRecords(self.Target())
28408392b74bSAdrian Hunter
284176099f98SAdrian Hunter# Brance data model level two item
284276099f98SAdrian Hunter
284376099f98SAdrian Hunterclass BranchLevelTwoItem():
284476099f98SAdrian Hunter
2845530e22fdSAdrian Hunter	def __init__(self, row, col, text, parent_item):
284676099f98SAdrian Hunter		self.row = row
284776099f98SAdrian Hunter		self.parent_item = parent_item
2848530e22fdSAdrian Hunter		self.data = [""] * (col + 1)
2849530e22fdSAdrian Hunter		self.data[col] = text
285076099f98SAdrian Hunter		self.level = 2
285176099f98SAdrian Hunter
285276099f98SAdrian Hunter	def getParentItem(self):
285376099f98SAdrian Hunter		return self.parent_item
285476099f98SAdrian Hunter
285576099f98SAdrian Hunter	def getRow(self):
285676099f98SAdrian Hunter		return self.row
285776099f98SAdrian Hunter
285876099f98SAdrian Hunter	def childCount(self):
285976099f98SAdrian Hunter		return 0
286076099f98SAdrian Hunter
286176099f98SAdrian Hunter	def hasChildren(self):
286276099f98SAdrian Hunter		return False
286376099f98SAdrian Hunter
286476099f98SAdrian Hunter	def getData(self, column):
286576099f98SAdrian Hunter		return self.data[column]
286676099f98SAdrian Hunter
286776099f98SAdrian Hunter# Brance data model level one item
286876099f98SAdrian Hunter
286976099f98SAdrian Hunterclass BranchLevelOneItem():
287076099f98SAdrian Hunter
287176099f98SAdrian Hunter	def __init__(self, glb, row, data, parent_item):
287276099f98SAdrian Hunter		self.glb = glb
287376099f98SAdrian Hunter		self.row = row
287476099f98SAdrian Hunter		self.parent_item = parent_item
287576099f98SAdrian Hunter		self.child_count = 0
287676099f98SAdrian Hunter		self.child_items = []
287776099f98SAdrian Hunter		self.data = data[1:]
287876099f98SAdrian Hunter		self.dbid = data[0]
287976099f98SAdrian Hunter		self.level = 1
288076099f98SAdrian Hunter		self.query_done = False
2881530e22fdSAdrian Hunter		self.br_col = len(self.data) - 1
288276099f98SAdrian Hunter
288376099f98SAdrian Hunter	def getChildItem(self, row):
288476099f98SAdrian Hunter		return self.child_items[row]
288576099f98SAdrian Hunter
288676099f98SAdrian Hunter	def getParentItem(self):
288776099f98SAdrian Hunter		return self.parent_item
288876099f98SAdrian Hunter
288976099f98SAdrian Hunter	def getRow(self):
289076099f98SAdrian Hunter		return self.row
289176099f98SAdrian Hunter
289276099f98SAdrian Hunter	def Select(self):
289376099f98SAdrian Hunter		self.query_done = True
289476099f98SAdrian Hunter
289576099f98SAdrian Hunter		if not self.glb.have_disassembler:
289676099f98SAdrian Hunter			return
289776099f98SAdrian Hunter
289876099f98SAdrian Hunter		query = QSqlQuery(self.glb.db)
289976099f98SAdrian Hunter
290076099f98SAdrian Hunter		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
290176099f98SAdrian Hunter				  " FROM samples"
290276099f98SAdrian Hunter				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
290376099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
290476099f98SAdrian Hunter				  " WHERE samples.id = " + str(self.dbid))
290576099f98SAdrian Hunter		if not query.next():
290676099f98SAdrian Hunter			return
290776099f98SAdrian Hunter		cpu = query.value(0)
290876099f98SAdrian Hunter		dso = query.value(1)
290976099f98SAdrian Hunter		sym = query.value(2)
291076099f98SAdrian Hunter		if dso == 0 or sym == 0:
291176099f98SAdrian Hunter			return
291276099f98SAdrian Hunter		off = query.value(3)
291376099f98SAdrian Hunter		short_name = query.value(4)
291476099f98SAdrian Hunter		long_name = query.value(5)
291576099f98SAdrian Hunter		build_id = query.value(6)
291676099f98SAdrian Hunter		sym_start = query.value(7)
291776099f98SAdrian Hunter		ip = query.value(8)
291876099f98SAdrian Hunter
291976099f98SAdrian Hunter		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
292076099f98SAdrian Hunter				  " FROM samples"
292176099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
292276099f98SAdrian Hunter				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
292376099f98SAdrian Hunter				  " ORDER BY samples.id"
292476099f98SAdrian Hunter				  " LIMIT 1")
292576099f98SAdrian Hunter		if not query.next():
292676099f98SAdrian Hunter			return
292776099f98SAdrian Hunter		if query.value(0) != dso:
292876099f98SAdrian Hunter			# Cannot disassemble from one dso to another
292976099f98SAdrian Hunter			return
293076099f98SAdrian Hunter		bsym = query.value(1)
293176099f98SAdrian Hunter		boff = query.value(2)
293276099f98SAdrian Hunter		bsym_start = query.value(3)
293376099f98SAdrian Hunter		if bsym == 0:
293476099f98SAdrian Hunter			return
293576099f98SAdrian Hunter		tot = bsym_start + boff + 1 - sym_start - off
293676099f98SAdrian Hunter		if tot <= 0 or tot > 16384:
293776099f98SAdrian Hunter			return
293876099f98SAdrian Hunter
293976099f98SAdrian Hunter		inst = self.glb.disassembler.Instruction()
294076099f98SAdrian Hunter		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
294176099f98SAdrian Hunter		if not f:
294276099f98SAdrian Hunter			return
294376099f98SAdrian Hunter		mode = 0 if Is64Bit(f) else 1
294476099f98SAdrian Hunter		self.glb.disassembler.SetMode(inst, mode)
294576099f98SAdrian Hunter
294676099f98SAdrian Hunter		buf_sz = tot + 16
294776099f98SAdrian Hunter		buf = create_string_buffer(tot + 16)
294876099f98SAdrian Hunter		f.seek(sym_start + off)
294976099f98SAdrian Hunter		buf.value = f.read(buf_sz)
295076099f98SAdrian Hunter		buf_ptr = addressof(buf)
295176099f98SAdrian Hunter		i = 0
295276099f98SAdrian Hunter		while tot > 0:
295376099f98SAdrian Hunter			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
295476099f98SAdrian Hunter			if cnt:
295576099f98SAdrian Hunter				byte_str = tohex(ip).rjust(16)
295676099f98SAdrian Hunter				for k in xrange(cnt):
295776099f98SAdrian Hunter					byte_str += " %02x" % ord(buf[i])
295876099f98SAdrian Hunter					i += 1
295976099f98SAdrian Hunter				while k < 15:
296076099f98SAdrian Hunter					byte_str += "   "
296176099f98SAdrian Hunter					k += 1
2962530e22fdSAdrian Hunter				self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
296376099f98SAdrian Hunter				self.child_count += 1
296476099f98SAdrian Hunter			else:
296576099f98SAdrian Hunter				return
296676099f98SAdrian Hunter			buf_ptr += cnt
296776099f98SAdrian Hunter			tot -= cnt
296876099f98SAdrian Hunter			buf_sz -= cnt
296976099f98SAdrian Hunter			ip += cnt
297076099f98SAdrian Hunter
297176099f98SAdrian Hunter	def childCount(self):
297276099f98SAdrian Hunter		if not self.query_done:
297376099f98SAdrian Hunter			self.Select()
297476099f98SAdrian Hunter			if not self.child_count:
297576099f98SAdrian Hunter				return -1
297676099f98SAdrian Hunter		return self.child_count
297776099f98SAdrian Hunter
297876099f98SAdrian Hunter	def hasChildren(self):
297976099f98SAdrian Hunter		if not self.query_done:
298076099f98SAdrian Hunter			return True
298176099f98SAdrian Hunter		return self.child_count > 0
298276099f98SAdrian Hunter
298376099f98SAdrian Hunter	def getData(self, column):
298476099f98SAdrian Hunter		return self.data[column]
298576099f98SAdrian Hunter
298676099f98SAdrian Hunter# Brance data model root item
298776099f98SAdrian Hunter
298876099f98SAdrian Hunterclass BranchRootItem():
298976099f98SAdrian Hunter
299076099f98SAdrian Hunter	def __init__(self):
299176099f98SAdrian Hunter		self.child_count = 0
299276099f98SAdrian Hunter		self.child_items = []
299376099f98SAdrian Hunter		self.level = 0
299476099f98SAdrian Hunter
299576099f98SAdrian Hunter	def getChildItem(self, row):
299676099f98SAdrian Hunter		return self.child_items[row]
299776099f98SAdrian Hunter
299876099f98SAdrian Hunter	def getParentItem(self):
299976099f98SAdrian Hunter		return None
300076099f98SAdrian Hunter
300176099f98SAdrian Hunter	def getRow(self):
300276099f98SAdrian Hunter		return 0
300376099f98SAdrian Hunter
300476099f98SAdrian Hunter	def childCount(self):
300576099f98SAdrian Hunter		return self.child_count
300676099f98SAdrian Hunter
300776099f98SAdrian Hunter	def hasChildren(self):
300876099f98SAdrian Hunter		return self.child_count > 0
300976099f98SAdrian Hunter
301076099f98SAdrian Hunter	def getData(self, column):
301176099f98SAdrian Hunter		return ""
301276099f98SAdrian Hunter
3013530e22fdSAdrian Hunter# Calculate instructions per cycle
3014530e22fdSAdrian Hunter
3015530e22fdSAdrian Hunterdef CalcIPC(cyc_cnt, insn_cnt):
3016530e22fdSAdrian Hunter	if cyc_cnt and insn_cnt:
3017530e22fdSAdrian Hunter		ipc = Decimal(float(insn_cnt) / cyc_cnt)
3018530e22fdSAdrian Hunter		ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
3019530e22fdSAdrian Hunter	else:
3020530e22fdSAdrian Hunter		ipc = "0"
3021530e22fdSAdrian Hunter	return ipc
3022530e22fdSAdrian Hunter
302376099f98SAdrian Hunter# Branch data preparation
302476099f98SAdrian Hunter
3025530e22fdSAdrian Hunterdef BranchDataPrepBr(query, data):
3026530e22fdSAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
3027530e22fdSAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
3028530e22fdSAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
3029530e22fdSAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
3030530e22fdSAdrian Hunter
3031530e22fdSAdrian Hunterdef BranchDataPrepIPC(query, data):
3032530e22fdSAdrian Hunter	insn_cnt = query.value(16)
3033530e22fdSAdrian Hunter	cyc_cnt = query.value(17)
3034530e22fdSAdrian Hunter	ipc = CalcIPC(cyc_cnt, insn_cnt)
3035530e22fdSAdrian Hunter	data.append(insn_cnt)
3036530e22fdSAdrian Hunter	data.append(cyc_cnt)
3037530e22fdSAdrian Hunter	data.append(ipc)
3038530e22fdSAdrian Hunter
303976099f98SAdrian Hunterdef BranchDataPrep(query):
304076099f98SAdrian Hunter	data = []
304176099f98SAdrian Hunter	for i in xrange(0, 8):
304276099f98SAdrian Hunter		data.append(query.value(i))
3043530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
304476099f98SAdrian Hunter	return data
304576099f98SAdrian Hunter
30468453c936SAdrian Hunterdef BranchDataPrepWA(query):
30478453c936SAdrian Hunter	data = []
30488453c936SAdrian Hunter	data.append(query.value(0))
30498453c936SAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
30508453c936SAdrian Hunter	data.append("{:>19}".format(query.value(1)))
30518453c936SAdrian Hunter	for i in xrange(2, 8):
30528453c936SAdrian Hunter		data.append(query.value(i))
3053530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
3054530e22fdSAdrian Hunter	return data
3055530e22fdSAdrian Hunter
3056530e22fdSAdrian Hunterdef BranchDataWithIPCPrep(query):
3057530e22fdSAdrian Hunter	data = []
3058530e22fdSAdrian Hunter	for i in xrange(0, 8):
3059530e22fdSAdrian Hunter		data.append(query.value(i))
3060530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
3061530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
3062530e22fdSAdrian Hunter	return data
3063530e22fdSAdrian Hunter
3064530e22fdSAdrian Hunterdef BranchDataWithIPCPrepWA(query):
3065530e22fdSAdrian Hunter	data = []
3066530e22fdSAdrian Hunter	data.append(query.value(0))
3067530e22fdSAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3068530e22fdSAdrian Hunter	data.append("{:>19}".format(query.value(1)))
3069530e22fdSAdrian Hunter	for i in xrange(2, 8):
3070530e22fdSAdrian Hunter		data.append(query.value(i))
3071530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
3072530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
30738453c936SAdrian Hunter	return data
30748453c936SAdrian Hunter
307576099f98SAdrian Hunter# Branch data model
307676099f98SAdrian Hunter
307776099f98SAdrian Hunterclass BranchModel(TreeModel):
307876099f98SAdrian Hunter
307976099f98SAdrian Hunter	progress = Signal(object)
308076099f98SAdrian Hunter
308176099f98SAdrian Hunter	def __init__(self, glb, event_id, where_clause, parent=None):
30824a0979d4SAdrian Hunter		super(BranchModel, self).__init__(glb, None, parent)
308376099f98SAdrian Hunter		self.event_id = event_id
308476099f98SAdrian Hunter		self.more = True
308576099f98SAdrian Hunter		self.populated = 0
3086530e22fdSAdrian Hunter		self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
3087530e22fdSAdrian Hunter		if self.have_ipc:
3088530e22fdSAdrian Hunter			select_ipc = ", insn_count, cyc_count"
3089530e22fdSAdrian Hunter			prep_fn = BranchDataWithIPCPrep
3090530e22fdSAdrian Hunter			prep_wa_fn = BranchDataWithIPCPrepWA
3091530e22fdSAdrian Hunter		else:
3092530e22fdSAdrian Hunter			select_ipc = ""
3093530e22fdSAdrian Hunter			prep_fn = BranchDataPrep
3094530e22fdSAdrian Hunter			prep_wa_fn = BranchDataPrepWA
309576099f98SAdrian Hunter		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
309676099f98SAdrian Hunter			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
309776099f98SAdrian Hunter			" ip, symbols.name, sym_offset, dsos.short_name,"
309876099f98SAdrian Hunter			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
3099530e22fdSAdrian Hunter			+ select_ipc +
310076099f98SAdrian Hunter			" FROM samples"
310176099f98SAdrian Hunter			" INNER JOIN comms ON comm_id = comms.id"
310276099f98SAdrian Hunter			" INNER JOIN threads ON thread_id = threads.id"
310376099f98SAdrian Hunter			" INNER JOIN branch_types ON branch_type = branch_types.id"
310476099f98SAdrian Hunter			" INNER JOIN symbols ON symbol_id = symbols.id"
310576099f98SAdrian Hunter			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
310676099f98SAdrian Hunter			" INNER JOIN dsos ON samples.dso_id = dsos.id"
310776099f98SAdrian Hunter			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
310876099f98SAdrian Hunter			" WHERE samples.id > $$last_id$$" + where_clause +
310976099f98SAdrian Hunter			" AND evsel_id = " + str(self.event_id) +
311076099f98SAdrian Hunter			" ORDER BY samples.id"
311176099f98SAdrian Hunter			" LIMIT " + str(glb_chunk_sz))
31128453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
3113530e22fdSAdrian Hunter			prep = prep_fn
31148453c936SAdrian Hunter		else:
3115530e22fdSAdrian Hunter			prep = prep_wa_fn
31168453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
311776099f98SAdrian Hunter		self.fetcher.done.connect(self.Update)
311876099f98SAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
311976099f98SAdrian Hunter
3120a448ba23SAdrian Hunter	def GetRoot(self):
3121a448ba23SAdrian Hunter		return BranchRootItem()
3122a448ba23SAdrian Hunter
312376099f98SAdrian Hunter	def columnCount(self, parent=None):
3124530e22fdSAdrian Hunter		if self.have_ipc:
3125530e22fdSAdrian Hunter			return 11
3126530e22fdSAdrian Hunter		else:
312776099f98SAdrian Hunter			return 8
312876099f98SAdrian Hunter
312976099f98SAdrian Hunter	def columnHeader(self, column):
3130530e22fdSAdrian Hunter		if self.have_ipc:
3131530e22fdSAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
3132530e22fdSAdrian Hunter		else:
313376099f98SAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
313476099f98SAdrian Hunter
313576099f98SAdrian Hunter	def columnFont(self, column):
3136530e22fdSAdrian Hunter		if self.have_ipc:
3137530e22fdSAdrian Hunter			br_col = 10
3138530e22fdSAdrian Hunter		else:
3139530e22fdSAdrian Hunter			br_col = 7
3140530e22fdSAdrian Hunter		if column != br_col:
314176099f98SAdrian Hunter			return None
314276099f98SAdrian Hunter		return QFont("Monospace")
314376099f98SAdrian Hunter
314476099f98SAdrian Hunter	def DisplayData(self, item, index):
314576099f98SAdrian Hunter		if item.level == 1:
314676099f98SAdrian Hunter			self.FetchIfNeeded(item.row)
314776099f98SAdrian Hunter		return item.getData(index.column())
314876099f98SAdrian Hunter
314976099f98SAdrian Hunter	def AddSample(self, data):
315076099f98SAdrian Hunter		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
315176099f98SAdrian Hunter		self.root.child_items.append(child)
315276099f98SAdrian Hunter		self.populated += 1
315376099f98SAdrian Hunter
315476099f98SAdrian Hunter	def Update(self, fetched):
315576099f98SAdrian Hunter		if not fetched:
315676099f98SAdrian Hunter			self.more = False
315776099f98SAdrian Hunter			self.progress.emit(0)
315876099f98SAdrian Hunter		child_count = self.root.child_count
315976099f98SAdrian Hunter		count = self.populated - child_count
316076099f98SAdrian Hunter		if count > 0:
316176099f98SAdrian Hunter			parent = QModelIndex()
316276099f98SAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
316376099f98SAdrian Hunter			self.insertRows(child_count, count, parent)
316476099f98SAdrian Hunter			self.root.child_count += count
316576099f98SAdrian Hunter			self.endInsertRows()
316676099f98SAdrian Hunter			self.progress.emit(self.root.child_count)
316776099f98SAdrian Hunter
316876099f98SAdrian Hunter	def FetchMoreRecords(self, count):
316976099f98SAdrian Hunter		current = self.root.child_count
317076099f98SAdrian Hunter		if self.more:
317176099f98SAdrian Hunter			self.fetcher.Fetch(count)
317276099f98SAdrian Hunter		else:
317376099f98SAdrian Hunter			self.progress.emit(0)
317476099f98SAdrian Hunter		return current
317576099f98SAdrian Hunter
317676099f98SAdrian Hunter	def HasMoreRecords(self):
317776099f98SAdrian Hunter		return self.more
317876099f98SAdrian Hunter
31790bf0947aSAdrian Hunter# Report Variables
31800bf0947aSAdrian Hunter
31810bf0947aSAdrian Hunterclass ReportVars():
31820bf0947aSAdrian Hunter
3183cd358012SAdrian Hunter	def __init__(self, name = "", where_clause = "", limit = ""):
3184947cc38dSAdrian Hunter		self.name = name
31850bf0947aSAdrian Hunter		self.where_clause = where_clause
3186cd358012SAdrian Hunter		self.limit = limit
31870bf0947aSAdrian Hunter
31880bf0947aSAdrian Hunter	def UniqueId(self):
3189cd358012SAdrian Hunter		return str(self.where_clause + ";" + self.limit)
31900bf0947aSAdrian Hunter
319176099f98SAdrian Hunter# Branch window
319276099f98SAdrian Hunter
319376099f98SAdrian Hunterclass BranchWindow(QMdiSubWindow):
319476099f98SAdrian Hunter
3195947cc38dSAdrian Hunter	def __init__(self, glb, event_id, report_vars, parent=None):
319676099f98SAdrian Hunter		super(BranchWindow, self).__init__(parent)
319776099f98SAdrian Hunter
31980bf0947aSAdrian Hunter		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
319976099f98SAdrian Hunter
32000bf0947aSAdrian Hunter		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
320176099f98SAdrian Hunter
320276099f98SAdrian Hunter		self.view = QTreeView()
320376099f98SAdrian Hunter		self.view.setUniformRowHeights(True)
320496c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
320596c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
320676099f98SAdrian Hunter		self.view.setModel(self.model)
320776099f98SAdrian Hunter
320876099f98SAdrian Hunter		self.ResizeColumnsToContents()
320976099f98SAdrian Hunter
32109bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
32119bc4e4bfSAdrian Hunter
321276099f98SAdrian Hunter		self.find_bar = FindBar(self, self, True)
321376099f98SAdrian Hunter
321476099f98SAdrian Hunter		self.finder = ChildDataItemFinder(self.model.root)
321576099f98SAdrian Hunter
321676099f98SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
321776099f98SAdrian Hunter
321876099f98SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
321976099f98SAdrian Hunter
322076099f98SAdrian Hunter		self.setWidget(self.vbox.Widget())
322176099f98SAdrian Hunter
3222947cc38dSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
322376099f98SAdrian Hunter
322476099f98SAdrian Hunter	def ResizeColumnToContents(self, column, n):
322576099f98SAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
322676099f98SAdrian Hunter		# so implement a crude alternative
322776099f98SAdrian Hunter		mm = "MM" if column else "MMMM"
322876099f98SAdrian Hunter		font = self.view.font()
322976099f98SAdrian Hunter		metrics = QFontMetrics(font)
323076099f98SAdrian Hunter		max = 0
323176099f98SAdrian Hunter		for row in xrange(n):
323276099f98SAdrian Hunter			val = self.model.root.child_items[row].data[column]
323376099f98SAdrian Hunter			len = metrics.width(str(val) + mm)
323476099f98SAdrian Hunter			max = len if len > max else max
323576099f98SAdrian Hunter		val = self.model.columnHeader(column)
323676099f98SAdrian Hunter		len = metrics.width(str(val) + mm)
323776099f98SAdrian Hunter		max = len if len > max else max
323876099f98SAdrian Hunter		self.view.setColumnWidth(column, max)
323976099f98SAdrian Hunter
324076099f98SAdrian Hunter	def ResizeColumnsToContents(self):
324176099f98SAdrian Hunter		n = min(self.model.root.child_count, 100)
324276099f98SAdrian Hunter		if n < 1:
324376099f98SAdrian Hunter			# No data yet, so connect a signal to notify when there is
324476099f98SAdrian Hunter			self.model.rowsInserted.connect(self.UpdateColumnWidths)
324576099f98SAdrian Hunter			return
324676099f98SAdrian Hunter		columns = self.model.columnCount()
324776099f98SAdrian Hunter		for i in xrange(columns):
324876099f98SAdrian Hunter			self.ResizeColumnToContents(i, n)
324976099f98SAdrian Hunter
325076099f98SAdrian Hunter	def UpdateColumnWidths(self, *x):
325176099f98SAdrian Hunter		# This only needs to be done once, so disconnect the signal now
325276099f98SAdrian Hunter		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
325376099f98SAdrian Hunter		self.ResizeColumnsToContents()
325476099f98SAdrian Hunter
325576099f98SAdrian Hunter	def Find(self, value, direction, pattern, context):
325676099f98SAdrian Hunter		self.view.setFocus()
325776099f98SAdrian Hunter		self.find_bar.Busy()
325876099f98SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
325976099f98SAdrian Hunter
326076099f98SAdrian Hunter	def FindDone(self, row):
326176099f98SAdrian Hunter		self.find_bar.Idle()
326276099f98SAdrian Hunter		if row >= 0:
326376099f98SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
326476099f98SAdrian Hunter		else:
326576099f98SAdrian Hunter			self.find_bar.NotFound()
326676099f98SAdrian Hunter
32671c3ca1b3SAdrian Hunter# Line edit data item
32681c3ca1b3SAdrian Hunter
32691c3ca1b3SAdrian Hunterclass LineEditDataItem(object):
32701c3ca1b3SAdrian Hunter
3271cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
32721c3ca1b3SAdrian Hunter		self.glb = glb
32731c3ca1b3SAdrian Hunter		self.label = label
32741c3ca1b3SAdrian Hunter		self.placeholder_text = placeholder_text
32751c3ca1b3SAdrian Hunter		self.parent = parent
32761c3ca1b3SAdrian Hunter		self.id = id
32771c3ca1b3SAdrian Hunter
3278cd358012SAdrian Hunter		self.value = default
32791c3ca1b3SAdrian Hunter
3280cd358012SAdrian Hunter		self.widget = QLineEdit(default)
32811c3ca1b3SAdrian Hunter		self.widget.editingFinished.connect(self.Validate)
32821c3ca1b3SAdrian Hunter		self.widget.textChanged.connect(self.Invalidate)
32831c3ca1b3SAdrian Hunter		self.red = False
32841c3ca1b3SAdrian Hunter		self.error = ""
32851c3ca1b3SAdrian Hunter		self.validated = True
32861c3ca1b3SAdrian Hunter
32871c3ca1b3SAdrian Hunter		if placeholder_text:
32881c3ca1b3SAdrian Hunter			self.widget.setPlaceholderText(placeholder_text)
32891c3ca1b3SAdrian Hunter
32901c3ca1b3SAdrian Hunter	def TurnTextRed(self):
32911c3ca1b3SAdrian Hunter		if not self.red:
32921c3ca1b3SAdrian Hunter			palette = QPalette()
32931c3ca1b3SAdrian Hunter			palette.setColor(QPalette.Text,Qt.red)
32941c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
32951c3ca1b3SAdrian Hunter			self.red = True
32961c3ca1b3SAdrian Hunter
32971c3ca1b3SAdrian Hunter	def TurnTextNormal(self):
32981c3ca1b3SAdrian Hunter		if self.red:
32991c3ca1b3SAdrian Hunter			palette = QPalette()
33001c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
33011c3ca1b3SAdrian Hunter			self.red = False
33021c3ca1b3SAdrian Hunter
33031c3ca1b3SAdrian Hunter	def InvalidValue(self, value):
33041c3ca1b3SAdrian Hunter		self.value = ""
33051c3ca1b3SAdrian Hunter		self.TurnTextRed()
33061c3ca1b3SAdrian Hunter		self.error = self.label + " invalid value '" + value + "'"
33071c3ca1b3SAdrian Hunter		self.parent.ShowMessage(self.error)
33081c3ca1b3SAdrian Hunter
33091c3ca1b3SAdrian Hunter	def Invalidate(self):
33101c3ca1b3SAdrian Hunter		self.validated = False
33111c3ca1b3SAdrian Hunter
33121c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
33131c3ca1b3SAdrian Hunter		self.value = input_string.strip()
33141c3ca1b3SAdrian Hunter
33151c3ca1b3SAdrian Hunter	def Validate(self):
33161c3ca1b3SAdrian Hunter		self.validated = True
33171c3ca1b3SAdrian Hunter		self.error = ""
33181c3ca1b3SAdrian Hunter		self.TurnTextNormal()
33191c3ca1b3SAdrian Hunter		self.parent.ClearMessage()
33201c3ca1b3SAdrian Hunter		input_string = self.widget.text()
33211c3ca1b3SAdrian Hunter		if not len(input_string.strip()):
33221c3ca1b3SAdrian Hunter			self.value = ""
33231c3ca1b3SAdrian Hunter			return
33241c3ca1b3SAdrian Hunter		self.DoValidate(input_string)
33251c3ca1b3SAdrian Hunter
33261c3ca1b3SAdrian Hunter	def IsValid(self):
33271c3ca1b3SAdrian Hunter		if not self.validated:
33281c3ca1b3SAdrian Hunter			self.Validate()
33291c3ca1b3SAdrian Hunter		if len(self.error):
33301c3ca1b3SAdrian Hunter			self.parent.ShowMessage(self.error)
33311c3ca1b3SAdrian Hunter			return False
33321c3ca1b3SAdrian Hunter		return True
33331c3ca1b3SAdrian Hunter
33341c3ca1b3SAdrian Hunter	def IsNumber(self, value):
33351c3ca1b3SAdrian Hunter		try:
33361c3ca1b3SAdrian Hunter			x = int(value)
33371c3ca1b3SAdrian Hunter		except:
33381c3ca1b3SAdrian Hunter			x = 0
33391c3ca1b3SAdrian Hunter		return str(x) == value
33401c3ca1b3SAdrian Hunter
33411c3ca1b3SAdrian Hunter# Non-negative integer ranges dialog data item
33421c3ca1b3SAdrian Hunter
33431c3ca1b3SAdrian Hunterclass NonNegativeIntegerRangesDataItem(LineEditDataItem):
33441c3ca1b3SAdrian Hunter
33451c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
33461c3ca1b3SAdrian Hunter		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
33471c3ca1b3SAdrian Hunter
33481c3ca1b3SAdrian Hunter		self.column_name = column_name
33491c3ca1b3SAdrian Hunter
33501c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
33511c3ca1b3SAdrian Hunter		singles = []
33521c3ca1b3SAdrian Hunter		ranges = []
33531c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
33541c3ca1b3SAdrian Hunter			if "-" in value:
33551c3ca1b3SAdrian Hunter				vrange = value.split("-")
33561c3ca1b3SAdrian Hunter				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
33571c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
33581c3ca1b3SAdrian Hunter				ranges.append(vrange)
33591c3ca1b3SAdrian Hunter			else:
33601c3ca1b3SAdrian Hunter				if not self.IsNumber(value):
33611c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
33621c3ca1b3SAdrian Hunter				singles.append(value)
33631c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
33641c3ca1b3SAdrian Hunter		if len(singles):
33651c3ca1b3SAdrian Hunter			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
33661c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
33671c3ca1b3SAdrian Hunter
3368cd358012SAdrian Hunter# Positive integer dialog data item
3369cd358012SAdrian Hunter
3370cd358012SAdrian Hunterclass PositiveIntegerDataItem(LineEditDataItem):
3371cd358012SAdrian Hunter
3372cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
3373cd358012SAdrian Hunter		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
3374cd358012SAdrian Hunter
3375cd358012SAdrian Hunter	def DoValidate(self, input_string):
3376cd358012SAdrian Hunter		if not self.IsNumber(input_string.strip()):
3377cd358012SAdrian Hunter			return self.InvalidValue(input_string)
3378cd358012SAdrian Hunter		value = int(input_string.strip())
3379cd358012SAdrian Hunter		if value <= 0:
3380cd358012SAdrian Hunter			return self.InvalidValue(input_string)
3381cd358012SAdrian Hunter		self.value = str(value)
3382cd358012SAdrian Hunter
33831c3ca1b3SAdrian Hunter# Dialog data item converted and validated using a SQL table
33841c3ca1b3SAdrian Hunter
33851c3ca1b3SAdrian Hunterclass SQLTableDataItem(LineEditDataItem):
33861c3ca1b3SAdrian Hunter
33871c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
33881c3ca1b3SAdrian Hunter		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
33891c3ca1b3SAdrian Hunter
33901c3ca1b3SAdrian Hunter		self.table_name = table_name
33911c3ca1b3SAdrian Hunter		self.match_column = match_column
33921c3ca1b3SAdrian Hunter		self.column_name1 = column_name1
33931c3ca1b3SAdrian Hunter		self.column_name2 = column_name2
33941c3ca1b3SAdrian Hunter
33951c3ca1b3SAdrian Hunter	def ValueToIds(self, value):
33961c3ca1b3SAdrian Hunter		ids = []
33971c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
33981c3ca1b3SAdrian Hunter		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
33991c3ca1b3SAdrian Hunter		ret = query.exec_(stmt)
34001c3ca1b3SAdrian Hunter		if ret:
34011c3ca1b3SAdrian Hunter			while query.next():
34021c3ca1b3SAdrian Hunter				ids.append(str(query.value(0)))
34031c3ca1b3SAdrian Hunter		return ids
34041c3ca1b3SAdrian Hunter
34051c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
34061c3ca1b3SAdrian Hunter		all_ids = []
34071c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
34081c3ca1b3SAdrian Hunter			ids = self.ValueToIds(value)
34091c3ca1b3SAdrian Hunter			if len(ids):
34101c3ca1b3SAdrian Hunter				all_ids.extend(ids)
34111c3ca1b3SAdrian Hunter			else:
34121c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
34131c3ca1b3SAdrian Hunter		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
34141c3ca1b3SAdrian Hunter		if self.column_name2:
34151c3ca1b3SAdrian Hunter			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
34161c3ca1b3SAdrian Hunter
34171c3ca1b3SAdrian Hunter# Sample time ranges dialog data item converted and validated using 'samples' SQL table
34181c3ca1b3SAdrian Hunter
34191c3ca1b3SAdrian Hunterclass SampleTimeRangesDataItem(LineEditDataItem):
34201c3ca1b3SAdrian Hunter
34211c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
34221c3ca1b3SAdrian Hunter		self.column_name = column_name
34231c3ca1b3SAdrian Hunter
34241c3ca1b3SAdrian Hunter		self.last_id = 0
34251c3ca1b3SAdrian Hunter		self.first_time = 0
34261c3ca1b3SAdrian Hunter		self.last_time = 2 ** 64
34271c3ca1b3SAdrian Hunter
34281c3ca1b3SAdrian Hunter		query = QSqlQuery(glb.db)
34291c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
34301c3ca1b3SAdrian Hunter		if query.next():
34311c3ca1b3SAdrian Hunter			self.last_id = int(query.value(0))
34329a9dae36SAdrian Hunter		self.first_time = int(glb.HostStartTime())
34339a9dae36SAdrian Hunter		self.last_time = int(glb.HostFinishTime())
34341c3ca1b3SAdrian Hunter		if placeholder_text:
34351c3ca1b3SAdrian Hunter			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
34361c3ca1b3SAdrian Hunter
34371c3ca1b3SAdrian Hunter		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
34381c3ca1b3SAdrian Hunter
34391c3ca1b3SAdrian Hunter	def IdBetween(self, query, lower_id, higher_id, order):
34401c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
34411c3ca1b3SAdrian Hunter		if query.next():
34421c3ca1b3SAdrian Hunter			return True, int(query.value(0))
34431c3ca1b3SAdrian Hunter		else:
34441c3ca1b3SAdrian Hunter			return False, 0
34451c3ca1b3SAdrian Hunter
34461c3ca1b3SAdrian Hunter	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
34471c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
34481c3ca1b3SAdrian Hunter		while True:
34491c3ca1b3SAdrian Hunter			next_id = int((lower_id + higher_id) / 2)
34501c3ca1b3SAdrian Hunter			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
34511c3ca1b3SAdrian Hunter			if not query.next():
34521c3ca1b3SAdrian Hunter				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
34531c3ca1b3SAdrian Hunter				if not ok:
34541c3ca1b3SAdrian Hunter					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
34551c3ca1b3SAdrian Hunter					if not ok:
34561c3ca1b3SAdrian Hunter						return str(higher_id)
34571c3ca1b3SAdrian Hunter				next_id = dbid
34581c3ca1b3SAdrian Hunter				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
34591c3ca1b3SAdrian Hunter			next_time = int(query.value(0))
34601c3ca1b3SAdrian Hunter			if get_floor:
34611c3ca1b3SAdrian Hunter				if target_time > next_time:
34621c3ca1b3SAdrian Hunter					lower_id = next_id
34631c3ca1b3SAdrian Hunter				else:
34641c3ca1b3SAdrian Hunter					higher_id = next_id
34651c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
34661c3ca1b3SAdrian Hunter					return str(higher_id)
34671c3ca1b3SAdrian Hunter			else:
34681c3ca1b3SAdrian Hunter				if target_time >= next_time:
34691c3ca1b3SAdrian Hunter					lower_id = next_id
34701c3ca1b3SAdrian Hunter				else:
34711c3ca1b3SAdrian Hunter					higher_id = next_id
34721c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
34731c3ca1b3SAdrian Hunter					return str(lower_id)
34741c3ca1b3SAdrian Hunter
34751c3ca1b3SAdrian Hunter	def ConvertRelativeTime(self, val):
34761c3ca1b3SAdrian Hunter		mult = 1
34771c3ca1b3SAdrian Hunter		suffix = val[-2:]
34781c3ca1b3SAdrian Hunter		if suffix == "ms":
34791c3ca1b3SAdrian Hunter			mult = 1000000
34801c3ca1b3SAdrian Hunter		elif suffix == "us":
34811c3ca1b3SAdrian Hunter			mult = 1000
34821c3ca1b3SAdrian Hunter		elif suffix == "ns":
34831c3ca1b3SAdrian Hunter			mult = 1
34841c3ca1b3SAdrian Hunter		else:
34851c3ca1b3SAdrian Hunter			return val
34861c3ca1b3SAdrian Hunter		val = val[:-2].strip()
34871c3ca1b3SAdrian Hunter		if not self.IsNumber(val):
34881c3ca1b3SAdrian Hunter			return val
34891c3ca1b3SAdrian Hunter		val = int(val) * mult
34901c3ca1b3SAdrian Hunter		if val >= 0:
34911c3ca1b3SAdrian Hunter			val += self.first_time
34921c3ca1b3SAdrian Hunter		else:
34931c3ca1b3SAdrian Hunter			val += self.last_time
34941c3ca1b3SAdrian Hunter		return str(val)
34951c3ca1b3SAdrian Hunter
34961c3ca1b3SAdrian Hunter	def ConvertTimeRange(self, vrange):
34971c3ca1b3SAdrian Hunter		if vrange[0] == "":
34981c3ca1b3SAdrian Hunter			vrange[0] = str(self.first_time)
34991c3ca1b3SAdrian Hunter		if vrange[1] == "":
35001c3ca1b3SAdrian Hunter			vrange[1] = str(self.last_time)
35011c3ca1b3SAdrian Hunter		vrange[0] = self.ConvertRelativeTime(vrange[0])
35021c3ca1b3SAdrian Hunter		vrange[1] = self.ConvertRelativeTime(vrange[1])
35031c3ca1b3SAdrian Hunter		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
35041c3ca1b3SAdrian Hunter			return False
35051c3ca1b3SAdrian Hunter		beg_range = max(int(vrange[0]), self.first_time)
35061c3ca1b3SAdrian Hunter		end_range = min(int(vrange[1]), self.last_time)
35071c3ca1b3SAdrian Hunter		if beg_range > self.last_time or end_range < self.first_time:
35081c3ca1b3SAdrian Hunter			return False
35091c3ca1b3SAdrian Hunter		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
35101c3ca1b3SAdrian Hunter		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
35111c3ca1b3SAdrian Hunter		return True
35121c3ca1b3SAdrian Hunter
35131c3ca1b3SAdrian Hunter	def AddTimeRange(self, value, ranges):
35141c3ca1b3SAdrian Hunter		n = value.count("-")
35151c3ca1b3SAdrian Hunter		if n == 1:
35161c3ca1b3SAdrian Hunter			pass
35171c3ca1b3SAdrian Hunter		elif n == 2:
35181c3ca1b3SAdrian Hunter			if value.split("-")[1].strip() == "":
35191c3ca1b3SAdrian Hunter				n = 1
35201c3ca1b3SAdrian Hunter		elif n == 3:
35211c3ca1b3SAdrian Hunter			n = 2
35221c3ca1b3SAdrian Hunter		else:
35231c3ca1b3SAdrian Hunter			return False
35241c3ca1b3SAdrian Hunter		pos = findnth(value, "-", n)
35251c3ca1b3SAdrian Hunter		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
35261c3ca1b3SAdrian Hunter		if self.ConvertTimeRange(vrange):
35271c3ca1b3SAdrian Hunter			ranges.append(vrange)
35281c3ca1b3SAdrian Hunter			return True
35291c3ca1b3SAdrian Hunter		return False
35301c3ca1b3SAdrian Hunter
35311c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
35321c3ca1b3SAdrian Hunter		ranges = []
35331c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
35341c3ca1b3SAdrian Hunter			if not self.AddTimeRange(value, ranges):
35351c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
35361c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
35371c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
35381c3ca1b3SAdrian Hunter
35390924cd68SAdrian Hunter# Report Dialog Base
3540210cf1f9SAdrian Hunter
35410924cd68SAdrian Hunterclass ReportDialogBase(QDialog):
3542210cf1f9SAdrian Hunter
35430924cd68SAdrian Hunter	def __init__(self, glb, title, items, partial, parent=None):
35440924cd68SAdrian Hunter		super(ReportDialogBase, self).__init__(parent)
3545210cf1f9SAdrian Hunter
3546210cf1f9SAdrian Hunter		self.glb = glb
3547210cf1f9SAdrian Hunter
35480bf0947aSAdrian Hunter		self.report_vars = ReportVars()
3549210cf1f9SAdrian Hunter
35500924cd68SAdrian Hunter		self.setWindowTitle(title)
3551210cf1f9SAdrian Hunter		self.setMinimumWidth(600)
3552210cf1f9SAdrian Hunter
35531c3ca1b3SAdrian Hunter		self.data_items = [x(glb, self) for x in items]
3554210cf1f9SAdrian Hunter
35550924cd68SAdrian Hunter		self.partial = partial
35560924cd68SAdrian Hunter
3557210cf1f9SAdrian Hunter		self.grid = QGridLayout()
3558210cf1f9SAdrian Hunter
3559210cf1f9SAdrian Hunter		for row in xrange(len(self.data_items)):
3560210cf1f9SAdrian Hunter			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
3561210cf1f9SAdrian Hunter			self.grid.addWidget(self.data_items[row].widget, row, 1)
3562210cf1f9SAdrian Hunter
3563210cf1f9SAdrian Hunter		self.status = QLabel()
3564210cf1f9SAdrian Hunter
3565210cf1f9SAdrian Hunter		self.ok_button = QPushButton("Ok", self)
3566210cf1f9SAdrian Hunter		self.ok_button.setDefault(True)
3567210cf1f9SAdrian Hunter		self.ok_button.released.connect(self.Ok)
3568210cf1f9SAdrian Hunter		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
3569210cf1f9SAdrian Hunter
3570210cf1f9SAdrian Hunter		self.cancel_button = QPushButton("Cancel", self)
3571210cf1f9SAdrian Hunter		self.cancel_button.released.connect(self.reject)
3572210cf1f9SAdrian Hunter		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
3573210cf1f9SAdrian Hunter
3574210cf1f9SAdrian Hunter		self.hbox = QHBoxLayout()
3575210cf1f9SAdrian Hunter		#self.hbox.addStretch()
3576210cf1f9SAdrian Hunter		self.hbox.addWidget(self.status)
3577210cf1f9SAdrian Hunter		self.hbox.addWidget(self.ok_button)
3578210cf1f9SAdrian Hunter		self.hbox.addWidget(self.cancel_button)
3579210cf1f9SAdrian Hunter
3580210cf1f9SAdrian Hunter		self.vbox = QVBoxLayout()
3581210cf1f9SAdrian Hunter		self.vbox.addLayout(self.grid)
3582210cf1f9SAdrian Hunter		self.vbox.addLayout(self.hbox)
3583210cf1f9SAdrian Hunter
358426688729SAdrian Hunter		self.setLayout(self.vbox)
3585210cf1f9SAdrian Hunter
3586210cf1f9SAdrian Hunter	def Ok(self):
35870bf0947aSAdrian Hunter		vars = self.report_vars
35881c3ca1b3SAdrian Hunter		for d in self.data_items:
35891c3ca1b3SAdrian Hunter			if d.id == "REPORTNAME":
35901c3ca1b3SAdrian Hunter				vars.name = d.value
3591947cc38dSAdrian Hunter		if not vars.name:
3592210cf1f9SAdrian Hunter			self.ShowMessage("Report name is required")
3593210cf1f9SAdrian Hunter			return
3594210cf1f9SAdrian Hunter		for d in self.data_items:
3595210cf1f9SAdrian Hunter			if not d.IsValid():
3596210cf1f9SAdrian Hunter				return
3597210cf1f9SAdrian Hunter		for d in self.data_items[1:]:
3598cd358012SAdrian Hunter			if d.id == "LIMIT":
3599cd358012SAdrian Hunter				vars.limit = d.value
3600cd358012SAdrian Hunter			elif len(d.value):
36010bf0947aSAdrian Hunter				if len(vars.where_clause):
36020bf0947aSAdrian Hunter					vars.where_clause += " AND "
36030bf0947aSAdrian Hunter				vars.where_clause += d.value
36040bf0947aSAdrian Hunter		if len(vars.where_clause):
36050924cd68SAdrian Hunter			if self.partial:
36060bf0947aSAdrian Hunter				vars.where_clause = " AND ( " + vars.where_clause + " ) "
3607210cf1f9SAdrian Hunter			else:
36080bf0947aSAdrian Hunter				vars.where_clause = " WHERE " + vars.where_clause + " "
3609210cf1f9SAdrian Hunter		self.accept()
3610210cf1f9SAdrian Hunter
3611210cf1f9SAdrian Hunter	def ShowMessage(self, msg):
3612210cf1f9SAdrian Hunter		self.status.setText("<font color=#FF0000>" + msg)
3613210cf1f9SAdrian Hunter
3614210cf1f9SAdrian Hunter	def ClearMessage(self):
3615210cf1f9SAdrian Hunter		self.status.setText("")
3616210cf1f9SAdrian Hunter
36170924cd68SAdrian Hunter# Selected branch report creation dialog
36180924cd68SAdrian Hunter
36190924cd68SAdrian Hunterclass SelectedBranchDialog(ReportDialogBase):
36200924cd68SAdrian Hunter
36210924cd68SAdrian Hunter	def __init__(self, glb, parent=None):
36220924cd68SAdrian Hunter		title = "Selected Branches"
36231c3ca1b3SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
36241c3ca1b3SAdrian Hunter			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
36251c3ca1b3SAdrian Hunter			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
36261c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
36271c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
36281c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
36291c3ca1b3SAdrian 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),
36301c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
36311c3ca1b3SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
36320924cd68SAdrian Hunter		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
36330924cd68SAdrian Hunter
363476099f98SAdrian Hunter# Event list
363576099f98SAdrian Hunter
363676099f98SAdrian Hunterdef GetEventList(db):
363776099f98SAdrian Hunter	events = []
363876099f98SAdrian Hunter	query = QSqlQuery(db)
363976099f98SAdrian Hunter	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
364076099f98SAdrian Hunter	while query.next():
364176099f98SAdrian Hunter		events.append(query.value(0))
364276099f98SAdrian Hunter	return events
364376099f98SAdrian Hunter
3644655cb952SAdrian Hunter# Is a table selectable
3645655cb952SAdrian Hunter
3646530e22fdSAdrian Hunterdef IsSelectable(db, table, sql = "", columns = "*"):
3647655cb952SAdrian Hunter	query = QSqlQuery(db)
3648655cb952SAdrian Hunter	try:
3649530e22fdSAdrian Hunter		QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
3650655cb952SAdrian Hunter	except:
3651655cb952SAdrian Hunter		return False
3652655cb952SAdrian Hunter	return True
3653655cb952SAdrian Hunter
36548392b74bSAdrian Hunter# SQL table data model item
36558392b74bSAdrian Hunter
36568392b74bSAdrian Hunterclass SQLTableItem():
36578392b74bSAdrian Hunter
36588392b74bSAdrian Hunter	def __init__(self, row, data):
36598392b74bSAdrian Hunter		self.row = row
36608392b74bSAdrian Hunter		self.data = data
36618392b74bSAdrian Hunter
36628392b74bSAdrian Hunter	def getData(self, column):
36638392b74bSAdrian Hunter		return self.data[column]
36648392b74bSAdrian Hunter
36658392b74bSAdrian Hunter# SQL table data model
36668392b74bSAdrian Hunter
36678392b74bSAdrian Hunterclass SQLTableModel(TableModel):
36688392b74bSAdrian Hunter
36698392b74bSAdrian Hunter	progress = Signal(object)
36708392b74bSAdrian Hunter
36718c90fef9SAdrian Hunter	def __init__(self, glb, sql, column_headers, parent=None):
36728392b74bSAdrian Hunter		super(SQLTableModel, self).__init__(parent)
36738392b74bSAdrian Hunter		self.glb = glb
36748392b74bSAdrian Hunter		self.more = True
36758392b74bSAdrian Hunter		self.populated = 0
36768c90fef9SAdrian Hunter		self.column_headers = column_headers
36778453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
36788392b74bSAdrian Hunter		self.fetcher.done.connect(self.Update)
36798392b74bSAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
36808392b74bSAdrian Hunter
36818392b74bSAdrian Hunter	def DisplayData(self, item, index):
36828392b74bSAdrian Hunter		self.FetchIfNeeded(item.row)
36838392b74bSAdrian Hunter		return item.getData(index.column())
36848392b74bSAdrian Hunter
36858392b74bSAdrian Hunter	def AddSample(self, data):
36868392b74bSAdrian Hunter		child = SQLTableItem(self.populated, data)
36878392b74bSAdrian Hunter		self.child_items.append(child)
36888392b74bSAdrian Hunter		self.populated += 1
36898392b74bSAdrian Hunter
36908392b74bSAdrian Hunter	def Update(self, fetched):
36918392b74bSAdrian Hunter		if not fetched:
36928392b74bSAdrian Hunter			self.more = False
36938392b74bSAdrian Hunter			self.progress.emit(0)
36948392b74bSAdrian Hunter		child_count = self.child_count
36958392b74bSAdrian Hunter		count = self.populated - child_count
36968392b74bSAdrian Hunter		if count > 0:
36978392b74bSAdrian Hunter			parent = QModelIndex()
36988392b74bSAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
36998392b74bSAdrian Hunter			self.insertRows(child_count, count, parent)
37008392b74bSAdrian Hunter			self.child_count += count
37018392b74bSAdrian Hunter			self.endInsertRows()
37028392b74bSAdrian Hunter			self.progress.emit(self.child_count)
37038392b74bSAdrian Hunter
37048392b74bSAdrian Hunter	def FetchMoreRecords(self, count):
37058392b74bSAdrian Hunter		current = self.child_count
37068392b74bSAdrian Hunter		if self.more:
37078392b74bSAdrian Hunter			self.fetcher.Fetch(count)
37088392b74bSAdrian Hunter		else:
37098392b74bSAdrian Hunter			self.progress.emit(0)
37108392b74bSAdrian Hunter		return current
37118392b74bSAdrian Hunter
37128392b74bSAdrian Hunter	def HasMoreRecords(self):
37138392b74bSAdrian Hunter		return self.more
37148392b74bSAdrian Hunter
37158c90fef9SAdrian Hunter	def columnCount(self, parent=None):
37168c90fef9SAdrian Hunter		return len(self.column_headers)
37178c90fef9SAdrian Hunter
37188c90fef9SAdrian Hunter	def columnHeader(self, column):
37198c90fef9SAdrian Hunter		return self.column_headers[column]
37208c90fef9SAdrian Hunter
37218453c936SAdrian Hunter	def SQLTableDataPrep(self, query, count):
37228453c936SAdrian Hunter		data = []
37238453c936SAdrian Hunter		for i in xrange(count):
37248453c936SAdrian Hunter			data.append(query.value(i))
37258453c936SAdrian Hunter		return data
37268453c936SAdrian Hunter
37278392b74bSAdrian Hunter# SQL automatic table data model
37288392b74bSAdrian Hunter
37298392b74bSAdrian Hunterclass SQLAutoTableModel(SQLTableModel):
37308392b74bSAdrian Hunter
37318392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
37328392b74bSAdrian Hunter		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
37338392b74bSAdrian Hunter		if table_name == "comm_threads_view":
37348392b74bSAdrian Hunter			# For now, comm_threads_view has no id column
37358392b74bSAdrian Hunter			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
37368c90fef9SAdrian Hunter		column_headers = []
37378392b74bSAdrian Hunter		query = QSqlQuery(glb.db)
37388392b74bSAdrian Hunter		if glb.dbref.is_sqlite3:
37398392b74bSAdrian Hunter			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
37408392b74bSAdrian Hunter			while query.next():
37418c90fef9SAdrian Hunter				column_headers.append(query.value(1))
37428392b74bSAdrian Hunter			if table_name == "sqlite_master":
37438392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
37448392b74bSAdrian Hunter		else:
37458392b74bSAdrian Hunter			if table_name[:19] == "information_schema.":
37468392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
37478392b74bSAdrian Hunter				select_table_name = table_name[19:]
37488392b74bSAdrian Hunter				schema = "information_schema"
37498392b74bSAdrian Hunter			else:
37508392b74bSAdrian Hunter				select_table_name = table_name
37518392b74bSAdrian Hunter				schema = "public"
37528392b74bSAdrian Hunter			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
37538392b74bSAdrian Hunter			while query.next():
37548c90fef9SAdrian Hunter				column_headers.append(query.value(0))
37558453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
37568453c936SAdrian Hunter			if table_name == "samples_view":
37578453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_view_DataPrep
37588453c936SAdrian Hunter			if table_name == "samples":
37598453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_DataPrep
37608c90fef9SAdrian Hunter		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
37618392b74bSAdrian Hunter
37628453c936SAdrian Hunter	def samples_view_DataPrep(self, query, count):
37638453c936SAdrian Hunter		data = []
37648453c936SAdrian Hunter		data.append(query.value(0))
37658453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
37668453c936SAdrian Hunter		data.append("{:>19}".format(query.value(1)))
37678453c936SAdrian Hunter		for i in xrange(2, count):
37688453c936SAdrian Hunter			data.append(query.value(i))
37698453c936SAdrian Hunter		return data
37708453c936SAdrian Hunter
37718453c936SAdrian Hunter	def samples_DataPrep(self, query, count):
37728453c936SAdrian Hunter		data = []
37738453c936SAdrian Hunter		for i in xrange(9):
37748453c936SAdrian Hunter			data.append(query.value(i))
37758453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
37768453c936SAdrian Hunter		data.append("{:>19}".format(query.value(9)))
37778453c936SAdrian Hunter		for i in xrange(10, count):
37788453c936SAdrian Hunter			data.append(query.value(i))
37798453c936SAdrian Hunter		return data
37808453c936SAdrian Hunter
37818392b74bSAdrian Hunter# Base class for custom ResizeColumnsToContents
37828392b74bSAdrian Hunter
37838392b74bSAdrian Hunterclass ResizeColumnsToContentsBase(QObject):
37848392b74bSAdrian Hunter
37858392b74bSAdrian Hunter	def __init__(self, parent=None):
37868392b74bSAdrian Hunter		super(ResizeColumnsToContentsBase, self).__init__(parent)
37878392b74bSAdrian Hunter
37888392b74bSAdrian Hunter	def ResizeColumnToContents(self, column, n):
37898392b74bSAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
37908392b74bSAdrian Hunter		# so implement a crude alternative
37918392b74bSAdrian Hunter		font = self.view.font()
37928392b74bSAdrian Hunter		metrics = QFontMetrics(font)
37938392b74bSAdrian Hunter		max = 0
37948392b74bSAdrian Hunter		for row in xrange(n):
37958392b74bSAdrian Hunter			val = self.data_model.child_items[row].data[column]
37968392b74bSAdrian Hunter			len = metrics.width(str(val) + "MM")
37978392b74bSAdrian Hunter			max = len if len > max else max
37988392b74bSAdrian Hunter		val = self.data_model.columnHeader(column)
37998392b74bSAdrian Hunter		len = metrics.width(str(val) + "MM")
38008392b74bSAdrian Hunter		max = len if len > max else max
38018392b74bSAdrian Hunter		self.view.setColumnWidth(column, max)
38028392b74bSAdrian Hunter
38038392b74bSAdrian Hunter	def ResizeColumnsToContents(self):
38048392b74bSAdrian Hunter		n = min(self.data_model.child_count, 100)
38058392b74bSAdrian Hunter		if n < 1:
38068392b74bSAdrian Hunter			# No data yet, so connect a signal to notify when there is
38078392b74bSAdrian Hunter			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
38088392b74bSAdrian Hunter			return
38098392b74bSAdrian Hunter		columns = self.data_model.columnCount()
38108392b74bSAdrian Hunter		for i in xrange(columns):
38118392b74bSAdrian Hunter			self.ResizeColumnToContents(i, n)
38128392b74bSAdrian Hunter
38138392b74bSAdrian Hunter	def UpdateColumnWidths(self, *x):
38148392b74bSAdrian Hunter		# This only needs to be done once, so disconnect the signal now
38158392b74bSAdrian Hunter		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
38168392b74bSAdrian Hunter		self.ResizeColumnsToContents()
38178392b74bSAdrian Hunter
381896c43b9aSAdrian Hunter# Convert value to CSV
381996c43b9aSAdrian Hunter
382096c43b9aSAdrian Hunterdef ToCSValue(val):
382196c43b9aSAdrian Hunter	if '"' in val:
382296c43b9aSAdrian Hunter		val = val.replace('"', '""')
382396c43b9aSAdrian Hunter	if "," in val or '"' in val:
382496c43b9aSAdrian Hunter		val = '"' + val + '"'
382596c43b9aSAdrian Hunter	return val
382696c43b9aSAdrian Hunter
382796c43b9aSAdrian Hunter# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
382896c43b9aSAdrian Hunter
382996c43b9aSAdrian Hunterglb_max_cols = 1000
383096c43b9aSAdrian Hunter
383196c43b9aSAdrian Hunterdef RowColumnKey(a):
383296c43b9aSAdrian Hunter	return a.row() * glb_max_cols + a.column()
383396c43b9aSAdrian Hunter
383496c43b9aSAdrian Hunter# Copy selected table cells to clipboard
383596c43b9aSAdrian Hunter
383696c43b9aSAdrian Hunterdef CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
383796c43b9aSAdrian Hunter	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
383896c43b9aSAdrian Hunter	idx_cnt = len(indexes)
383996c43b9aSAdrian Hunter	if not idx_cnt:
384096c43b9aSAdrian Hunter		return
384196c43b9aSAdrian Hunter	if idx_cnt == 1:
384296c43b9aSAdrian Hunter		with_hdr=False
384396c43b9aSAdrian Hunter	min_row = indexes[0].row()
384496c43b9aSAdrian Hunter	max_row = indexes[0].row()
384596c43b9aSAdrian Hunter	min_col = indexes[0].column()
384696c43b9aSAdrian Hunter	max_col = indexes[0].column()
384796c43b9aSAdrian Hunter	for i in indexes:
384896c43b9aSAdrian Hunter		min_row = min(min_row, i.row())
384996c43b9aSAdrian Hunter		max_row = max(max_row, i.row())
385096c43b9aSAdrian Hunter		min_col = min(min_col, i.column())
385196c43b9aSAdrian Hunter		max_col = max(max_col, i.column())
385296c43b9aSAdrian Hunter	if max_col > glb_max_cols:
385396c43b9aSAdrian Hunter		raise RuntimeError("glb_max_cols is too low")
385496c43b9aSAdrian Hunter	max_width = [0] * (1 + max_col - min_col)
385596c43b9aSAdrian Hunter	for i in indexes:
385696c43b9aSAdrian Hunter		c = i.column() - min_col
385796c43b9aSAdrian Hunter		max_width[c] = max(max_width[c], len(str(i.data())))
385896c43b9aSAdrian Hunter	text = ""
385996c43b9aSAdrian Hunter	pad = ""
386096c43b9aSAdrian Hunter	sep = ""
386196c43b9aSAdrian Hunter	if with_hdr:
386296c43b9aSAdrian Hunter		model = indexes[0].model()
386396c43b9aSAdrian Hunter		for col in range(min_col, max_col + 1):
386496c43b9aSAdrian Hunter			val = model.headerData(col, Qt.Horizontal)
386596c43b9aSAdrian Hunter			if as_csv:
386696c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
386796c43b9aSAdrian Hunter				sep = ","
386896c43b9aSAdrian Hunter			else:
386996c43b9aSAdrian Hunter				c = col - min_col
387096c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
387196c43b9aSAdrian Hunter				width = max_width[c]
387296c43b9aSAdrian Hunter				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
387396c43b9aSAdrian Hunter				if align & Qt.AlignRight:
387496c43b9aSAdrian Hunter					val = val.rjust(width)
387596c43b9aSAdrian Hunter				text += pad + sep + val
387696c43b9aSAdrian Hunter				pad = " " * (width - len(val))
387796c43b9aSAdrian Hunter				sep = "  "
387896c43b9aSAdrian Hunter		text += "\n"
387996c43b9aSAdrian Hunter		pad = ""
388096c43b9aSAdrian Hunter		sep = ""
388196c43b9aSAdrian Hunter	last_row = min_row
388296c43b9aSAdrian Hunter	for i in indexes:
388396c43b9aSAdrian Hunter		if i.row() > last_row:
388496c43b9aSAdrian Hunter			last_row = i.row()
388596c43b9aSAdrian Hunter			text += "\n"
388696c43b9aSAdrian Hunter			pad = ""
388796c43b9aSAdrian Hunter			sep = ""
388896c43b9aSAdrian Hunter		if as_csv:
388996c43b9aSAdrian Hunter			text += sep + ToCSValue(str(i.data()))
389096c43b9aSAdrian Hunter			sep = ","
389196c43b9aSAdrian Hunter		else:
389296c43b9aSAdrian Hunter			width = max_width[i.column() - min_col]
389396c43b9aSAdrian Hunter			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
389496c43b9aSAdrian Hunter				val = str(i.data()).rjust(width)
389596c43b9aSAdrian Hunter			else:
389696c43b9aSAdrian Hunter				val = str(i.data())
389796c43b9aSAdrian Hunter			text += pad + sep + val
389896c43b9aSAdrian Hunter			pad = " " * (width - len(val))
389996c43b9aSAdrian Hunter			sep = "  "
390096c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
390196c43b9aSAdrian Hunter
390296c43b9aSAdrian Hunterdef CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
390396c43b9aSAdrian Hunter	indexes = view.selectedIndexes()
390496c43b9aSAdrian Hunter	if not len(indexes):
390596c43b9aSAdrian Hunter		return
390696c43b9aSAdrian Hunter
390796c43b9aSAdrian Hunter	selection = view.selectionModel()
390896c43b9aSAdrian Hunter
390996c43b9aSAdrian Hunter	first = None
391096c43b9aSAdrian Hunter	for i in indexes:
391196c43b9aSAdrian Hunter		above = view.indexAbove(i)
391296c43b9aSAdrian Hunter		if not selection.isSelected(above):
391396c43b9aSAdrian Hunter			first = i
391496c43b9aSAdrian Hunter			break
391596c43b9aSAdrian Hunter
391696c43b9aSAdrian Hunter	if first is None:
391796c43b9aSAdrian Hunter		raise RuntimeError("CopyTreeCellsToClipboard internal error")
391896c43b9aSAdrian Hunter
391996c43b9aSAdrian Hunter	model = first.model()
392096c43b9aSAdrian Hunter	row_cnt = 0
392196c43b9aSAdrian Hunter	col_cnt = model.columnCount(first)
392296c43b9aSAdrian Hunter	max_width = [0] * col_cnt
392396c43b9aSAdrian Hunter
392496c43b9aSAdrian Hunter	indent_sz = 2
392596c43b9aSAdrian Hunter	indent_str = " " * indent_sz
392696c43b9aSAdrian Hunter
392796c43b9aSAdrian Hunter	expanded_mark_sz = 2
392896c43b9aSAdrian Hunter	if sys.version_info[0] == 3:
392996c43b9aSAdrian Hunter		expanded_mark = "\u25BC "
393096c43b9aSAdrian Hunter		not_expanded_mark = "\u25B6 "
393196c43b9aSAdrian Hunter	else:
393296c43b9aSAdrian Hunter		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
393396c43b9aSAdrian Hunter		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
393496c43b9aSAdrian Hunter	leaf_mark = "  "
393596c43b9aSAdrian Hunter
393696c43b9aSAdrian Hunter	if not as_csv:
393796c43b9aSAdrian Hunter		pos = first
393896c43b9aSAdrian Hunter		while True:
393996c43b9aSAdrian Hunter			row_cnt += 1
394096c43b9aSAdrian Hunter			row = pos.row()
394196c43b9aSAdrian Hunter			for c in range(col_cnt):
394296c43b9aSAdrian Hunter				i = pos.sibling(row, c)
394396c43b9aSAdrian Hunter				if c:
394496c43b9aSAdrian Hunter					n = len(str(i.data()))
394596c43b9aSAdrian Hunter				else:
394696c43b9aSAdrian Hunter					n = len(str(i.data()).strip())
394796c43b9aSAdrian Hunter					n += (i.internalPointer().level - 1) * indent_sz
394896c43b9aSAdrian Hunter					n += expanded_mark_sz
394996c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], n)
395096c43b9aSAdrian Hunter			pos = view.indexBelow(pos)
395196c43b9aSAdrian Hunter			if not selection.isSelected(pos):
395296c43b9aSAdrian Hunter				break
395396c43b9aSAdrian Hunter
395496c43b9aSAdrian Hunter	text = ""
395596c43b9aSAdrian Hunter	pad = ""
395696c43b9aSAdrian Hunter	sep = ""
395796c43b9aSAdrian Hunter	if with_hdr:
395896c43b9aSAdrian Hunter		for c in range(col_cnt):
395996c43b9aSAdrian Hunter			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
396096c43b9aSAdrian Hunter			if as_csv:
396196c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
396296c43b9aSAdrian Hunter				sep = ","
396396c43b9aSAdrian Hunter			else:
396496c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
396596c43b9aSAdrian Hunter				width = max_width[c]
396696c43b9aSAdrian Hunter				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
396796c43b9aSAdrian Hunter				if align & Qt.AlignRight:
396896c43b9aSAdrian Hunter					val = val.rjust(width)
396996c43b9aSAdrian Hunter				text += pad + sep + val
397096c43b9aSAdrian Hunter				pad = " " * (width - len(val))
397196c43b9aSAdrian Hunter				sep = "   "
397296c43b9aSAdrian Hunter		text += "\n"
397396c43b9aSAdrian Hunter		pad = ""
397496c43b9aSAdrian Hunter		sep = ""
397596c43b9aSAdrian Hunter
397696c43b9aSAdrian Hunter	pos = first
397796c43b9aSAdrian Hunter	while True:
397896c43b9aSAdrian Hunter		row = pos.row()
397996c43b9aSAdrian Hunter		for c in range(col_cnt):
398096c43b9aSAdrian Hunter			i = pos.sibling(row, c)
398196c43b9aSAdrian Hunter			val = str(i.data())
398296c43b9aSAdrian Hunter			if not c:
398396c43b9aSAdrian Hunter				if model.hasChildren(i):
398496c43b9aSAdrian Hunter					if view.isExpanded(i):
398596c43b9aSAdrian Hunter						mark = expanded_mark
398696c43b9aSAdrian Hunter					else:
398796c43b9aSAdrian Hunter						mark = not_expanded_mark
398896c43b9aSAdrian Hunter				else:
398996c43b9aSAdrian Hunter					mark = leaf_mark
399096c43b9aSAdrian Hunter				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
399196c43b9aSAdrian Hunter			if as_csv:
399296c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
399396c43b9aSAdrian Hunter				sep = ","
399496c43b9aSAdrian Hunter			else:
399596c43b9aSAdrian Hunter				width = max_width[c]
399696c43b9aSAdrian Hunter				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
399796c43b9aSAdrian Hunter					val = val.rjust(width)
399896c43b9aSAdrian Hunter				text += pad + sep + val
399996c43b9aSAdrian Hunter				pad = " " * (width - len(val))
400096c43b9aSAdrian Hunter				sep = "   "
400196c43b9aSAdrian Hunter		pos = view.indexBelow(pos)
400296c43b9aSAdrian Hunter		if not selection.isSelected(pos):
400396c43b9aSAdrian Hunter			break
400496c43b9aSAdrian Hunter		text = text.rstrip() + "\n"
400596c43b9aSAdrian Hunter		pad = ""
400696c43b9aSAdrian Hunter		sep = ""
400796c43b9aSAdrian Hunter
400896c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
400996c43b9aSAdrian Hunter
401096c43b9aSAdrian Hunterdef CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
401196c43b9aSAdrian Hunter	view.CopyCellsToClipboard(view, as_csv, with_hdr)
401296c43b9aSAdrian Hunter
401396c43b9aSAdrian Hunterdef CopyCellsToClipboardHdr(view):
401496c43b9aSAdrian Hunter	CopyCellsToClipboard(view, False, True)
401596c43b9aSAdrian Hunter
401696c43b9aSAdrian Hunterdef CopyCellsToClipboardCSV(view):
401796c43b9aSAdrian Hunter	CopyCellsToClipboard(view, True, True)
401896c43b9aSAdrian Hunter
40199bc4e4bfSAdrian Hunter# Context menu
40209bc4e4bfSAdrian Hunter
40219bc4e4bfSAdrian Hunterclass ContextMenu(object):
40229bc4e4bfSAdrian Hunter
40239bc4e4bfSAdrian Hunter	def __init__(self, view):
40249bc4e4bfSAdrian Hunter		self.view = view
40259bc4e4bfSAdrian Hunter		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
40269bc4e4bfSAdrian Hunter		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
40279bc4e4bfSAdrian Hunter
40289bc4e4bfSAdrian Hunter	def ShowContextMenu(self, pos):
40299bc4e4bfSAdrian Hunter		menu = QMenu(self.view)
40309bc4e4bfSAdrian Hunter		self.AddActions(menu)
40319bc4e4bfSAdrian Hunter		menu.exec_(self.view.mapToGlobal(pos))
40329bc4e4bfSAdrian Hunter
40339bc4e4bfSAdrian Hunter	def AddCopy(self, menu):
40349bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
40359bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
40369bc4e4bfSAdrian Hunter
40379bc4e4bfSAdrian Hunter	def AddActions(self, menu):
40389bc4e4bfSAdrian Hunter		self.AddCopy(menu)
40399bc4e4bfSAdrian Hunter
40409bc4e4bfSAdrian Hunterclass TreeContextMenu(ContextMenu):
40419bc4e4bfSAdrian Hunter
40429bc4e4bfSAdrian Hunter	def __init__(self, view):
40439bc4e4bfSAdrian Hunter		super(TreeContextMenu, self).__init__(view)
40449bc4e4bfSAdrian Hunter
40459bc4e4bfSAdrian Hunter	def AddActions(self, menu):
40469bc4e4bfSAdrian Hunter		i = self.view.currentIndex()
40479bc4e4bfSAdrian Hunter		text = str(i.data()).strip()
40489bc4e4bfSAdrian Hunter		if len(text):
40499bc4e4bfSAdrian Hunter			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
40509bc4e4bfSAdrian Hunter		self.AddCopy(menu)
40519bc4e4bfSAdrian Hunter
40528392b74bSAdrian Hunter# Table window
40538392b74bSAdrian Hunter
40548392b74bSAdrian Hunterclass TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
40558392b74bSAdrian Hunter
40568392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
40578392b74bSAdrian Hunter		super(TableWindow, self).__init__(parent)
40588392b74bSAdrian Hunter
40598392b74bSAdrian Hunter		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
40608392b74bSAdrian Hunter
40618392b74bSAdrian Hunter		self.model = QSortFilterProxyModel()
40628392b74bSAdrian Hunter		self.model.setSourceModel(self.data_model)
40638392b74bSAdrian Hunter
40648392b74bSAdrian Hunter		self.view = QTableView()
40658392b74bSAdrian Hunter		self.view.setModel(self.model)
40668392b74bSAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
40678392b74bSAdrian Hunter		self.view.verticalHeader().setVisible(False)
40688392b74bSAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
40698392b74bSAdrian Hunter		self.view.setSortingEnabled(True)
407096c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
407196c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
40728392b74bSAdrian Hunter
40738392b74bSAdrian Hunter		self.ResizeColumnsToContents()
40748392b74bSAdrian Hunter
40759bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
40769bc4e4bfSAdrian Hunter
40778392b74bSAdrian Hunter		self.find_bar = FindBar(self, self, True)
40788392b74bSAdrian Hunter
40798392b74bSAdrian Hunter		self.finder = ChildDataItemFinder(self.data_model)
40808392b74bSAdrian Hunter
40818392b74bSAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
40828392b74bSAdrian Hunter
40838392b74bSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
40848392b74bSAdrian Hunter
40858392b74bSAdrian Hunter		self.setWidget(self.vbox.Widget())
40868392b74bSAdrian Hunter
40878392b74bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
40888392b74bSAdrian Hunter
40898392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context):
40908392b74bSAdrian Hunter		self.view.setFocus()
40918392b74bSAdrian Hunter		self.find_bar.Busy()
40928392b74bSAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
40938392b74bSAdrian Hunter
40948392b74bSAdrian Hunter	def FindDone(self, row):
40958392b74bSAdrian Hunter		self.find_bar.Idle()
40968392b74bSAdrian Hunter		if row >= 0:
409735fa1ceeSAdrian Hunter			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
40988392b74bSAdrian Hunter		else:
40998392b74bSAdrian Hunter			self.find_bar.NotFound()
41008392b74bSAdrian Hunter
41018392b74bSAdrian Hunter# Table list
41028392b74bSAdrian Hunter
41038392b74bSAdrian Hunterdef GetTableList(glb):
41048392b74bSAdrian Hunter	tables = []
41058392b74bSAdrian Hunter	query = QSqlQuery(glb.db)
41068392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
41078392b74bSAdrian Hunter		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
41088392b74bSAdrian Hunter	else:
41098392b74bSAdrian 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")
41108392b74bSAdrian Hunter	while query.next():
41118392b74bSAdrian Hunter		tables.append(query.value(0))
41128392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
41138392b74bSAdrian Hunter		tables.append("sqlite_master")
41148392b74bSAdrian Hunter	else:
41158392b74bSAdrian Hunter		tables.append("information_schema.tables")
41168392b74bSAdrian Hunter		tables.append("information_schema.views")
41178392b74bSAdrian Hunter		tables.append("information_schema.columns")
41188392b74bSAdrian Hunter	return tables
41198392b74bSAdrian Hunter
4120cd358012SAdrian Hunter# Top Calls data model
4121cd358012SAdrian Hunter
4122cd358012SAdrian Hunterclass TopCallsModel(SQLTableModel):
4123cd358012SAdrian Hunter
4124cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
4125cd358012SAdrian Hunter		text = ""
4126cd358012SAdrian Hunter		if not glb.dbref.is_sqlite3:
4127cd358012SAdrian Hunter			text = "::text"
4128cd358012SAdrian Hunter		limit = ""
4129cd358012SAdrian Hunter		if len(report_vars.limit):
4130cd358012SAdrian Hunter			limit = " LIMIT " + report_vars.limit
4131cd358012SAdrian Hunter		sql = ("SELECT comm, pid, tid, name,"
4132cd358012SAdrian Hunter			" CASE"
4133cd358012SAdrian Hunter			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
4134cd358012SAdrian Hunter			" ELSE short_name"
4135cd358012SAdrian Hunter			" END AS dso,"
4136cd358012SAdrian Hunter			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
4137cd358012SAdrian Hunter			" CASE"
4138cd358012SAdrian Hunter			" WHEN (calls.flags = 1) THEN 'no call'" + text +
4139cd358012SAdrian Hunter			" WHEN (calls.flags = 2) THEN 'no return'" + text +
4140cd358012SAdrian Hunter			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
4141cd358012SAdrian Hunter			" ELSE ''" + text +
4142cd358012SAdrian Hunter			" END AS flags"
4143cd358012SAdrian Hunter			" FROM calls"
4144cd358012SAdrian Hunter			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
4145cd358012SAdrian Hunter			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
4146cd358012SAdrian Hunter			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
4147cd358012SAdrian Hunter			" INNER JOIN comms ON calls.comm_id = comms.id"
4148cd358012SAdrian Hunter			" INNER JOIN threads ON calls.thread_id = threads.id" +
4149cd358012SAdrian Hunter			report_vars.where_clause +
4150cd358012SAdrian Hunter			" ORDER BY elapsed_time DESC" +
4151cd358012SAdrian Hunter			limit
4152cd358012SAdrian Hunter			)
4153cd358012SAdrian Hunter		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
4154cd358012SAdrian Hunter		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
4155cd358012SAdrian Hunter		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
4156cd358012SAdrian Hunter
4157cd358012SAdrian Hunter	def columnAlignment(self, column):
4158cd358012SAdrian Hunter		return self.alignment[column]
4159cd358012SAdrian Hunter
4160cd358012SAdrian Hunter# Top Calls report creation dialog
4161cd358012SAdrian Hunter
4162cd358012SAdrian Hunterclass TopCallsDialog(ReportDialogBase):
4163cd358012SAdrian Hunter
4164cd358012SAdrian Hunter	def __init__(self, glb, parent=None):
4165cd358012SAdrian Hunter		title = "Top Calls by Elapsed Time"
4166cd358012SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
4167cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
4168cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
4169cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
4170cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
4171cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
4172cd358012SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
4173cd358012SAdrian Hunter			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
4174cd358012SAdrian Hunter		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
4175cd358012SAdrian Hunter
4176cd358012SAdrian Hunter# Top Calls window
4177cd358012SAdrian Hunter
4178cd358012SAdrian Hunterclass TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
4179cd358012SAdrian Hunter
4180cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
4181cd358012SAdrian Hunter		super(TopCallsWindow, self).__init__(parent)
4182cd358012SAdrian Hunter
4183cd358012SAdrian Hunter		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
4184cd358012SAdrian Hunter		self.model = self.data_model
4185cd358012SAdrian Hunter
4186cd358012SAdrian Hunter		self.view = QTableView()
4187cd358012SAdrian Hunter		self.view.setModel(self.model)
4188cd358012SAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
4189cd358012SAdrian Hunter		self.view.verticalHeader().setVisible(False)
419096c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
419196c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
4192cd358012SAdrian Hunter
41939bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
41949bc4e4bfSAdrian Hunter
4195cd358012SAdrian Hunter		self.ResizeColumnsToContents()
4196cd358012SAdrian Hunter
4197cd358012SAdrian Hunter		self.find_bar = FindBar(self, self, True)
4198cd358012SAdrian Hunter
4199cd358012SAdrian Hunter		self.finder = ChildDataItemFinder(self.model)
4200cd358012SAdrian Hunter
4201cd358012SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
4202cd358012SAdrian Hunter
4203cd358012SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
4204cd358012SAdrian Hunter
4205cd358012SAdrian Hunter		self.setWidget(self.vbox.Widget())
4206cd358012SAdrian Hunter
4207cd358012SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
4208cd358012SAdrian Hunter
4209cd358012SAdrian Hunter	def Find(self, value, direction, pattern, context):
4210cd358012SAdrian Hunter		self.view.setFocus()
4211cd358012SAdrian Hunter		self.find_bar.Busy()
4212cd358012SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
4213cd358012SAdrian Hunter
4214cd358012SAdrian Hunter	def FindDone(self, row):
4215cd358012SAdrian Hunter		self.find_bar.Idle()
4216cd358012SAdrian Hunter		if row >= 0:
4217cd358012SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
4218cd358012SAdrian Hunter		else:
4219cd358012SAdrian Hunter			self.find_bar.NotFound()
4220cd358012SAdrian Hunter
42211beb5c7bSAdrian Hunter# Action Definition
42221beb5c7bSAdrian Hunter
42231beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None):
42241beb5c7bSAdrian Hunter	action = QAction(label, parent)
42251beb5c7bSAdrian Hunter	if shortcut != None:
42261beb5c7bSAdrian Hunter		action.setShortcuts(shortcut)
42271beb5c7bSAdrian Hunter	action.setStatusTip(tip)
42281beb5c7bSAdrian Hunter	action.triggered.connect(callback)
42291beb5c7bSAdrian Hunter	return action
42301beb5c7bSAdrian Hunter
42311beb5c7bSAdrian Hunter# Typical application actions
42321beb5c7bSAdrian Hunter
42331beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None):
42341beb5c7bSAdrian Hunter	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
42351beb5c7bSAdrian Hunter
42361beb5c7bSAdrian Hunter# Typical MDI actions
42371beb5c7bSAdrian Hunter
42381beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area):
42391beb5c7bSAdrian Hunter	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
42401beb5c7bSAdrian Hunter
42411beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area):
42421beb5c7bSAdrian Hunter	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
42431beb5c7bSAdrian Hunter
42441beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area):
42451beb5c7bSAdrian Hunter	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
42461beb5c7bSAdrian Hunter
42471beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area):
42481beb5c7bSAdrian Hunter	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
42491beb5c7bSAdrian Hunter
42501beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area):
42511beb5c7bSAdrian Hunter	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
42521beb5c7bSAdrian Hunter
42531beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area):
42541beb5c7bSAdrian Hunter	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
42551beb5c7bSAdrian Hunter
42561beb5c7bSAdrian Hunter# Typical MDI window menu
42571beb5c7bSAdrian Hunter
42581beb5c7bSAdrian Hunterclass WindowMenu():
42591beb5c7bSAdrian Hunter
42601beb5c7bSAdrian Hunter	def __init__(self, mdi_area, menu):
42611beb5c7bSAdrian Hunter		self.mdi_area = mdi_area
42621beb5c7bSAdrian Hunter		self.window_menu = menu.addMenu("&Windows")
42631beb5c7bSAdrian Hunter		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
42641beb5c7bSAdrian Hunter		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
42651beb5c7bSAdrian Hunter		self.tile_windows = CreateTileWindowsAction(mdi_area)
42661beb5c7bSAdrian Hunter		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
42671beb5c7bSAdrian Hunter		self.next_window = CreateNextWindowAction(mdi_area)
42681beb5c7bSAdrian Hunter		self.previous_window = CreatePreviousWindowAction(mdi_area)
42691beb5c7bSAdrian Hunter		self.window_menu.aboutToShow.connect(self.Update)
42701beb5c7bSAdrian Hunter
42711beb5c7bSAdrian Hunter	def Update(self):
42721beb5c7bSAdrian Hunter		self.window_menu.clear()
42731beb5c7bSAdrian Hunter		sub_window_count = len(self.mdi_area.subWindowList())
42741beb5c7bSAdrian Hunter		have_sub_windows = sub_window_count != 0
42751beb5c7bSAdrian Hunter		self.close_active_window.setEnabled(have_sub_windows)
42761beb5c7bSAdrian Hunter		self.close_all_windows.setEnabled(have_sub_windows)
42771beb5c7bSAdrian Hunter		self.tile_windows.setEnabled(have_sub_windows)
42781beb5c7bSAdrian Hunter		self.cascade_windows.setEnabled(have_sub_windows)
42791beb5c7bSAdrian Hunter		self.next_window.setEnabled(have_sub_windows)
42801beb5c7bSAdrian Hunter		self.previous_window.setEnabled(have_sub_windows)
42811beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_active_window)
42821beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_all_windows)
42831beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
42841beb5c7bSAdrian Hunter		self.window_menu.addAction(self.tile_windows)
42851beb5c7bSAdrian Hunter		self.window_menu.addAction(self.cascade_windows)
42861beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
42871beb5c7bSAdrian Hunter		self.window_menu.addAction(self.next_window)
42881beb5c7bSAdrian Hunter		self.window_menu.addAction(self.previous_window)
42891beb5c7bSAdrian Hunter		if sub_window_count == 0:
42901beb5c7bSAdrian Hunter			return
42911beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
42921beb5c7bSAdrian Hunter		nr = 1
42931beb5c7bSAdrian Hunter		for sub_window in self.mdi_area.subWindowList():
42941beb5c7bSAdrian Hunter			label = str(nr) + " " + sub_window.name
42951beb5c7bSAdrian Hunter			if nr < 10:
42961beb5c7bSAdrian Hunter				label = "&" + label
42971beb5c7bSAdrian Hunter			action = self.window_menu.addAction(label)
42981beb5c7bSAdrian Hunter			action.setCheckable(True)
42991beb5c7bSAdrian Hunter			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
4300df8ea22aSAdrian Hunter			action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
43011beb5c7bSAdrian Hunter			self.window_menu.addAction(action)
43021beb5c7bSAdrian Hunter			nr += 1
43031beb5c7bSAdrian Hunter
43041beb5c7bSAdrian Hunter	def setActiveSubWindow(self, nr):
43051beb5c7bSAdrian Hunter		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
43061beb5c7bSAdrian Hunter
430765b24292SAdrian Hunter# Help text
430865b24292SAdrian Hunter
430965b24292SAdrian Hunterglb_help_text = """
431065b24292SAdrian Hunter<h1>Contents</h1>
431165b24292SAdrian Hunter<style>
431265b24292SAdrian Hunterp.c1 {
431365b24292SAdrian Hunter    text-indent: 40px;
431465b24292SAdrian Hunter}
431565b24292SAdrian Hunterp.c2 {
431665b24292SAdrian Hunter    text-indent: 80px;
431765b24292SAdrian Hunter}
431865b24292SAdrian Hunter}
431965b24292SAdrian Hunter</style>
432065b24292SAdrian Hunter<p class=c1><a href=#reports>1. Reports</a></p>
432165b24292SAdrian Hunter<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
4322ae8b887cSAdrian Hunter<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
4323ae8b887cSAdrian Hunter<p class=c2><a href=#allbranches>1.3 All branches</a></p>
4324ae8b887cSAdrian Hunter<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
4325ae8b887cSAdrian Hunter<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
4326*b3700f21SAdrian Hunter<p class=c1><a href=#charts>2. Charts</a></p>
4327*b3700f21SAdrian Hunter<p class=c2><a href=#timechartbycpu>2.1 Time chart by CPU</a></p>
4328*b3700f21SAdrian Hunter<p class=c1><a href=#tables>3. Tables</a></p>
432965b24292SAdrian Hunter<h1 id=reports>1. Reports</h1>
433065b24292SAdrian Hunter<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
433165b24292SAdrian HunterThe result is a GUI window with a tree representing a context-sensitive
433265b24292SAdrian Huntercall-graph. Expanding a couple of levels of the tree and adjusting column
433365b24292SAdrian Hunterwidths to suit will display something like:
433465b24292SAdrian Hunter<pre>
433565b24292SAdrian Hunter                                         Call Graph: pt_example
433665b24292SAdrian HunterCall Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
433765b24292SAdrian Hunterv- ls
433865b24292SAdrian Hunter    v- 2638:2638
433965b24292SAdrian Hunter        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
434065b24292SAdrian Hunter          |- unknown               unknown       1        13198     0.1              1              0.0
434165b24292SAdrian Hunter          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
434265b24292SAdrian Hunter          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
434365b24292SAdrian Hunter          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
434465b24292SAdrian Hunter             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
434565b24292SAdrian Hunter             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
434665b24292SAdrian Hunter             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
434765b24292SAdrian Hunter             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
434865b24292SAdrian Hunter             v- main               ls            1      8182043    99.6         180254             99.9
434965b24292SAdrian Hunter</pre>
435065b24292SAdrian Hunter<h3>Points to note:</h3>
435165b24292SAdrian Hunter<ul>
435265b24292SAdrian Hunter<li>The top level is a command name (comm)</li>
435365b24292SAdrian Hunter<li>The next level is a thread (pid:tid)</li>
435465b24292SAdrian Hunter<li>Subsequent levels are functions</li>
435565b24292SAdrian Hunter<li>'Count' is the number of calls</li>
435665b24292SAdrian Hunter<li>'Time' is the elapsed time until the function returns</li>
435765b24292SAdrian Hunter<li>Percentages are relative to the level above</li>
435865b24292SAdrian Hunter<li>'Branch Count' is the total number of branches for that function and all functions that it calls
435965b24292SAdrian Hunter</ul>
436065b24292SAdrian Hunter<h3>Find</h3>
436165b24292SAdrian HunterCtrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
436265b24292SAdrian HunterThe pattern matching symbols are ? for any character and * for zero or more characters.
4363ae8b887cSAdrian Hunter<h2 id=calltree>1.2 Call Tree</h2>
4364ae8b887cSAdrian HunterThe Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
4365ae8b887cSAdrian HunterAlso the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
4366ae8b887cSAdrian Hunter<h2 id=allbranches>1.3 All branches</h2>
436765b24292SAdrian HunterThe All branches report displays all branches in chronological order.
436865b24292SAdrian HunterNot all data is fetched immediately. More records can be fetched using the Fetch bar provided.
436965b24292SAdrian Hunter<h3>Disassembly</h3>
437065b24292SAdrian HunterOpen a branch to display disassembly. This only works if:
437165b24292SAdrian Hunter<ol>
437265b24292SAdrian Hunter<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
437365b24292SAdrian Hunter<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
437465b24292SAdrian HunterThe default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
437565b24292SAdrian HunterOne exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
437665b24292SAdrian Hunteror alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
437765b24292SAdrian Hunter</ol>
437865b24292SAdrian Hunter<h4 id=xed>Intel XED Setup</h4>
437965b24292SAdrian HunterTo use Intel XED, libxed.so must be present.  To build and install libxed.so:
438065b24292SAdrian Hunter<pre>
438165b24292SAdrian Huntergit clone https://github.com/intelxed/mbuild.git mbuild
438265b24292SAdrian Huntergit clone https://github.com/intelxed/xed
438365b24292SAdrian Huntercd xed
438465b24292SAdrian Hunter./mfile.py --share
438565b24292SAdrian Huntersudo ./mfile.py --prefix=/usr/local install
438665b24292SAdrian Huntersudo ldconfig
438765b24292SAdrian Hunter</pre>
4388530e22fdSAdrian Hunter<h3>Instructions per Cycle (IPC)</h3>
4389530e22fdSAdrian HunterIf available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
4390530e22fdSAdrian Hunter<p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
4391530e22fdSAdrian HunterDue to the granularity of timing information, the number of cycles for some code blocks will not be known.
4392530e22fdSAdrian HunterIn that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
4393530e22fdSAdrian Huntersince the previous displayed 'IPC'.
439465b24292SAdrian Hunter<h3>Find</h3>
439565b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
439665b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
439765b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
4398ae8b887cSAdrian Hunter<h2 id=selectedbranches>1.4 Selected branches</h2>
439965b24292SAdrian HunterThis is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
440065b24292SAdrian Hunterby various selection criteria. A dialog box displays available criteria which are AND'ed together.
4401ae8b887cSAdrian Hunter<h3>1.4.1 Time ranges</h3>
440265b24292SAdrian HunterThe time ranges hint text shows the total time range. Relative time ranges can also be entered in
440365b24292SAdrian Hunterms, us or ns. Also, negative values are relative to the end of trace.  Examples:
440465b24292SAdrian Hunter<pre>
440565b24292SAdrian Hunter	81073085947329-81073085958238	From 81073085947329 to 81073085958238
440665b24292SAdrian Hunter	100us-200us		From 100us to 200us
440765b24292SAdrian Hunter	10ms-			From 10ms to the end
440865b24292SAdrian Hunter	-100ns			The first 100ns
440965b24292SAdrian Hunter	-10ms-			The last 10ms
441065b24292SAdrian Hunter</pre>
441165b24292SAdrian HunterN.B. Due to the granularity of timestamps, there could be no branches in any given time range.
4412ae8b887cSAdrian Hunter<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
4413cd358012SAdrian 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.
4414cd358012SAdrian HunterThe data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
4415cd358012SAdrian HunterIf not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
4416*b3700f21SAdrian Hunter<h1 id=charts>2. Charts</h1>
4417*b3700f21SAdrian Hunter<h2 id=timechartbycpu>2.1 Time chart by CPU</h2>
4418*b3700f21SAdrian HunterThis chart displays context switch information when that data is available. Refer to context_switches_view on the Tables menu.
4419*b3700f21SAdrian Hunter<h3>Features</h3>
4420*b3700f21SAdrian Hunter<ol>
4421*b3700f21SAdrian Hunter<li>Mouse over to highight the task and show the time</li>
4422*b3700f21SAdrian Hunter<li>Drag the mouse to select a region and zoom by pushing the Zoom button</li>
4423*b3700f21SAdrian Hunter<li>Go back and forward by pressing the arrow buttons</li>
4424*b3700f21SAdrian Hunter<li>If call information is available, right-click to show a call tree opened to that task and time.
4425*b3700f21SAdrian HunterNote, the call tree may take some time to appear, and there may not be call information for the task or time selected.
4426*b3700f21SAdrian Hunter</li>
4427*b3700f21SAdrian Hunter</ol>
4428*b3700f21SAdrian Hunter<h3>Important</h3>
4429*b3700f21SAdrian HunterThe graph can be misleading in the following respects:
4430*b3700f21SAdrian Hunter<ol>
4431*b3700f21SAdrian Hunter<li>The graph shows the first task on each CPU as running from the beginning of the time range.
4432*b3700f21SAdrian HunterBecause tracing might start on different CPUs at different times, that is not necessarily the case.
4433*b3700f21SAdrian HunterRefer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4434*b3700f21SAdrian Hunter<li>Similarly, the last task on each CPU can be showing running longer than it really was.
4435*b3700f21SAdrian HunterAgain, refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4436*b3700f21SAdrian Hunter<li>When the mouse is over a task, the highlighted task might not be visible on the legend without scrolling if the legend does not fit fully in the window</li>
4437*b3700f21SAdrian Hunter</ol>
4438*b3700f21SAdrian Hunter<h1 id=tables>3. Tables</h1>
443965b24292SAdrian HunterThe Tables menu shows all tables and views in the database. Most tables have an associated view
444065b24292SAdrian Hunterwhich displays the information in a more friendly way. Not all data for large tables is fetched
444165b24292SAdrian Hunterimmediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
444265b24292SAdrian Hunterbut that can be slow for large tables.
444365b24292SAdrian Hunter<p>There are also tables of database meta-information.
444465b24292SAdrian HunterFor SQLite3 databases, the sqlite_master table is included.
444565b24292SAdrian HunterFor PostgreSQL databases, information_schema.tables/views/columns are included.
444665b24292SAdrian Hunter<h3>Find</h3>
444765b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
444865b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
444965b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
445035fa1ceeSAdrian Hunter<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
445135fa1ceeSAdrian Hunterwill go to the next/previous result in id order, instead of display order.
445265b24292SAdrian Hunter"""
445365b24292SAdrian Hunter
445465b24292SAdrian Hunter# Help window
445565b24292SAdrian Hunter
445665b24292SAdrian Hunterclass HelpWindow(QMdiSubWindow):
445765b24292SAdrian Hunter
445865b24292SAdrian Hunter	def __init__(self, glb, parent=None):
445965b24292SAdrian Hunter		super(HelpWindow, self).__init__(parent)
446065b24292SAdrian Hunter
446165b24292SAdrian Hunter		self.text = QTextBrowser()
446265b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
446365b24292SAdrian Hunter		self.text.setReadOnly(True)
446465b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
446565b24292SAdrian Hunter
446665b24292SAdrian Hunter		self.setWidget(self.text)
446765b24292SAdrian Hunter
446865b24292SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
446965b24292SAdrian Hunter
447065b24292SAdrian Hunter# Main window that only displays the help text
447165b24292SAdrian Hunter
447265b24292SAdrian Hunterclass HelpOnlyWindow(QMainWindow):
447365b24292SAdrian Hunter
447465b24292SAdrian Hunter	def __init__(self, parent=None):
447565b24292SAdrian Hunter		super(HelpOnlyWindow, self).__init__(parent)
447665b24292SAdrian Hunter
447765b24292SAdrian Hunter		self.setMinimumSize(200, 100)
447865b24292SAdrian Hunter		self.resize(800, 600)
447965b24292SAdrian Hunter		self.setWindowTitle("Exported SQL Viewer Help")
448065b24292SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
448165b24292SAdrian Hunter
448265b24292SAdrian Hunter		self.text = QTextBrowser()
448365b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
448465b24292SAdrian Hunter		self.text.setReadOnly(True)
448565b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
448665b24292SAdrian Hunter
448765b24292SAdrian Hunter		self.setCentralWidget(self.text)
448865b24292SAdrian Hunter
4489b62d18abSAdrian Hunter# PostqreSQL server version
4490b62d18abSAdrian Hunter
4491b62d18abSAdrian Hunterdef PostqreSQLServerVersion(db):
4492b62d18abSAdrian Hunter	query = QSqlQuery(db)
4493b62d18abSAdrian Hunter	QueryExec(query, "SELECT VERSION()")
4494b62d18abSAdrian Hunter	if query.next():
4495b62d18abSAdrian Hunter		v_str = query.value(0)
4496b62d18abSAdrian Hunter		v_list = v_str.strip().split(" ")
4497b62d18abSAdrian Hunter		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
4498b62d18abSAdrian Hunter			return v_list[1]
4499b62d18abSAdrian Hunter		return v_str
4500b62d18abSAdrian Hunter	return "Unknown"
4501b62d18abSAdrian Hunter
4502b62d18abSAdrian Hunter# SQLite version
4503b62d18abSAdrian Hunter
4504b62d18abSAdrian Hunterdef SQLiteVersion(db):
4505b62d18abSAdrian Hunter	query = QSqlQuery(db)
4506b62d18abSAdrian Hunter	QueryExec(query, "SELECT sqlite_version()")
4507b62d18abSAdrian Hunter	if query.next():
4508b62d18abSAdrian Hunter		return query.value(0)
4509b62d18abSAdrian Hunter	return "Unknown"
4510b62d18abSAdrian Hunter
4511b62d18abSAdrian Hunter# About dialog
4512b62d18abSAdrian Hunter
4513b62d18abSAdrian Hunterclass AboutDialog(QDialog):
4514b62d18abSAdrian Hunter
4515b62d18abSAdrian Hunter	def __init__(self, glb, parent=None):
4516b62d18abSAdrian Hunter		super(AboutDialog, self).__init__(parent)
4517b62d18abSAdrian Hunter
4518b62d18abSAdrian Hunter		self.setWindowTitle("About Exported SQL Viewer")
4519b62d18abSAdrian Hunter		self.setMinimumWidth(300)
4520b62d18abSAdrian Hunter
4521b62d18abSAdrian Hunter		pyside_version = "1" if pyside_version_1 else "2"
4522b62d18abSAdrian Hunter
4523b62d18abSAdrian Hunter		text = "<pre>"
4524b62d18abSAdrian Hunter		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
4525b62d18abSAdrian Hunter		text += "PySide version:     " + pyside_version + "\n"
4526b62d18abSAdrian Hunter		text += "Qt version:         " + qVersion() + "\n"
4527b62d18abSAdrian Hunter		if glb.dbref.is_sqlite3:
4528b62d18abSAdrian Hunter			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
4529b62d18abSAdrian Hunter		else:
4530b62d18abSAdrian Hunter			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
4531b62d18abSAdrian Hunter		text += "</pre>"
4532b62d18abSAdrian Hunter
4533b62d18abSAdrian Hunter		self.text = QTextBrowser()
4534b62d18abSAdrian Hunter		self.text.setHtml(text)
4535b62d18abSAdrian Hunter		self.text.setReadOnly(True)
4536b62d18abSAdrian Hunter		self.text.setOpenExternalLinks(True)
4537b62d18abSAdrian Hunter
4538b62d18abSAdrian Hunter		self.vbox = QVBoxLayout()
4539b62d18abSAdrian Hunter		self.vbox.addWidget(self.text)
4540b62d18abSAdrian Hunter
454126688729SAdrian Hunter		self.setLayout(self.vbox)
4542b62d18abSAdrian Hunter
454382f68e28SAdrian Hunter# Font resize
454482f68e28SAdrian Hunter
454582f68e28SAdrian Hunterdef ResizeFont(widget, diff):
454682f68e28SAdrian Hunter	font = widget.font()
454782f68e28SAdrian Hunter	sz = font.pointSize()
454882f68e28SAdrian Hunter	font.setPointSize(sz + diff)
454982f68e28SAdrian Hunter	widget.setFont(font)
455082f68e28SAdrian Hunter
455182f68e28SAdrian Hunterdef ShrinkFont(widget):
455282f68e28SAdrian Hunter	ResizeFont(widget, -1)
455382f68e28SAdrian Hunter
455482f68e28SAdrian Hunterdef EnlargeFont(widget):
455582f68e28SAdrian Hunter	ResizeFont(widget, 1)
455682f68e28SAdrian Hunter
45571beb5c7bSAdrian Hunter# Unique name for sub-windows
45581beb5c7bSAdrian Hunter
45591beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr):
45601beb5c7bSAdrian Hunter	if nr > 1:
45611beb5c7bSAdrian Hunter		name += " <" + str(nr) + ">"
45621beb5c7bSAdrian Hunter	return name
45631beb5c7bSAdrian Hunter
45641beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name):
45651beb5c7bSAdrian Hunter	nr = 1
45661beb5c7bSAdrian Hunter	while True:
45671beb5c7bSAdrian Hunter		unique_name = NumberedWindowName(name, nr)
45681beb5c7bSAdrian Hunter		ok = True
45691beb5c7bSAdrian Hunter		for sub_window in mdi_area.subWindowList():
45701beb5c7bSAdrian Hunter			if sub_window.name == unique_name:
45711beb5c7bSAdrian Hunter				ok = False
45721beb5c7bSAdrian Hunter				break
45731beb5c7bSAdrian Hunter		if ok:
45741beb5c7bSAdrian Hunter			return unique_name
45751beb5c7bSAdrian Hunter		nr += 1
45761beb5c7bSAdrian Hunter
45771beb5c7bSAdrian Hunter# Add a sub-window
45781beb5c7bSAdrian Hunter
45791beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name):
45801beb5c7bSAdrian Hunter	unique_name = UniqueSubWindowName(mdi_area, name)
45811beb5c7bSAdrian Hunter	sub_window.setMinimumSize(200, 100)
45821beb5c7bSAdrian Hunter	sub_window.resize(800, 600)
45831beb5c7bSAdrian Hunter	sub_window.setWindowTitle(unique_name)
45841beb5c7bSAdrian Hunter	sub_window.setAttribute(Qt.WA_DeleteOnClose)
45851beb5c7bSAdrian Hunter	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
45861beb5c7bSAdrian Hunter	sub_window.name = unique_name
45871beb5c7bSAdrian Hunter	mdi_area.addSubWindow(sub_window)
45881beb5c7bSAdrian Hunter	sub_window.show()
45891beb5c7bSAdrian Hunter
4590031c2a00SAdrian Hunter# Main window
4591031c2a00SAdrian Hunter
4592031c2a00SAdrian Hunterclass MainWindow(QMainWindow):
4593031c2a00SAdrian Hunter
4594031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
4595031c2a00SAdrian Hunter		super(MainWindow, self).__init__(parent)
4596031c2a00SAdrian Hunter
4597031c2a00SAdrian Hunter		self.glb = glb
4598031c2a00SAdrian Hunter
45991beb5c7bSAdrian Hunter		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
4600031c2a00SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
4601031c2a00SAdrian Hunter		self.setMinimumSize(200, 100)
4602031c2a00SAdrian Hunter
46031beb5c7bSAdrian Hunter		self.mdi_area = QMdiArea()
46041beb5c7bSAdrian Hunter		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
46051beb5c7bSAdrian Hunter		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
4606031c2a00SAdrian Hunter
46071beb5c7bSAdrian Hunter		self.setCentralWidget(self.mdi_area)
4608031c2a00SAdrian Hunter
46091beb5c7bSAdrian Hunter		menu = self.menuBar()
4610031c2a00SAdrian Hunter
46111beb5c7bSAdrian Hunter		file_menu = menu.addMenu("&File")
46121beb5c7bSAdrian Hunter		file_menu.addAction(CreateExitAction(glb.app, self))
46131beb5c7bSAdrian Hunter
4614ebd70c7dSAdrian Hunter		edit_menu = menu.addMenu("&Edit")
461596c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
461696c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
4617ebd70c7dSAdrian Hunter		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
46188392b74bSAdrian Hunter		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
461982f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
462082f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
4621ebd70c7dSAdrian Hunter
46221beb5c7bSAdrian Hunter		reports_menu = menu.addMenu("&Reports")
4623655cb952SAdrian Hunter		if IsSelectable(glb.db, "calls"):
46241beb5c7bSAdrian Hunter			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
46251beb5c7bSAdrian Hunter
4626ae8b887cSAdrian Hunter		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
4627ae8b887cSAdrian Hunter			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
4628ae8b887cSAdrian Hunter
462976099f98SAdrian Hunter		self.EventMenu(GetEventList(glb.db), reports_menu)
463076099f98SAdrian Hunter
4631cd358012SAdrian Hunter		if IsSelectable(glb.db, "calls"):
4632cd358012SAdrian Hunter			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
4633cd358012SAdrian Hunter
4634*b3700f21SAdrian Hunter		if IsSelectable(glb.db, "context_switches"):
4635*b3700f21SAdrian Hunter			charts_menu = menu.addMenu("&Charts")
4636*b3700f21SAdrian Hunter			charts_menu.addAction(CreateAction("&Time chart by CPU", "Create a new window displaying time charts by CPU", self.TimeChartByCPU, self))
4637*b3700f21SAdrian Hunter
46388392b74bSAdrian Hunter		self.TableMenu(GetTableList(glb), menu)
46398392b74bSAdrian Hunter
46401beb5c7bSAdrian Hunter		self.window_menu = WindowMenu(self.mdi_area, menu)
46411beb5c7bSAdrian Hunter
464265b24292SAdrian Hunter		help_menu = menu.addMenu("&Help")
464365b24292SAdrian Hunter		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
4644b62d18abSAdrian Hunter		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
464565b24292SAdrian Hunter
46464b208453SAdrian Hunter	def Try(self, fn):
46474b208453SAdrian Hunter		win = self.mdi_area.activeSubWindow()
46484b208453SAdrian Hunter		if win:
46494b208453SAdrian Hunter			try:
46504b208453SAdrian Hunter				fn(win.view)
46514b208453SAdrian Hunter			except:
46524b208453SAdrian Hunter				pass
46534b208453SAdrian Hunter
465496c43b9aSAdrian Hunter	def CopyToClipboard(self):
465596c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardHdr)
465696c43b9aSAdrian Hunter
465796c43b9aSAdrian Hunter	def CopyToClipboardCSV(self):
465896c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardCSV)
465996c43b9aSAdrian Hunter
4660ebd70c7dSAdrian Hunter	def Find(self):
4661ebd70c7dSAdrian Hunter		win = self.mdi_area.activeSubWindow()
4662ebd70c7dSAdrian Hunter		if win:
4663ebd70c7dSAdrian Hunter			try:
4664ebd70c7dSAdrian Hunter				win.find_bar.Activate()
4665ebd70c7dSAdrian Hunter			except:
4666ebd70c7dSAdrian Hunter				pass
4667ebd70c7dSAdrian Hunter
46688392b74bSAdrian Hunter	def FetchMoreRecords(self):
46698392b74bSAdrian Hunter		win = self.mdi_area.activeSubWindow()
46708392b74bSAdrian Hunter		if win:
46718392b74bSAdrian Hunter			try:
46728392b74bSAdrian Hunter				win.fetch_bar.Activate()
46738392b74bSAdrian Hunter			except:
46748392b74bSAdrian Hunter				pass
46758392b74bSAdrian Hunter
467682f68e28SAdrian Hunter	def ShrinkFont(self):
46774b208453SAdrian Hunter		self.Try(ShrinkFont)
467882f68e28SAdrian Hunter
467982f68e28SAdrian Hunter	def EnlargeFont(self):
46804b208453SAdrian Hunter		self.Try(EnlargeFont)
468182f68e28SAdrian Hunter
468276099f98SAdrian Hunter	def EventMenu(self, events, reports_menu):
468376099f98SAdrian Hunter		branches_events = 0
468476099f98SAdrian Hunter		for event in events:
468576099f98SAdrian Hunter			event = event.split(":")[0]
468676099f98SAdrian Hunter			if event == "branches":
468776099f98SAdrian Hunter				branches_events += 1
468876099f98SAdrian Hunter		dbid = 0
468976099f98SAdrian Hunter		for event in events:
469076099f98SAdrian Hunter			dbid += 1
469176099f98SAdrian Hunter			event = event.split(":")[0]
469276099f98SAdrian Hunter			if event == "branches":
469376099f98SAdrian Hunter				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
4694df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
4695210cf1f9SAdrian Hunter				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
4696df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
469776099f98SAdrian Hunter
4698*b3700f21SAdrian Hunter	def TimeChartByCPU(self):
4699*b3700f21SAdrian Hunter		TimeChartByCPUWindow(self.glb, self)
4700*b3700f21SAdrian Hunter
47018392b74bSAdrian Hunter	def TableMenu(self, tables, menu):
47028392b74bSAdrian Hunter		table_menu = menu.addMenu("&Tables")
47038392b74bSAdrian Hunter		for table in tables:
4704df8ea22aSAdrian Hunter			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
47058392b74bSAdrian Hunter
47061beb5c7bSAdrian Hunter	def NewCallGraph(self):
47071beb5c7bSAdrian Hunter		CallGraphWindow(self.glb, self)
4708031c2a00SAdrian Hunter
4709ae8b887cSAdrian Hunter	def NewCallTree(self):
4710ae8b887cSAdrian Hunter		CallTreeWindow(self.glb, self)
4711ae8b887cSAdrian Hunter
4712cd358012SAdrian Hunter	def NewTopCalls(self):
4713cd358012SAdrian Hunter		dialog = TopCallsDialog(self.glb, self)
4714cd358012SAdrian Hunter		ret = dialog.exec_()
4715cd358012SAdrian Hunter		if ret:
4716cd358012SAdrian Hunter			TopCallsWindow(self.glb, dialog.report_vars, self)
4717cd358012SAdrian Hunter
471876099f98SAdrian Hunter	def NewBranchView(self, event_id):
4719947cc38dSAdrian Hunter		BranchWindow(self.glb, event_id, ReportVars(), self)
472076099f98SAdrian Hunter
4721210cf1f9SAdrian Hunter	def NewSelectedBranchView(self, event_id):
4722210cf1f9SAdrian Hunter		dialog = SelectedBranchDialog(self.glb, self)
4723210cf1f9SAdrian Hunter		ret = dialog.exec_()
4724210cf1f9SAdrian Hunter		if ret:
4725947cc38dSAdrian Hunter			BranchWindow(self.glb, event_id, dialog.report_vars, self)
4726210cf1f9SAdrian Hunter
47278392b74bSAdrian Hunter	def NewTableView(self, table_name):
47288392b74bSAdrian Hunter		TableWindow(self.glb, table_name, self)
47298392b74bSAdrian Hunter
473065b24292SAdrian Hunter	def Help(self):
473165b24292SAdrian Hunter		HelpWindow(self.glb, self)
473265b24292SAdrian Hunter
4733b62d18abSAdrian Hunter	def About(self):
4734b62d18abSAdrian Hunter		dialog = AboutDialog(self.glb, self)
4735b62d18abSAdrian Hunter		dialog.exec_()
4736b62d18abSAdrian Hunter
473776099f98SAdrian Hunter# XED Disassembler
473876099f98SAdrian Hunter
473976099f98SAdrian Hunterclass xed_state_t(Structure):
474076099f98SAdrian Hunter
474176099f98SAdrian Hunter	_fields_ = [
474276099f98SAdrian Hunter		("mode", c_int),
474376099f98SAdrian Hunter		("width", c_int)
474476099f98SAdrian Hunter	]
474576099f98SAdrian Hunter
474676099f98SAdrian Hunterclass XEDInstruction():
474776099f98SAdrian Hunter
474876099f98SAdrian Hunter	def __init__(self, libxed):
474976099f98SAdrian Hunter		# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
475076099f98SAdrian Hunter		xedd_t = c_byte * 512
475176099f98SAdrian Hunter		self.xedd = xedd_t()
475276099f98SAdrian Hunter		self.xedp = addressof(self.xedd)
475376099f98SAdrian Hunter		libxed.xed_decoded_inst_zero(self.xedp)
475476099f98SAdrian Hunter		self.state = xed_state_t()
475576099f98SAdrian Hunter		self.statep = addressof(self.state)
475676099f98SAdrian Hunter		# Buffer for disassembled instruction text
475776099f98SAdrian Hunter		self.buffer = create_string_buffer(256)
475876099f98SAdrian Hunter		self.bufferp = addressof(self.buffer)
475976099f98SAdrian Hunter
476076099f98SAdrian Hunterclass LibXED():
476176099f98SAdrian Hunter
476276099f98SAdrian Hunter	def __init__(self):
47635ed4419dSAdrian Hunter		try:
476476099f98SAdrian Hunter			self.libxed = CDLL("libxed.so")
47655ed4419dSAdrian Hunter		except:
47665ed4419dSAdrian Hunter			self.libxed = None
47675ed4419dSAdrian Hunter		if not self.libxed:
47685ed4419dSAdrian Hunter			self.libxed = CDLL("/usr/local/lib/libxed.so")
476976099f98SAdrian Hunter
477076099f98SAdrian Hunter		self.xed_tables_init = self.libxed.xed_tables_init
477176099f98SAdrian Hunter		self.xed_tables_init.restype = None
477276099f98SAdrian Hunter		self.xed_tables_init.argtypes = []
477376099f98SAdrian Hunter
477476099f98SAdrian Hunter		self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
477576099f98SAdrian Hunter		self.xed_decoded_inst_zero.restype = None
477676099f98SAdrian Hunter		self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
477776099f98SAdrian Hunter
477876099f98SAdrian Hunter		self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
477976099f98SAdrian Hunter		self.xed_operand_values_set_mode.restype = None
478076099f98SAdrian Hunter		self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
478176099f98SAdrian Hunter
478276099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
478376099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.restype = None
478476099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
478576099f98SAdrian Hunter
478676099f98SAdrian Hunter		self.xed_decode = self.libxed.xed_decode
478776099f98SAdrian Hunter		self.xed_decode.restype = c_int
478876099f98SAdrian Hunter		self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
478976099f98SAdrian Hunter
479076099f98SAdrian Hunter		self.xed_format_context = self.libxed.xed_format_context
479176099f98SAdrian Hunter		self.xed_format_context.restype = c_uint
479276099f98SAdrian Hunter		self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
479376099f98SAdrian Hunter
479476099f98SAdrian Hunter		self.xed_tables_init()
479576099f98SAdrian Hunter
479676099f98SAdrian Hunter	def Instruction(self):
479776099f98SAdrian Hunter		return XEDInstruction(self)
479876099f98SAdrian Hunter
479976099f98SAdrian Hunter	def SetMode(self, inst, mode):
480076099f98SAdrian Hunter		if mode:
480176099f98SAdrian Hunter			inst.state.mode = 4 # 32-bit
480276099f98SAdrian Hunter			inst.state.width = 4 # 4 bytes
480376099f98SAdrian Hunter		else:
480476099f98SAdrian Hunter			inst.state.mode = 1 # 64-bit
480576099f98SAdrian Hunter			inst.state.width = 8 # 8 bytes
480676099f98SAdrian Hunter		self.xed_operand_values_set_mode(inst.xedp, inst.statep)
480776099f98SAdrian Hunter
480876099f98SAdrian Hunter	def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
480976099f98SAdrian Hunter		self.xed_decoded_inst_zero_keep_mode(inst.xedp)
481076099f98SAdrian Hunter		err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
481176099f98SAdrian Hunter		if err:
481276099f98SAdrian Hunter			return 0, ""
481376099f98SAdrian Hunter		# Use AT&T mode (2), alternative is Intel (3)
481476099f98SAdrian Hunter		ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
481576099f98SAdrian Hunter		if not ok:
481676099f98SAdrian Hunter			return 0, ""
4817606bd60aSAdrian Hunter		if sys.version_info[0] == 2:
4818606bd60aSAdrian Hunter			result = inst.buffer.value
4819606bd60aSAdrian Hunter		else:
4820606bd60aSAdrian Hunter			result = inst.buffer.value.decode()
482176099f98SAdrian Hunter		# Return instruction length and the disassembled instruction text
482276099f98SAdrian Hunter		# For now, assume the length is in byte 166
4823606bd60aSAdrian Hunter		return inst.xedd[166], result
482476099f98SAdrian Hunter
482576099f98SAdrian Hunterdef TryOpen(file_name):
482676099f98SAdrian Hunter	try:
482776099f98SAdrian Hunter		return open(file_name, "rb")
482876099f98SAdrian Hunter	except:
482976099f98SAdrian Hunter		return None
483076099f98SAdrian Hunter
483176099f98SAdrian Hunterdef Is64Bit(f):
483276099f98SAdrian Hunter	result = sizeof(c_void_p)
483376099f98SAdrian Hunter	# ELF support only
483476099f98SAdrian Hunter	pos = f.tell()
483576099f98SAdrian Hunter	f.seek(0)
483676099f98SAdrian Hunter	header = f.read(7)
483776099f98SAdrian Hunter	f.seek(pos)
483876099f98SAdrian Hunter	magic = header[0:4]
4839606bd60aSAdrian Hunter	if sys.version_info[0] == 2:
484076099f98SAdrian Hunter		eclass = ord(header[4])
484176099f98SAdrian Hunter		encoding = ord(header[5])
484276099f98SAdrian Hunter		version = ord(header[6])
4843606bd60aSAdrian Hunter	else:
4844606bd60aSAdrian Hunter		eclass = header[4]
4845606bd60aSAdrian Hunter		encoding = header[5]
4846606bd60aSAdrian Hunter		version = header[6]
484776099f98SAdrian Hunter	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
484876099f98SAdrian Hunter		result = True if eclass == 2 else False
484976099f98SAdrian Hunter	return result
485076099f98SAdrian Hunter
4851031c2a00SAdrian Hunter# Global data
4852031c2a00SAdrian Hunter
4853031c2a00SAdrian Hunterclass Glb():
4854031c2a00SAdrian Hunter
4855031c2a00SAdrian Hunter	def __init__(self, dbref, db, dbname):
4856031c2a00SAdrian Hunter		self.dbref = dbref
4857031c2a00SAdrian Hunter		self.db = db
4858031c2a00SAdrian Hunter		self.dbname = dbname
485976099f98SAdrian Hunter		self.home_dir = os.path.expanduser("~")
486076099f98SAdrian Hunter		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
486176099f98SAdrian Hunter		if self.buildid_dir:
486276099f98SAdrian Hunter			self.buildid_dir += "/.build-id/"
486376099f98SAdrian Hunter		else:
486476099f98SAdrian Hunter			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
4865031c2a00SAdrian Hunter		self.app = None
4866031c2a00SAdrian Hunter		self.mainwindow = None
48678392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit = weakref.WeakSet()
486876099f98SAdrian Hunter		try:
486976099f98SAdrian Hunter			self.disassembler = LibXED()
487076099f98SAdrian Hunter			self.have_disassembler = True
487176099f98SAdrian Hunter		except:
487276099f98SAdrian Hunter			self.have_disassembler = False
48739a9dae36SAdrian Hunter		self.host_machine_id = 0
48749a9dae36SAdrian Hunter		self.host_start_time = 0
48759a9dae36SAdrian Hunter		self.host_finish_time = 0
487676099f98SAdrian Hunter
487776099f98SAdrian Hunter	def FileFromBuildId(self, build_id):
487876099f98SAdrian Hunter		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
487976099f98SAdrian Hunter		return TryOpen(file_name)
488076099f98SAdrian Hunter
488176099f98SAdrian Hunter	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
488276099f98SAdrian Hunter		# Assume current machine i.e. no support for virtualization
488376099f98SAdrian Hunter		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
488476099f98SAdrian Hunter			file_name = os.getenv("PERF_KCORE")
488576099f98SAdrian Hunter			f = TryOpen(file_name) if file_name else None
488676099f98SAdrian Hunter			if f:
488776099f98SAdrian Hunter				return f
488876099f98SAdrian Hunter			# For now, no special handling if long_name is /proc/kcore
488976099f98SAdrian Hunter			f = TryOpen(long_name)
489076099f98SAdrian Hunter			if f:
489176099f98SAdrian Hunter				return f
489276099f98SAdrian Hunter		f = self.FileFromBuildId(build_id)
489376099f98SAdrian Hunter		if f:
489476099f98SAdrian Hunter			return f
489576099f98SAdrian Hunter		return None
48968392b74bSAdrian Hunter
48978392b74bSAdrian Hunter	def AddInstanceToShutdownOnExit(self, instance):
48988392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit.add(instance)
48998392b74bSAdrian Hunter
49008392b74bSAdrian Hunter	# Shutdown any background processes or threads
49018392b74bSAdrian Hunter	def ShutdownInstances(self):
49028392b74bSAdrian Hunter		for x in self.instances_to_shutdown_on_exit:
49038392b74bSAdrian Hunter			try:
49048392b74bSAdrian Hunter				x.Shutdown()
49058392b74bSAdrian Hunter			except:
49068392b74bSAdrian Hunter				pass
4907031c2a00SAdrian Hunter
49089a9dae36SAdrian Hunter	def GetHostMachineId(self):
49099a9dae36SAdrian Hunter		query = QSqlQuery(self.db)
49109a9dae36SAdrian Hunter		QueryExec(query, "SELECT id FROM machines WHERE pid = -1")
49119a9dae36SAdrian Hunter		if query.next():
49129a9dae36SAdrian Hunter			self.host_machine_id = query.value(0)
49139a9dae36SAdrian Hunter		else:
49149a9dae36SAdrian Hunter			self.host_machine_id = 0
49159a9dae36SAdrian Hunter		return self.host_machine_id
49169a9dae36SAdrian Hunter
49179a9dae36SAdrian Hunter	def HostMachineId(self):
49189a9dae36SAdrian Hunter		if self.host_machine_id:
49199a9dae36SAdrian Hunter			return self.host_machine_id
49209a9dae36SAdrian Hunter		return self.GetHostMachineId()
49219a9dae36SAdrian Hunter
49229a9dae36SAdrian Hunter	def SelectValue(self, sql):
49239a9dae36SAdrian Hunter		query = QSqlQuery(self.db)
49249a9dae36SAdrian Hunter		try:
49259a9dae36SAdrian Hunter			QueryExec(query, sql)
49269a9dae36SAdrian Hunter		except:
49279a9dae36SAdrian Hunter			return None
49289a9dae36SAdrian Hunter		if query.next():
49299a9dae36SAdrian Hunter			return Decimal(query.value(0))
49309a9dae36SAdrian Hunter		return None
49319a9dae36SAdrian Hunter
49329a9dae36SAdrian Hunter	def SwitchesMinTime(self, machine_id):
49339a9dae36SAdrian Hunter		return self.SelectValue("SELECT time"
49349a9dae36SAdrian Hunter					" FROM context_switches"
49359a9dae36SAdrian Hunter					" WHERE time != 0 AND machine_id = " + str(machine_id) +
49369a9dae36SAdrian Hunter					" ORDER BY id LIMIT 1")
49379a9dae36SAdrian Hunter
49389a9dae36SAdrian Hunter	def SwitchesMaxTime(self, machine_id):
49399a9dae36SAdrian Hunter		return self.SelectValue("SELECT time"
49409a9dae36SAdrian Hunter					" FROM context_switches"
49419a9dae36SAdrian Hunter					" WHERE time != 0 AND machine_id = " + str(machine_id) +
49429a9dae36SAdrian Hunter					" ORDER BY id DESC LIMIT 1")
49439a9dae36SAdrian Hunter
49449a9dae36SAdrian Hunter	def SamplesMinTime(self, machine_id):
49459a9dae36SAdrian Hunter		return self.SelectValue("SELECT time"
49469a9dae36SAdrian Hunter					" FROM samples"
49479a9dae36SAdrian Hunter					" WHERE time != 0 AND machine_id = " + str(machine_id) +
49489a9dae36SAdrian Hunter					" ORDER BY id LIMIT 1")
49499a9dae36SAdrian Hunter
49509a9dae36SAdrian Hunter	def SamplesMaxTime(self, machine_id):
49519a9dae36SAdrian Hunter		return self.SelectValue("SELECT time"
49529a9dae36SAdrian Hunter					" FROM samples"
49539a9dae36SAdrian Hunter					" WHERE time != 0 AND machine_id = " + str(machine_id) +
49549a9dae36SAdrian Hunter					" ORDER BY id DESC LIMIT 1")
49559a9dae36SAdrian Hunter
49569a9dae36SAdrian Hunter	def CallsMinTime(self, machine_id):
49579a9dae36SAdrian Hunter		return self.SelectValue("SELECT calls.call_time"
49589a9dae36SAdrian Hunter					" FROM calls"
49599a9dae36SAdrian Hunter					" INNER JOIN threads ON threads.thread_id = calls.thread_id"
49609a9dae36SAdrian Hunter					" WHERE calls.call_time != 0 AND threads.machine_id = " + str(machine_id) +
49619a9dae36SAdrian Hunter					" ORDER BY calls.id LIMIT 1")
49629a9dae36SAdrian Hunter
49639a9dae36SAdrian Hunter	def CallsMaxTime(self, machine_id):
49649a9dae36SAdrian Hunter		return self.SelectValue("SELECT calls.return_time"
49659a9dae36SAdrian Hunter					" FROM calls"
49669a9dae36SAdrian Hunter					" INNER JOIN threads ON threads.thread_id = calls.thread_id"
49679a9dae36SAdrian Hunter					" WHERE calls.return_time != 0 AND threads.machine_id = " + str(machine_id) +
49689a9dae36SAdrian Hunter					" ORDER BY calls.return_time DESC LIMIT 1")
49699a9dae36SAdrian Hunter
49709a9dae36SAdrian Hunter	def GetStartTime(self, machine_id):
49719a9dae36SAdrian Hunter		t0 = self.SwitchesMinTime(machine_id)
49729a9dae36SAdrian Hunter		t1 = self.SamplesMinTime(machine_id)
49739a9dae36SAdrian Hunter		t2 = self.CallsMinTime(machine_id)
49749a9dae36SAdrian Hunter		if t0 is None or (not(t1 is None) and t1 < t0):
49759a9dae36SAdrian Hunter			t0 = t1
49769a9dae36SAdrian Hunter		if t0 is None or (not(t2 is None) and t2 < t0):
49779a9dae36SAdrian Hunter			t0 = t2
49789a9dae36SAdrian Hunter		return t0
49799a9dae36SAdrian Hunter
49809a9dae36SAdrian Hunter	def GetFinishTime(self, machine_id):
49819a9dae36SAdrian Hunter		t0 = self.SwitchesMaxTime(machine_id)
49829a9dae36SAdrian Hunter		t1 = self.SamplesMaxTime(machine_id)
49839a9dae36SAdrian Hunter		t2 = self.CallsMaxTime(machine_id)
49849a9dae36SAdrian Hunter		if t0 is None or (not(t1 is None) and t1 > t0):
49859a9dae36SAdrian Hunter			t0 = t1
49869a9dae36SAdrian Hunter		if t0 is None or (not(t2 is None) and t2 > t0):
49879a9dae36SAdrian Hunter			t0 = t2
49889a9dae36SAdrian Hunter		return t0
49899a9dae36SAdrian Hunter
49909a9dae36SAdrian Hunter	def HostStartTime(self):
49919a9dae36SAdrian Hunter		if self.host_start_time:
49929a9dae36SAdrian Hunter			return self.host_start_time
49939a9dae36SAdrian Hunter		self.host_start_time = self.GetStartTime(self.HostMachineId())
49949a9dae36SAdrian Hunter		return self.host_start_time
49959a9dae36SAdrian Hunter
49969a9dae36SAdrian Hunter	def HostFinishTime(self):
49979a9dae36SAdrian Hunter		if self.host_finish_time:
49989a9dae36SAdrian Hunter			return self.host_finish_time
49999a9dae36SAdrian Hunter		self.host_finish_time = self.GetFinishTime(self.HostMachineId())
50009a9dae36SAdrian Hunter		return self.host_finish_time
50019a9dae36SAdrian Hunter
50029a9dae36SAdrian Hunter	def StartTime(self, machine_id):
50039a9dae36SAdrian Hunter		if machine_id == self.HostMachineId():
50049a9dae36SAdrian Hunter			return self.HostStartTime()
50059a9dae36SAdrian Hunter		return self.GetStartTime(machine_id)
50069a9dae36SAdrian Hunter
50079a9dae36SAdrian Hunter	def FinishTime(self, machine_id):
50089a9dae36SAdrian Hunter		if machine_id == self.HostMachineId():
50099a9dae36SAdrian Hunter			return self.HostFinishTime()
50109a9dae36SAdrian Hunter		return self.GetFinishTime(machine_id)
50119a9dae36SAdrian Hunter
5012031c2a00SAdrian Hunter# Database reference
5013031c2a00SAdrian Hunter
5014031c2a00SAdrian Hunterclass DBRef():
5015031c2a00SAdrian Hunter
5016031c2a00SAdrian Hunter	def __init__(self, is_sqlite3, dbname):
5017031c2a00SAdrian Hunter		self.is_sqlite3 = is_sqlite3
5018031c2a00SAdrian Hunter		self.dbname = dbname
5019031c2a00SAdrian Hunter
5020031c2a00SAdrian Hunter	def Open(self, connection_name):
5021031c2a00SAdrian Hunter		dbname = self.dbname
5022031c2a00SAdrian Hunter		if self.is_sqlite3:
5023031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
5024031c2a00SAdrian Hunter		else:
5025031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
5026031c2a00SAdrian Hunter			opts = dbname.split()
5027031c2a00SAdrian Hunter			for opt in opts:
5028031c2a00SAdrian Hunter				if "=" in opt:
5029031c2a00SAdrian Hunter					opt = opt.split("=")
5030031c2a00SAdrian Hunter					if opt[0] == "hostname":
5031031c2a00SAdrian Hunter						db.setHostName(opt[1])
5032031c2a00SAdrian Hunter					elif opt[0] == "port":
5033031c2a00SAdrian Hunter						db.setPort(int(opt[1]))
5034031c2a00SAdrian Hunter					elif opt[0] == "username":
5035031c2a00SAdrian Hunter						db.setUserName(opt[1])
5036031c2a00SAdrian Hunter					elif opt[0] == "password":
5037031c2a00SAdrian Hunter						db.setPassword(opt[1])
5038031c2a00SAdrian Hunter					elif opt[0] == "dbname":
5039031c2a00SAdrian Hunter						dbname = opt[1]
5040031c2a00SAdrian Hunter				else:
5041031c2a00SAdrian Hunter					dbname = opt
5042031c2a00SAdrian Hunter
5043031c2a00SAdrian Hunter		db.setDatabaseName(dbname)
5044031c2a00SAdrian Hunter		if not db.open():
5045031c2a00SAdrian Hunter			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
5046031c2a00SAdrian Hunter		return db, dbname
5047031c2a00SAdrian Hunter
5048031c2a00SAdrian Hunter# Main
5049031c2a00SAdrian Hunter
5050031c2a00SAdrian Hunterdef Main():
50511ed7f47fSAdrian Hunter	usage_str =	"exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
50521ed7f47fSAdrian Hunter			"   or: exported-sql-viewer.py --help-only"
50531ed7f47fSAdrian Hunter	ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
5054df8ea22aSAdrian Hunter	ap.add_argument("--pyside-version-1", action='store_true')
50551ed7f47fSAdrian Hunter	ap.add_argument("dbname", nargs="?")
50561ed7f47fSAdrian Hunter	ap.add_argument("--help-only", action='store_true')
50571ed7f47fSAdrian Hunter	args = ap.parse_args()
5058031c2a00SAdrian Hunter
50591ed7f47fSAdrian Hunter	if args.help_only:
506065b24292SAdrian Hunter		app = QApplication(sys.argv)
506165b24292SAdrian Hunter		mainwindow = HelpOnlyWindow()
506265b24292SAdrian Hunter		mainwindow.show()
506365b24292SAdrian Hunter		err = app.exec_()
506465b24292SAdrian Hunter		sys.exit(err)
5065031c2a00SAdrian Hunter
50661ed7f47fSAdrian Hunter	dbname = args.dbname
50671ed7f47fSAdrian Hunter	if dbname is None:
50681ed7f47fSAdrian Hunter		ap.print_usage()
50691ed7f47fSAdrian Hunter		print("Too few arguments")
50701ed7f47fSAdrian Hunter		sys.exit(1)
50711ed7f47fSAdrian Hunter
5072031c2a00SAdrian Hunter	is_sqlite3 = False
5073031c2a00SAdrian Hunter	try:
5074beda0e72STony Jones		f = open(dbname, "rb")
5075beda0e72STony Jones		if f.read(15) == b'SQLite format 3':
5076031c2a00SAdrian Hunter			is_sqlite3 = True
5077031c2a00SAdrian Hunter		f.close()
5078031c2a00SAdrian Hunter	except:
5079031c2a00SAdrian Hunter		pass
5080031c2a00SAdrian Hunter
5081031c2a00SAdrian Hunter	dbref = DBRef(is_sqlite3, dbname)
5082031c2a00SAdrian Hunter	db, dbname = dbref.Open("main")
5083031c2a00SAdrian Hunter	glb = Glb(dbref, db, dbname)
5084031c2a00SAdrian Hunter	app = QApplication(sys.argv)
5085031c2a00SAdrian Hunter	glb.app = app
5086031c2a00SAdrian Hunter	mainwindow = MainWindow(glb)
5087031c2a00SAdrian Hunter	glb.mainwindow = mainwindow
5088031c2a00SAdrian Hunter	mainwindow.show()
5089031c2a00SAdrian Hunter	err = app.exec_()
50908392b74bSAdrian Hunter	glb.ShutdownInstances()
5091031c2a00SAdrian Hunter	db.close()
5092031c2a00SAdrian Hunter	sys.exit(err)
5093031c2a00SAdrian Hunter
5094031c2a00SAdrian Hunterif __name__ == "__main__":
5095031c2a00SAdrian Hunter	Main()
5096