xref: /openbmc/linux/tools/perf/scripts/python/exported-sql-viewer.py (revision 762f99f4f3cb41a775b5157dd761217beba65873)
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
94f56299a9SAdrian Hunter# Only change warnings if the python -W option was not used
95f56299a9SAdrian Hunterif not sys.warnoptions:
96f56299a9SAdrian Hunter	import warnings
97f56299a9SAdrian Hunter	# PySide2 causes deprecation warnings, ignore them.
98f56299a9SAdrian Hunter	warnings.filterwarnings("ignore", category=DeprecationWarning)
991ed7f47fSAdrian Hunterimport argparse
1001beb5c7bSAdrian Hunterimport weakref
1011beb5c7bSAdrian Hunterimport threading
102ebd70c7dSAdrian Hunterimport string
103beda0e72STony Jonestry:
104beda0e72STony Jones	# Python2
105beda0e72STony Jones	import cPickle as pickle
106beda0e72STony Jones	# size of pickled integer big enough for record size
107beda0e72STony Jones	glb_nsz = 8
108beda0e72STony Jonesexcept ImportError:
109beda0e72STony Jones	import pickle
110beda0e72STony Jones	glb_nsz = 16
1118392b74bSAdrian Hunterimport re
1128392b74bSAdrian Hunterimport os
113b3700f21SAdrian Hunterimport random
114b3700f21SAdrian Hunterimport copy
115b3700f21SAdrian Hunterimport math
116*2b87386cSAdrian Hunterfrom libxed import LibXED
117df8ea22aSAdrian Hunter
118df8ea22aSAdrian Hunterpyside_version_1 = True
119df8ea22aSAdrian Hunterif not "--pyside-version-1" in sys.argv:
120df8ea22aSAdrian Hunter	try:
121df8ea22aSAdrian Hunter		from PySide2.QtCore import *
122df8ea22aSAdrian Hunter		from PySide2.QtGui import *
123df8ea22aSAdrian Hunter		from PySide2.QtSql import *
124df8ea22aSAdrian Hunter		from PySide2.QtWidgets import *
125df8ea22aSAdrian Hunter		pyside_version_1 = False
126df8ea22aSAdrian Hunter	except:
127df8ea22aSAdrian Hunter		pass
128df8ea22aSAdrian Hunter
129df8ea22aSAdrian Hunterif pyside_version_1:
130031c2a00SAdrian Hunter	from PySide.QtCore import *
131031c2a00SAdrian Hunter	from PySide.QtGui import *
132031c2a00SAdrian Hunter	from PySide.QtSql import *
133df8ea22aSAdrian Hunter
134fd931b2eSAdrian Hunterfrom decimal import Decimal, ROUND_HALF_UP
135fd931b2eSAdrian Hunterfrom ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \
136fd931b2eSAdrian Hunter		   c_void_p, c_bool, c_byte, c_char, c_int, c_uint, c_longlong, c_ulonglong
1378392b74bSAdrian Hunterfrom multiprocessing import Process, Array, Value, Event
138031c2a00SAdrian Hunter
139beda0e72STony Jones# xrange is range in Python3
140beda0e72STony Jonestry:
141beda0e72STony Jones	xrange
142beda0e72STony Jonesexcept NameError:
143beda0e72STony Jones	xrange = range
144beda0e72STony Jones
145beda0e72STony Jonesdef printerr(*args, **keyword_args):
146beda0e72STony Jones	print(*args, file=sys.stderr, **keyword_args)
147beda0e72STony Jones
148031c2a00SAdrian Hunter# Data formatting helpers
149031c2a00SAdrian Hunter
15076099f98SAdrian Hunterdef tohex(ip):
15176099f98SAdrian Hunter	if ip < 0:
15276099f98SAdrian Hunter		ip += 1 << 64
15376099f98SAdrian Hunter	return "%x" % ip
15476099f98SAdrian Hunter
15576099f98SAdrian Hunterdef offstr(offset):
15676099f98SAdrian Hunter	if offset:
15776099f98SAdrian Hunter		return "+0x%x" % offset
15876099f98SAdrian Hunter	return ""
15976099f98SAdrian Hunter
160031c2a00SAdrian Hunterdef dsoname(name):
161031c2a00SAdrian Hunter	if name == "[kernel.kallsyms]":
162031c2a00SAdrian Hunter		return "[kernel]"
163031c2a00SAdrian Hunter	return name
164031c2a00SAdrian Hunter
165210cf1f9SAdrian Hunterdef findnth(s, sub, n, offs=0):
166210cf1f9SAdrian Hunter	pos = s.find(sub)
167210cf1f9SAdrian Hunter	if pos < 0:
168210cf1f9SAdrian Hunter		return pos
169210cf1f9SAdrian Hunter	if n <= 1:
170210cf1f9SAdrian Hunter		return offs + pos
171210cf1f9SAdrian Hunter	return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
172210cf1f9SAdrian Hunter
173031c2a00SAdrian Hunter# Percent to one decimal place
174031c2a00SAdrian Hunter
175031c2a00SAdrian Hunterdef PercentToOneDP(n, d):
176031c2a00SAdrian Hunter	if not d:
177031c2a00SAdrian Hunter		return "0.0"
178031c2a00SAdrian Hunter	x = (n * Decimal(100)) / d
179031c2a00SAdrian Hunter	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
180031c2a00SAdrian Hunter
181031c2a00SAdrian Hunter# Helper for queries that must not fail
182031c2a00SAdrian Hunter
183031c2a00SAdrian Hunterdef QueryExec(query, stmt):
184031c2a00SAdrian Hunter	ret = query.exec_(stmt)
185031c2a00SAdrian Hunter	if not ret:
186031c2a00SAdrian Hunter		raise Exception("Query failed: " + query.lastError().text())
187031c2a00SAdrian Hunter
188ebd70c7dSAdrian Hunter# Background thread
189ebd70c7dSAdrian Hunter
190ebd70c7dSAdrian Hunterclass Thread(QThread):
191ebd70c7dSAdrian Hunter
192ebd70c7dSAdrian Hunter	done = Signal(object)
193ebd70c7dSAdrian Hunter
194ebd70c7dSAdrian Hunter	def __init__(self, task, param=None, parent=None):
195ebd70c7dSAdrian Hunter		super(Thread, self).__init__(parent)
196ebd70c7dSAdrian Hunter		self.task = task
197ebd70c7dSAdrian Hunter		self.param = param
198ebd70c7dSAdrian Hunter
199ebd70c7dSAdrian Hunter	def run(self):
200ebd70c7dSAdrian Hunter		while True:
201ebd70c7dSAdrian Hunter			if self.param is None:
202ebd70c7dSAdrian Hunter				done, result = self.task()
203ebd70c7dSAdrian Hunter			else:
204ebd70c7dSAdrian Hunter				done, result = self.task(self.param)
205ebd70c7dSAdrian Hunter			self.done.emit(result)
206ebd70c7dSAdrian Hunter			if done:
207ebd70c7dSAdrian Hunter				break
208ebd70c7dSAdrian Hunter
209031c2a00SAdrian Hunter# Tree data model
210031c2a00SAdrian Hunter
211031c2a00SAdrian Hunterclass TreeModel(QAbstractItemModel):
212031c2a00SAdrian Hunter
2134a0979d4SAdrian Hunter	def __init__(self, glb, params, parent=None):
214031c2a00SAdrian Hunter		super(TreeModel, self).__init__(parent)
215a448ba23SAdrian Hunter		self.glb = glb
2164a0979d4SAdrian Hunter		self.params = params
217a448ba23SAdrian Hunter		self.root = self.GetRoot()
218031c2a00SAdrian Hunter		self.last_row_read = 0
219031c2a00SAdrian Hunter
220031c2a00SAdrian Hunter	def Item(self, parent):
221031c2a00SAdrian Hunter		if parent.isValid():
222031c2a00SAdrian Hunter			return parent.internalPointer()
223031c2a00SAdrian Hunter		else:
224031c2a00SAdrian Hunter			return self.root
225031c2a00SAdrian Hunter
226031c2a00SAdrian Hunter	def rowCount(self, parent):
227031c2a00SAdrian Hunter		result = self.Item(parent).childCount()
228031c2a00SAdrian Hunter		if result < 0:
229031c2a00SAdrian Hunter			result = 0
230031c2a00SAdrian Hunter			self.dataChanged.emit(parent, parent)
231031c2a00SAdrian Hunter		return result
232031c2a00SAdrian Hunter
233031c2a00SAdrian Hunter	def hasChildren(self, parent):
234031c2a00SAdrian Hunter		return self.Item(parent).hasChildren()
235031c2a00SAdrian Hunter
236031c2a00SAdrian Hunter	def headerData(self, section, orientation, role):
237031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
238031c2a00SAdrian Hunter			return self.columnAlignment(section)
239031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
240031c2a00SAdrian Hunter			return None
241031c2a00SAdrian Hunter		if orientation != Qt.Horizontal:
242031c2a00SAdrian Hunter			return None
243031c2a00SAdrian Hunter		return self.columnHeader(section)
244031c2a00SAdrian Hunter
245031c2a00SAdrian Hunter	def parent(self, child):
246031c2a00SAdrian Hunter		child_item = child.internalPointer()
247031c2a00SAdrian Hunter		if child_item is self.root:
248031c2a00SAdrian Hunter			return QModelIndex()
249031c2a00SAdrian Hunter		parent_item = child_item.getParentItem()
250031c2a00SAdrian Hunter		return self.createIndex(parent_item.getRow(), 0, parent_item)
251031c2a00SAdrian Hunter
252031c2a00SAdrian Hunter	def index(self, row, column, parent):
253031c2a00SAdrian Hunter		child_item = self.Item(parent).getChildItem(row)
254031c2a00SAdrian Hunter		return self.createIndex(row, column, child_item)
255031c2a00SAdrian Hunter
256031c2a00SAdrian Hunter	def DisplayData(self, item, index):
257031c2a00SAdrian Hunter		return item.getData(index.column())
258031c2a00SAdrian Hunter
2598392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
2608392b74bSAdrian Hunter		if row > self.last_row_read:
2618392b74bSAdrian Hunter			self.last_row_read = row
2628392b74bSAdrian Hunter			if row + 10 >= self.root.child_count:
2638392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
2648392b74bSAdrian Hunter
2658392b74bSAdrian Hunter	def columnAlignment(self, column):
2668392b74bSAdrian Hunter		return Qt.AlignLeft
2678392b74bSAdrian Hunter
2688392b74bSAdrian Hunter	def columnFont(self, column):
2698392b74bSAdrian Hunter		return None
2708392b74bSAdrian Hunter
2718392b74bSAdrian Hunter	def data(self, index, role):
2728392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
2738392b74bSAdrian Hunter			return self.columnAlignment(index.column())
2748392b74bSAdrian Hunter		if role == Qt.FontRole:
2758392b74bSAdrian Hunter			return self.columnFont(index.column())
2768392b74bSAdrian Hunter		if role != Qt.DisplayRole:
2778392b74bSAdrian Hunter			return None
2788392b74bSAdrian Hunter		item = index.internalPointer()
2798392b74bSAdrian Hunter		return self.DisplayData(item, index)
2808392b74bSAdrian Hunter
2818392b74bSAdrian Hunter# Table data model
2828392b74bSAdrian Hunter
2838392b74bSAdrian Hunterclass TableModel(QAbstractTableModel):
2848392b74bSAdrian Hunter
2858392b74bSAdrian Hunter	def __init__(self, parent=None):
2868392b74bSAdrian Hunter		super(TableModel, self).__init__(parent)
2878392b74bSAdrian Hunter		self.child_count = 0
2888392b74bSAdrian Hunter		self.child_items = []
2898392b74bSAdrian Hunter		self.last_row_read = 0
2908392b74bSAdrian Hunter
2918392b74bSAdrian Hunter	def Item(self, parent):
2928392b74bSAdrian Hunter		if parent.isValid():
2938392b74bSAdrian Hunter			return parent.internalPointer()
2948392b74bSAdrian Hunter		else:
2958392b74bSAdrian Hunter			return self
2968392b74bSAdrian Hunter
2978392b74bSAdrian Hunter	def rowCount(self, parent):
2988392b74bSAdrian Hunter		return self.child_count
2998392b74bSAdrian Hunter
3008392b74bSAdrian Hunter	def headerData(self, section, orientation, role):
3018392b74bSAdrian Hunter		if role == Qt.TextAlignmentRole:
3028392b74bSAdrian Hunter			return self.columnAlignment(section)
3038392b74bSAdrian Hunter		if role != Qt.DisplayRole:
3048392b74bSAdrian Hunter			return None
3058392b74bSAdrian Hunter		if orientation != Qt.Horizontal:
3068392b74bSAdrian Hunter			return None
3078392b74bSAdrian Hunter		return self.columnHeader(section)
3088392b74bSAdrian Hunter
3098392b74bSAdrian Hunter	def index(self, row, column, parent):
3108392b74bSAdrian Hunter		return self.createIndex(row, column, self.child_items[row])
3118392b74bSAdrian Hunter
3128392b74bSAdrian Hunter	def DisplayData(self, item, index):
3138392b74bSAdrian Hunter		return item.getData(index.column())
3148392b74bSAdrian Hunter
3158392b74bSAdrian Hunter	def FetchIfNeeded(self, row):
3168392b74bSAdrian Hunter		if row > self.last_row_read:
3178392b74bSAdrian Hunter			self.last_row_read = row
3188392b74bSAdrian Hunter			if row + 10 >= self.child_count:
3198392b74bSAdrian Hunter				self.fetcher.Fetch(glb_chunk_sz)
3208392b74bSAdrian Hunter
321031c2a00SAdrian Hunter	def columnAlignment(self, column):
322031c2a00SAdrian Hunter		return Qt.AlignLeft
323031c2a00SAdrian Hunter
324031c2a00SAdrian Hunter	def columnFont(self, column):
325031c2a00SAdrian Hunter		return None
326031c2a00SAdrian Hunter
327031c2a00SAdrian Hunter	def data(self, index, role):
328031c2a00SAdrian Hunter		if role == Qt.TextAlignmentRole:
329031c2a00SAdrian Hunter			return self.columnAlignment(index.column())
330031c2a00SAdrian Hunter		if role == Qt.FontRole:
331031c2a00SAdrian Hunter			return self.columnFont(index.column())
332031c2a00SAdrian Hunter		if role != Qt.DisplayRole:
333031c2a00SAdrian Hunter			return None
334031c2a00SAdrian Hunter		item = index.internalPointer()
335031c2a00SAdrian Hunter		return self.DisplayData(item, index)
336031c2a00SAdrian Hunter
3371beb5c7bSAdrian Hunter# Model cache
3381beb5c7bSAdrian Hunter
3391beb5c7bSAdrian Huntermodel_cache = weakref.WeakValueDictionary()
3401beb5c7bSAdrian Huntermodel_cache_lock = threading.Lock()
3411beb5c7bSAdrian Hunter
3421beb5c7bSAdrian Hunterdef LookupCreateModel(model_name, create_fn):
3431beb5c7bSAdrian Hunter	model_cache_lock.acquire()
3441beb5c7bSAdrian Hunter	try:
3451beb5c7bSAdrian Hunter		model = model_cache[model_name]
3461beb5c7bSAdrian Hunter	except:
3471beb5c7bSAdrian Hunter		model = None
3481beb5c7bSAdrian Hunter	if model is None:
3491beb5c7bSAdrian Hunter		model = create_fn()
3501beb5c7bSAdrian Hunter		model_cache[model_name] = model
3511beb5c7bSAdrian Hunter	model_cache_lock.release()
3521beb5c7bSAdrian Hunter	return model
3531beb5c7bSAdrian Hunter
354181ea40aSAdrian Hunterdef LookupModel(model_name):
355181ea40aSAdrian Hunter	model_cache_lock.acquire()
356181ea40aSAdrian Hunter	try:
357181ea40aSAdrian Hunter		model = model_cache[model_name]
358181ea40aSAdrian Hunter	except:
359181ea40aSAdrian Hunter		model = None
360181ea40aSAdrian Hunter	model_cache_lock.release()
361181ea40aSAdrian Hunter	return model
362181ea40aSAdrian Hunter
363ebd70c7dSAdrian Hunter# Find bar
364ebd70c7dSAdrian Hunter
365ebd70c7dSAdrian Hunterclass FindBar():
366ebd70c7dSAdrian Hunter
367ebd70c7dSAdrian Hunter	def __init__(self, parent, finder, is_reg_expr=False):
368ebd70c7dSAdrian Hunter		self.finder = finder
369ebd70c7dSAdrian Hunter		self.context = []
370ebd70c7dSAdrian Hunter		self.last_value = None
371ebd70c7dSAdrian Hunter		self.last_pattern = None
372ebd70c7dSAdrian Hunter
373ebd70c7dSAdrian Hunter		label = QLabel("Find:")
374ebd70c7dSAdrian Hunter		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
375ebd70c7dSAdrian Hunter
376ebd70c7dSAdrian Hunter		self.textbox = QComboBox()
377ebd70c7dSAdrian Hunter		self.textbox.setEditable(True)
378ebd70c7dSAdrian Hunter		self.textbox.currentIndexChanged.connect(self.ValueChanged)
379ebd70c7dSAdrian Hunter
380ebd70c7dSAdrian Hunter		self.progress = QProgressBar()
381ebd70c7dSAdrian Hunter		self.progress.setRange(0, 0)
382ebd70c7dSAdrian Hunter		self.progress.hide()
383ebd70c7dSAdrian Hunter
384ebd70c7dSAdrian Hunter		if is_reg_expr:
385ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Regular Expression")
386ebd70c7dSAdrian Hunter		else:
387ebd70c7dSAdrian Hunter			self.pattern = QCheckBox("Pattern")
388ebd70c7dSAdrian Hunter		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
389ebd70c7dSAdrian Hunter
390ebd70c7dSAdrian Hunter		self.next_button = QToolButton()
391ebd70c7dSAdrian Hunter		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
392ebd70c7dSAdrian Hunter		self.next_button.released.connect(lambda: self.NextPrev(1))
393ebd70c7dSAdrian Hunter
394ebd70c7dSAdrian Hunter		self.prev_button = QToolButton()
395ebd70c7dSAdrian Hunter		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
396ebd70c7dSAdrian Hunter		self.prev_button.released.connect(lambda: self.NextPrev(-1))
397ebd70c7dSAdrian Hunter
398ebd70c7dSAdrian Hunter		self.close_button = QToolButton()
399ebd70c7dSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
400ebd70c7dSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
401ebd70c7dSAdrian Hunter
402ebd70c7dSAdrian Hunter		self.hbox = QHBoxLayout()
403ebd70c7dSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
404ebd70c7dSAdrian Hunter
405ebd70c7dSAdrian Hunter		self.hbox.addWidget(label)
406ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.textbox)
407ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.progress)
408ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.pattern)
409ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.next_button)
410ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.prev_button)
411ebd70c7dSAdrian Hunter		self.hbox.addWidget(self.close_button)
412ebd70c7dSAdrian Hunter
413ebd70c7dSAdrian Hunter		self.bar = QWidget()
41426688729SAdrian Hunter		self.bar.setLayout(self.hbox)
415ebd70c7dSAdrian Hunter		self.bar.hide()
416ebd70c7dSAdrian Hunter
417ebd70c7dSAdrian Hunter	def Widget(self):
418ebd70c7dSAdrian Hunter		return self.bar
419ebd70c7dSAdrian Hunter
420ebd70c7dSAdrian Hunter	def Activate(self):
421ebd70c7dSAdrian Hunter		self.bar.show()
42280b3fb64SAdrian Hunter		self.textbox.lineEdit().selectAll()
423ebd70c7dSAdrian Hunter		self.textbox.setFocus()
424ebd70c7dSAdrian Hunter
425ebd70c7dSAdrian Hunter	def Deactivate(self):
426ebd70c7dSAdrian Hunter		self.bar.hide()
427ebd70c7dSAdrian Hunter
428ebd70c7dSAdrian Hunter	def Busy(self):
429ebd70c7dSAdrian Hunter		self.textbox.setEnabled(False)
430ebd70c7dSAdrian Hunter		self.pattern.hide()
431ebd70c7dSAdrian Hunter		self.next_button.hide()
432ebd70c7dSAdrian Hunter		self.prev_button.hide()
433ebd70c7dSAdrian Hunter		self.progress.show()
434ebd70c7dSAdrian Hunter
435ebd70c7dSAdrian Hunter	def Idle(self):
436ebd70c7dSAdrian Hunter		self.textbox.setEnabled(True)
437ebd70c7dSAdrian Hunter		self.progress.hide()
438ebd70c7dSAdrian Hunter		self.pattern.show()
439ebd70c7dSAdrian Hunter		self.next_button.show()
440ebd70c7dSAdrian Hunter		self.prev_button.show()
441ebd70c7dSAdrian Hunter
442ebd70c7dSAdrian Hunter	def Find(self, direction):
443ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
444ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
445ebd70c7dSAdrian Hunter		self.last_value = value
446ebd70c7dSAdrian Hunter		self.last_pattern = pattern
447ebd70c7dSAdrian Hunter		self.finder.Find(value, direction, pattern, self.context)
448ebd70c7dSAdrian Hunter
449ebd70c7dSAdrian Hunter	def ValueChanged(self):
450ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
451ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
452ebd70c7dSAdrian Hunter		index = self.textbox.currentIndex()
453ebd70c7dSAdrian Hunter		data = self.textbox.itemData(index)
454ebd70c7dSAdrian Hunter		# Store the pattern in the combo box to keep it with the text value
455ebd70c7dSAdrian Hunter		if data == None:
456ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
457ebd70c7dSAdrian Hunter		else:
458ebd70c7dSAdrian Hunter			self.pattern.setChecked(data)
459ebd70c7dSAdrian Hunter		self.Find(0)
460ebd70c7dSAdrian Hunter
461ebd70c7dSAdrian Hunter	def NextPrev(self, direction):
462ebd70c7dSAdrian Hunter		value = self.textbox.currentText()
463ebd70c7dSAdrian Hunter		pattern = self.pattern.isChecked()
464ebd70c7dSAdrian Hunter		if value != self.last_value:
465ebd70c7dSAdrian Hunter			index = self.textbox.findText(value)
466ebd70c7dSAdrian Hunter			# Allow for a button press before the value has been added to the combo box
467ebd70c7dSAdrian Hunter			if index < 0:
468ebd70c7dSAdrian Hunter				index = self.textbox.count()
469ebd70c7dSAdrian Hunter				self.textbox.addItem(value, pattern)
470ebd70c7dSAdrian Hunter				self.textbox.setCurrentIndex(index)
471ebd70c7dSAdrian Hunter				return
472ebd70c7dSAdrian Hunter			else:
473ebd70c7dSAdrian Hunter				self.textbox.setItemData(index, pattern)
474ebd70c7dSAdrian Hunter		elif pattern != self.last_pattern:
475ebd70c7dSAdrian Hunter			# Keep the pattern recorded in the combo box up to date
476ebd70c7dSAdrian Hunter			index = self.textbox.currentIndex()
477ebd70c7dSAdrian Hunter			self.textbox.setItemData(index, pattern)
478ebd70c7dSAdrian Hunter		self.Find(direction)
479ebd70c7dSAdrian Hunter
480ebd70c7dSAdrian Hunter	def NotFound(self):
481ebd70c7dSAdrian Hunter		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
482ebd70c7dSAdrian Hunter
483031c2a00SAdrian Hunter# Context-sensitive call graph data model item base
484031c2a00SAdrian Hunter
485031c2a00SAdrian Hunterclass CallGraphLevelItemBase(object):
486031c2a00SAdrian Hunter
4874a0979d4SAdrian Hunter	def __init__(self, glb, params, row, parent_item):
488031c2a00SAdrian Hunter		self.glb = glb
4894a0979d4SAdrian Hunter		self.params = params
490031c2a00SAdrian Hunter		self.row = row
491031c2a00SAdrian Hunter		self.parent_item = parent_item
49226688729SAdrian Hunter		self.query_done = False
493031c2a00SAdrian Hunter		self.child_count = 0
494031c2a00SAdrian Hunter		self.child_items = []
4953ac641f4SAdrian Hunter		if parent_item:
4963ac641f4SAdrian Hunter			self.level = parent_item.level + 1
4973ac641f4SAdrian Hunter		else:
4983ac641f4SAdrian Hunter			self.level = 0
499031c2a00SAdrian Hunter
500031c2a00SAdrian Hunter	def getChildItem(self, row):
501031c2a00SAdrian Hunter		return self.child_items[row]
502031c2a00SAdrian Hunter
503031c2a00SAdrian Hunter	def getParentItem(self):
504031c2a00SAdrian Hunter		return self.parent_item
505031c2a00SAdrian Hunter
506031c2a00SAdrian Hunter	def getRow(self):
507031c2a00SAdrian Hunter		return self.row
508031c2a00SAdrian Hunter
509031c2a00SAdrian Hunter	def childCount(self):
510031c2a00SAdrian Hunter		if not self.query_done:
511031c2a00SAdrian Hunter			self.Select()
512031c2a00SAdrian Hunter			if not self.child_count:
513031c2a00SAdrian Hunter				return -1
514031c2a00SAdrian Hunter		return self.child_count
515031c2a00SAdrian Hunter
516031c2a00SAdrian Hunter	def hasChildren(self):
517031c2a00SAdrian Hunter		if not self.query_done:
518031c2a00SAdrian Hunter			return True
519031c2a00SAdrian Hunter		return self.child_count > 0
520031c2a00SAdrian Hunter
521031c2a00SAdrian Hunter	def getData(self, column):
522031c2a00SAdrian Hunter		return self.data[column]
523031c2a00SAdrian Hunter
524031c2a00SAdrian Hunter# Context-sensitive call graph data model level 2+ item base
525031c2a00SAdrian Hunter
526031c2a00SAdrian Hunterclass CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
527031c2a00SAdrian Hunter
52838a846d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
5294a0979d4SAdrian Hunter		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
530031c2a00SAdrian Hunter		self.comm_id = comm_id
531031c2a00SAdrian Hunter		self.thread_id = thread_id
532031c2a00SAdrian Hunter		self.call_path_id = call_path_id
53338a846d4SAdrian Hunter		self.insn_cnt = insn_cnt
53438a846d4SAdrian Hunter		self.cyc_cnt = cyc_cnt
535031c2a00SAdrian Hunter		self.branch_count = branch_count
536031c2a00SAdrian Hunter		self.time = time
537031c2a00SAdrian Hunter
538031c2a00SAdrian Hunter	def Select(self):
53926688729SAdrian Hunter		self.query_done = True
540031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
54138a846d4SAdrian Hunter		if self.params.have_ipc:
54238a846d4SAdrian Hunter			ipc_str = ", SUM(insn_count), SUM(cyc_count)"
54338a846d4SAdrian Hunter		else:
54438a846d4SAdrian Hunter			ipc_str = ""
54538a846d4SAdrian Hunter		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
546031c2a00SAdrian Hunter					" FROM calls"
547031c2a00SAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
548031c2a00SAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
549031c2a00SAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
550031c2a00SAdrian Hunter					" WHERE parent_call_path_id = " + str(self.call_path_id) +
551031c2a00SAdrian Hunter					" AND comm_id = " + str(self.comm_id) +
552031c2a00SAdrian Hunter					" AND thread_id = " + str(self.thread_id) +
553031c2a00SAdrian Hunter					" GROUP BY call_path_id, name, short_name"
554031c2a00SAdrian Hunter					" ORDER BY call_path_id")
555031c2a00SAdrian Hunter		while query.next():
55638a846d4SAdrian Hunter			if self.params.have_ipc:
55738a846d4SAdrian Hunter				insn_cnt = int(query.value(5))
55838a846d4SAdrian Hunter				cyc_cnt = int(query.value(6))
55938a846d4SAdrian Hunter				branch_count = int(query.value(7))
56038a846d4SAdrian Hunter			else:
56138a846d4SAdrian Hunter				insn_cnt = 0
56238a846d4SAdrian Hunter				cyc_cnt = 0
56338a846d4SAdrian Hunter				branch_count = int(query.value(5))
56438a846d4SAdrian 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)
565031c2a00SAdrian Hunter			self.child_items.append(child_item)
566031c2a00SAdrian Hunter			self.child_count += 1
567031c2a00SAdrian Hunter
568031c2a00SAdrian Hunter# Context-sensitive call graph data model level three item
569031c2a00SAdrian Hunter
570031c2a00SAdrian Hunterclass CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
571031c2a00SAdrian Hunter
57238a846d4SAdrian 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):
57338a846d4SAdrian Hunter		super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
574031c2a00SAdrian Hunter		dso = dsoname(dso)
57538a846d4SAdrian Hunter		if self.params.have_ipc:
57638a846d4SAdrian Hunter			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
57738a846d4SAdrian Hunter			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
57838a846d4SAdrian Hunter			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
57938a846d4SAdrian Hunter			ipc = CalcIPC(cyc_cnt, insn_cnt)
58038a846d4SAdrian 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 ]
58138a846d4SAdrian Hunter		else:
582031c2a00SAdrian Hunter			self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
583031c2a00SAdrian Hunter		self.dbid = call_path_id
584031c2a00SAdrian Hunter
585031c2a00SAdrian Hunter# Context-sensitive call graph data model level two item
586031c2a00SAdrian Hunter
587031c2a00SAdrian Hunterclass CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
588031c2a00SAdrian Hunter
5894a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
59038a846d4SAdrian Hunter		super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
59138a846d4SAdrian Hunter		if self.params.have_ipc:
59238a846d4SAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
59338a846d4SAdrian Hunter		else:
594031c2a00SAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
595031c2a00SAdrian Hunter		self.dbid = thread_id
596031c2a00SAdrian Hunter
597031c2a00SAdrian Hunter	def Select(self):
598031c2a00SAdrian Hunter		super(CallGraphLevelTwoItem, self).Select()
599031c2a00SAdrian Hunter		for child_item in self.child_items:
600031c2a00SAdrian Hunter			self.time += child_item.time
60138a846d4SAdrian Hunter			self.insn_cnt += child_item.insn_cnt
60238a846d4SAdrian Hunter			self.cyc_cnt += child_item.cyc_cnt
603031c2a00SAdrian Hunter			self.branch_count += child_item.branch_count
604031c2a00SAdrian Hunter		for child_item in self.child_items:
605031c2a00SAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
60638a846d4SAdrian Hunter			if self.params.have_ipc:
60738a846d4SAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
60838a846d4SAdrian Hunter				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
60938a846d4SAdrian Hunter				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
61038a846d4SAdrian Hunter			else:
611031c2a00SAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
612031c2a00SAdrian Hunter
613031c2a00SAdrian Hunter# Context-sensitive call graph data model level one item
614031c2a00SAdrian Hunter
615031c2a00SAdrian Hunterclass CallGraphLevelOneItem(CallGraphLevelItemBase):
616031c2a00SAdrian Hunter
6174a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, comm, parent_item):
6184a0979d4SAdrian Hunter		super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
61938a846d4SAdrian Hunter		if self.params.have_ipc:
62038a846d4SAdrian Hunter			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
62138a846d4SAdrian Hunter		else:
622031c2a00SAdrian Hunter			self.data = [comm, "", "", "", "", "", ""]
623031c2a00SAdrian Hunter		self.dbid = comm_id
624031c2a00SAdrian Hunter
625031c2a00SAdrian Hunter	def Select(self):
62626688729SAdrian Hunter		self.query_done = True
627031c2a00SAdrian Hunter		query = QSqlQuery(self.glb.db)
628031c2a00SAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
629031c2a00SAdrian Hunter					" FROM comm_threads"
630031c2a00SAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
631031c2a00SAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
632031c2a00SAdrian Hunter		while query.next():
6334a0979d4SAdrian Hunter			child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
634031c2a00SAdrian Hunter			self.child_items.append(child_item)
635031c2a00SAdrian Hunter			self.child_count += 1
636031c2a00SAdrian Hunter
637031c2a00SAdrian Hunter# Context-sensitive call graph data model root item
638031c2a00SAdrian Hunter
639031c2a00SAdrian Hunterclass CallGraphRootItem(CallGraphLevelItemBase):
640031c2a00SAdrian Hunter
6414a0979d4SAdrian Hunter	def __init__(self, glb, params):
6424a0979d4SAdrian Hunter		super(CallGraphRootItem, self).__init__(glb, params, 0, None)
643031c2a00SAdrian Hunter		self.dbid = 0
64426688729SAdrian Hunter		self.query_done = True
64526c11206SAdrian Hunter		if_has_calls = ""
64626c11206SAdrian Hunter		if IsSelectable(glb.db, "comms", columns = "has_calls"):
647af833988SAdrian Hunter			if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
648031c2a00SAdrian Hunter		query = QSqlQuery(glb.db)
64926c11206SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
650031c2a00SAdrian Hunter		while query.next():
651031c2a00SAdrian Hunter			if not query.value(0):
652031c2a00SAdrian Hunter				continue
6534a0979d4SAdrian Hunter			child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
654031c2a00SAdrian Hunter			self.child_items.append(child_item)
655031c2a00SAdrian Hunter			self.child_count += 1
656031c2a00SAdrian Hunter
6574a0979d4SAdrian Hunter# Call graph model parameters
6584a0979d4SAdrian Hunter
6594a0979d4SAdrian Hunterclass CallGraphModelParams():
6604a0979d4SAdrian Hunter
6614a0979d4SAdrian Hunter	def __init__(self, glb, parent=None):
6624a0979d4SAdrian Hunter		self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
6634a0979d4SAdrian Hunter
664254c0d82SAdrian Hunter# Context-sensitive call graph data model base
665031c2a00SAdrian Hunter
666254c0d82SAdrian Hunterclass CallGraphModelBase(TreeModel):
667031c2a00SAdrian Hunter
668031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
6694a0979d4SAdrian Hunter		super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
670031c2a00SAdrian Hunter
671ebd70c7dSAdrian Hunter	def FindSelect(self, value, pattern, query):
672ebd70c7dSAdrian Hunter		if pattern:
673ebd70c7dSAdrian Hunter			# postgresql and sqlite pattern patching differences:
674ebd70c7dSAdrian Hunter			#   postgresql LIKE is case sensitive but sqlite LIKE is not
675ebd70c7dSAdrian Hunter			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
676ebd70c7dSAdrian Hunter			#   postgresql supports ILIKE which is case insensitive
677ebd70c7dSAdrian Hunter			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
678ebd70c7dSAdrian Hunter			if not self.glb.dbref.is_sqlite3:
679ebd70c7dSAdrian Hunter				# Escape % and _
680ebd70c7dSAdrian Hunter				s = value.replace("%", "\%")
681ebd70c7dSAdrian Hunter				s = s.replace("_", "\_")
682ebd70c7dSAdrian Hunter				# Translate * and ? into SQL LIKE pattern characters % and _
683ebd70c7dSAdrian Hunter				trans = string.maketrans("*?", "%_")
684ebd70c7dSAdrian Hunter				match = " LIKE '" + str(s).translate(trans) + "'"
685ebd70c7dSAdrian Hunter			else:
686ebd70c7dSAdrian Hunter				match = " GLOB '" + str(value) + "'"
687ebd70c7dSAdrian Hunter		else:
688ebd70c7dSAdrian Hunter			match = " = '" + str(value) + "'"
689254c0d82SAdrian Hunter		self.DoFindSelect(query, match)
690ebd70c7dSAdrian Hunter
691ebd70c7dSAdrian Hunter	def Found(self, query, found):
692ebd70c7dSAdrian Hunter		if found:
693ebd70c7dSAdrian Hunter			return self.FindPath(query)
694ebd70c7dSAdrian Hunter		return []
695ebd70c7dSAdrian Hunter
696ebd70c7dSAdrian Hunter	def FindValue(self, value, pattern, query, last_value, last_pattern):
697ebd70c7dSAdrian Hunter		if last_value == value and pattern == last_pattern:
698ebd70c7dSAdrian Hunter			found = query.first()
699ebd70c7dSAdrian Hunter		else:
700ebd70c7dSAdrian Hunter			self.FindSelect(value, pattern, query)
701ebd70c7dSAdrian Hunter			found = query.next()
702ebd70c7dSAdrian Hunter		return self.Found(query, found)
703ebd70c7dSAdrian Hunter
704ebd70c7dSAdrian Hunter	def FindNext(self, query):
705ebd70c7dSAdrian Hunter		found = query.next()
706ebd70c7dSAdrian Hunter		if not found:
707ebd70c7dSAdrian Hunter			found = query.first()
708ebd70c7dSAdrian Hunter		return self.Found(query, found)
709ebd70c7dSAdrian Hunter
710ebd70c7dSAdrian Hunter	def FindPrev(self, query):
711ebd70c7dSAdrian Hunter		found = query.previous()
712ebd70c7dSAdrian Hunter		if not found:
713ebd70c7dSAdrian Hunter			found = query.last()
714ebd70c7dSAdrian Hunter		return self.Found(query, found)
715ebd70c7dSAdrian Hunter
716ebd70c7dSAdrian Hunter	def FindThread(self, c):
717ebd70c7dSAdrian Hunter		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
718ebd70c7dSAdrian Hunter			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
719ebd70c7dSAdrian Hunter		elif c.direction > 0:
720ebd70c7dSAdrian Hunter			ids = self.FindNext(c.query)
721ebd70c7dSAdrian Hunter		else:
722ebd70c7dSAdrian Hunter			ids = self.FindPrev(c.query)
723ebd70c7dSAdrian Hunter		return (True, ids)
724ebd70c7dSAdrian Hunter
725ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
726ebd70c7dSAdrian Hunter		class Context():
727ebd70c7dSAdrian Hunter			def __init__(self, *x):
728ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
729ebd70c7dSAdrian Hunter			def Update(self, *x):
730ebd70c7dSAdrian Hunter				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
731ebd70c7dSAdrian Hunter		if len(context):
732ebd70c7dSAdrian Hunter			context[0].Update(value, direction, pattern)
733ebd70c7dSAdrian Hunter		else:
734ebd70c7dSAdrian Hunter			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
735ebd70c7dSAdrian Hunter		# Use a thread so the UI is not blocked during the SELECT
736ebd70c7dSAdrian Hunter		thread = Thread(self.FindThread, context[0])
737ebd70c7dSAdrian Hunter		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
738ebd70c7dSAdrian Hunter		thread.start()
739ebd70c7dSAdrian Hunter
740ebd70c7dSAdrian Hunter	def FindDone(self, thread, callback, ids):
741ebd70c7dSAdrian Hunter		callback(ids)
742ebd70c7dSAdrian Hunter
743254c0d82SAdrian Hunter# Context-sensitive call graph data model
744254c0d82SAdrian Hunter
745254c0d82SAdrian Hunterclass CallGraphModel(CallGraphModelBase):
746254c0d82SAdrian Hunter
747254c0d82SAdrian Hunter	def __init__(self, glb, parent=None):
748254c0d82SAdrian Hunter		super(CallGraphModel, self).__init__(glb, parent)
749254c0d82SAdrian Hunter
750254c0d82SAdrian Hunter	def GetRoot(self):
7514a0979d4SAdrian Hunter		return CallGraphRootItem(self.glb, self.params)
752254c0d82SAdrian Hunter
753254c0d82SAdrian Hunter	def columnCount(self, parent=None):
75438a846d4SAdrian Hunter		if self.params.have_ipc:
75538a846d4SAdrian Hunter			return 12
75638a846d4SAdrian Hunter		else:
757254c0d82SAdrian Hunter			return 7
758254c0d82SAdrian Hunter
759254c0d82SAdrian Hunter	def columnHeader(self, column):
76038a846d4SAdrian Hunter		if self.params.have_ipc:
76138a846d4SAdrian Hunter			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
76238a846d4SAdrian Hunter		else:
763254c0d82SAdrian Hunter			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
764254c0d82SAdrian Hunter		return headers[column]
765254c0d82SAdrian Hunter
766254c0d82SAdrian Hunter	def columnAlignment(self, column):
76738a846d4SAdrian Hunter		if self.params.have_ipc:
76838a846d4SAdrian 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 ]
76938a846d4SAdrian Hunter		else:
770254c0d82SAdrian Hunter			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
771254c0d82SAdrian Hunter		return alignment[column]
772254c0d82SAdrian Hunter
773254c0d82SAdrian Hunter	def DoFindSelect(self, query, match):
774254c0d82SAdrian Hunter		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
775254c0d82SAdrian Hunter						" FROM calls"
776254c0d82SAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
777254c0d82SAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
7787ff520b0SAdrian Hunter						" WHERE calls.id <> 0"
7797ff520b0SAdrian Hunter						" AND symbols.name" + match +
780254c0d82SAdrian Hunter						" GROUP BY comm_id, thread_id, call_path_id"
781254c0d82SAdrian Hunter						" ORDER BY comm_id, thread_id, call_path_id")
782254c0d82SAdrian Hunter
783254c0d82SAdrian Hunter	def FindPath(self, query):
784254c0d82SAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
785254c0d82SAdrian Hunter		# to open the tree at the right place.
786254c0d82SAdrian Hunter		ids = []
787254c0d82SAdrian Hunter		parent_id = query.value(0)
788254c0d82SAdrian Hunter		while parent_id:
789254c0d82SAdrian Hunter			ids.insert(0, parent_id)
790254c0d82SAdrian Hunter			q2 = QSqlQuery(self.glb.db)
791254c0d82SAdrian Hunter			QueryExec(q2, "SELECT parent_id"
792254c0d82SAdrian Hunter					" FROM call_paths"
793254c0d82SAdrian Hunter					" WHERE id = " + str(parent_id))
794254c0d82SAdrian Hunter			if not q2.next():
795254c0d82SAdrian Hunter				break
796254c0d82SAdrian Hunter			parent_id = q2.value(0)
797254c0d82SAdrian Hunter		# The call path root is not used
798254c0d82SAdrian Hunter		if ids[0] == 1:
799254c0d82SAdrian Hunter			del ids[0]
800254c0d82SAdrian Hunter		ids.insert(0, query.value(2))
801254c0d82SAdrian Hunter		ids.insert(0, query.value(1))
802254c0d82SAdrian Hunter		return ids
803254c0d82SAdrian Hunter
804ae8b887cSAdrian Hunter# Call tree data model level 2+ item base
805ae8b887cSAdrian Hunter
806ae8b887cSAdrian Hunterclass CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
807ae8b887cSAdrian Hunter
808da4264f5SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
8094a0979d4SAdrian Hunter		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
810ae8b887cSAdrian Hunter		self.comm_id = comm_id
811ae8b887cSAdrian Hunter		self.thread_id = thread_id
812ae8b887cSAdrian Hunter		self.calls_id = calls_id
813da4264f5SAdrian Hunter		self.call_time = call_time
814da4264f5SAdrian Hunter		self.time = time
815b3b66079SAdrian Hunter		self.insn_cnt = insn_cnt
816b3b66079SAdrian Hunter		self.cyc_cnt = cyc_cnt
817ae8b887cSAdrian Hunter		self.branch_count = branch_count
818ae8b887cSAdrian Hunter
819ae8b887cSAdrian Hunter	def Select(self):
82026688729SAdrian Hunter		self.query_done = True
821ae8b887cSAdrian Hunter		if self.calls_id == 0:
822ae8b887cSAdrian Hunter			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
823ae8b887cSAdrian Hunter		else:
824ae8b887cSAdrian Hunter			comm_thread = ""
825b3b66079SAdrian Hunter		if self.params.have_ipc:
826b3b66079SAdrian Hunter			ipc_str = ", insn_count, cyc_count"
827b3b66079SAdrian Hunter		else:
828b3b66079SAdrian Hunter			ipc_str = ""
829ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
830b3b66079SAdrian Hunter		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
831ae8b887cSAdrian Hunter					" FROM calls"
832ae8b887cSAdrian Hunter					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
833ae8b887cSAdrian Hunter					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
834ae8b887cSAdrian Hunter					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
835ae8b887cSAdrian Hunter					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
836ae8b887cSAdrian Hunter					" ORDER BY call_time, calls.id")
837ae8b887cSAdrian Hunter		while query.next():
838b3b66079SAdrian Hunter			if self.params.have_ipc:
839b3b66079SAdrian Hunter				insn_cnt = int(query.value(5))
840b3b66079SAdrian Hunter				cyc_cnt = int(query.value(6))
841b3b66079SAdrian Hunter				branch_count = int(query.value(7))
842b3b66079SAdrian Hunter			else:
843b3b66079SAdrian Hunter				insn_cnt = 0
844b3b66079SAdrian Hunter				cyc_cnt = 0
845b3b66079SAdrian Hunter				branch_count = int(query.value(5))
846b3b66079SAdrian 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)
847ae8b887cSAdrian Hunter			self.child_items.append(child_item)
848ae8b887cSAdrian Hunter			self.child_count += 1
849ae8b887cSAdrian Hunter
850ae8b887cSAdrian Hunter# Call tree data model level three item
851ae8b887cSAdrian Hunter
852ae8b887cSAdrian Hunterclass CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
853ae8b887cSAdrian Hunter
854da4264f5SAdrian 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):
855da4264f5SAdrian 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)
856ae8b887cSAdrian Hunter		dso = dsoname(dso)
857b3b66079SAdrian Hunter		if self.params.have_ipc:
858b3b66079SAdrian Hunter			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
859b3b66079SAdrian Hunter			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
860b3b66079SAdrian Hunter			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
861b3b66079SAdrian Hunter			ipc = CalcIPC(cyc_cnt, insn_cnt)
862da4264f5SAdrian 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 ]
863b3b66079SAdrian Hunter		else:
864da4264f5SAdrian 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) ]
865ae8b887cSAdrian Hunter		self.dbid = calls_id
866ae8b887cSAdrian Hunter
867ae8b887cSAdrian Hunter# Call tree data model level two item
868ae8b887cSAdrian Hunter
869ae8b887cSAdrian Hunterclass CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
870ae8b887cSAdrian Hunter
8714a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
872da4264f5SAdrian Hunter		super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, 0, parent_item)
873b3b66079SAdrian Hunter		if self.params.have_ipc:
874b3b66079SAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
875b3b66079SAdrian Hunter		else:
876ae8b887cSAdrian Hunter			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
877ae8b887cSAdrian Hunter		self.dbid = thread_id
878ae8b887cSAdrian Hunter
879ae8b887cSAdrian Hunter	def Select(self):
880ae8b887cSAdrian Hunter		super(CallTreeLevelTwoItem, self).Select()
881ae8b887cSAdrian Hunter		for child_item in self.child_items:
882ae8b887cSAdrian Hunter			self.time += child_item.time
883b3b66079SAdrian Hunter			self.insn_cnt += child_item.insn_cnt
884b3b66079SAdrian Hunter			self.cyc_cnt += child_item.cyc_cnt
885ae8b887cSAdrian Hunter			self.branch_count += child_item.branch_count
886ae8b887cSAdrian Hunter		for child_item in self.child_items:
887ae8b887cSAdrian Hunter			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
888b3b66079SAdrian Hunter			if self.params.have_ipc:
889b3b66079SAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
890b3b66079SAdrian Hunter				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
891b3b66079SAdrian Hunter				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
892b3b66079SAdrian Hunter			else:
893ae8b887cSAdrian Hunter				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
894ae8b887cSAdrian Hunter
895ae8b887cSAdrian Hunter# Call tree data model level one item
896ae8b887cSAdrian Hunter
897ae8b887cSAdrian Hunterclass CallTreeLevelOneItem(CallGraphLevelItemBase):
898ae8b887cSAdrian Hunter
8994a0979d4SAdrian Hunter	def __init__(self, glb, params, row, comm_id, comm, parent_item):
9004a0979d4SAdrian Hunter		super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
901b3b66079SAdrian Hunter		if self.params.have_ipc:
902b3b66079SAdrian Hunter			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
903b3b66079SAdrian Hunter		else:
904ae8b887cSAdrian Hunter			self.data = [comm, "", "", "", "", "", ""]
905ae8b887cSAdrian Hunter		self.dbid = comm_id
906ae8b887cSAdrian Hunter
907ae8b887cSAdrian Hunter	def Select(self):
90826688729SAdrian Hunter		self.query_done = True
909ae8b887cSAdrian Hunter		query = QSqlQuery(self.glb.db)
910ae8b887cSAdrian Hunter		QueryExec(query, "SELECT thread_id, pid, tid"
911ae8b887cSAdrian Hunter					" FROM comm_threads"
912ae8b887cSAdrian Hunter					" INNER JOIN threads ON thread_id = threads.id"
913ae8b887cSAdrian Hunter					" WHERE comm_id = " + str(self.dbid))
914ae8b887cSAdrian Hunter		while query.next():
9154a0979d4SAdrian Hunter			child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
916ae8b887cSAdrian Hunter			self.child_items.append(child_item)
917ae8b887cSAdrian Hunter			self.child_count += 1
918ae8b887cSAdrian Hunter
919ae8b887cSAdrian Hunter# Call tree data model root item
920ae8b887cSAdrian Hunter
921ae8b887cSAdrian Hunterclass CallTreeRootItem(CallGraphLevelItemBase):
922ae8b887cSAdrian Hunter
9234a0979d4SAdrian Hunter	def __init__(self, glb, params):
9244a0979d4SAdrian Hunter		super(CallTreeRootItem, self).__init__(glb, params, 0, None)
925ae8b887cSAdrian Hunter		self.dbid = 0
92626688729SAdrian Hunter		self.query_done = True
92726c11206SAdrian Hunter		if_has_calls = ""
92826c11206SAdrian Hunter		if IsSelectable(glb.db, "comms", columns = "has_calls"):
929af833988SAdrian Hunter			if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
930ae8b887cSAdrian Hunter		query = QSqlQuery(glb.db)
93126c11206SAdrian Hunter		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
932ae8b887cSAdrian Hunter		while query.next():
933ae8b887cSAdrian Hunter			if not query.value(0):
934ae8b887cSAdrian Hunter				continue
9354a0979d4SAdrian Hunter			child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
936ae8b887cSAdrian Hunter			self.child_items.append(child_item)
937ae8b887cSAdrian Hunter			self.child_count += 1
938ae8b887cSAdrian Hunter
939ae8b887cSAdrian Hunter# Call Tree data model
940ae8b887cSAdrian Hunter
941ae8b887cSAdrian Hunterclass CallTreeModel(CallGraphModelBase):
942ae8b887cSAdrian Hunter
943ae8b887cSAdrian Hunter	def __init__(self, glb, parent=None):
944ae8b887cSAdrian Hunter		super(CallTreeModel, self).__init__(glb, parent)
945ae8b887cSAdrian Hunter
946ae8b887cSAdrian Hunter	def GetRoot(self):
9474a0979d4SAdrian Hunter		return CallTreeRootItem(self.glb, self.params)
948ae8b887cSAdrian Hunter
949ae8b887cSAdrian Hunter	def columnCount(self, parent=None):
950b3b66079SAdrian Hunter		if self.params.have_ipc:
951b3b66079SAdrian Hunter			return 12
952b3b66079SAdrian Hunter		else:
953ae8b887cSAdrian Hunter			return 7
954ae8b887cSAdrian Hunter
955ae8b887cSAdrian Hunter	def columnHeader(self, column):
956b3b66079SAdrian Hunter		if self.params.have_ipc:
957b3b66079SAdrian Hunter			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
958b3b66079SAdrian Hunter		else:
959ae8b887cSAdrian Hunter			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
960ae8b887cSAdrian Hunter		return headers[column]
961ae8b887cSAdrian Hunter
962ae8b887cSAdrian Hunter	def columnAlignment(self, column):
963b3b66079SAdrian Hunter		if self.params.have_ipc:
964b3b66079SAdrian 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 ]
965b3b66079SAdrian Hunter		else:
966ae8b887cSAdrian Hunter			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
967ae8b887cSAdrian Hunter		return alignment[column]
968ae8b887cSAdrian Hunter
969ae8b887cSAdrian Hunter	def DoFindSelect(self, query, match):
970ae8b887cSAdrian Hunter		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
971ae8b887cSAdrian Hunter						" FROM calls"
972ae8b887cSAdrian Hunter						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
973ae8b887cSAdrian Hunter						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
974031c8d5eSAdrian Hunter						" WHERE calls.id <> 0"
975031c8d5eSAdrian Hunter						" AND symbols.name" + match +
976ae8b887cSAdrian Hunter						" ORDER BY comm_id, thread_id, call_time, calls.id")
977ae8b887cSAdrian Hunter
978ae8b887cSAdrian Hunter	def FindPath(self, query):
979ae8b887cSAdrian Hunter		# Turn the query result into a list of ids that the tree view can walk
980ae8b887cSAdrian Hunter		# to open the tree at the right place.
981ae8b887cSAdrian Hunter		ids = []
982ae8b887cSAdrian Hunter		parent_id = query.value(0)
983ae8b887cSAdrian Hunter		while parent_id:
984ae8b887cSAdrian Hunter			ids.insert(0, parent_id)
985ae8b887cSAdrian Hunter			q2 = QSqlQuery(self.glb.db)
986ae8b887cSAdrian Hunter			QueryExec(q2, "SELECT parent_id"
987ae8b887cSAdrian Hunter					" FROM calls"
988ae8b887cSAdrian Hunter					" WHERE id = " + str(parent_id))
989ae8b887cSAdrian Hunter			if not q2.next():
990ae8b887cSAdrian Hunter				break
991ae8b887cSAdrian Hunter			parent_id = q2.value(0)
992ae8b887cSAdrian Hunter		ids.insert(0, query.value(2))
993ae8b887cSAdrian Hunter		ids.insert(0, query.value(1))
994ae8b887cSAdrian Hunter		return ids
995ae8b887cSAdrian Hunter
99642c303ffSAdrian Hunter# Vertical layout
99742c303ffSAdrian Hunter
99842c303ffSAdrian Hunterclass HBoxLayout(QHBoxLayout):
99942c303ffSAdrian Hunter
100042c303ffSAdrian Hunter	def __init__(self, *children):
100142c303ffSAdrian Hunter		super(HBoxLayout, self).__init__()
100242c303ffSAdrian Hunter
100342c303ffSAdrian Hunter		self.layout().setContentsMargins(0, 0, 0, 0)
100442c303ffSAdrian Hunter		for child in children:
100542c303ffSAdrian Hunter			if child.isWidgetType():
100642c303ffSAdrian Hunter				self.layout().addWidget(child)
100742c303ffSAdrian Hunter			else:
100842c303ffSAdrian Hunter				self.layout().addLayout(child)
100942c303ffSAdrian Hunter
101042c303ffSAdrian Hunter# Horizontal layout
101142c303ffSAdrian Hunter
101242c303ffSAdrian Hunterclass VBoxLayout(QVBoxLayout):
101342c303ffSAdrian Hunter
101442c303ffSAdrian Hunter	def __init__(self, *children):
101542c303ffSAdrian Hunter		super(VBoxLayout, self).__init__()
101642c303ffSAdrian Hunter
101742c303ffSAdrian Hunter		self.layout().setContentsMargins(0, 0, 0, 0)
101842c303ffSAdrian Hunter		for child in children:
101942c303ffSAdrian Hunter			if child.isWidgetType():
102042c303ffSAdrian Hunter				self.layout().addWidget(child)
102142c303ffSAdrian Hunter			else:
102242c303ffSAdrian Hunter				self.layout().addLayout(child)
102342c303ffSAdrian Hunter
102442c303ffSAdrian Hunter# Vertical layout widget
1025ebd70c7dSAdrian Hunter
1026ebd70c7dSAdrian Hunterclass VBox():
1027ebd70c7dSAdrian Hunter
102842c303ffSAdrian Hunter	def __init__(self, *children):
1029ebd70c7dSAdrian Hunter		self.vbox = QWidget()
103042c303ffSAdrian Hunter		self.vbox.setLayout(VBoxLayout(*children))
1031ebd70c7dSAdrian Hunter
1032ebd70c7dSAdrian Hunter	def Widget(self):
1033ebd70c7dSAdrian Hunter		return self.vbox
1034ebd70c7dSAdrian Hunter
1035a731cc4cSAdrian Hunter# Tree window base
10361beb5c7bSAdrian Hunter
1037a731cc4cSAdrian Hunterclass TreeWindowBase(QMdiSubWindow):
10381beb5c7bSAdrian Hunter
1039a731cc4cSAdrian Hunter	def __init__(self, parent=None):
1040a731cc4cSAdrian Hunter		super(TreeWindowBase, self).__init__(parent)
10411beb5c7bSAdrian Hunter
1042a731cc4cSAdrian Hunter		self.model = None
1043a731cc4cSAdrian Hunter		self.find_bar = None
10441beb5c7bSAdrian Hunter
1045be6e7471SAdrian Hunter		self.view = QTreeView()
104696c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
104796c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1048be6e7471SAdrian Hunter
10499bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
10509bc4e4bfSAdrian Hunter
1051ebd70c7dSAdrian Hunter	def DisplayFound(self, ids):
1052ebd70c7dSAdrian Hunter		if not len(ids):
1053ebd70c7dSAdrian Hunter			return False
1054ebd70c7dSAdrian Hunter		parent = QModelIndex()
1055ebd70c7dSAdrian Hunter		for dbid in ids:
1056ebd70c7dSAdrian Hunter			found = False
1057ebd70c7dSAdrian Hunter			n = self.model.rowCount(parent)
1058ebd70c7dSAdrian Hunter			for row in xrange(n):
1059ebd70c7dSAdrian Hunter				child = self.model.index(row, 0, parent)
1060ebd70c7dSAdrian Hunter				if child.internalPointer().dbid == dbid:
1061ebd70c7dSAdrian Hunter					found = True
10623a3cf7c5SAdrian Hunter					self.view.setExpanded(parent, True)
1063ebd70c7dSAdrian Hunter					self.view.setCurrentIndex(child)
1064ebd70c7dSAdrian Hunter					parent = child
1065ebd70c7dSAdrian Hunter					break
1066ebd70c7dSAdrian Hunter			if not found:
1067ebd70c7dSAdrian Hunter				break
1068ebd70c7dSAdrian Hunter		return found
1069ebd70c7dSAdrian Hunter
1070ebd70c7dSAdrian Hunter	def Find(self, value, direction, pattern, context):
1071ebd70c7dSAdrian Hunter		self.view.setFocus()
1072ebd70c7dSAdrian Hunter		self.find_bar.Busy()
1073ebd70c7dSAdrian Hunter		self.model.Find(value, direction, pattern, context, self.FindDone)
1074ebd70c7dSAdrian Hunter
1075ebd70c7dSAdrian Hunter	def FindDone(self, ids):
1076ebd70c7dSAdrian Hunter		found = True
1077ebd70c7dSAdrian Hunter		if not self.DisplayFound(ids):
1078ebd70c7dSAdrian Hunter			found = False
1079ebd70c7dSAdrian Hunter		self.find_bar.Idle()
1080ebd70c7dSAdrian Hunter		if not found:
1081ebd70c7dSAdrian Hunter			self.find_bar.NotFound()
1082ebd70c7dSAdrian Hunter
1083a731cc4cSAdrian Hunter
1084a731cc4cSAdrian Hunter# Context-sensitive call graph window
1085a731cc4cSAdrian Hunter
1086a731cc4cSAdrian Hunterclass CallGraphWindow(TreeWindowBase):
1087a731cc4cSAdrian Hunter
1088a731cc4cSAdrian Hunter	def __init__(self, glb, parent=None):
1089a731cc4cSAdrian Hunter		super(CallGraphWindow, self).__init__(parent)
1090a731cc4cSAdrian Hunter
1091a731cc4cSAdrian Hunter		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
1092a731cc4cSAdrian Hunter
1093a731cc4cSAdrian Hunter		self.view.setModel(self.model)
1094a731cc4cSAdrian Hunter
1095a731cc4cSAdrian Hunter		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1096a731cc4cSAdrian Hunter			self.view.setColumnWidth(c, w)
1097a731cc4cSAdrian Hunter
1098a731cc4cSAdrian Hunter		self.find_bar = FindBar(self, self)
1099a731cc4cSAdrian Hunter
1100a731cc4cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
1101a731cc4cSAdrian Hunter
1102a731cc4cSAdrian Hunter		self.setWidget(self.vbox.Widget())
1103a731cc4cSAdrian Hunter
1104a731cc4cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
1105a731cc4cSAdrian Hunter
1106ae8b887cSAdrian Hunter# Call tree window
1107ae8b887cSAdrian Hunter
1108ae8b887cSAdrian Hunterclass CallTreeWindow(TreeWindowBase):
1109ae8b887cSAdrian Hunter
1110e69d5df7SAdrian Hunter	def __init__(self, glb, parent=None, thread_at_time=None):
1111ae8b887cSAdrian Hunter		super(CallTreeWindow, self).__init__(parent)
1112ae8b887cSAdrian Hunter
1113ae8b887cSAdrian Hunter		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
1114ae8b887cSAdrian Hunter
1115ae8b887cSAdrian Hunter		self.view.setModel(self.model)
1116ae8b887cSAdrian Hunter
1117ae8b887cSAdrian Hunter		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1118ae8b887cSAdrian Hunter			self.view.setColumnWidth(c, w)
1119ae8b887cSAdrian Hunter
1120ae8b887cSAdrian Hunter		self.find_bar = FindBar(self, self)
1121ae8b887cSAdrian Hunter
1122ae8b887cSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget())
1123ae8b887cSAdrian Hunter
1124ae8b887cSAdrian Hunter		self.setWidget(self.vbox.Widget())
1125ae8b887cSAdrian Hunter
1126ae8b887cSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
1127ae8b887cSAdrian Hunter
1128e69d5df7SAdrian Hunter		if thread_at_time:
1129e69d5df7SAdrian Hunter			self.DisplayThreadAtTime(*thread_at_time)
1130e69d5df7SAdrian Hunter
1131e69d5df7SAdrian Hunter	def DisplayThreadAtTime(self, comm_id, thread_id, time):
1132e69d5df7SAdrian Hunter		parent = QModelIndex()
1133e69d5df7SAdrian Hunter		for dbid in (comm_id, thread_id):
1134e69d5df7SAdrian Hunter			found = False
1135e69d5df7SAdrian Hunter			n = self.model.rowCount(parent)
1136e69d5df7SAdrian Hunter			for row in xrange(n):
1137e69d5df7SAdrian Hunter				child = self.model.index(row, 0, parent)
1138e69d5df7SAdrian Hunter				if child.internalPointer().dbid == dbid:
1139e69d5df7SAdrian Hunter					found = True
1140f18d5cf8SAdrian Hunter					self.view.setExpanded(parent, True)
1141e69d5df7SAdrian Hunter					self.view.setCurrentIndex(child)
1142e69d5df7SAdrian Hunter					parent = child
1143e69d5df7SAdrian Hunter					break
1144e69d5df7SAdrian Hunter			if not found:
1145e69d5df7SAdrian Hunter				return
1146e69d5df7SAdrian Hunter		found = False
1147e69d5df7SAdrian Hunter		while True:
1148e69d5df7SAdrian Hunter			n = self.model.rowCount(parent)
1149e69d5df7SAdrian Hunter			if not n:
1150e69d5df7SAdrian Hunter				return
1151e69d5df7SAdrian Hunter			last_child = None
1152e69d5df7SAdrian Hunter			for row in xrange(n):
1153f18d5cf8SAdrian Hunter				self.view.setExpanded(parent, True)
1154e69d5df7SAdrian Hunter				child = self.model.index(row, 0, parent)
1155e69d5df7SAdrian Hunter				child_call_time = child.internalPointer().call_time
1156e69d5df7SAdrian Hunter				if child_call_time < time:
1157e69d5df7SAdrian Hunter					last_child = child
1158e69d5df7SAdrian Hunter				elif child_call_time == time:
1159e69d5df7SAdrian Hunter					self.view.setCurrentIndex(child)
1160e69d5df7SAdrian Hunter					return
1161e69d5df7SAdrian Hunter				elif child_call_time > time:
1162e69d5df7SAdrian Hunter					break
1163e69d5df7SAdrian Hunter			if not last_child:
1164e69d5df7SAdrian Hunter				if not found:
1165e69d5df7SAdrian Hunter					child = self.model.index(0, 0, parent)
1166f18d5cf8SAdrian Hunter					self.view.setExpanded(parent, True)
1167e69d5df7SAdrian Hunter					self.view.setCurrentIndex(child)
1168e69d5df7SAdrian Hunter				return
1169e69d5df7SAdrian Hunter			found = True
1170f18d5cf8SAdrian Hunter			self.view.setExpanded(parent, True)
1171e69d5df7SAdrian Hunter			self.view.setCurrentIndex(last_child)
1172e69d5df7SAdrian Hunter			parent = last_child
1173e69d5df7SAdrian Hunter
1174b3700f21SAdrian Hunter# ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name
1175b3700f21SAdrian Hunter
1176b3700f21SAdrian Hunterdef ExecComm(db, thread_id, time):
1177b3700f21SAdrian Hunter	query = QSqlQuery(db)
1178b3700f21SAdrian Hunter	QueryExec(query, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag"
1179b3700f21SAdrian Hunter				" FROM comm_threads"
1180b3700f21SAdrian Hunter				" INNER JOIN comms ON comms.id = comm_threads.comm_id"
1181b3700f21SAdrian Hunter				" WHERE comm_threads.thread_id = " + str(thread_id) +
1182b3700f21SAdrian Hunter				" ORDER BY comms.c_time, comms.id")
1183b3700f21SAdrian Hunter	first = None
1184b3700f21SAdrian Hunter	last = None
1185b3700f21SAdrian Hunter	while query.next():
1186b3700f21SAdrian Hunter		if first is None:
1187b3700f21SAdrian Hunter			first = query.value(0)
1188b3700f21SAdrian Hunter		if query.value(2) and Decimal(query.value(1)) <= Decimal(time):
1189b3700f21SAdrian Hunter			last = query.value(0)
1190b3700f21SAdrian Hunter	if not(last is None):
1191b3700f21SAdrian Hunter		return last
1192b3700f21SAdrian Hunter	return first
1193b3700f21SAdrian Hunter
1194b3700f21SAdrian Hunter# Container for (x, y) data
1195b3700f21SAdrian Hunter
1196b3700f21SAdrian Hunterclass XY():
1197b3700f21SAdrian Hunter	def __init__(self, x=0, y=0):
1198b3700f21SAdrian Hunter		self.x = x
1199b3700f21SAdrian Hunter		self.y = y
1200b3700f21SAdrian Hunter
1201b3700f21SAdrian Hunter	def __str__(self):
1202b3700f21SAdrian Hunter		return "XY({}, {})".format(str(self.x), str(self.y))
1203b3700f21SAdrian Hunter
1204b3700f21SAdrian Hunter# Container for sub-range data
1205b3700f21SAdrian Hunter
1206b3700f21SAdrian Hunterclass Subrange():
1207b3700f21SAdrian Hunter	def __init__(self, lo=0, hi=0):
1208b3700f21SAdrian Hunter		self.lo = lo
1209b3700f21SAdrian Hunter		self.hi = hi
1210b3700f21SAdrian Hunter
1211b3700f21SAdrian Hunter	def __str__(self):
1212b3700f21SAdrian Hunter		return "Subrange({}, {})".format(str(self.lo), str(self.hi))
1213b3700f21SAdrian Hunter
1214b3700f21SAdrian Hunter# Graph data region base class
1215b3700f21SAdrian Hunter
1216b3700f21SAdrian Hunterclass GraphDataRegion(object):
1217b3700f21SAdrian Hunter
1218b3700f21SAdrian Hunter	def __init__(self, key, title = "", ordinal = ""):
1219b3700f21SAdrian Hunter		self.key = key
1220b3700f21SAdrian Hunter		self.title = title
1221b3700f21SAdrian Hunter		self.ordinal = ordinal
1222b3700f21SAdrian Hunter
1223b3700f21SAdrian Hunter# Function to sort GraphDataRegion
1224b3700f21SAdrian Hunter
1225b3700f21SAdrian Hunterdef GraphDataRegionOrdinal(data_region):
1226b3700f21SAdrian Hunter	return data_region.ordinal
1227b3700f21SAdrian Hunter
1228b3700f21SAdrian Hunter# Attributes for a graph region
1229b3700f21SAdrian Hunter
1230b3700f21SAdrian Hunterclass GraphRegionAttribute():
1231b3700f21SAdrian Hunter
1232b3700f21SAdrian Hunter	def __init__(self, colour):
1233b3700f21SAdrian Hunter		self.colour = colour
1234b3700f21SAdrian Hunter
1235b3700f21SAdrian Hunter# Switch graph data region represents a task
1236b3700f21SAdrian Hunter
1237b3700f21SAdrian Hunterclass SwitchGraphDataRegion(GraphDataRegion):
1238b3700f21SAdrian Hunter
1239b3700f21SAdrian Hunter	def __init__(self, key, exec_comm_id, pid, tid, comm, thread_id, comm_id):
1240b3700f21SAdrian Hunter		super(SwitchGraphDataRegion, self).__init__(key)
1241b3700f21SAdrian Hunter
1242b3700f21SAdrian Hunter		self.title = str(pid) + " / " + str(tid) + " " + comm
1243b3700f21SAdrian Hunter		# Order graph legend within exec comm by pid / tid / time
1244b3700f21SAdrian Hunter		self.ordinal = str(pid).rjust(16) + str(exec_comm_id).rjust(8) + str(tid).rjust(16)
1245b3700f21SAdrian Hunter		self.exec_comm_id = exec_comm_id
1246b3700f21SAdrian Hunter		self.pid = pid
1247b3700f21SAdrian Hunter		self.tid = tid
1248b3700f21SAdrian Hunter		self.comm = comm
1249b3700f21SAdrian Hunter		self.thread_id = thread_id
1250b3700f21SAdrian Hunter		self.comm_id = comm_id
1251b3700f21SAdrian Hunter
1252b3700f21SAdrian Hunter# Graph data point
1253b3700f21SAdrian Hunter
1254b3700f21SAdrian Hunterclass GraphDataPoint():
1255b3700f21SAdrian Hunter
1256b3700f21SAdrian Hunter	def __init__(self, data, index, x, y, altx=None, alty=None, hregion=None, vregion=None):
1257b3700f21SAdrian Hunter		self.data = data
1258b3700f21SAdrian Hunter		self.index = index
1259b3700f21SAdrian Hunter		self.x = x
1260b3700f21SAdrian Hunter		self.y = y
1261b3700f21SAdrian Hunter		self.altx = altx
1262b3700f21SAdrian Hunter		self.alty = alty
1263b3700f21SAdrian Hunter		self.hregion = hregion
1264b3700f21SAdrian Hunter		self.vregion = vregion
1265b3700f21SAdrian Hunter
1266b3700f21SAdrian Hunter# Graph data (single graph) base class
1267b3700f21SAdrian Hunter
1268b3700f21SAdrian Hunterclass GraphData(object):
1269b3700f21SAdrian Hunter
1270b3700f21SAdrian Hunter	def __init__(self, collection, xbase=Decimal(0), ybase=Decimal(0)):
1271b3700f21SAdrian Hunter		self.collection = collection
1272b3700f21SAdrian Hunter		self.points = []
1273b3700f21SAdrian Hunter		self.xbase = xbase
1274b3700f21SAdrian Hunter		self.ybase = ybase
1275b3700f21SAdrian Hunter		self.title = ""
1276b3700f21SAdrian Hunter
1277b3700f21SAdrian Hunter	def AddPoint(self, x, y, altx=None, alty=None, hregion=None, vregion=None):
1278b3700f21SAdrian Hunter		index = len(self.points)
1279b3700f21SAdrian Hunter
1280b3700f21SAdrian Hunter		x = float(Decimal(x) - self.xbase)
1281b3700f21SAdrian Hunter		y = float(Decimal(y) - self.ybase)
1282b3700f21SAdrian Hunter
1283b3700f21SAdrian Hunter		self.points.append(GraphDataPoint(self, index, x, y, altx, alty, hregion, vregion))
1284b3700f21SAdrian Hunter
1285b3700f21SAdrian Hunter	def XToData(self, x):
1286b3700f21SAdrian Hunter		return Decimal(x) + self.xbase
1287b3700f21SAdrian Hunter
1288b3700f21SAdrian Hunter	def YToData(self, y):
1289b3700f21SAdrian Hunter		return Decimal(y) + self.ybase
1290b3700f21SAdrian Hunter
1291b3700f21SAdrian Hunter# Switch graph data (for one CPU)
1292b3700f21SAdrian Hunter
1293b3700f21SAdrian Hunterclass SwitchGraphData(GraphData):
1294b3700f21SAdrian Hunter
1295b3700f21SAdrian Hunter	def __init__(self, db, collection, cpu, xbase):
1296b3700f21SAdrian Hunter		super(SwitchGraphData, self).__init__(collection, xbase)
1297b3700f21SAdrian Hunter
1298b3700f21SAdrian Hunter		self.cpu = cpu
1299b3700f21SAdrian Hunter		self.title = "CPU " + str(cpu)
1300b3700f21SAdrian Hunter		self.SelectSwitches(db)
1301b3700f21SAdrian Hunter
1302b3700f21SAdrian Hunter	def SelectComms(self, db, thread_id, last_comm_id, start_time, end_time):
1303b3700f21SAdrian Hunter		query = QSqlQuery(db)
1304b3700f21SAdrian Hunter		QueryExec(query, "SELECT id, c_time"
1305b3700f21SAdrian Hunter					" FROM comms"
1306b3700f21SAdrian Hunter					" WHERE c_thread_id = " + str(thread_id) +
1307af833988SAdrian Hunter					"   AND exec_flag = " + self.collection.glb.dbref.TRUE +
1308b3700f21SAdrian Hunter					"   AND c_time >= " + str(start_time) +
1309b3700f21SAdrian Hunter					"   AND c_time <= " + str(end_time) +
1310b3700f21SAdrian Hunter					" ORDER BY c_time, id")
1311b3700f21SAdrian Hunter		while query.next():
1312b3700f21SAdrian Hunter			comm_id = query.value(0)
1313b3700f21SAdrian Hunter			if comm_id == last_comm_id:
1314b3700f21SAdrian Hunter				continue
1315b3700f21SAdrian Hunter			time = query.value(1)
1316b3700f21SAdrian Hunter			hregion = self.HRegion(db, thread_id, comm_id, time)
1317b3700f21SAdrian Hunter			self.AddPoint(time, 1000, None, None, hregion)
1318b3700f21SAdrian Hunter
1319b3700f21SAdrian Hunter	def SelectSwitches(self, db):
1320b3700f21SAdrian Hunter		last_time = None
1321b3700f21SAdrian Hunter		last_comm_id = None
1322b3700f21SAdrian Hunter		last_thread_id = None
1323b3700f21SAdrian Hunter		query = QSqlQuery(db)
1324b3700f21SAdrian Hunter		QueryExec(query, "SELECT time, thread_out_id, thread_in_id, comm_out_id, comm_in_id, flags"
1325b3700f21SAdrian Hunter					" FROM context_switches"
1326b3700f21SAdrian Hunter					" WHERE machine_id = " + str(self.collection.machine_id) +
1327b3700f21SAdrian Hunter					"   AND cpu = " + str(self.cpu) +
1328b3700f21SAdrian Hunter					" ORDER BY time, id")
1329b3700f21SAdrian Hunter		while query.next():
1330b3700f21SAdrian Hunter			flags = int(query.value(5))
1331b3700f21SAdrian Hunter			if flags & 1:
1332b3700f21SAdrian Hunter				# Schedule-out: detect and add exec's
1333b3700f21SAdrian Hunter				if last_thread_id == query.value(1) and last_comm_id is not None and last_comm_id != query.value(3):
1334b3700f21SAdrian Hunter					self.SelectComms(db, last_thread_id, last_comm_id, last_time, query.value(0))
1335b3700f21SAdrian Hunter				continue
1336b3700f21SAdrian Hunter			# Schedule-in: add data point
1337b3700f21SAdrian Hunter			if len(self.points) == 0:
1338b3700f21SAdrian Hunter				start_time = self.collection.glb.StartTime(self.collection.machine_id)
1339b3700f21SAdrian Hunter				hregion = self.HRegion(db, query.value(1), query.value(3), start_time)
1340b3700f21SAdrian Hunter				self.AddPoint(start_time, 1000, None, None, hregion)
1341b3700f21SAdrian Hunter			time = query.value(0)
1342b3700f21SAdrian Hunter			comm_id = query.value(4)
1343b3700f21SAdrian Hunter			thread_id = query.value(2)
1344b3700f21SAdrian Hunter			hregion = self.HRegion(db, thread_id, comm_id, time)
1345b3700f21SAdrian Hunter			self.AddPoint(time, 1000, None, None, hregion)
1346b3700f21SAdrian Hunter			last_time = time
1347b3700f21SAdrian Hunter			last_comm_id = comm_id
1348b3700f21SAdrian Hunter			last_thread_id = thread_id
1349b3700f21SAdrian Hunter
1350b3700f21SAdrian Hunter	def NewHRegion(self, db, key, thread_id, comm_id, time):
1351b3700f21SAdrian Hunter		exec_comm_id = ExecComm(db, thread_id, time)
1352b3700f21SAdrian Hunter		query = QSqlQuery(db)
1353b3700f21SAdrian Hunter		QueryExec(query, "SELECT pid, tid FROM threads WHERE id = " + str(thread_id))
1354b3700f21SAdrian Hunter		if query.next():
1355b3700f21SAdrian Hunter			pid = query.value(0)
1356b3700f21SAdrian Hunter			tid = query.value(1)
1357b3700f21SAdrian Hunter		else:
1358b3700f21SAdrian Hunter			pid = -1
1359b3700f21SAdrian Hunter			tid = -1
1360b3700f21SAdrian Hunter		query = QSqlQuery(db)
1361b3700f21SAdrian Hunter		QueryExec(query, "SELECT comm FROM comms WHERE id = " + str(comm_id))
1362b3700f21SAdrian Hunter		if query.next():
1363b3700f21SAdrian Hunter			comm = query.value(0)
1364b3700f21SAdrian Hunter		else:
1365b3700f21SAdrian Hunter			comm = ""
1366b3700f21SAdrian Hunter		return SwitchGraphDataRegion(key, exec_comm_id, pid, tid, comm, thread_id, comm_id)
1367b3700f21SAdrian Hunter
1368b3700f21SAdrian Hunter	def HRegion(self, db, thread_id, comm_id, time):
1369b3700f21SAdrian Hunter		key = str(thread_id) + ":" + str(comm_id)
1370b3700f21SAdrian Hunter		hregion = self.collection.LookupHRegion(key)
1371b3700f21SAdrian Hunter		if hregion is None:
1372b3700f21SAdrian Hunter			hregion = self.NewHRegion(db, key, thread_id, comm_id, time)
1373b3700f21SAdrian Hunter			self.collection.AddHRegion(key, hregion)
1374b3700f21SAdrian Hunter		return hregion
1375b3700f21SAdrian Hunter
1376b3700f21SAdrian Hunter# Graph data collection (multiple related graphs) base class
1377b3700f21SAdrian Hunter
1378b3700f21SAdrian Hunterclass GraphDataCollection(object):
1379b3700f21SAdrian Hunter
1380b3700f21SAdrian Hunter	def __init__(self, glb):
1381b3700f21SAdrian Hunter		self.glb = glb
1382b3700f21SAdrian Hunter		self.data = []
1383b3700f21SAdrian Hunter		self.hregions = {}
1384b3700f21SAdrian Hunter		self.xrangelo = None
1385b3700f21SAdrian Hunter		self.xrangehi = None
1386b3700f21SAdrian Hunter		self.yrangelo = None
1387b3700f21SAdrian Hunter		self.yrangehi = None
1388b3700f21SAdrian Hunter		self.dp = XY(0, 0)
1389b3700f21SAdrian Hunter
1390b3700f21SAdrian Hunter	def AddGraphData(self, data):
1391b3700f21SAdrian Hunter		self.data.append(data)
1392b3700f21SAdrian Hunter
1393b3700f21SAdrian Hunter	def LookupHRegion(self, key):
1394b3700f21SAdrian Hunter		if key in self.hregions:
1395b3700f21SAdrian Hunter			return self.hregions[key]
1396b3700f21SAdrian Hunter		return None
1397b3700f21SAdrian Hunter
1398b3700f21SAdrian Hunter	def AddHRegion(self, key, hregion):
1399b3700f21SAdrian Hunter		self.hregions[key] = hregion
1400b3700f21SAdrian Hunter
1401b3700f21SAdrian Hunter# Switch graph data collection (SwitchGraphData for each CPU)
1402b3700f21SAdrian Hunter
1403b3700f21SAdrian Hunterclass SwitchGraphDataCollection(GraphDataCollection):
1404b3700f21SAdrian Hunter
1405b3700f21SAdrian Hunter	def __init__(self, glb, db, machine_id):
1406b3700f21SAdrian Hunter		super(SwitchGraphDataCollection, self).__init__(glb)
1407b3700f21SAdrian Hunter
1408b3700f21SAdrian Hunter		self.machine_id = machine_id
1409b3700f21SAdrian Hunter		self.cpus = self.SelectCPUs(db)
1410b3700f21SAdrian Hunter
1411b3700f21SAdrian Hunter		self.xrangelo = glb.StartTime(machine_id)
1412b3700f21SAdrian Hunter		self.xrangehi = glb.FinishTime(machine_id)
1413b3700f21SAdrian Hunter
1414b3700f21SAdrian Hunter		self.yrangelo = Decimal(0)
1415b3700f21SAdrian Hunter		self.yrangehi = Decimal(1000)
1416b3700f21SAdrian Hunter
1417b3700f21SAdrian Hunter		for cpu in self.cpus:
1418b3700f21SAdrian Hunter			self.AddGraphData(SwitchGraphData(db, self, cpu, self.xrangelo))
1419b3700f21SAdrian Hunter
1420b3700f21SAdrian Hunter	def SelectCPUs(self, db):
1421b3700f21SAdrian Hunter		cpus = []
1422b3700f21SAdrian Hunter		query = QSqlQuery(db)
1423b3700f21SAdrian Hunter		QueryExec(query, "SELECT DISTINCT cpu"
1424b3700f21SAdrian Hunter					" FROM context_switches"
1425b3700f21SAdrian Hunter					" WHERE machine_id = " + str(self.machine_id))
1426b3700f21SAdrian Hunter		while query.next():
1427b3700f21SAdrian Hunter			cpus.append(int(query.value(0)))
1428b3700f21SAdrian Hunter		return sorted(cpus)
1429b3700f21SAdrian Hunter
1430b3700f21SAdrian Hunter# Switch graph data graphics item displays the graphed data
1431b3700f21SAdrian Hunter
1432b3700f21SAdrian Hunterclass SwitchGraphDataGraphicsItem(QGraphicsItem):
1433b3700f21SAdrian Hunter
1434b3700f21SAdrian Hunter	def __init__(self, data, graph_width, graph_height, attrs, event_handler, parent=None):
1435b3700f21SAdrian Hunter		super(SwitchGraphDataGraphicsItem, self).__init__(parent)
1436b3700f21SAdrian Hunter
1437b3700f21SAdrian Hunter		self.data = data
1438b3700f21SAdrian Hunter		self.graph_width = graph_width
1439b3700f21SAdrian Hunter		self.graph_height = graph_height
1440b3700f21SAdrian Hunter		self.attrs = attrs
1441b3700f21SAdrian Hunter		self.event_handler = event_handler
1442b3700f21SAdrian Hunter		self.setAcceptHoverEvents(True)
1443b3700f21SAdrian Hunter
1444b3700f21SAdrian Hunter	def boundingRect(self):
1445b3700f21SAdrian Hunter		return QRectF(0, 0, self.graph_width, self.graph_height)
1446b3700f21SAdrian Hunter
1447b3700f21SAdrian Hunter	def PaintPoint(self, painter, last, x):
1448b3700f21SAdrian Hunter		if not(last is None or last.hregion.pid == 0 or x < self.attrs.subrange.x.lo):
1449b3700f21SAdrian Hunter			if last.x < self.attrs.subrange.x.lo:
1450b3700f21SAdrian Hunter				x0 = self.attrs.subrange.x.lo
1451b3700f21SAdrian Hunter			else:
1452b3700f21SAdrian Hunter				x0 = last.x
1453b3700f21SAdrian Hunter			if x > self.attrs.subrange.x.hi:
1454b3700f21SAdrian Hunter				x1 = self.attrs.subrange.x.hi
1455b3700f21SAdrian Hunter			else:
1456b3700f21SAdrian Hunter				x1 = x - 1
1457b3700f21SAdrian Hunter			x0 = self.attrs.XToPixel(x0)
1458b3700f21SAdrian Hunter			x1 = self.attrs.XToPixel(x1)
1459b3700f21SAdrian Hunter
1460b3700f21SAdrian Hunter			y0 = self.attrs.YToPixel(last.y)
1461b3700f21SAdrian Hunter
1462b3700f21SAdrian Hunter			colour = self.attrs.region_attributes[last.hregion.key].colour
1463b3700f21SAdrian Hunter
1464b3700f21SAdrian Hunter			width = x1 - x0 + 1
1465b3700f21SAdrian Hunter			if width < 2:
1466b3700f21SAdrian Hunter				painter.setPen(colour)
1467b3700f21SAdrian Hunter				painter.drawLine(x0, self.graph_height - y0, x0, self.graph_height)
1468b3700f21SAdrian Hunter			else:
1469b3700f21SAdrian Hunter				painter.fillRect(x0, self.graph_height - y0, width, self.graph_height - 1, colour)
1470b3700f21SAdrian Hunter
1471b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1472b3700f21SAdrian Hunter		last = None
1473b3700f21SAdrian Hunter		for point in self.data.points:
1474b3700f21SAdrian Hunter			self.PaintPoint(painter, last, point.x)
1475b3700f21SAdrian Hunter			if point.x > self.attrs.subrange.x.hi:
1476b3700f21SAdrian Hunter				break;
1477b3700f21SAdrian Hunter			last = point
1478b3700f21SAdrian Hunter		self.PaintPoint(painter, last, self.attrs.subrange.x.hi + 1)
1479b3700f21SAdrian Hunter
1480b3700f21SAdrian Hunter	def BinarySearchPoint(self, target):
1481b3700f21SAdrian Hunter		lower_pos = 0
1482b3700f21SAdrian Hunter		higher_pos = len(self.data.points)
1483b3700f21SAdrian Hunter		while True:
1484b3700f21SAdrian Hunter			pos = int((lower_pos + higher_pos) / 2)
1485b3700f21SAdrian Hunter			val = self.data.points[pos].x
1486b3700f21SAdrian Hunter			if target >= val:
1487b3700f21SAdrian Hunter				lower_pos = pos
1488b3700f21SAdrian Hunter			else:
1489b3700f21SAdrian Hunter				higher_pos = pos
1490b3700f21SAdrian Hunter			if higher_pos <= lower_pos + 1:
1491b3700f21SAdrian Hunter				return lower_pos
1492b3700f21SAdrian Hunter
1493b3700f21SAdrian Hunter	def XPixelToData(self, x):
1494b3700f21SAdrian Hunter		x = self.attrs.PixelToX(x)
1495b3700f21SAdrian Hunter		if x < self.data.points[0].x:
1496b3700f21SAdrian Hunter			x = 0
1497b3700f21SAdrian Hunter			pos = 0
1498b3700f21SAdrian Hunter			low = True
1499b3700f21SAdrian Hunter		else:
1500b3700f21SAdrian Hunter			pos = self.BinarySearchPoint(x)
1501b3700f21SAdrian Hunter			low = False
1502b3700f21SAdrian Hunter		return (low, pos, self.data.XToData(x))
1503b3700f21SAdrian Hunter
1504b3700f21SAdrian Hunter	def EventToData(self, event):
1505b3700f21SAdrian Hunter		no_data = (None,) * 4
1506b3700f21SAdrian Hunter		if len(self.data.points) < 1:
1507b3700f21SAdrian Hunter			return no_data
1508b3700f21SAdrian Hunter		x = event.pos().x()
1509b3700f21SAdrian Hunter		if x < 0:
1510b3700f21SAdrian Hunter			return no_data
1511b3700f21SAdrian Hunter		low0, pos0, time_from = self.XPixelToData(x)
1512b3700f21SAdrian Hunter		low1, pos1, time_to = self.XPixelToData(x + 1)
1513b3700f21SAdrian Hunter		hregions = set()
1514b3700f21SAdrian Hunter		hregion_times = []
1515b3700f21SAdrian Hunter		if not low1:
1516b3700f21SAdrian Hunter			for i in xrange(pos0, pos1 + 1):
1517b3700f21SAdrian Hunter				hregion = self.data.points[i].hregion
1518b3700f21SAdrian Hunter				hregions.add(hregion)
1519b3700f21SAdrian Hunter				if i == pos0:
1520b3700f21SAdrian Hunter					time = time_from
1521b3700f21SAdrian Hunter				else:
1522b3700f21SAdrian Hunter					time = self.data.XToData(self.data.points[i].x)
1523b3700f21SAdrian Hunter				hregion_times.append((hregion, time))
1524b3700f21SAdrian Hunter		return (time_from, time_to, hregions, hregion_times)
1525b3700f21SAdrian Hunter
1526b3700f21SAdrian Hunter	def hoverMoveEvent(self, event):
1527b3700f21SAdrian Hunter		time_from, time_to, hregions, hregion_times = self.EventToData(event)
1528b3700f21SAdrian Hunter		if time_from is not None:
1529b3700f21SAdrian Hunter			self.event_handler.PointEvent(self.data.cpu, time_from, time_to, hregions)
1530b3700f21SAdrian Hunter
1531b3700f21SAdrian Hunter	def hoverLeaveEvent(self, event):
1532b3700f21SAdrian Hunter		self.event_handler.NoPointEvent()
1533b3700f21SAdrian Hunter
1534b3700f21SAdrian Hunter	def mousePressEvent(self, event):
1535b3700f21SAdrian Hunter		if event.button() != Qt.RightButton:
1536b3700f21SAdrian Hunter			super(SwitchGraphDataGraphicsItem, self).mousePressEvent(event)
1537b3700f21SAdrian Hunter			return
1538b3700f21SAdrian Hunter		time_from, time_to, hregions, hregion_times = self.EventToData(event)
1539b3700f21SAdrian Hunter		if hregion_times:
1540b3700f21SAdrian Hunter			self.event_handler.RightClickEvent(self.data.cpu, hregion_times, event.screenPos())
1541b3700f21SAdrian Hunter
1542b3700f21SAdrian Hunter# X-axis graphics item
1543b3700f21SAdrian Hunter
1544b3700f21SAdrian Hunterclass XAxisGraphicsItem(QGraphicsItem):
1545b3700f21SAdrian Hunter
1546b3700f21SAdrian Hunter	def __init__(self, width, parent=None):
1547b3700f21SAdrian Hunter		super(XAxisGraphicsItem, self).__init__(parent)
1548b3700f21SAdrian Hunter
1549b3700f21SAdrian Hunter		self.width = width
1550b3700f21SAdrian Hunter		self.max_mark_sz = 4
1551b3700f21SAdrian Hunter		self.height = self.max_mark_sz + 1
1552b3700f21SAdrian Hunter
1553b3700f21SAdrian Hunter	def boundingRect(self):
1554b3700f21SAdrian Hunter		return QRectF(0, 0, self.width, self.height)
1555b3700f21SAdrian Hunter
1556b3700f21SAdrian Hunter	def Step(self):
1557b3700f21SAdrian Hunter		attrs = self.parentItem().attrs
1558b3700f21SAdrian Hunter		subrange = attrs.subrange.x
1559b3700f21SAdrian Hunter		t = subrange.hi - subrange.lo
1560b3700f21SAdrian Hunter		s = (3.0 * t) / self.width
1561b3700f21SAdrian Hunter		n = 1.0
1562b3700f21SAdrian Hunter		while s > n:
1563b3700f21SAdrian Hunter			n = n * 10.0
1564b3700f21SAdrian Hunter		return n
1565b3700f21SAdrian Hunter
1566b3700f21SAdrian Hunter	def PaintMarks(self, painter, at_y, lo, hi, step, i):
1567b3700f21SAdrian Hunter		attrs = self.parentItem().attrs
1568b3700f21SAdrian Hunter		x = lo
1569b3700f21SAdrian Hunter		while x <= hi:
1570b3700f21SAdrian Hunter			xp = attrs.XToPixel(x)
1571b3700f21SAdrian Hunter			if i % 10:
1572b3700f21SAdrian Hunter				if i % 5:
1573b3700f21SAdrian Hunter					sz = 1
1574b3700f21SAdrian Hunter				else:
1575b3700f21SAdrian Hunter					sz = 2
1576b3700f21SAdrian Hunter			else:
1577b3700f21SAdrian Hunter				sz = self.max_mark_sz
1578b3700f21SAdrian Hunter				i = 0
1579b3700f21SAdrian Hunter			painter.drawLine(xp, at_y, xp, at_y + sz)
1580b3700f21SAdrian Hunter			x += step
1581b3700f21SAdrian Hunter			i += 1
1582b3700f21SAdrian Hunter
1583b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1584b3700f21SAdrian Hunter		# Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1
1585b3700f21SAdrian Hunter		painter.drawLine(0, 0, self.width - 1, 0)
1586b3700f21SAdrian Hunter		n = self.Step()
1587b3700f21SAdrian Hunter		attrs = self.parentItem().attrs
1588b3700f21SAdrian Hunter		subrange = attrs.subrange.x
1589b3700f21SAdrian Hunter		if subrange.lo:
1590b3700f21SAdrian Hunter			x_offset = n - (subrange.lo % n)
1591b3700f21SAdrian Hunter		else:
1592b3700f21SAdrian Hunter			x_offset = 0.0
1593b3700f21SAdrian Hunter		x = subrange.lo + x_offset
1594b3700f21SAdrian Hunter		i = (x / n) % 10
1595b3700f21SAdrian Hunter		self.PaintMarks(painter, 0, x, subrange.hi, n, i)
1596b3700f21SAdrian Hunter
1597b3700f21SAdrian Hunter	def ScaleDimensions(self):
1598b3700f21SAdrian Hunter		n = self.Step()
1599b3700f21SAdrian Hunter		attrs = self.parentItem().attrs
1600b3700f21SAdrian Hunter		lo = attrs.subrange.x.lo
1601b3700f21SAdrian Hunter		hi = (n * 10.0) + lo
1602b3700f21SAdrian Hunter		width = attrs.XToPixel(hi)
1603b3700f21SAdrian Hunter		if width > 500:
1604b3700f21SAdrian Hunter			width = 0
1605b3700f21SAdrian Hunter		return (n, lo, hi, width)
1606b3700f21SAdrian Hunter
1607b3700f21SAdrian Hunter	def PaintScale(self, painter, at_x, at_y):
1608b3700f21SAdrian Hunter		n, lo, hi, width = self.ScaleDimensions()
1609b3700f21SAdrian Hunter		if not width:
1610b3700f21SAdrian Hunter			return
1611b3700f21SAdrian Hunter		painter.drawLine(at_x, at_y, at_x + width, at_y)
1612b3700f21SAdrian Hunter		self.PaintMarks(painter, at_y, lo, hi, n, 0)
1613b3700f21SAdrian Hunter
1614b3700f21SAdrian Hunter	def ScaleWidth(self):
1615b3700f21SAdrian Hunter		n, lo, hi, width = self.ScaleDimensions()
1616b3700f21SAdrian Hunter		return width
1617b3700f21SAdrian Hunter
1618b3700f21SAdrian Hunter	def ScaleHeight(self):
1619b3700f21SAdrian Hunter		return self.height
1620b3700f21SAdrian Hunter
1621b3700f21SAdrian Hunter	def ScaleUnit(self):
1622b3700f21SAdrian Hunter		return self.Step() * 10
1623b3700f21SAdrian Hunter
1624b3700f21SAdrian Hunter# Scale graphics item base class
1625b3700f21SAdrian Hunter
1626b3700f21SAdrian Hunterclass ScaleGraphicsItem(QGraphicsItem):
1627b3700f21SAdrian Hunter
1628b3700f21SAdrian Hunter	def __init__(self, axis, parent=None):
1629b3700f21SAdrian Hunter		super(ScaleGraphicsItem, self).__init__(parent)
1630b3700f21SAdrian Hunter		self.axis = axis
1631b3700f21SAdrian Hunter
1632b3700f21SAdrian Hunter	def boundingRect(self):
1633b3700f21SAdrian Hunter		scale_width = self.axis.ScaleWidth()
1634b3700f21SAdrian Hunter		if not scale_width:
1635b3700f21SAdrian Hunter			return QRectF()
1636b3700f21SAdrian Hunter		return QRectF(0, 0, self.axis.ScaleWidth() + 100, self.axis.ScaleHeight())
1637b3700f21SAdrian Hunter
1638b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1639b3700f21SAdrian Hunter		scale_width = self.axis.ScaleWidth()
1640b3700f21SAdrian Hunter		if not scale_width:
1641b3700f21SAdrian Hunter			return
1642b3700f21SAdrian Hunter		self.axis.PaintScale(painter, 0, 5)
1643b3700f21SAdrian Hunter		x = scale_width + 4
1644b3700f21SAdrian Hunter		painter.drawText(QPointF(x, 10), self.Text())
1645b3700f21SAdrian Hunter
1646b3700f21SAdrian Hunter	def Unit(self):
1647b3700f21SAdrian Hunter		return self.axis.ScaleUnit()
1648b3700f21SAdrian Hunter
1649b3700f21SAdrian Hunter	def Text(self):
1650b3700f21SAdrian Hunter		return ""
1651b3700f21SAdrian Hunter
1652b3700f21SAdrian Hunter# Switch graph scale graphics item
1653b3700f21SAdrian Hunter
1654b3700f21SAdrian Hunterclass SwitchScaleGraphicsItem(ScaleGraphicsItem):
1655b3700f21SAdrian Hunter
1656b3700f21SAdrian Hunter	def __init__(self, axis, parent=None):
1657b3700f21SAdrian Hunter		super(SwitchScaleGraphicsItem, self).__init__(axis, parent)
1658b3700f21SAdrian Hunter
1659b3700f21SAdrian Hunter	def Text(self):
1660b3700f21SAdrian Hunter		unit = self.Unit()
1661b3700f21SAdrian Hunter		if unit >= 1000000000:
1662b3700f21SAdrian Hunter			unit = int(unit / 1000000000)
1663b3700f21SAdrian Hunter			us = "s"
1664b3700f21SAdrian Hunter		elif unit >= 1000000:
1665b3700f21SAdrian Hunter			unit = int(unit / 1000000)
1666b3700f21SAdrian Hunter			us = "ms"
1667b3700f21SAdrian Hunter		elif unit >= 1000:
1668b3700f21SAdrian Hunter			unit = int(unit / 1000)
1669b3700f21SAdrian Hunter			us = "us"
1670b3700f21SAdrian Hunter		else:
1671b3700f21SAdrian Hunter			unit = int(unit)
1672b3700f21SAdrian Hunter			us = "ns"
1673b3700f21SAdrian Hunter		return " = " + str(unit) + " " + us
1674b3700f21SAdrian Hunter
1675b3700f21SAdrian Hunter# Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data
1676b3700f21SAdrian Hunter
1677b3700f21SAdrian Hunterclass SwitchGraphGraphicsItem(QGraphicsItem):
1678b3700f21SAdrian Hunter
1679b3700f21SAdrian Hunter	def __init__(self, collection, data, attrs, event_handler, first, parent=None):
1680b3700f21SAdrian Hunter		super(SwitchGraphGraphicsItem, self).__init__(parent)
1681b3700f21SAdrian Hunter		self.collection = collection
1682b3700f21SAdrian Hunter		self.data = data
1683b3700f21SAdrian Hunter		self.attrs = attrs
1684b3700f21SAdrian Hunter		self.event_handler = event_handler
1685b3700f21SAdrian Hunter
1686b3700f21SAdrian Hunter		margin = 20
1687b3700f21SAdrian Hunter		title_width = 50
1688b3700f21SAdrian Hunter
1689b3700f21SAdrian Hunter		self.title_graphics = QGraphicsSimpleTextItem(data.title, self)
1690b3700f21SAdrian Hunter
1691b3700f21SAdrian Hunter		self.title_graphics.setPos(margin, margin)
1692b3700f21SAdrian Hunter		graph_width = attrs.XToPixel(attrs.subrange.x.hi) + 1
1693b3700f21SAdrian Hunter		graph_height = attrs.YToPixel(attrs.subrange.y.hi) + 1
1694b3700f21SAdrian Hunter
1695b3700f21SAdrian Hunter		self.graph_origin_x = margin + title_width + margin
1696b3700f21SAdrian Hunter		self.graph_origin_y = graph_height + margin
1697b3700f21SAdrian Hunter
1698b3700f21SAdrian Hunter		x_axis_size = 1
1699b3700f21SAdrian Hunter		y_axis_size = 1
1700b3700f21SAdrian Hunter		self.yline = QGraphicsLineItem(0, 0, 0, graph_height, self)
1701b3700f21SAdrian Hunter
1702b3700f21SAdrian Hunter		self.x_axis = XAxisGraphicsItem(graph_width, self)
1703b3700f21SAdrian Hunter		self.x_axis.setPos(self.graph_origin_x, self.graph_origin_y + 1)
1704b3700f21SAdrian Hunter
1705b3700f21SAdrian Hunter		if first:
1706b3700f21SAdrian Hunter			self.scale_item = SwitchScaleGraphicsItem(self.x_axis, self)
1707b3700f21SAdrian Hunter			self.scale_item.setPos(self.graph_origin_x, self.graph_origin_y + 10)
1708b3700f21SAdrian Hunter
1709b3700f21SAdrian Hunter		self.yline.setPos(self.graph_origin_x - y_axis_size, self.graph_origin_y - graph_height)
1710b3700f21SAdrian Hunter
1711b3700f21SAdrian Hunter		self.axis_point = QGraphicsLineItem(0, 0, 0, 0, self)
1712b3700f21SAdrian Hunter		self.axis_point.setPos(self.graph_origin_x - 1, self.graph_origin_y +1)
1713b3700f21SAdrian Hunter
1714b3700f21SAdrian Hunter		self.width = self.graph_origin_x + graph_width + margin
1715b3700f21SAdrian Hunter		self.height = self.graph_origin_y + margin
1716b3700f21SAdrian Hunter
1717b3700f21SAdrian Hunter		self.graph = SwitchGraphDataGraphicsItem(data, graph_width, graph_height, attrs, event_handler, self)
1718b3700f21SAdrian Hunter		self.graph.setPos(self.graph_origin_x, self.graph_origin_y - graph_height)
1719b3700f21SAdrian Hunter
1720b3700f21SAdrian Hunter		if parent and 'EnableRubberBand' in dir(parent):
1721b3700f21SAdrian Hunter			parent.EnableRubberBand(self.graph_origin_x, self.graph_origin_x + graph_width - 1, self)
1722b3700f21SAdrian Hunter
1723b3700f21SAdrian Hunter	def boundingRect(self):
1724b3700f21SAdrian Hunter		return QRectF(0, 0, self.width, self.height)
1725b3700f21SAdrian Hunter
1726b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1727b3700f21SAdrian Hunter		pass
1728b3700f21SAdrian Hunter
1729b3700f21SAdrian Hunter	def RBXToPixel(self, x):
1730b3700f21SAdrian Hunter		return self.attrs.PixelToX(x - self.graph_origin_x)
1731b3700f21SAdrian Hunter
1732b3700f21SAdrian Hunter	def RBXRangeToPixel(self, x0, x1):
1733b3700f21SAdrian Hunter		return (self.RBXToPixel(x0), self.RBXToPixel(x1 + 1))
1734b3700f21SAdrian Hunter
1735b3700f21SAdrian Hunter	def RBPixelToTime(self, x):
1736b3700f21SAdrian Hunter		if x < self.data.points[0].x:
1737b3700f21SAdrian Hunter			return self.data.XToData(0)
1738b3700f21SAdrian Hunter		return self.data.XToData(x)
1739b3700f21SAdrian Hunter
1740b3700f21SAdrian Hunter	def RBEventTimes(self, x0, x1):
1741b3700f21SAdrian Hunter		x0, x1 = self.RBXRangeToPixel(x0, x1)
1742b3700f21SAdrian Hunter		time_from = self.RBPixelToTime(x0)
1743b3700f21SAdrian Hunter		time_to = self.RBPixelToTime(x1)
1744b3700f21SAdrian Hunter		return (time_from, time_to)
1745b3700f21SAdrian Hunter
1746b3700f21SAdrian Hunter	def RBEvent(self, x0, x1):
1747b3700f21SAdrian Hunter		time_from, time_to = self.RBEventTimes(x0, x1)
1748b3700f21SAdrian Hunter		self.event_handler.RangeEvent(time_from, time_to)
1749b3700f21SAdrian Hunter
1750b3700f21SAdrian Hunter	def RBMoveEvent(self, x0, x1):
1751b3700f21SAdrian Hunter		if x1 < x0:
1752b3700f21SAdrian Hunter			x0, x1 = x1, x0
1753b3700f21SAdrian Hunter		self.RBEvent(x0, x1)
1754b3700f21SAdrian Hunter
1755b3700f21SAdrian Hunter	def RBReleaseEvent(self, x0, x1, selection_state):
1756b3700f21SAdrian Hunter		if x1 < x0:
1757b3700f21SAdrian Hunter			x0, x1 = x1, x0
1758b3700f21SAdrian Hunter		x0, x1 = self.RBXRangeToPixel(x0, x1)
1759b3700f21SAdrian Hunter		self.event_handler.SelectEvent(x0, x1, selection_state)
1760b3700f21SAdrian Hunter
1761b3700f21SAdrian Hunter# Graphics item to draw a vertical bracket (used to highlight "forward" sub-range)
1762b3700f21SAdrian Hunter
1763b3700f21SAdrian Hunterclass VerticalBracketGraphicsItem(QGraphicsItem):
1764b3700f21SAdrian Hunter
1765b3700f21SAdrian Hunter	def __init__(self, parent=None):
1766b3700f21SAdrian Hunter		super(VerticalBracketGraphicsItem, self).__init__(parent)
1767b3700f21SAdrian Hunter
1768b3700f21SAdrian Hunter		self.width = 0
1769b3700f21SAdrian Hunter		self.height = 0
1770b3700f21SAdrian Hunter		self.hide()
1771b3700f21SAdrian Hunter
1772b3700f21SAdrian Hunter	def SetSize(self, width, height):
1773b3700f21SAdrian Hunter		self.width = width + 1
1774b3700f21SAdrian Hunter		self.height = height + 1
1775b3700f21SAdrian Hunter
1776b3700f21SAdrian Hunter	def boundingRect(self):
1777b3700f21SAdrian Hunter		return QRectF(0, 0, self.width, self.height)
1778b3700f21SAdrian Hunter
1779b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1780b3700f21SAdrian Hunter		colour = QColor(255, 255, 0, 32)
1781b3700f21SAdrian Hunter		painter.fillRect(0, 0, self.width, self.height, colour)
1782b3700f21SAdrian Hunter		x1 = self.width - 1
1783b3700f21SAdrian Hunter		y1 = self.height - 1
1784b3700f21SAdrian Hunter		painter.drawLine(0, 0, x1, 0)
1785b3700f21SAdrian Hunter		painter.drawLine(0, 0, 0, 3)
1786b3700f21SAdrian Hunter		painter.drawLine(x1, 0, x1, 3)
1787b3700f21SAdrian Hunter		painter.drawLine(0, y1, x1, y1)
1788b3700f21SAdrian Hunter		painter.drawLine(0, y1, 0, y1 - 3)
1789b3700f21SAdrian Hunter		painter.drawLine(x1, y1, x1, y1 - 3)
1790b3700f21SAdrian Hunter
1791b3700f21SAdrian Hunter# Graphics item to contain graphs arranged vertically
1792b3700f21SAdrian Hunter
1793b3700f21SAdrian Hunterclass VertcalGraphSetGraphicsItem(QGraphicsItem):
1794b3700f21SAdrian Hunter
1795b3700f21SAdrian Hunter	def __init__(self, collection, attrs, event_handler, child_class, parent=None):
1796b3700f21SAdrian Hunter		super(VertcalGraphSetGraphicsItem, self).__init__(parent)
1797b3700f21SAdrian Hunter
1798b3700f21SAdrian Hunter		self.collection = collection
1799b3700f21SAdrian Hunter
1800b3700f21SAdrian Hunter		self.top = 10
1801b3700f21SAdrian Hunter
1802b3700f21SAdrian Hunter		self.width = 0
1803b3700f21SAdrian Hunter		self.height = self.top
1804b3700f21SAdrian Hunter
1805b3700f21SAdrian Hunter		self.rubber_band = None
1806b3700f21SAdrian Hunter		self.rb_enabled = False
1807b3700f21SAdrian Hunter
1808b3700f21SAdrian Hunter		first = True
1809b3700f21SAdrian Hunter		for data in collection.data:
1810b3700f21SAdrian Hunter			child = child_class(collection, data, attrs, event_handler, first, self)
1811b3700f21SAdrian Hunter			child.setPos(0, self.height + 1)
1812b3700f21SAdrian Hunter			rect = child.boundingRect()
1813b3700f21SAdrian Hunter			if rect.right() > self.width:
1814b3700f21SAdrian Hunter				self.width = rect.right()
1815b3700f21SAdrian Hunter			self.height = self.height + rect.bottom() + 1
1816b3700f21SAdrian Hunter			first = False
1817b3700f21SAdrian Hunter
1818b3700f21SAdrian Hunter		self.bracket = VerticalBracketGraphicsItem(self)
1819b3700f21SAdrian Hunter
1820b3700f21SAdrian Hunter	def EnableRubberBand(self, xlo, xhi, rb_event_handler):
1821b3700f21SAdrian Hunter		if self.rb_enabled:
1822b3700f21SAdrian Hunter			return
1823b3700f21SAdrian Hunter		self.rb_enabled = True
1824b3700f21SAdrian Hunter		self.rb_in_view = False
1825b3700f21SAdrian Hunter		self.setAcceptedMouseButtons(Qt.LeftButton)
1826b3700f21SAdrian Hunter		self.rb_xlo = xlo
1827b3700f21SAdrian Hunter		self.rb_xhi = xhi
1828b3700f21SAdrian Hunter		self.rb_event_handler = rb_event_handler
1829b3700f21SAdrian Hunter		self.mousePressEvent = self.MousePressEvent
1830b3700f21SAdrian Hunter		self.mouseMoveEvent = self.MouseMoveEvent
1831b3700f21SAdrian Hunter		self.mouseReleaseEvent = self.MouseReleaseEvent
1832b3700f21SAdrian Hunter
1833b3700f21SAdrian Hunter	def boundingRect(self):
1834b3700f21SAdrian Hunter		return QRectF(0, 0, self.width, self.height)
1835b3700f21SAdrian Hunter
1836b3700f21SAdrian Hunter	def paint(self, painter, option, widget):
1837b3700f21SAdrian Hunter		pass
1838b3700f21SAdrian Hunter
1839b3700f21SAdrian Hunter	def RubberBandParent(self):
1840b3700f21SAdrian Hunter		scene = self.scene()
1841b3700f21SAdrian Hunter		view = scene.views()[0]
1842b3700f21SAdrian Hunter		viewport = view.viewport()
1843b3700f21SAdrian Hunter		return viewport
1844b3700f21SAdrian Hunter
1845b3700f21SAdrian Hunter	def RubberBandSetGeometry(self, rect):
1846b3700f21SAdrian Hunter		scene_rectf = self.mapRectToScene(QRectF(rect))
1847b3700f21SAdrian Hunter		scene = self.scene()
1848b3700f21SAdrian Hunter		view = scene.views()[0]
1849b3700f21SAdrian Hunter		poly = view.mapFromScene(scene_rectf)
1850b3700f21SAdrian Hunter		self.rubber_band.setGeometry(poly.boundingRect())
1851b3700f21SAdrian Hunter
1852b3700f21SAdrian Hunter	def SetSelection(self, selection_state):
1853b3700f21SAdrian Hunter		if self.rubber_band:
1854b3700f21SAdrian Hunter			if selection_state:
1855b3700f21SAdrian Hunter				self.RubberBandSetGeometry(selection_state)
1856b3700f21SAdrian Hunter				self.rubber_band.show()
1857b3700f21SAdrian Hunter			else:
1858b3700f21SAdrian Hunter				self.rubber_band.hide()
1859b3700f21SAdrian Hunter
1860b3700f21SAdrian Hunter	def SetBracket(self, rect):
1861b3700f21SAdrian Hunter		if rect:
1862b3700f21SAdrian Hunter			x, y, width, height = rect.x(), rect.y(), rect.width(), rect.height()
1863b3700f21SAdrian Hunter			self.bracket.setPos(x, y)
1864b3700f21SAdrian Hunter			self.bracket.SetSize(width, height)
1865b3700f21SAdrian Hunter			self.bracket.show()
1866b3700f21SAdrian Hunter		else:
1867b3700f21SAdrian Hunter			self.bracket.hide()
1868b3700f21SAdrian Hunter
1869b3700f21SAdrian Hunter	def RubberBandX(self, event):
1870b3700f21SAdrian Hunter		x = event.pos().toPoint().x()
1871b3700f21SAdrian Hunter		if x < self.rb_xlo:
1872b3700f21SAdrian Hunter			x = self.rb_xlo
1873b3700f21SAdrian Hunter		elif x > self.rb_xhi:
1874b3700f21SAdrian Hunter			x = self.rb_xhi
1875b3700f21SAdrian Hunter		else:
1876b3700f21SAdrian Hunter			self.rb_in_view = True
1877b3700f21SAdrian Hunter		return x
1878b3700f21SAdrian Hunter
1879b3700f21SAdrian Hunter	def RubberBandRect(self, x):
1880b3700f21SAdrian Hunter		if self.rb_origin.x() <= x:
1881b3700f21SAdrian Hunter			width = x - self.rb_origin.x()
1882b3700f21SAdrian Hunter			rect = QRect(self.rb_origin, QSize(width, self.height))
1883b3700f21SAdrian Hunter		else:
1884b3700f21SAdrian Hunter			width = self.rb_origin.x() - x
1885b3700f21SAdrian Hunter			top_left = QPoint(self.rb_origin.x() - width, self.rb_origin.y())
1886b3700f21SAdrian Hunter			rect = QRect(top_left, QSize(width, self.height))
1887b3700f21SAdrian Hunter		return rect
1888b3700f21SAdrian Hunter
1889b3700f21SAdrian Hunter	def MousePressEvent(self, event):
1890b3700f21SAdrian Hunter		self.rb_in_view = False
1891b3700f21SAdrian Hunter		x = self.RubberBandX(event)
1892b3700f21SAdrian Hunter		self.rb_origin = QPoint(x, self.top)
1893b3700f21SAdrian Hunter		if self.rubber_band is None:
1894b3700f21SAdrian Hunter			self.rubber_band = QRubberBand(QRubberBand.Rectangle, self.RubberBandParent())
1895b3700f21SAdrian Hunter		self.RubberBandSetGeometry(QRect(self.rb_origin, QSize(0, self.height)))
1896b3700f21SAdrian Hunter		if self.rb_in_view:
1897b3700f21SAdrian Hunter			self.rubber_band.show()
1898b3700f21SAdrian Hunter			self.rb_event_handler.RBMoveEvent(x, x)
1899b3700f21SAdrian Hunter		else:
1900b3700f21SAdrian Hunter			self.rubber_band.hide()
1901b3700f21SAdrian Hunter
1902b3700f21SAdrian Hunter	def MouseMoveEvent(self, event):
1903b3700f21SAdrian Hunter		x = self.RubberBandX(event)
1904b3700f21SAdrian Hunter		rect = self.RubberBandRect(x)
1905b3700f21SAdrian Hunter		self.RubberBandSetGeometry(rect)
1906b3700f21SAdrian Hunter		if self.rb_in_view:
1907b3700f21SAdrian Hunter			self.rubber_band.show()
1908b3700f21SAdrian Hunter			self.rb_event_handler.RBMoveEvent(self.rb_origin.x(), x)
1909b3700f21SAdrian Hunter
1910b3700f21SAdrian Hunter	def MouseReleaseEvent(self, event):
1911b3700f21SAdrian Hunter		x = self.RubberBandX(event)
1912b3700f21SAdrian Hunter		if self.rb_in_view:
1913b3700f21SAdrian Hunter			selection_state = self.RubberBandRect(x)
1914b3700f21SAdrian Hunter		else:
1915b3700f21SAdrian Hunter			selection_state = None
1916b3700f21SAdrian Hunter		self.rb_event_handler.RBReleaseEvent(self.rb_origin.x(), x, selection_state)
1917b3700f21SAdrian Hunter
1918b3700f21SAdrian Hunter# Switch graph legend data model
1919b3700f21SAdrian Hunter
1920b3700f21SAdrian Hunterclass SwitchGraphLegendModel(QAbstractTableModel):
1921b3700f21SAdrian Hunter
1922b3700f21SAdrian Hunter	def __init__(self, collection, region_attributes, parent=None):
1923b3700f21SAdrian Hunter		super(SwitchGraphLegendModel, self).__init__(parent)
1924b3700f21SAdrian Hunter
1925b3700f21SAdrian Hunter		self.region_attributes = region_attributes
1926b3700f21SAdrian Hunter
1927b3700f21SAdrian Hunter		self.child_items = sorted(collection.hregions.values(), key=GraphDataRegionOrdinal)
1928b3700f21SAdrian Hunter		self.child_count = len(self.child_items)
1929b3700f21SAdrian Hunter
1930b3700f21SAdrian Hunter		self.highlight_set = set()
1931b3700f21SAdrian Hunter
1932b3700f21SAdrian Hunter		self.column_headers = ("pid", "tid", "comm")
1933b3700f21SAdrian Hunter
1934b3700f21SAdrian Hunter	def rowCount(self, parent):
1935b3700f21SAdrian Hunter		return self.child_count
1936b3700f21SAdrian Hunter
1937b3700f21SAdrian Hunter	def headerData(self, section, orientation, role):
1938b3700f21SAdrian Hunter		if role != Qt.DisplayRole:
1939b3700f21SAdrian Hunter			return None
1940b3700f21SAdrian Hunter		if orientation != Qt.Horizontal:
1941b3700f21SAdrian Hunter			return None
1942b3700f21SAdrian Hunter		return self.columnHeader(section)
1943b3700f21SAdrian Hunter
1944b3700f21SAdrian Hunter	def index(self, row, column, parent):
1945b3700f21SAdrian Hunter		return self.createIndex(row, column, self.child_items[row])
1946b3700f21SAdrian Hunter
1947b3700f21SAdrian Hunter	def columnCount(self, parent=None):
1948b3700f21SAdrian Hunter		return len(self.column_headers)
1949b3700f21SAdrian Hunter
1950b3700f21SAdrian Hunter	def columnHeader(self, column):
1951b3700f21SAdrian Hunter		return self.column_headers[column]
1952b3700f21SAdrian Hunter
1953b3700f21SAdrian Hunter	def data(self, index, role):
1954b3700f21SAdrian Hunter		if role == Qt.BackgroundRole:
1955b3700f21SAdrian Hunter			child = self.child_items[index.row()]
1956b3700f21SAdrian Hunter			if child in self.highlight_set:
1957b3700f21SAdrian Hunter				return self.region_attributes[child.key].colour
1958b3700f21SAdrian Hunter			return None
1959b3700f21SAdrian Hunter		if role == Qt.ForegroundRole:
1960b3700f21SAdrian Hunter			child = self.child_items[index.row()]
1961b3700f21SAdrian Hunter			if child in self.highlight_set:
1962b3700f21SAdrian Hunter				return QColor(255, 255, 255)
1963b3700f21SAdrian Hunter			return self.region_attributes[child.key].colour
1964b3700f21SAdrian Hunter		if role != Qt.DisplayRole:
1965b3700f21SAdrian Hunter			return None
1966b3700f21SAdrian Hunter		hregion = self.child_items[index.row()]
1967b3700f21SAdrian Hunter		col = index.column()
1968b3700f21SAdrian Hunter		if col == 0:
1969b3700f21SAdrian Hunter			return hregion.pid
1970b3700f21SAdrian Hunter		if col == 1:
1971b3700f21SAdrian Hunter			return hregion.tid
1972b3700f21SAdrian Hunter		if col == 2:
1973b3700f21SAdrian Hunter			return hregion.comm
1974b3700f21SAdrian Hunter		return None
1975b3700f21SAdrian Hunter
1976b3700f21SAdrian Hunter	def SetHighlight(self, row, set_highlight):
1977b3700f21SAdrian Hunter		child = self.child_items[row]
1978b3700f21SAdrian Hunter		top_left = self.createIndex(row, 0, child)
1979b3700f21SAdrian Hunter		bottom_right = self.createIndex(row, len(self.column_headers) - 1, child)
1980b3700f21SAdrian Hunter		self.dataChanged.emit(top_left, bottom_right)
1981b3700f21SAdrian Hunter
1982b3700f21SAdrian Hunter	def Highlight(self, highlight_set):
1983b3700f21SAdrian Hunter		for row in xrange(self.child_count):
1984b3700f21SAdrian Hunter			child = self.child_items[row]
1985b3700f21SAdrian Hunter			if child in self.highlight_set:
1986b3700f21SAdrian Hunter				if child not in highlight_set:
1987b3700f21SAdrian Hunter					self.SetHighlight(row, False)
1988b3700f21SAdrian Hunter			elif child in highlight_set:
1989b3700f21SAdrian Hunter				self.SetHighlight(row, True)
1990b3700f21SAdrian Hunter		self.highlight_set = highlight_set
1991b3700f21SAdrian Hunter
1992b3700f21SAdrian Hunter# Switch graph legend is a table
1993b3700f21SAdrian Hunter
1994b3700f21SAdrian Hunterclass SwitchGraphLegend(QWidget):
1995b3700f21SAdrian Hunter
1996b3700f21SAdrian Hunter	def __init__(self, collection, region_attributes, parent=None):
1997b3700f21SAdrian Hunter		super(SwitchGraphLegend, self).__init__(parent)
1998b3700f21SAdrian Hunter
1999b3700f21SAdrian Hunter		self.data_model = SwitchGraphLegendModel(collection, region_attributes)
2000b3700f21SAdrian Hunter
2001b3700f21SAdrian Hunter		self.model = QSortFilterProxyModel()
2002b3700f21SAdrian Hunter		self.model.setSourceModel(self.data_model)
2003b3700f21SAdrian Hunter
2004b3700f21SAdrian Hunter		self.view = QTableView()
2005b3700f21SAdrian Hunter		self.view.setModel(self.model)
2006b3700f21SAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2007b3700f21SAdrian Hunter		self.view.verticalHeader().setVisible(False)
2008b3700f21SAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
2009b3700f21SAdrian Hunter		self.view.setSortingEnabled(True)
2010b3700f21SAdrian Hunter		self.view.resizeColumnsToContents()
2011b3700f21SAdrian Hunter		self.view.resizeRowsToContents()
2012b3700f21SAdrian Hunter
2013b3700f21SAdrian Hunter		self.vbox = VBoxLayout(self.view)
2014b3700f21SAdrian Hunter		self.setLayout(self.vbox)
2015b3700f21SAdrian Hunter
2016b3700f21SAdrian Hunter		sz1 = self.view.columnWidth(0) + self.view.columnWidth(1) + self.view.columnWidth(2) + 2
2017b3700f21SAdrian Hunter		sz1 = sz1 + self.view.verticalScrollBar().sizeHint().width()
2018b3700f21SAdrian Hunter		self.saved_size = sz1
2019b3700f21SAdrian Hunter
2020b3700f21SAdrian Hunter	def resizeEvent(self, event):
2021b3700f21SAdrian Hunter		self.saved_size = self.size().width()
2022b3700f21SAdrian Hunter		super(SwitchGraphLegend, self).resizeEvent(event)
2023b3700f21SAdrian Hunter
2024b3700f21SAdrian Hunter	def Highlight(self, highlight_set):
2025b3700f21SAdrian Hunter		self.data_model.Highlight(highlight_set)
2026b3700f21SAdrian Hunter		self.update()
2027b3700f21SAdrian Hunter
2028b3700f21SAdrian Hunter	def changeEvent(self, event):
2029b3700f21SAdrian Hunter		if event.type() == QEvent.FontChange:
2030b3700f21SAdrian Hunter			self.view.resizeRowsToContents()
2031b3700f21SAdrian Hunter			self.view.resizeColumnsToContents()
2032b3700f21SAdrian Hunter			# Need to resize rows again after column resize
2033b3700f21SAdrian Hunter			self.view.resizeRowsToContents()
2034b3700f21SAdrian Hunter		super(SwitchGraphLegend, self).changeEvent(event)
2035b3700f21SAdrian Hunter
2036b3700f21SAdrian Hunter# Random colour generation
2037b3700f21SAdrian Hunter
2038b3700f21SAdrian Hunterdef RGBColourTooLight(r, g, b):
2039b3700f21SAdrian Hunter	if g > 230:
2040b3700f21SAdrian Hunter		return True
2041b3700f21SAdrian Hunter	if g <= 160:
2042b3700f21SAdrian Hunter		return False
2043b3700f21SAdrian Hunter	if r <= 180 and g <= 180:
2044b3700f21SAdrian Hunter		return False
2045b3700f21SAdrian Hunter	if r < 60:
2046b3700f21SAdrian Hunter		return False
2047b3700f21SAdrian Hunter	return True
2048b3700f21SAdrian Hunter
2049b3700f21SAdrian Hunterdef GenerateColours(x):
2050b3700f21SAdrian Hunter	cs = [0]
2051b3700f21SAdrian Hunter	for i in xrange(1, x):
2052b3700f21SAdrian Hunter		cs.append(int((255.0 / i) + 0.5))
2053b3700f21SAdrian Hunter	colours = []
2054b3700f21SAdrian Hunter	for r in cs:
2055b3700f21SAdrian Hunter		for g in cs:
2056b3700f21SAdrian Hunter			for b in cs:
2057b3700f21SAdrian Hunter				# Exclude black and colours that look too light against a white background
2058b3700f21SAdrian Hunter				if (r, g, b) == (0, 0, 0) or RGBColourTooLight(r, g, b):
2059b3700f21SAdrian Hunter					continue
2060b3700f21SAdrian Hunter				colours.append(QColor(r, g, b))
2061b3700f21SAdrian Hunter	return colours
2062b3700f21SAdrian Hunter
2063b3700f21SAdrian Hunterdef GenerateNColours(n):
2064b3700f21SAdrian Hunter	for x in xrange(2, n + 2):
2065b3700f21SAdrian Hunter		colours = GenerateColours(x)
2066b3700f21SAdrian Hunter		if len(colours) >= n:
2067b3700f21SAdrian Hunter			return colours
2068b3700f21SAdrian Hunter	return []
2069b3700f21SAdrian Hunter
2070b3700f21SAdrian Hunterdef GenerateNRandomColours(n, seed):
2071b3700f21SAdrian Hunter	colours = GenerateNColours(n)
2072b3700f21SAdrian Hunter	random.seed(seed)
2073b3700f21SAdrian Hunter	random.shuffle(colours)
2074b3700f21SAdrian Hunter	return colours
2075b3700f21SAdrian Hunter
2076b3700f21SAdrian Hunter# Graph attributes, in particular the scale and subrange that change when zooming
2077b3700f21SAdrian Hunter
2078b3700f21SAdrian Hunterclass GraphAttributes():
2079b3700f21SAdrian Hunter
2080b3700f21SAdrian Hunter	def __init__(self, scale, subrange, region_attributes, dp):
2081b3700f21SAdrian Hunter		self.scale = scale
2082b3700f21SAdrian Hunter		self.subrange = subrange
2083b3700f21SAdrian Hunter		self.region_attributes = region_attributes
2084b3700f21SAdrian Hunter		# Rounding avoids errors due to finite floating point precision
2085b3700f21SAdrian Hunter		self.dp = dp	# data decimal places
2086b3700f21SAdrian Hunter		self.Update()
2087b3700f21SAdrian Hunter
2088b3700f21SAdrian Hunter	def XToPixel(self, x):
2089b3700f21SAdrian Hunter		return int(round((x - self.subrange.x.lo) * self.scale.x, self.pdp.x))
2090b3700f21SAdrian Hunter
2091b3700f21SAdrian Hunter	def YToPixel(self, y):
2092b3700f21SAdrian Hunter		return int(round((y - self.subrange.y.lo) * self.scale.y, self.pdp.y))
2093b3700f21SAdrian Hunter
2094b3700f21SAdrian Hunter	def PixelToXRounded(self, px):
2095b3700f21SAdrian Hunter		return round((round(px, 0) / self.scale.x), self.dp.x) + self.subrange.x.lo
2096b3700f21SAdrian Hunter
2097b3700f21SAdrian Hunter	def PixelToYRounded(self, py):
2098b3700f21SAdrian Hunter		return round((round(py, 0) / self.scale.y), self.dp.y) + self.subrange.y.lo
2099b3700f21SAdrian Hunter
2100b3700f21SAdrian Hunter	def PixelToX(self, px):
2101b3700f21SAdrian Hunter		x = self.PixelToXRounded(px)
2102b3700f21SAdrian Hunter		if self.pdp.x == 0:
2103b3700f21SAdrian Hunter			rt = self.XToPixel(x)
2104b3700f21SAdrian Hunter			if rt > px:
2105b3700f21SAdrian Hunter				return x - 1
2106b3700f21SAdrian Hunter		return x
2107b3700f21SAdrian Hunter
2108b3700f21SAdrian Hunter	def PixelToY(self, py):
2109b3700f21SAdrian Hunter		y = self.PixelToYRounded(py)
2110b3700f21SAdrian Hunter		if self.pdp.y == 0:
2111b3700f21SAdrian Hunter			rt = self.YToPixel(y)
2112b3700f21SAdrian Hunter			if rt > py:
2113b3700f21SAdrian Hunter				return y - 1
2114b3700f21SAdrian Hunter		return y
2115b3700f21SAdrian Hunter
2116b3700f21SAdrian Hunter	def ToPDP(self, dp, scale):
2117b3700f21SAdrian Hunter		# Calculate pixel decimal places:
2118b3700f21SAdrian Hunter		#    (10 ** dp) is the minimum delta in the data
2119b3700f21SAdrian Hunter		#    scale it to get the minimum delta in pixels
2120b3700f21SAdrian Hunter		#    log10 gives the number of decimals places negatively
2121b3700f21SAdrian Hunter		#    subtrace 1 to divide by 10
2122b3700f21SAdrian Hunter		#    round to the lower negative number
2123b3700f21SAdrian Hunter		#    change the sign to get the number of decimals positively
2124b3700f21SAdrian Hunter		x = math.log10((10 ** dp) * scale)
2125b3700f21SAdrian Hunter		if x < 0:
2126b3700f21SAdrian Hunter			x -= 1
2127b3700f21SAdrian Hunter			x = -int(math.floor(x) - 0.1)
2128b3700f21SAdrian Hunter		else:
2129b3700f21SAdrian Hunter			x = 0
2130b3700f21SAdrian Hunter		return x
2131b3700f21SAdrian Hunter
2132b3700f21SAdrian Hunter	def Update(self):
2133b3700f21SAdrian Hunter		x = self.ToPDP(self.dp.x, self.scale.x)
2134b3700f21SAdrian Hunter		y = self.ToPDP(self.dp.y, self.scale.y)
2135b3700f21SAdrian Hunter		self.pdp = XY(x, y) # pixel decimal places
2136b3700f21SAdrian Hunter
2137b3700f21SAdrian Hunter# Switch graph splitter which divides the CPU graphs from the legend
2138b3700f21SAdrian Hunter
2139b3700f21SAdrian Hunterclass SwitchGraphSplitter(QSplitter):
2140b3700f21SAdrian Hunter
2141b3700f21SAdrian Hunter	def __init__(self, parent=None):
2142b3700f21SAdrian Hunter		super(SwitchGraphSplitter, self).__init__(parent)
2143b3700f21SAdrian Hunter
2144b3700f21SAdrian Hunter		self.first_time = False
2145b3700f21SAdrian Hunter
2146b3700f21SAdrian Hunter	def resizeEvent(self, ev):
2147b3700f21SAdrian Hunter		if self.first_time:
2148b3700f21SAdrian Hunter			self.first_time = False
2149b3700f21SAdrian Hunter			sz1 = self.widget(1).view.columnWidth(0) + self.widget(1).view.columnWidth(1) + self.widget(1).view.columnWidth(2) + 2
2150b3700f21SAdrian Hunter			sz1 = sz1 + self.widget(1).view.verticalScrollBar().sizeHint().width()
2151b3700f21SAdrian Hunter			sz0 = self.size().width() - self.handleWidth() - sz1
2152b3700f21SAdrian Hunter			self.setSizes([sz0, sz1])
2153b3700f21SAdrian Hunter		elif not(self.widget(1).saved_size is None):
2154b3700f21SAdrian Hunter			sz1 = self.widget(1).saved_size
2155b3700f21SAdrian Hunter			sz0 = self.size().width() - self.handleWidth() - sz1
2156b3700f21SAdrian Hunter			self.setSizes([sz0, sz1])
2157b3700f21SAdrian Hunter		super(SwitchGraphSplitter, self).resizeEvent(ev)
2158b3700f21SAdrian Hunter
2159b3700f21SAdrian Hunter# Graph widget base class
2160b3700f21SAdrian Hunter
2161b3700f21SAdrian Hunterclass GraphWidget(QWidget):
2162b3700f21SAdrian Hunter
2163b3700f21SAdrian Hunter	graph_title_changed = Signal(object)
2164b3700f21SAdrian Hunter
2165b3700f21SAdrian Hunter	def __init__(self, parent=None):
2166b3700f21SAdrian Hunter		super(GraphWidget, self).__init__(parent)
2167b3700f21SAdrian Hunter
2168b3700f21SAdrian Hunter	def GraphTitleChanged(self, title):
2169b3700f21SAdrian Hunter		self.graph_title_changed.emit(title)
2170b3700f21SAdrian Hunter
2171b3700f21SAdrian Hunter	def Title(self):
2172b3700f21SAdrian Hunter		return ""
2173b3700f21SAdrian Hunter
2174b3700f21SAdrian Hunter# Display time in s, ms, us or ns
2175b3700f21SAdrian Hunter
2176b3700f21SAdrian Hunterdef ToTimeStr(val):
2177b3700f21SAdrian Hunter	val = Decimal(val)
2178b3700f21SAdrian Hunter	if val >= 1000000000:
2179b3700f21SAdrian Hunter		return "{} s".format((val / 1000000000).quantize(Decimal("0.000000001")))
2180b3700f21SAdrian Hunter	if val >= 1000000:
2181b3700f21SAdrian Hunter		return "{} ms".format((val / 1000000).quantize(Decimal("0.000001")))
2182b3700f21SAdrian Hunter	if val >= 1000:
2183b3700f21SAdrian Hunter		return "{} us".format((val / 1000).quantize(Decimal("0.001")))
2184b3700f21SAdrian Hunter	return "{} ns".format(val.quantize(Decimal("1")))
2185b3700f21SAdrian Hunter
2186b3700f21SAdrian 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
2187b3700f21SAdrian Hunter
2188b3700f21SAdrian Hunterclass SwitchGraphWidget(GraphWidget):
2189b3700f21SAdrian Hunter
2190b3700f21SAdrian Hunter	def __init__(self, glb, collection, parent=None):
2191b3700f21SAdrian Hunter		super(SwitchGraphWidget, self).__init__(parent)
2192b3700f21SAdrian Hunter
2193b3700f21SAdrian Hunter		self.glb = glb
2194b3700f21SAdrian Hunter		self.collection = collection
2195b3700f21SAdrian Hunter
2196b3700f21SAdrian Hunter		self.back_state = []
2197b3700f21SAdrian Hunter		self.forward_state = []
2198b3700f21SAdrian Hunter		self.selection_state = (None, None)
2199b3700f21SAdrian Hunter		self.fwd_rect = None
2200b3700f21SAdrian Hunter		self.start_time = self.glb.StartTime(collection.machine_id)
2201b3700f21SAdrian Hunter
2202b3700f21SAdrian Hunter		i = 0
2203b3700f21SAdrian Hunter		hregions = collection.hregions.values()
2204b3700f21SAdrian Hunter		colours = GenerateNRandomColours(len(hregions), 1013)
2205b3700f21SAdrian Hunter		region_attributes = {}
2206b3700f21SAdrian Hunter		for hregion in hregions:
2207b3700f21SAdrian Hunter			if hregion.pid == 0 and hregion.tid == 0:
2208b3700f21SAdrian Hunter				region_attributes[hregion.key] = GraphRegionAttribute(QColor(0, 0, 0))
2209b3700f21SAdrian Hunter			else:
2210b3700f21SAdrian Hunter				region_attributes[hregion.key] = GraphRegionAttribute(colours[i])
2211b3700f21SAdrian Hunter				i = i + 1
2212b3700f21SAdrian Hunter
2213b3700f21SAdrian Hunter		# Default to entire range
2214b3700f21SAdrian Hunter		xsubrange = Subrange(0.0, float(collection.xrangehi - collection.xrangelo) + 1.0)
2215b3700f21SAdrian Hunter		ysubrange = Subrange(0.0, float(collection.yrangehi - collection.yrangelo) + 1.0)
2216b3700f21SAdrian Hunter		subrange = XY(xsubrange, ysubrange)
2217b3700f21SAdrian Hunter
2218b3700f21SAdrian Hunter		scale = self.GetScaleForRange(subrange)
2219b3700f21SAdrian Hunter
2220b3700f21SAdrian Hunter		self.attrs = GraphAttributes(scale, subrange, region_attributes, collection.dp)
2221b3700f21SAdrian Hunter
2222b3700f21SAdrian Hunter		self.item = VertcalGraphSetGraphicsItem(collection, self.attrs, self, SwitchGraphGraphicsItem)
2223b3700f21SAdrian Hunter
2224b3700f21SAdrian Hunter		self.scene = QGraphicsScene()
2225b3700f21SAdrian Hunter		self.scene.addItem(self.item)
2226b3700f21SAdrian Hunter
2227b3700f21SAdrian Hunter		self.view = QGraphicsView(self.scene)
2228b3700f21SAdrian Hunter		self.view.centerOn(0, 0)
2229b3700f21SAdrian Hunter		self.view.setAlignment(Qt.AlignLeft | Qt.AlignTop)
2230b3700f21SAdrian Hunter
2231b3700f21SAdrian Hunter		self.legend = SwitchGraphLegend(collection, region_attributes)
2232b3700f21SAdrian Hunter
2233b3700f21SAdrian Hunter		self.splitter = SwitchGraphSplitter()
2234b3700f21SAdrian Hunter		self.splitter.addWidget(self.view)
2235b3700f21SAdrian Hunter		self.splitter.addWidget(self.legend)
2236b3700f21SAdrian Hunter
2237b3700f21SAdrian Hunter		self.point_label = QLabel("")
2238b3700f21SAdrian Hunter		self.point_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
2239b3700f21SAdrian Hunter
2240b3700f21SAdrian Hunter		self.back_button = QToolButton()
2241b3700f21SAdrian Hunter		self.back_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowLeft))
2242b3700f21SAdrian Hunter		self.back_button.setDisabled(True)
2243b3700f21SAdrian Hunter		self.back_button.released.connect(lambda: self.Back())
2244b3700f21SAdrian Hunter
2245b3700f21SAdrian Hunter		self.forward_button = QToolButton()
2246b3700f21SAdrian Hunter		self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowRight))
2247b3700f21SAdrian Hunter		self.forward_button.setDisabled(True)
2248b3700f21SAdrian Hunter		self.forward_button.released.connect(lambda: self.Forward())
2249b3700f21SAdrian Hunter
2250b3700f21SAdrian Hunter		self.zoom_button = QToolButton()
2251b3700f21SAdrian Hunter		self.zoom_button.setText("Zoom")
2252b3700f21SAdrian Hunter		self.zoom_button.setDisabled(True)
2253b3700f21SAdrian Hunter		self.zoom_button.released.connect(lambda: self.Zoom())
2254b3700f21SAdrian Hunter
2255b3700f21SAdrian Hunter		self.hbox = HBoxLayout(self.back_button, self.forward_button, self.zoom_button, self.point_label)
2256b3700f21SAdrian Hunter
2257b3700f21SAdrian Hunter		self.vbox = VBoxLayout(self.splitter, self.hbox)
2258b3700f21SAdrian Hunter
2259b3700f21SAdrian Hunter		self.setLayout(self.vbox)
2260b3700f21SAdrian Hunter
2261b3700f21SAdrian Hunter	def GetScaleForRangeX(self, xsubrange):
2262b3700f21SAdrian Hunter		# Default graph 1000 pixels wide
2263b3700f21SAdrian Hunter		dflt = 1000.0
2264b3700f21SAdrian Hunter		r = xsubrange.hi - xsubrange.lo
2265b3700f21SAdrian Hunter		return dflt / r
2266b3700f21SAdrian Hunter
2267b3700f21SAdrian Hunter	def GetScaleForRangeY(self, ysubrange):
2268b3700f21SAdrian Hunter		# Default graph 50 pixels high
2269b3700f21SAdrian Hunter		dflt = 50.0
2270b3700f21SAdrian Hunter		r = ysubrange.hi - ysubrange.lo
2271b3700f21SAdrian Hunter		return dflt / r
2272b3700f21SAdrian Hunter
2273b3700f21SAdrian Hunter	def GetScaleForRange(self, subrange):
2274b3700f21SAdrian Hunter		# Default graph 1000 pixels wide, 50 pixels high
2275b3700f21SAdrian Hunter		xscale = self.GetScaleForRangeX(subrange.x)
2276b3700f21SAdrian Hunter		yscale = self.GetScaleForRangeY(subrange.y)
2277b3700f21SAdrian Hunter		return XY(xscale, yscale)
2278b3700f21SAdrian Hunter
2279b3700f21SAdrian Hunter	def PointEvent(self, cpu, time_from, time_to, hregions):
2280b3700f21SAdrian Hunter		text = "CPU: " + str(cpu)
2281b3700f21SAdrian Hunter		time_from = time_from.quantize(Decimal(1))
2282b3700f21SAdrian Hunter		rel_time_from = time_from - self.glb.StartTime(self.collection.machine_id)
2283b3700f21SAdrian Hunter		text = text + " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ")"
2284b3700f21SAdrian Hunter		self.point_label.setText(text)
2285b3700f21SAdrian Hunter		self.legend.Highlight(hregions)
2286b3700f21SAdrian Hunter
2287b3700f21SAdrian Hunter	def RightClickEvent(self, cpu, hregion_times, pos):
2288b3700f21SAdrian Hunter		if not IsSelectable(self.glb.db, "calls", "WHERE parent_id >= 0"):
2289b3700f21SAdrian Hunter			return
2290b3700f21SAdrian Hunter		menu = QMenu(self.view)
2291b3700f21SAdrian Hunter		for hregion, time in hregion_times:
2292b3700f21SAdrian Hunter			thread_at_time = (hregion.exec_comm_id, hregion.thread_id, time)
2293b3700f21SAdrian Hunter			menu_text = "Show Call Tree for {} {}:{} at {}".format(hregion.comm, hregion.pid, hregion.tid, time)
2294b3700f21SAdrian Hunter			menu.addAction(CreateAction(menu_text, "Show Call Tree", lambda a=None, args=thread_at_time: self.RightClickSelect(args), self.view))
2295b3700f21SAdrian Hunter		menu.exec_(pos)
2296b3700f21SAdrian Hunter
2297b3700f21SAdrian Hunter	def RightClickSelect(self, args):
2298b3700f21SAdrian Hunter		CallTreeWindow(self.glb, self.glb.mainwindow, thread_at_time=args)
2299b3700f21SAdrian Hunter
2300b3700f21SAdrian Hunter	def NoPointEvent(self):
2301b3700f21SAdrian Hunter		self.point_label.setText("")
2302b3700f21SAdrian Hunter		self.legend.Highlight({})
2303b3700f21SAdrian Hunter
2304b3700f21SAdrian Hunter	def RangeEvent(self, time_from, time_to):
2305b3700f21SAdrian Hunter		time_from = time_from.quantize(Decimal(1))
2306b3700f21SAdrian Hunter		time_to = time_to.quantize(Decimal(1))
2307b3700f21SAdrian Hunter		if time_to <= time_from:
2308b3700f21SAdrian Hunter			self.point_label.setText("")
2309b3700f21SAdrian Hunter			return
2310b3700f21SAdrian Hunter		rel_time_from = time_from - self.start_time
2311b3700f21SAdrian Hunter		rel_time_to = time_to - self.start_time
2312b3700f21SAdrian Hunter		text = " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ") to: " + str(time_to) + " (+" + ToTimeStr(rel_time_to) + ")"
2313b3700f21SAdrian Hunter		text = text + " duration: " + ToTimeStr(time_to - time_from)
2314b3700f21SAdrian Hunter		self.point_label.setText(text)
2315b3700f21SAdrian Hunter
2316b3700f21SAdrian Hunter	def BackState(self):
2317b3700f21SAdrian Hunter		return (self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect)
2318b3700f21SAdrian Hunter
2319b3700f21SAdrian Hunter	def PushBackState(self):
2320b3700f21SAdrian Hunter		state = copy.deepcopy(self.BackState())
2321b3700f21SAdrian Hunter		self.back_state.append(state)
2322b3700f21SAdrian Hunter		self.back_button.setEnabled(True)
2323b3700f21SAdrian Hunter
2324b3700f21SAdrian Hunter	def PopBackState(self):
2325b3700f21SAdrian Hunter		self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.back_state.pop()
2326b3700f21SAdrian Hunter		self.attrs.Update()
2327b3700f21SAdrian Hunter		if not self.back_state:
2328b3700f21SAdrian Hunter			self.back_button.setDisabled(True)
2329b3700f21SAdrian Hunter
2330b3700f21SAdrian Hunter	def PushForwardState(self):
2331b3700f21SAdrian Hunter		state = copy.deepcopy(self.BackState())
2332b3700f21SAdrian Hunter		self.forward_state.append(state)
2333b3700f21SAdrian Hunter		self.forward_button.setEnabled(True)
2334b3700f21SAdrian Hunter
2335b3700f21SAdrian Hunter	def PopForwardState(self):
2336b3700f21SAdrian Hunter		self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.forward_state.pop()
2337b3700f21SAdrian Hunter		self.attrs.Update()
2338b3700f21SAdrian Hunter		if not self.forward_state:
2339b3700f21SAdrian Hunter			self.forward_button.setDisabled(True)
2340b3700f21SAdrian Hunter
2341b3700f21SAdrian Hunter	def Title(self):
2342b3700f21SAdrian Hunter		time_from = self.collection.xrangelo + Decimal(self.attrs.subrange.x.lo)
2343b3700f21SAdrian Hunter		time_to = self.collection.xrangelo + Decimal(self.attrs.subrange.x.hi)
2344b3700f21SAdrian Hunter		rel_time_from = time_from - self.start_time
2345b3700f21SAdrian Hunter		rel_time_to = time_to - self.start_time
2346b3700f21SAdrian Hunter		title = "+" + ToTimeStr(rel_time_from) + " to +" + ToTimeStr(rel_time_to)
2347b3700f21SAdrian Hunter		title = title + " (" + ToTimeStr(time_to - time_from) + ")"
2348b3700f21SAdrian Hunter		return title
2349b3700f21SAdrian Hunter
2350b3700f21SAdrian Hunter	def Update(self):
2351b3700f21SAdrian Hunter		selected_subrange, selection_state = self.selection_state
2352b3700f21SAdrian Hunter		self.item.SetSelection(selection_state)
2353b3700f21SAdrian Hunter		self.item.SetBracket(self.fwd_rect)
2354b3700f21SAdrian Hunter		self.zoom_button.setDisabled(selected_subrange is None)
2355b3700f21SAdrian Hunter		self.GraphTitleChanged(self.Title())
2356b3700f21SAdrian Hunter		self.item.update(self.item.boundingRect())
2357b3700f21SAdrian Hunter
2358b3700f21SAdrian Hunter	def Back(self):
2359b3700f21SAdrian Hunter		if not self.back_state:
2360b3700f21SAdrian Hunter			return
2361b3700f21SAdrian Hunter		self.PushForwardState()
2362b3700f21SAdrian Hunter		self.PopBackState()
2363b3700f21SAdrian Hunter		self.Update()
2364b3700f21SAdrian Hunter
2365b3700f21SAdrian Hunter	def Forward(self):
2366b3700f21SAdrian Hunter		if not self.forward_state:
2367b3700f21SAdrian Hunter			return
2368b3700f21SAdrian Hunter		self.PushBackState()
2369b3700f21SAdrian Hunter		self.PopForwardState()
2370b3700f21SAdrian Hunter		self.Update()
2371b3700f21SAdrian Hunter
2372b3700f21SAdrian Hunter	def SelectEvent(self, x0, x1, selection_state):
2373b3700f21SAdrian Hunter		if selection_state is None:
2374b3700f21SAdrian Hunter			selected_subrange = None
2375b3700f21SAdrian Hunter		else:
2376b3700f21SAdrian Hunter			if x1 - x0 < 1.0:
2377b3700f21SAdrian Hunter				x1 += 1.0
2378b3700f21SAdrian Hunter			selected_subrange = Subrange(x0, x1)
2379b3700f21SAdrian Hunter		self.selection_state = (selected_subrange, selection_state)
2380b3700f21SAdrian Hunter		self.zoom_button.setDisabled(selected_subrange is None)
2381b3700f21SAdrian Hunter
2382b3700f21SAdrian Hunter	def Zoom(self):
2383b3700f21SAdrian Hunter		selected_subrange, selection_state = self.selection_state
2384b3700f21SAdrian Hunter		if selected_subrange is None:
2385b3700f21SAdrian Hunter			return
2386b3700f21SAdrian Hunter		self.fwd_rect = selection_state
2387b3700f21SAdrian Hunter		self.item.SetSelection(None)
2388b3700f21SAdrian Hunter		self.PushBackState()
2389b3700f21SAdrian Hunter		self.attrs.subrange.x = selected_subrange
2390b3700f21SAdrian Hunter		self.forward_state = []
2391b3700f21SAdrian Hunter		self.forward_button.setDisabled(True)
2392b3700f21SAdrian Hunter		self.selection_state = (None, None)
2393b3700f21SAdrian Hunter		self.fwd_rect = None
2394b3700f21SAdrian Hunter		self.attrs.scale.x = self.GetScaleForRangeX(self.attrs.subrange.x)
2395b3700f21SAdrian Hunter		self.attrs.Update()
2396b3700f21SAdrian Hunter		self.Update()
2397b3700f21SAdrian Hunter
2398b3700f21SAdrian Hunter# Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting
2399b3700f21SAdrian Hunter
2400b3700f21SAdrian Hunterclass SlowInitClass():
2401b3700f21SAdrian Hunter
2402b3700f21SAdrian Hunter	def __init__(self, glb, title, init_fn):
2403b3700f21SAdrian Hunter		self.init_fn = init_fn
2404b3700f21SAdrian Hunter		self.done = False
2405b3700f21SAdrian Hunter		self.result = None
2406b3700f21SAdrian Hunter
2407b3700f21SAdrian Hunter		self.msg_box = QMessageBox(glb.mainwindow)
2408b3700f21SAdrian Hunter		self.msg_box.setText("Initializing " + title + ". Please wait.")
2409b3700f21SAdrian Hunter		self.msg_box.setWindowTitle("Initializing " + title)
2410b3700f21SAdrian Hunter		self.msg_box.setWindowIcon(glb.mainwindow.style().standardIcon(QStyle.SP_MessageBoxInformation))
2411b3700f21SAdrian Hunter
2412b3700f21SAdrian Hunter		self.init_thread = Thread(self.ThreadFn, glb)
2413b3700f21SAdrian Hunter		self.init_thread.done.connect(lambda: self.Done(), Qt.QueuedConnection)
2414b3700f21SAdrian Hunter
2415b3700f21SAdrian Hunter		self.init_thread.start()
2416b3700f21SAdrian Hunter
2417b3700f21SAdrian Hunter	def Done(self):
2418b3700f21SAdrian Hunter		self.msg_box.done(0)
2419b3700f21SAdrian Hunter
2420b3700f21SAdrian Hunter	def ThreadFn(self, glb):
2421b3700f21SAdrian Hunter		conn_name = "SlowInitClass" + str(os.getpid())
2422b3700f21SAdrian Hunter		db, dbname = glb.dbref.Open(conn_name)
2423b3700f21SAdrian Hunter		self.result = self.init_fn(db)
2424b3700f21SAdrian Hunter		self.done = True
2425b3700f21SAdrian Hunter		return (True, 0)
2426b3700f21SAdrian Hunter
2427b3700f21SAdrian Hunter	def Result(self):
2428b3700f21SAdrian Hunter		while not self.done:
2429b3700f21SAdrian Hunter			self.msg_box.exec_()
2430b3700f21SAdrian Hunter		self.init_thread.wait()
2431b3700f21SAdrian Hunter		return self.result
2432b3700f21SAdrian Hunter
2433b3700f21SAdrian Hunterdef SlowInit(glb, title, init_fn):
2434b3700f21SAdrian Hunter	init = SlowInitClass(glb, title, init_fn)
2435b3700f21SAdrian Hunter	return init.Result()
2436b3700f21SAdrian Hunter
2437b3700f21SAdrian Hunter# Time chart by CPU window
2438b3700f21SAdrian Hunter
2439b3700f21SAdrian Hunterclass TimeChartByCPUWindow(QMdiSubWindow):
2440b3700f21SAdrian Hunter
2441b3700f21SAdrian Hunter	def __init__(self, glb, parent=None):
2442b3700f21SAdrian Hunter		super(TimeChartByCPUWindow, self).__init__(parent)
2443b3700f21SAdrian Hunter
2444b3700f21SAdrian Hunter		self.glb = glb
2445b3700f21SAdrian Hunter		self.machine_id = glb.HostMachineId()
2446b3700f21SAdrian Hunter		self.collection_name = "SwitchGraphDataCollection " + str(self.machine_id)
2447b3700f21SAdrian Hunter
2448b3700f21SAdrian Hunter		collection = LookupModel(self.collection_name)
2449b3700f21SAdrian Hunter		if collection is None:
2450b3700f21SAdrian Hunter			collection = SlowInit(glb, "Time Chart", self.Init)
2451b3700f21SAdrian Hunter
2452b3700f21SAdrian Hunter		self.widget = SwitchGraphWidget(glb, collection, self)
2453b3700f21SAdrian Hunter		self.view = self.widget
2454b3700f21SAdrian Hunter
2455b3700f21SAdrian Hunter		self.base_title = "Time Chart by CPU"
2456b3700f21SAdrian Hunter		self.setWindowTitle(self.base_title + self.widget.Title())
2457b3700f21SAdrian Hunter		self.widget.graph_title_changed.connect(self.GraphTitleChanged)
2458b3700f21SAdrian Hunter
2459b3700f21SAdrian Hunter		self.setWidget(self.widget)
2460b3700f21SAdrian Hunter
2461b3700f21SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, self.windowTitle())
2462b3700f21SAdrian Hunter
2463b3700f21SAdrian Hunter	def Init(self, db):
2464b3700f21SAdrian Hunter		return LookupCreateModel(self.collection_name, lambda : SwitchGraphDataCollection(self.glb, db, self.machine_id))
2465b3700f21SAdrian Hunter
2466b3700f21SAdrian Hunter	def GraphTitleChanged(self, title):
2467b3700f21SAdrian Hunter		self.setWindowTitle(self.base_title + " : " + title)
2468b3700f21SAdrian Hunter
24698392b74bSAdrian Hunter# Child data item  finder
24708392b74bSAdrian Hunter
24718392b74bSAdrian Hunterclass ChildDataItemFinder():
24728392b74bSAdrian Hunter
24738392b74bSAdrian Hunter	def __init__(self, root):
24748392b74bSAdrian Hunter		self.root = root
24758392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
24768392b74bSAdrian Hunter		self.rows = []
24778392b74bSAdrian Hunter		self.pos = 0
24788392b74bSAdrian Hunter
24798392b74bSAdrian Hunter	def FindSelect(self):
24808392b74bSAdrian Hunter		self.rows = []
24818392b74bSAdrian Hunter		if self.pattern:
24828392b74bSAdrian Hunter			pattern = re.compile(self.value)
24838392b74bSAdrian Hunter			for child in self.root.child_items:
24848392b74bSAdrian Hunter				for column_data in child.data:
24858392b74bSAdrian Hunter					if re.search(pattern, str(column_data)) is not None:
24868392b74bSAdrian Hunter						self.rows.append(child.row)
24878392b74bSAdrian Hunter						break
24888392b74bSAdrian Hunter		else:
24898392b74bSAdrian Hunter			for child in self.root.child_items:
24908392b74bSAdrian Hunter				for column_data in child.data:
24918392b74bSAdrian Hunter					if self.value in str(column_data):
24928392b74bSAdrian Hunter						self.rows.append(child.row)
24938392b74bSAdrian Hunter						break
24948392b74bSAdrian Hunter
24958392b74bSAdrian Hunter	def FindValue(self):
24968392b74bSAdrian Hunter		self.pos = 0
24978392b74bSAdrian Hunter		if self.last_value != self.value or self.pattern != self.last_pattern:
24988392b74bSAdrian Hunter			self.FindSelect()
24998392b74bSAdrian Hunter		if not len(self.rows):
25008392b74bSAdrian Hunter			return -1
25018392b74bSAdrian Hunter		return self.rows[self.pos]
25028392b74bSAdrian Hunter
25038392b74bSAdrian Hunter	def FindThread(self):
25048392b74bSAdrian Hunter		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
25058392b74bSAdrian Hunter			row = self.FindValue()
25068392b74bSAdrian Hunter		elif len(self.rows):
25078392b74bSAdrian Hunter			if self.direction > 0:
25088392b74bSAdrian Hunter				self.pos += 1
25098392b74bSAdrian Hunter				if self.pos >= len(self.rows):
25108392b74bSAdrian Hunter					self.pos = 0
25118392b74bSAdrian Hunter			else:
25128392b74bSAdrian Hunter				self.pos -= 1
25138392b74bSAdrian Hunter				if self.pos < 0:
25148392b74bSAdrian Hunter					self.pos = len(self.rows) - 1
25158392b74bSAdrian Hunter			row = self.rows[self.pos]
25168392b74bSAdrian Hunter		else:
25178392b74bSAdrian Hunter			row = -1
25188392b74bSAdrian Hunter		return (True, row)
25198392b74bSAdrian Hunter
25208392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context, callback):
25218392b74bSAdrian Hunter		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
25228392b74bSAdrian Hunter		# Use a thread so the UI is not blocked
25238392b74bSAdrian Hunter		thread = Thread(self.FindThread)
25248392b74bSAdrian Hunter		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
25258392b74bSAdrian Hunter		thread.start()
25268392b74bSAdrian Hunter
25278392b74bSAdrian Hunter	def FindDone(self, thread, callback, row):
25288392b74bSAdrian Hunter		callback(row)
25298392b74bSAdrian Hunter
25308392b74bSAdrian Hunter# Number of database records to fetch in one go
25318392b74bSAdrian Hunter
25328392b74bSAdrian Hunterglb_chunk_sz = 10000
25338392b74bSAdrian Hunter
25348392b74bSAdrian Hunter# Background process for SQL data fetcher
25358392b74bSAdrian Hunter
25368392b74bSAdrian Hunterclass SQLFetcherProcess():
25378392b74bSAdrian Hunter
25388392b74bSAdrian Hunter	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
25398392b74bSAdrian Hunter		# Need a unique connection name
25408392b74bSAdrian Hunter		conn_name = "SQLFetcher" + str(os.getpid())
25418392b74bSAdrian Hunter		self.db, dbname = dbref.Open(conn_name)
25428392b74bSAdrian Hunter		self.sql = sql
25438392b74bSAdrian Hunter		self.buffer = buffer
25448392b74bSAdrian Hunter		self.head = head
25458392b74bSAdrian Hunter		self.tail = tail
25468392b74bSAdrian Hunter		self.fetch_count = fetch_count
25478392b74bSAdrian Hunter		self.fetching_done = fetching_done
25488392b74bSAdrian Hunter		self.process_target = process_target
25498392b74bSAdrian Hunter		self.wait_event = wait_event
25508392b74bSAdrian Hunter		self.fetched_event = fetched_event
25518392b74bSAdrian Hunter		self.prep = prep
25528392b74bSAdrian Hunter		self.query = QSqlQuery(self.db)
25538392b74bSAdrian Hunter		self.query_limit = 0 if "$$last_id$$" in sql else 2
25548392b74bSAdrian Hunter		self.last_id = -1
25558392b74bSAdrian Hunter		self.fetched = 0
25568392b74bSAdrian Hunter		self.more = True
25578392b74bSAdrian Hunter		self.local_head = self.head.value
25588392b74bSAdrian Hunter		self.local_tail = self.tail.value
25598392b74bSAdrian Hunter
25608392b74bSAdrian Hunter	def Select(self):
25618392b74bSAdrian Hunter		if self.query_limit:
25628392b74bSAdrian Hunter			if self.query_limit == 1:
25638392b74bSAdrian Hunter				return
25648392b74bSAdrian Hunter			self.query_limit -= 1
25658392b74bSAdrian Hunter		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
25668392b74bSAdrian Hunter		QueryExec(self.query, stmt)
25678392b74bSAdrian Hunter
25688392b74bSAdrian Hunter	def Next(self):
25698392b74bSAdrian Hunter		if not self.query.next():
25708392b74bSAdrian Hunter			self.Select()
25718392b74bSAdrian Hunter			if not self.query.next():
25728392b74bSAdrian Hunter				return None
25738392b74bSAdrian Hunter		self.last_id = self.query.value(0)
25748392b74bSAdrian Hunter		return self.prep(self.query)
25758392b74bSAdrian Hunter
25768392b74bSAdrian Hunter	def WaitForTarget(self):
25778392b74bSAdrian Hunter		while True:
25788392b74bSAdrian Hunter			self.wait_event.clear()
25798392b74bSAdrian Hunter			target = self.process_target.value
25808392b74bSAdrian Hunter			if target > self.fetched or target < 0:
25818392b74bSAdrian Hunter				break
25828392b74bSAdrian Hunter			self.wait_event.wait()
25838392b74bSAdrian Hunter		return target
25848392b74bSAdrian Hunter
25858392b74bSAdrian Hunter	def HasSpace(self, sz):
25868392b74bSAdrian Hunter		if self.local_tail <= self.local_head:
25878392b74bSAdrian Hunter			space = len(self.buffer) - self.local_head
25888392b74bSAdrian Hunter			if space > sz:
25898392b74bSAdrian Hunter				return True
25908392b74bSAdrian Hunter			if space >= glb_nsz:
25918392b74bSAdrian Hunter				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
2592beda0e72STony Jones				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
25938392b74bSAdrian Hunter				self.buffer[self.local_head : self.local_head + len(nd)] = nd
25948392b74bSAdrian Hunter			self.local_head = 0
25958392b74bSAdrian Hunter		if self.local_tail - self.local_head > sz:
25968392b74bSAdrian Hunter			return True
25978392b74bSAdrian Hunter		return False
25988392b74bSAdrian Hunter
25998392b74bSAdrian Hunter	def WaitForSpace(self, sz):
26008392b74bSAdrian Hunter		if self.HasSpace(sz):
26018392b74bSAdrian Hunter			return
26028392b74bSAdrian Hunter		while True:
26038392b74bSAdrian Hunter			self.wait_event.clear()
26048392b74bSAdrian Hunter			self.local_tail = self.tail.value
26058392b74bSAdrian Hunter			if self.HasSpace(sz):
26068392b74bSAdrian Hunter				return
26078392b74bSAdrian Hunter			self.wait_event.wait()
26088392b74bSAdrian Hunter
26098392b74bSAdrian Hunter	def AddToBuffer(self, obj):
2610beda0e72STony Jones		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
26118392b74bSAdrian Hunter		n = len(d)
2612beda0e72STony Jones		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
26138392b74bSAdrian Hunter		sz = n + glb_nsz
26148392b74bSAdrian Hunter		self.WaitForSpace(sz)
26158392b74bSAdrian Hunter		pos = self.local_head
26168392b74bSAdrian Hunter		self.buffer[pos : pos + len(nd)] = nd
26178392b74bSAdrian Hunter		self.buffer[pos + glb_nsz : pos + sz] = d
26188392b74bSAdrian Hunter		self.local_head += sz
26198392b74bSAdrian Hunter
26208392b74bSAdrian Hunter	def FetchBatch(self, batch_size):
26218392b74bSAdrian Hunter		fetched = 0
26228392b74bSAdrian Hunter		while batch_size > fetched:
26238392b74bSAdrian Hunter			obj = self.Next()
26248392b74bSAdrian Hunter			if obj is None:
26258392b74bSAdrian Hunter				self.more = False
26268392b74bSAdrian Hunter				break
26278392b74bSAdrian Hunter			self.AddToBuffer(obj)
26288392b74bSAdrian Hunter			fetched += 1
26298392b74bSAdrian Hunter		if fetched:
26308392b74bSAdrian Hunter			self.fetched += fetched
26318392b74bSAdrian Hunter			with self.fetch_count.get_lock():
26328392b74bSAdrian Hunter				self.fetch_count.value += fetched
26338392b74bSAdrian Hunter			self.head.value = self.local_head
26348392b74bSAdrian Hunter			self.fetched_event.set()
26358392b74bSAdrian Hunter
26368392b74bSAdrian Hunter	def Run(self):
26378392b74bSAdrian Hunter		while self.more:
26388392b74bSAdrian Hunter			target = self.WaitForTarget()
26398392b74bSAdrian Hunter			if target < 0:
26408392b74bSAdrian Hunter				break
26418392b74bSAdrian Hunter			batch_size = min(glb_chunk_sz, target - self.fetched)
26428392b74bSAdrian Hunter			self.FetchBatch(batch_size)
26438392b74bSAdrian Hunter		self.fetching_done.value = True
26448392b74bSAdrian Hunter		self.fetched_event.set()
26458392b74bSAdrian Hunter
26468392b74bSAdrian Hunterdef SQLFetcherFn(*x):
26478392b74bSAdrian Hunter	process = SQLFetcherProcess(*x)
26488392b74bSAdrian Hunter	process.Run()
26498392b74bSAdrian Hunter
26508392b74bSAdrian Hunter# SQL data fetcher
26518392b74bSAdrian Hunter
26528392b74bSAdrian Hunterclass SQLFetcher(QObject):
26538392b74bSAdrian Hunter
26548392b74bSAdrian Hunter	done = Signal(object)
26558392b74bSAdrian Hunter
26568392b74bSAdrian Hunter	def __init__(self, glb, sql, prep, process_data, parent=None):
26578392b74bSAdrian Hunter		super(SQLFetcher, self).__init__(parent)
26588392b74bSAdrian Hunter		self.process_data = process_data
26598392b74bSAdrian Hunter		self.more = True
26608392b74bSAdrian Hunter		self.target = 0
26618392b74bSAdrian Hunter		self.last_target = 0
26628392b74bSAdrian Hunter		self.fetched = 0
26638392b74bSAdrian Hunter		self.buffer_size = 16 * 1024 * 1024
26648392b74bSAdrian Hunter		self.buffer = Array(c_char, self.buffer_size, lock=False)
26658392b74bSAdrian Hunter		self.head = Value(c_longlong)
26668392b74bSAdrian Hunter		self.tail = Value(c_longlong)
26678392b74bSAdrian Hunter		self.local_tail = 0
26688392b74bSAdrian Hunter		self.fetch_count = Value(c_longlong)
26698392b74bSAdrian Hunter		self.fetching_done = Value(c_bool)
26708392b74bSAdrian Hunter		self.last_count = 0
26718392b74bSAdrian Hunter		self.process_target = Value(c_longlong)
26728392b74bSAdrian Hunter		self.wait_event = Event()
26738392b74bSAdrian Hunter		self.fetched_event = Event()
26748392b74bSAdrian Hunter		glb.AddInstanceToShutdownOnExit(self)
26758392b74bSAdrian 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))
26768392b74bSAdrian Hunter		self.process.start()
26778392b74bSAdrian Hunter		self.thread = Thread(self.Thread)
26788392b74bSAdrian Hunter		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
26798392b74bSAdrian Hunter		self.thread.start()
26808392b74bSAdrian Hunter
26818392b74bSAdrian Hunter	def Shutdown(self):
26828392b74bSAdrian Hunter		# Tell the thread and process to exit
26838392b74bSAdrian Hunter		self.process_target.value = -1
26848392b74bSAdrian Hunter		self.wait_event.set()
26858392b74bSAdrian Hunter		self.more = False
26868392b74bSAdrian Hunter		self.fetching_done.value = True
26878392b74bSAdrian Hunter		self.fetched_event.set()
26888392b74bSAdrian Hunter
26898392b74bSAdrian Hunter	def Thread(self):
26908392b74bSAdrian Hunter		if not self.more:
26918392b74bSAdrian Hunter			return True, 0
26928392b74bSAdrian Hunter		while True:
26938392b74bSAdrian Hunter			self.fetched_event.clear()
26948392b74bSAdrian Hunter			fetch_count = self.fetch_count.value
26958392b74bSAdrian Hunter			if fetch_count != self.last_count:
26968392b74bSAdrian Hunter				break
26978392b74bSAdrian Hunter			if self.fetching_done.value:
26988392b74bSAdrian Hunter				self.more = False
26998392b74bSAdrian Hunter				return True, 0
27008392b74bSAdrian Hunter			self.fetched_event.wait()
27018392b74bSAdrian Hunter		count = fetch_count - self.last_count
27028392b74bSAdrian Hunter		self.last_count = fetch_count
27038392b74bSAdrian Hunter		self.fetched += count
27048392b74bSAdrian Hunter		return False, count
27058392b74bSAdrian Hunter
27068392b74bSAdrian Hunter	def Fetch(self, nr):
27078392b74bSAdrian Hunter		if not self.more:
27088392b74bSAdrian Hunter			# -1 inidcates there are no more
27098392b74bSAdrian Hunter			return -1
27108392b74bSAdrian Hunter		result = self.fetched
27118392b74bSAdrian Hunter		extra = result + nr - self.target
27128392b74bSAdrian Hunter		if extra > 0:
27138392b74bSAdrian Hunter			self.target += extra
27148392b74bSAdrian Hunter			# process_target < 0 indicates shutting down
27158392b74bSAdrian Hunter			if self.process_target.value >= 0:
27168392b74bSAdrian Hunter				self.process_target.value = self.target
27178392b74bSAdrian Hunter			self.wait_event.set()
27188392b74bSAdrian Hunter		return result
27198392b74bSAdrian Hunter
27208392b74bSAdrian Hunter	def RemoveFromBuffer(self):
27218392b74bSAdrian Hunter		pos = self.local_tail
27228392b74bSAdrian Hunter		if len(self.buffer) - pos < glb_nsz:
27238392b74bSAdrian Hunter			pos = 0
2724beda0e72STony Jones		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
27258392b74bSAdrian Hunter		if n == 0:
27268392b74bSAdrian Hunter			pos = 0
2727beda0e72STony Jones			n = pickle.loads(self.buffer[0 : glb_nsz])
27288392b74bSAdrian Hunter		pos += glb_nsz
2729beda0e72STony Jones		obj = pickle.loads(self.buffer[pos : pos + n])
27308392b74bSAdrian Hunter		self.local_tail = pos + n
27318392b74bSAdrian Hunter		return obj
27328392b74bSAdrian Hunter
27338392b74bSAdrian Hunter	def ProcessData(self, count):
27348392b74bSAdrian Hunter		for i in xrange(count):
27358392b74bSAdrian Hunter			obj = self.RemoveFromBuffer()
27368392b74bSAdrian Hunter			self.process_data(obj)
27378392b74bSAdrian Hunter		self.tail.value = self.local_tail
27388392b74bSAdrian Hunter		self.wait_event.set()
27398392b74bSAdrian Hunter		self.done.emit(count)
27408392b74bSAdrian Hunter
27418392b74bSAdrian Hunter# Fetch more records bar
27428392b74bSAdrian Hunter
27438392b74bSAdrian Hunterclass FetchMoreRecordsBar():
27448392b74bSAdrian Hunter
27458392b74bSAdrian Hunter	def __init__(self, model, parent):
27468392b74bSAdrian Hunter		self.model = model
27478392b74bSAdrian Hunter
27488392b74bSAdrian Hunter		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
27498392b74bSAdrian Hunter		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
27508392b74bSAdrian Hunter
27518392b74bSAdrian Hunter		self.fetch_count = QSpinBox()
27528392b74bSAdrian Hunter		self.fetch_count.setRange(1, 1000000)
27538392b74bSAdrian Hunter		self.fetch_count.setValue(10)
27548392b74bSAdrian Hunter		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
27558392b74bSAdrian Hunter
27568392b74bSAdrian Hunter		self.fetch = QPushButton("Go!")
27578392b74bSAdrian Hunter		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
27588392b74bSAdrian Hunter		self.fetch.released.connect(self.FetchMoreRecords)
27598392b74bSAdrian Hunter
27608392b74bSAdrian Hunter		self.progress = QProgressBar()
27618392b74bSAdrian Hunter		self.progress.setRange(0, 100)
27628392b74bSAdrian Hunter		self.progress.hide()
27638392b74bSAdrian Hunter
27648392b74bSAdrian Hunter		self.done_label = QLabel("All records fetched")
27658392b74bSAdrian Hunter		self.done_label.hide()
27668392b74bSAdrian Hunter
27678392b74bSAdrian Hunter		self.spacer = QLabel("")
27688392b74bSAdrian Hunter
27698392b74bSAdrian Hunter		self.close_button = QToolButton()
27708392b74bSAdrian Hunter		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
27718392b74bSAdrian Hunter		self.close_button.released.connect(self.Deactivate)
27728392b74bSAdrian Hunter
27738392b74bSAdrian Hunter		self.hbox = QHBoxLayout()
27748392b74bSAdrian Hunter		self.hbox.setContentsMargins(0, 0, 0, 0)
27758392b74bSAdrian Hunter
27768392b74bSAdrian Hunter		self.hbox.addWidget(self.label)
27778392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch_count)
27788392b74bSAdrian Hunter		self.hbox.addWidget(self.fetch)
27798392b74bSAdrian Hunter		self.hbox.addWidget(self.spacer)
27808392b74bSAdrian Hunter		self.hbox.addWidget(self.progress)
27818392b74bSAdrian Hunter		self.hbox.addWidget(self.done_label)
27828392b74bSAdrian Hunter		self.hbox.addWidget(self.close_button)
27838392b74bSAdrian Hunter
27848392b74bSAdrian Hunter		self.bar = QWidget()
278526688729SAdrian Hunter		self.bar.setLayout(self.hbox)
27868392b74bSAdrian Hunter		self.bar.show()
27878392b74bSAdrian Hunter
27888392b74bSAdrian Hunter		self.in_progress = False
27898392b74bSAdrian Hunter		self.model.progress.connect(self.Progress)
27908392b74bSAdrian Hunter
27918392b74bSAdrian Hunter		self.done = False
27928392b74bSAdrian Hunter
27938392b74bSAdrian Hunter		if not model.HasMoreRecords():
27948392b74bSAdrian Hunter			self.Done()
27958392b74bSAdrian Hunter
27968392b74bSAdrian Hunter	def Widget(self):
27978392b74bSAdrian Hunter		return self.bar
27988392b74bSAdrian Hunter
27998392b74bSAdrian Hunter	def Activate(self):
28008392b74bSAdrian Hunter		self.bar.show()
28018392b74bSAdrian Hunter		self.fetch.setFocus()
28028392b74bSAdrian Hunter
28038392b74bSAdrian Hunter	def Deactivate(self):
28048392b74bSAdrian Hunter		self.bar.hide()
28058392b74bSAdrian Hunter
28068392b74bSAdrian Hunter	def Enable(self, enable):
28078392b74bSAdrian Hunter		self.fetch.setEnabled(enable)
28088392b74bSAdrian Hunter		self.fetch_count.setEnabled(enable)
28098392b74bSAdrian Hunter
28108392b74bSAdrian Hunter	def Busy(self):
28118392b74bSAdrian Hunter		self.Enable(False)
28128392b74bSAdrian Hunter		self.fetch.hide()
28138392b74bSAdrian Hunter		self.spacer.hide()
28148392b74bSAdrian Hunter		self.progress.show()
28158392b74bSAdrian Hunter
28168392b74bSAdrian Hunter	def Idle(self):
28178392b74bSAdrian Hunter		self.in_progress = False
28188392b74bSAdrian Hunter		self.Enable(True)
28198392b74bSAdrian Hunter		self.progress.hide()
28208392b74bSAdrian Hunter		self.fetch.show()
28218392b74bSAdrian Hunter		self.spacer.show()
28228392b74bSAdrian Hunter
28238392b74bSAdrian Hunter	def Target(self):
28248392b74bSAdrian Hunter		return self.fetch_count.value() * glb_chunk_sz
28258392b74bSAdrian Hunter
28268392b74bSAdrian Hunter	def Done(self):
28278392b74bSAdrian Hunter		self.done = True
28288392b74bSAdrian Hunter		self.Idle()
28298392b74bSAdrian Hunter		self.label.hide()
28308392b74bSAdrian Hunter		self.fetch_count.hide()
28318392b74bSAdrian Hunter		self.fetch.hide()
28328392b74bSAdrian Hunter		self.spacer.hide()
28338392b74bSAdrian Hunter		self.done_label.show()
28348392b74bSAdrian Hunter
28358392b74bSAdrian Hunter	def Progress(self, count):
28368392b74bSAdrian Hunter		if self.in_progress:
28378392b74bSAdrian Hunter			if count:
28388392b74bSAdrian Hunter				percent = ((count - self.start) * 100) / self.Target()
28398392b74bSAdrian Hunter				if percent >= 100:
28408392b74bSAdrian Hunter					self.Idle()
28418392b74bSAdrian Hunter				else:
28428392b74bSAdrian Hunter					self.progress.setValue(percent)
28438392b74bSAdrian Hunter		if not count:
28448392b74bSAdrian Hunter			# Count value of zero means no more records
28458392b74bSAdrian Hunter			self.Done()
28468392b74bSAdrian Hunter
28478392b74bSAdrian Hunter	def FetchMoreRecords(self):
28488392b74bSAdrian Hunter		if self.done:
28498392b74bSAdrian Hunter			return
28508392b74bSAdrian Hunter		self.progress.setValue(0)
28518392b74bSAdrian Hunter		self.Busy()
28528392b74bSAdrian Hunter		self.in_progress = True
28538392b74bSAdrian Hunter		self.start = self.model.FetchMoreRecords(self.Target())
28548392b74bSAdrian Hunter
285576099f98SAdrian Hunter# Brance data model level two item
285676099f98SAdrian Hunter
285776099f98SAdrian Hunterclass BranchLevelTwoItem():
285876099f98SAdrian Hunter
2859530e22fdSAdrian Hunter	def __init__(self, row, col, text, parent_item):
286076099f98SAdrian Hunter		self.row = row
286176099f98SAdrian Hunter		self.parent_item = parent_item
2862530e22fdSAdrian Hunter		self.data = [""] * (col + 1)
2863530e22fdSAdrian Hunter		self.data[col] = text
286476099f98SAdrian Hunter		self.level = 2
286576099f98SAdrian Hunter
286676099f98SAdrian Hunter	def getParentItem(self):
286776099f98SAdrian Hunter		return self.parent_item
286876099f98SAdrian Hunter
286976099f98SAdrian Hunter	def getRow(self):
287076099f98SAdrian Hunter		return self.row
287176099f98SAdrian Hunter
287276099f98SAdrian Hunter	def childCount(self):
287376099f98SAdrian Hunter		return 0
287476099f98SAdrian Hunter
287576099f98SAdrian Hunter	def hasChildren(self):
287676099f98SAdrian Hunter		return False
287776099f98SAdrian Hunter
287876099f98SAdrian Hunter	def getData(self, column):
287976099f98SAdrian Hunter		return self.data[column]
288076099f98SAdrian Hunter
288176099f98SAdrian Hunter# Brance data model level one item
288276099f98SAdrian Hunter
288376099f98SAdrian Hunterclass BranchLevelOneItem():
288476099f98SAdrian Hunter
288576099f98SAdrian Hunter	def __init__(self, glb, row, data, parent_item):
288676099f98SAdrian Hunter		self.glb = glb
288776099f98SAdrian Hunter		self.row = row
288876099f98SAdrian Hunter		self.parent_item = parent_item
288976099f98SAdrian Hunter		self.child_count = 0
289076099f98SAdrian Hunter		self.child_items = []
289176099f98SAdrian Hunter		self.data = data[1:]
289276099f98SAdrian Hunter		self.dbid = data[0]
289376099f98SAdrian Hunter		self.level = 1
289476099f98SAdrian Hunter		self.query_done = False
2895530e22fdSAdrian Hunter		self.br_col = len(self.data) - 1
289676099f98SAdrian Hunter
289776099f98SAdrian Hunter	def getChildItem(self, row):
289876099f98SAdrian Hunter		return self.child_items[row]
289976099f98SAdrian Hunter
290076099f98SAdrian Hunter	def getParentItem(self):
290176099f98SAdrian Hunter		return self.parent_item
290276099f98SAdrian Hunter
290376099f98SAdrian Hunter	def getRow(self):
290476099f98SAdrian Hunter		return self.row
290576099f98SAdrian Hunter
290676099f98SAdrian Hunter	def Select(self):
290776099f98SAdrian Hunter		self.query_done = True
290876099f98SAdrian Hunter
290976099f98SAdrian Hunter		if not self.glb.have_disassembler:
291076099f98SAdrian Hunter			return
291176099f98SAdrian Hunter
291276099f98SAdrian Hunter		query = QSqlQuery(self.glb.db)
291376099f98SAdrian Hunter
291476099f98SAdrian Hunter		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
291576099f98SAdrian Hunter				  " FROM samples"
291676099f98SAdrian Hunter				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
291776099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
291876099f98SAdrian Hunter				  " WHERE samples.id = " + str(self.dbid))
291976099f98SAdrian Hunter		if not query.next():
292076099f98SAdrian Hunter			return
292176099f98SAdrian Hunter		cpu = query.value(0)
292276099f98SAdrian Hunter		dso = query.value(1)
292376099f98SAdrian Hunter		sym = query.value(2)
292476099f98SAdrian Hunter		if dso == 0 or sym == 0:
292576099f98SAdrian Hunter			return
292676099f98SAdrian Hunter		off = query.value(3)
292776099f98SAdrian Hunter		short_name = query.value(4)
292876099f98SAdrian Hunter		long_name = query.value(5)
292976099f98SAdrian Hunter		build_id = query.value(6)
293076099f98SAdrian Hunter		sym_start = query.value(7)
293176099f98SAdrian Hunter		ip = query.value(8)
293276099f98SAdrian Hunter
293376099f98SAdrian Hunter		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
293476099f98SAdrian Hunter				  " FROM samples"
293576099f98SAdrian Hunter				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
293676099f98SAdrian Hunter				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
293776099f98SAdrian Hunter				  " ORDER BY samples.id"
293876099f98SAdrian Hunter				  " LIMIT 1")
293976099f98SAdrian Hunter		if not query.next():
294076099f98SAdrian Hunter			return
294176099f98SAdrian Hunter		if query.value(0) != dso:
294276099f98SAdrian Hunter			# Cannot disassemble from one dso to another
294376099f98SAdrian Hunter			return
294476099f98SAdrian Hunter		bsym = query.value(1)
294576099f98SAdrian Hunter		boff = query.value(2)
294676099f98SAdrian Hunter		bsym_start = query.value(3)
294776099f98SAdrian Hunter		if bsym == 0:
294876099f98SAdrian Hunter			return
294976099f98SAdrian Hunter		tot = bsym_start + boff + 1 - sym_start - off
295076099f98SAdrian Hunter		if tot <= 0 or tot > 16384:
295176099f98SAdrian Hunter			return
295276099f98SAdrian Hunter
295376099f98SAdrian Hunter		inst = self.glb.disassembler.Instruction()
295476099f98SAdrian Hunter		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
295576099f98SAdrian Hunter		if not f:
295676099f98SAdrian Hunter			return
295776099f98SAdrian Hunter		mode = 0 if Is64Bit(f) else 1
295876099f98SAdrian Hunter		self.glb.disassembler.SetMode(inst, mode)
295976099f98SAdrian Hunter
296076099f98SAdrian Hunter		buf_sz = tot + 16
296176099f98SAdrian Hunter		buf = create_string_buffer(tot + 16)
296276099f98SAdrian Hunter		f.seek(sym_start + off)
296376099f98SAdrian Hunter		buf.value = f.read(buf_sz)
296476099f98SAdrian Hunter		buf_ptr = addressof(buf)
296576099f98SAdrian Hunter		i = 0
296676099f98SAdrian Hunter		while tot > 0:
296776099f98SAdrian Hunter			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
296876099f98SAdrian Hunter			if cnt:
296976099f98SAdrian Hunter				byte_str = tohex(ip).rjust(16)
297076099f98SAdrian Hunter				for k in xrange(cnt):
297176099f98SAdrian Hunter					byte_str += " %02x" % ord(buf[i])
297276099f98SAdrian Hunter					i += 1
297376099f98SAdrian Hunter				while k < 15:
297476099f98SAdrian Hunter					byte_str += "   "
297576099f98SAdrian Hunter					k += 1
2976530e22fdSAdrian Hunter				self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
297776099f98SAdrian Hunter				self.child_count += 1
297876099f98SAdrian Hunter			else:
297976099f98SAdrian Hunter				return
298076099f98SAdrian Hunter			buf_ptr += cnt
298176099f98SAdrian Hunter			tot -= cnt
298276099f98SAdrian Hunter			buf_sz -= cnt
298376099f98SAdrian Hunter			ip += cnt
298476099f98SAdrian Hunter
298576099f98SAdrian Hunter	def childCount(self):
298676099f98SAdrian Hunter		if not self.query_done:
298776099f98SAdrian Hunter			self.Select()
298876099f98SAdrian Hunter			if not self.child_count:
298976099f98SAdrian Hunter				return -1
299076099f98SAdrian Hunter		return self.child_count
299176099f98SAdrian Hunter
299276099f98SAdrian Hunter	def hasChildren(self):
299376099f98SAdrian Hunter		if not self.query_done:
299476099f98SAdrian Hunter			return True
299576099f98SAdrian Hunter		return self.child_count > 0
299676099f98SAdrian Hunter
299776099f98SAdrian Hunter	def getData(self, column):
299876099f98SAdrian Hunter		return self.data[column]
299976099f98SAdrian Hunter
300076099f98SAdrian Hunter# Brance data model root item
300176099f98SAdrian Hunter
300276099f98SAdrian Hunterclass BranchRootItem():
300376099f98SAdrian Hunter
300476099f98SAdrian Hunter	def __init__(self):
300576099f98SAdrian Hunter		self.child_count = 0
300676099f98SAdrian Hunter		self.child_items = []
300776099f98SAdrian Hunter		self.level = 0
300876099f98SAdrian Hunter
300976099f98SAdrian Hunter	def getChildItem(self, row):
301076099f98SAdrian Hunter		return self.child_items[row]
301176099f98SAdrian Hunter
301276099f98SAdrian Hunter	def getParentItem(self):
301376099f98SAdrian Hunter		return None
301476099f98SAdrian Hunter
301576099f98SAdrian Hunter	def getRow(self):
301676099f98SAdrian Hunter		return 0
301776099f98SAdrian Hunter
301876099f98SAdrian Hunter	def childCount(self):
301976099f98SAdrian Hunter		return self.child_count
302076099f98SAdrian Hunter
302176099f98SAdrian Hunter	def hasChildren(self):
302276099f98SAdrian Hunter		return self.child_count > 0
302376099f98SAdrian Hunter
302476099f98SAdrian Hunter	def getData(self, column):
302576099f98SAdrian Hunter		return ""
302676099f98SAdrian Hunter
3027530e22fdSAdrian Hunter# Calculate instructions per cycle
3028530e22fdSAdrian Hunter
3029530e22fdSAdrian Hunterdef CalcIPC(cyc_cnt, insn_cnt):
3030530e22fdSAdrian Hunter	if cyc_cnt and insn_cnt:
3031530e22fdSAdrian Hunter		ipc = Decimal(float(insn_cnt) / cyc_cnt)
3032530e22fdSAdrian Hunter		ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
3033530e22fdSAdrian Hunter	else:
3034530e22fdSAdrian Hunter		ipc = "0"
3035530e22fdSAdrian Hunter	return ipc
3036530e22fdSAdrian Hunter
303776099f98SAdrian Hunter# Branch data preparation
303876099f98SAdrian Hunter
3039530e22fdSAdrian Hunterdef BranchDataPrepBr(query, data):
3040530e22fdSAdrian Hunter	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
3041530e22fdSAdrian Hunter			" (" + dsoname(query.value(11)) + ")" + " -> " +
3042530e22fdSAdrian Hunter			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
3043530e22fdSAdrian Hunter			" (" + dsoname(query.value(15)) + ")")
3044530e22fdSAdrian Hunter
3045530e22fdSAdrian Hunterdef BranchDataPrepIPC(query, data):
3046530e22fdSAdrian Hunter	insn_cnt = query.value(16)
3047530e22fdSAdrian Hunter	cyc_cnt = query.value(17)
3048530e22fdSAdrian Hunter	ipc = CalcIPC(cyc_cnt, insn_cnt)
3049530e22fdSAdrian Hunter	data.append(insn_cnt)
3050530e22fdSAdrian Hunter	data.append(cyc_cnt)
3051530e22fdSAdrian Hunter	data.append(ipc)
3052530e22fdSAdrian Hunter
305376099f98SAdrian Hunterdef BranchDataPrep(query):
305476099f98SAdrian Hunter	data = []
305576099f98SAdrian Hunter	for i in xrange(0, 8):
305676099f98SAdrian Hunter		data.append(query.value(i))
3057530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
305876099f98SAdrian Hunter	return data
305976099f98SAdrian Hunter
30608453c936SAdrian Hunterdef BranchDataPrepWA(query):
30618453c936SAdrian Hunter	data = []
30628453c936SAdrian Hunter	data.append(query.value(0))
30638453c936SAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
30648453c936SAdrian Hunter	data.append("{:>19}".format(query.value(1)))
30658453c936SAdrian Hunter	for i in xrange(2, 8):
30668453c936SAdrian Hunter		data.append(query.value(i))
3067530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
3068530e22fdSAdrian Hunter	return data
3069530e22fdSAdrian Hunter
3070530e22fdSAdrian Hunterdef BranchDataWithIPCPrep(query):
3071530e22fdSAdrian Hunter	data = []
3072530e22fdSAdrian Hunter	for i in xrange(0, 8):
3073530e22fdSAdrian Hunter		data.append(query.value(i))
3074530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
3075530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
3076530e22fdSAdrian Hunter	return data
3077530e22fdSAdrian Hunter
3078530e22fdSAdrian Hunterdef BranchDataWithIPCPrepWA(query):
3079530e22fdSAdrian Hunter	data = []
3080530e22fdSAdrian Hunter	data.append(query.value(0))
3081530e22fdSAdrian Hunter	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3082530e22fdSAdrian Hunter	data.append("{:>19}".format(query.value(1)))
3083530e22fdSAdrian Hunter	for i in xrange(2, 8):
3084530e22fdSAdrian Hunter		data.append(query.value(i))
3085530e22fdSAdrian Hunter	BranchDataPrepIPC(query, data)
3086530e22fdSAdrian Hunter	BranchDataPrepBr(query, data)
30878453c936SAdrian Hunter	return data
30888453c936SAdrian Hunter
308976099f98SAdrian Hunter# Branch data model
309076099f98SAdrian Hunter
309176099f98SAdrian Hunterclass BranchModel(TreeModel):
309276099f98SAdrian Hunter
309376099f98SAdrian Hunter	progress = Signal(object)
309476099f98SAdrian Hunter
309576099f98SAdrian Hunter	def __init__(self, glb, event_id, where_clause, parent=None):
30964a0979d4SAdrian Hunter		super(BranchModel, self).__init__(glb, None, parent)
309776099f98SAdrian Hunter		self.event_id = event_id
309876099f98SAdrian Hunter		self.more = True
309976099f98SAdrian Hunter		self.populated = 0
3100530e22fdSAdrian Hunter		self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
3101530e22fdSAdrian Hunter		if self.have_ipc:
3102530e22fdSAdrian Hunter			select_ipc = ", insn_count, cyc_count"
3103530e22fdSAdrian Hunter			prep_fn = BranchDataWithIPCPrep
3104530e22fdSAdrian Hunter			prep_wa_fn = BranchDataWithIPCPrepWA
3105530e22fdSAdrian Hunter		else:
3106530e22fdSAdrian Hunter			select_ipc = ""
3107530e22fdSAdrian Hunter			prep_fn = BranchDataPrep
3108530e22fdSAdrian Hunter			prep_wa_fn = BranchDataPrepWA
310976099f98SAdrian Hunter		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
311076099f98SAdrian Hunter			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
311176099f98SAdrian Hunter			" ip, symbols.name, sym_offset, dsos.short_name,"
311276099f98SAdrian Hunter			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
3113530e22fdSAdrian Hunter			+ select_ipc +
311476099f98SAdrian Hunter			" FROM samples"
311576099f98SAdrian Hunter			" INNER JOIN comms ON comm_id = comms.id"
311676099f98SAdrian Hunter			" INNER JOIN threads ON thread_id = threads.id"
311776099f98SAdrian Hunter			" INNER JOIN branch_types ON branch_type = branch_types.id"
311876099f98SAdrian Hunter			" INNER JOIN symbols ON symbol_id = symbols.id"
311976099f98SAdrian Hunter			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
312076099f98SAdrian Hunter			" INNER JOIN dsos ON samples.dso_id = dsos.id"
312176099f98SAdrian Hunter			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
312276099f98SAdrian Hunter			" WHERE samples.id > $$last_id$$" + where_clause +
312376099f98SAdrian Hunter			" AND evsel_id = " + str(self.event_id) +
312476099f98SAdrian Hunter			" ORDER BY samples.id"
312576099f98SAdrian Hunter			" LIMIT " + str(glb_chunk_sz))
31268453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
3127530e22fdSAdrian Hunter			prep = prep_fn
31288453c936SAdrian Hunter		else:
3129530e22fdSAdrian Hunter			prep = prep_wa_fn
31308453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
313176099f98SAdrian Hunter		self.fetcher.done.connect(self.Update)
313276099f98SAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
313376099f98SAdrian Hunter
3134a448ba23SAdrian Hunter	def GetRoot(self):
3135a448ba23SAdrian Hunter		return BranchRootItem()
3136a448ba23SAdrian Hunter
313776099f98SAdrian Hunter	def columnCount(self, parent=None):
3138530e22fdSAdrian Hunter		if self.have_ipc:
3139530e22fdSAdrian Hunter			return 11
3140530e22fdSAdrian Hunter		else:
314176099f98SAdrian Hunter			return 8
314276099f98SAdrian Hunter
314376099f98SAdrian Hunter	def columnHeader(self, column):
3144530e22fdSAdrian Hunter		if self.have_ipc:
3145530e22fdSAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
3146530e22fdSAdrian Hunter		else:
314776099f98SAdrian Hunter			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
314876099f98SAdrian Hunter
314976099f98SAdrian Hunter	def columnFont(self, column):
3150530e22fdSAdrian Hunter		if self.have_ipc:
3151530e22fdSAdrian Hunter			br_col = 10
3152530e22fdSAdrian Hunter		else:
3153530e22fdSAdrian Hunter			br_col = 7
3154530e22fdSAdrian Hunter		if column != br_col:
315576099f98SAdrian Hunter			return None
315676099f98SAdrian Hunter		return QFont("Monospace")
315776099f98SAdrian Hunter
315876099f98SAdrian Hunter	def DisplayData(self, item, index):
315976099f98SAdrian Hunter		if item.level == 1:
316076099f98SAdrian Hunter			self.FetchIfNeeded(item.row)
316176099f98SAdrian Hunter		return item.getData(index.column())
316276099f98SAdrian Hunter
316376099f98SAdrian Hunter	def AddSample(self, data):
316476099f98SAdrian Hunter		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
316576099f98SAdrian Hunter		self.root.child_items.append(child)
316676099f98SAdrian Hunter		self.populated += 1
316776099f98SAdrian Hunter
316876099f98SAdrian Hunter	def Update(self, fetched):
316976099f98SAdrian Hunter		if not fetched:
317076099f98SAdrian Hunter			self.more = False
317176099f98SAdrian Hunter			self.progress.emit(0)
317276099f98SAdrian Hunter		child_count = self.root.child_count
317376099f98SAdrian Hunter		count = self.populated - child_count
317476099f98SAdrian Hunter		if count > 0:
317576099f98SAdrian Hunter			parent = QModelIndex()
317676099f98SAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
317776099f98SAdrian Hunter			self.insertRows(child_count, count, parent)
317876099f98SAdrian Hunter			self.root.child_count += count
317976099f98SAdrian Hunter			self.endInsertRows()
318076099f98SAdrian Hunter			self.progress.emit(self.root.child_count)
318176099f98SAdrian Hunter
318276099f98SAdrian Hunter	def FetchMoreRecords(self, count):
318376099f98SAdrian Hunter		current = self.root.child_count
318476099f98SAdrian Hunter		if self.more:
318576099f98SAdrian Hunter			self.fetcher.Fetch(count)
318676099f98SAdrian Hunter		else:
318776099f98SAdrian Hunter			self.progress.emit(0)
318876099f98SAdrian Hunter		return current
318976099f98SAdrian Hunter
319076099f98SAdrian Hunter	def HasMoreRecords(self):
319176099f98SAdrian Hunter		return self.more
319276099f98SAdrian Hunter
31930bf0947aSAdrian Hunter# Report Variables
31940bf0947aSAdrian Hunter
31950bf0947aSAdrian Hunterclass ReportVars():
31960bf0947aSAdrian Hunter
3197cd358012SAdrian Hunter	def __init__(self, name = "", where_clause = "", limit = ""):
3198947cc38dSAdrian Hunter		self.name = name
31990bf0947aSAdrian Hunter		self.where_clause = where_clause
3200cd358012SAdrian Hunter		self.limit = limit
32010bf0947aSAdrian Hunter
32020bf0947aSAdrian Hunter	def UniqueId(self):
3203cd358012SAdrian Hunter		return str(self.where_clause + ";" + self.limit)
32040bf0947aSAdrian Hunter
320576099f98SAdrian Hunter# Branch window
320676099f98SAdrian Hunter
320776099f98SAdrian Hunterclass BranchWindow(QMdiSubWindow):
320876099f98SAdrian Hunter
3209947cc38dSAdrian Hunter	def __init__(self, glb, event_id, report_vars, parent=None):
321076099f98SAdrian Hunter		super(BranchWindow, self).__init__(parent)
321176099f98SAdrian Hunter
32120bf0947aSAdrian Hunter		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
321376099f98SAdrian Hunter
32140bf0947aSAdrian Hunter		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
321576099f98SAdrian Hunter
321676099f98SAdrian Hunter		self.view = QTreeView()
321776099f98SAdrian Hunter		self.view.setUniformRowHeights(True)
321896c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
321996c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
322076099f98SAdrian Hunter		self.view.setModel(self.model)
322176099f98SAdrian Hunter
322276099f98SAdrian Hunter		self.ResizeColumnsToContents()
322376099f98SAdrian Hunter
32249bc4e4bfSAdrian Hunter		self.context_menu = TreeContextMenu(self.view)
32259bc4e4bfSAdrian Hunter
322676099f98SAdrian Hunter		self.find_bar = FindBar(self, self, True)
322776099f98SAdrian Hunter
322876099f98SAdrian Hunter		self.finder = ChildDataItemFinder(self.model.root)
322976099f98SAdrian Hunter
323076099f98SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
323176099f98SAdrian Hunter
323276099f98SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
323376099f98SAdrian Hunter
323476099f98SAdrian Hunter		self.setWidget(self.vbox.Widget())
323576099f98SAdrian Hunter
3236947cc38dSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
323776099f98SAdrian Hunter
323876099f98SAdrian Hunter	def ResizeColumnToContents(self, column, n):
323976099f98SAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
324076099f98SAdrian Hunter		# so implement a crude alternative
324176099f98SAdrian Hunter		mm = "MM" if column else "MMMM"
324276099f98SAdrian Hunter		font = self.view.font()
324376099f98SAdrian Hunter		metrics = QFontMetrics(font)
324476099f98SAdrian Hunter		max = 0
324576099f98SAdrian Hunter		for row in xrange(n):
324676099f98SAdrian Hunter			val = self.model.root.child_items[row].data[column]
324776099f98SAdrian Hunter			len = metrics.width(str(val) + mm)
324876099f98SAdrian Hunter			max = len if len > max else max
324976099f98SAdrian Hunter		val = self.model.columnHeader(column)
325076099f98SAdrian Hunter		len = metrics.width(str(val) + mm)
325176099f98SAdrian Hunter		max = len if len > max else max
325276099f98SAdrian Hunter		self.view.setColumnWidth(column, max)
325376099f98SAdrian Hunter
325476099f98SAdrian Hunter	def ResizeColumnsToContents(self):
325576099f98SAdrian Hunter		n = min(self.model.root.child_count, 100)
325676099f98SAdrian Hunter		if n < 1:
325776099f98SAdrian Hunter			# No data yet, so connect a signal to notify when there is
325876099f98SAdrian Hunter			self.model.rowsInserted.connect(self.UpdateColumnWidths)
325976099f98SAdrian Hunter			return
326076099f98SAdrian Hunter		columns = self.model.columnCount()
326176099f98SAdrian Hunter		for i in xrange(columns):
326276099f98SAdrian Hunter			self.ResizeColumnToContents(i, n)
326376099f98SAdrian Hunter
326476099f98SAdrian Hunter	def UpdateColumnWidths(self, *x):
326576099f98SAdrian Hunter		# This only needs to be done once, so disconnect the signal now
326676099f98SAdrian Hunter		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
326776099f98SAdrian Hunter		self.ResizeColumnsToContents()
326876099f98SAdrian Hunter
326976099f98SAdrian Hunter	def Find(self, value, direction, pattern, context):
327076099f98SAdrian Hunter		self.view.setFocus()
327176099f98SAdrian Hunter		self.find_bar.Busy()
327276099f98SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
327376099f98SAdrian Hunter
327476099f98SAdrian Hunter	def FindDone(self, row):
327576099f98SAdrian Hunter		self.find_bar.Idle()
327676099f98SAdrian Hunter		if row >= 0:
327776099f98SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
327876099f98SAdrian Hunter		else:
327976099f98SAdrian Hunter			self.find_bar.NotFound()
328076099f98SAdrian Hunter
32811c3ca1b3SAdrian Hunter# Line edit data item
32821c3ca1b3SAdrian Hunter
32831c3ca1b3SAdrian Hunterclass LineEditDataItem(object):
32841c3ca1b3SAdrian Hunter
3285cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
32861c3ca1b3SAdrian Hunter		self.glb = glb
32871c3ca1b3SAdrian Hunter		self.label = label
32881c3ca1b3SAdrian Hunter		self.placeholder_text = placeholder_text
32891c3ca1b3SAdrian Hunter		self.parent = parent
32901c3ca1b3SAdrian Hunter		self.id = id
32911c3ca1b3SAdrian Hunter
3292cd358012SAdrian Hunter		self.value = default
32931c3ca1b3SAdrian Hunter
3294cd358012SAdrian Hunter		self.widget = QLineEdit(default)
32951c3ca1b3SAdrian Hunter		self.widget.editingFinished.connect(self.Validate)
32961c3ca1b3SAdrian Hunter		self.widget.textChanged.connect(self.Invalidate)
32971c3ca1b3SAdrian Hunter		self.red = False
32981c3ca1b3SAdrian Hunter		self.error = ""
32991c3ca1b3SAdrian Hunter		self.validated = True
33001c3ca1b3SAdrian Hunter
33011c3ca1b3SAdrian Hunter		if placeholder_text:
33021c3ca1b3SAdrian Hunter			self.widget.setPlaceholderText(placeholder_text)
33031c3ca1b3SAdrian Hunter
33041c3ca1b3SAdrian Hunter	def TurnTextRed(self):
33051c3ca1b3SAdrian Hunter		if not self.red:
33061c3ca1b3SAdrian Hunter			palette = QPalette()
33071c3ca1b3SAdrian Hunter			palette.setColor(QPalette.Text,Qt.red)
33081c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
33091c3ca1b3SAdrian Hunter			self.red = True
33101c3ca1b3SAdrian Hunter
33111c3ca1b3SAdrian Hunter	def TurnTextNormal(self):
33121c3ca1b3SAdrian Hunter		if self.red:
33131c3ca1b3SAdrian Hunter			palette = QPalette()
33141c3ca1b3SAdrian Hunter			self.widget.setPalette(palette)
33151c3ca1b3SAdrian Hunter			self.red = False
33161c3ca1b3SAdrian Hunter
33171c3ca1b3SAdrian Hunter	def InvalidValue(self, value):
33181c3ca1b3SAdrian Hunter		self.value = ""
33191c3ca1b3SAdrian Hunter		self.TurnTextRed()
33201c3ca1b3SAdrian Hunter		self.error = self.label + " invalid value '" + value + "'"
33211c3ca1b3SAdrian Hunter		self.parent.ShowMessage(self.error)
33221c3ca1b3SAdrian Hunter
33231c3ca1b3SAdrian Hunter	def Invalidate(self):
33241c3ca1b3SAdrian Hunter		self.validated = False
33251c3ca1b3SAdrian Hunter
33261c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
33271c3ca1b3SAdrian Hunter		self.value = input_string.strip()
33281c3ca1b3SAdrian Hunter
33291c3ca1b3SAdrian Hunter	def Validate(self):
33301c3ca1b3SAdrian Hunter		self.validated = True
33311c3ca1b3SAdrian Hunter		self.error = ""
33321c3ca1b3SAdrian Hunter		self.TurnTextNormal()
33331c3ca1b3SAdrian Hunter		self.parent.ClearMessage()
33341c3ca1b3SAdrian Hunter		input_string = self.widget.text()
33351c3ca1b3SAdrian Hunter		if not len(input_string.strip()):
33361c3ca1b3SAdrian Hunter			self.value = ""
33371c3ca1b3SAdrian Hunter			return
33381c3ca1b3SAdrian Hunter		self.DoValidate(input_string)
33391c3ca1b3SAdrian Hunter
33401c3ca1b3SAdrian Hunter	def IsValid(self):
33411c3ca1b3SAdrian Hunter		if not self.validated:
33421c3ca1b3SAdrian Hunter			self.Validate()
33431c3ca1b3SAdrian Hunter		if len(self.error):
33441c3ca1b3SAdrian Hunter			self.parent.ShowMessage(self.error)
33451c3ca1b3SAdrian Hunter			return False
33461c3ca1b3SAdrian Hunter		return True
33471c3ca1b3SAdrian Hunter
33481c3ca1b3SAdrian Hunter	def IsNumber(self, value):
33491c3ca1b3SAdrian Hunter		try:
33501c3ca1b3SAdrian Hunter			x = int(value)
33511c3ca1b3SAdrian Hunter		except:
33521c3ca1b3SAdrian Hunter			x = 0
33531c3ca1b3SAdrian Hunter		return str(x) == value
33541c3ca1b3SAdrian Hunter
33551c3ca1b3SAdrian Hunter# Non-negative integer ranges dialog data item
33561c3ca1b3SAdrian Hunter
33571c3ca1b3SAdrian Hunterclass NonNegativeIntegerRangesDataItem(LineEditDataItem):
33581c3ca1b3SAdrian Hunter
33591c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
33601c3ca1b3SAdrian Hunter		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
33611c3ca1b3SAdrian Hunter
33621c3ca1b3SAdrian Hunter		self.column_name = column_name
33631c3ca1b3SAdrian Hunter
33641c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
33651c3ca1b3SAdrian Hunter		singles = []
33661c3ca1b3SAdrian Hunter		ranges = []
33671c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
33681c3ca1b3SAdrian Hunter			if "-" in value:
33691c3ca1b3SAdrian Hunter				vrange = value.split("-")
33701c3ca1b3SAdrian Hunter				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
33711c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
33721c3ca1b3SAdrian Hunter				ranges.append(vrange)
33731c3ca1b3SAdrian Hunter			else:
33741c3ca1b3SAdrian Hunter				if not self.IsNumber(value):
33751c3ca1b3SAdrian Hunter					return self.InvalidValue(value)
33761c3ca1b3SAdrian Hunter				singles.append(value)
33771c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
33781c3ca1b3SAdrian Hunter		if len(singles):
33791c3ca1b3SAdrian Hunter			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
33801c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
33811c3ca1b3SAdrian Hunter
3382cd358012SAdrian Hunter# Positive integer dialog data item
3383cd358012SAdrian Hunter
3384cd358012SAdrian Hunterclass PositiveIntegerDataItem(LineEditDataItem):
3385cd358012SAdrian Hunter
3386cd358012SAdrian Hunter	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
3387cd358012SAdrian Hunter		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
3388cd358012SAdrian Hunter
3389cd358012SAdrian Hunter	def DoValidate(self, input_string):
3390cd358012SAdrian Hunter		if not self.IsNumber(input_string.strip()):
3391cd358012SAdrian Hunter			return self.InvalidValue(input_string)
3392cd358012SAdrian Hunter		value = int(input_string.strip())
3393cd358012SAdrian Hunter		if value <= 0:
3394cd358012SAdrian Hunter			return self.InvalidValue(input_string)
3395cd358012SAdrian Hunter		self.value = str(value)
3396cd358012SAdrian Hunter
33971c3ca1b3SAdrian Hunter# Dialog data item converted and validated using a SQL table
33981c3ca1b3SAdrian Hunter
33991c3ca1b3SAdrian Hunterclass SQLTableDataItem(LineEditDataItem):
34001c3ca1b3SAdrian Hunter
34011c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
34021c3ca1b3SAdrian Hunter		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
34031c3ca1b3SAdrian Hunter
34041c3ca1b3SAdrian Hunter		self.table_name = table_name
34051c3ca1b3SAdrian Hunter		self.match_column = match_column
34061c3ca1b3SAdrian Hunter		self.column_name1 = column_name1
34071c3ca1b3SAdrian Hunter		self.column_name2 = column_name2
34081c3ca1b3SAdrian Hunter
34091c3ca1b3SAdrian Hunter	def ValueToIds(self, value):
34101c3ca1b3SAdrian Hunter		ids = []
34111c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
34121c3ca1b3SAdrian Hunter		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
34131c3ca1b3SAdrian Hunter		ret = query.exec_(stmt)
34141c3ca1b3SAdrian Hunter		if ret:
34151c3ca1b3SAdrian Hunter			while query.next():
34161c3ca1b3SAdrian Hunter				ids.append(str(query.value(0)))
34171c3ca1b3SAdrian Hunter		return ids
34181c3ca1b3SAdrian Hunter
34191c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
34201c3ca1b3SAdrian Hunter		all_ids = []
34211c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
34221c3ca1b3SAdrian Hunter			ids = self.ValueToIds(value)
34231c3ca1b3SAdrian Hunter			if len(ids):
34241c3ca1b3SAdrian Hunter				all_ids.extend(ids)
34251c3ca1b3SAdrian Hunter			else:
34261c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
34271c3ca1b3SAdrian Hunter		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
34281c3ca1b3SAdrian Hunter		if self.column_name2:
34291c3ca1b3SAdrian Hunter			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
34301c3ca1b3SAdrian Hunter
34311c3ca1b3SAdrian Hunter# Sample time ranges dialog data item converted and validated using 'samples' SQL table
34321c3ca1b3SAdrian Hunter
34331c3ca1b3SAdrian Hunterclass SampleTimeRangesDataItem(LineEditDataItem):
34341c3ca1b3SAdrian Hunter
34351c3ca1b3SAdrian Hunter	def __init__(self, glb, label, placeholder_text, column_name, parent):
34361c3ca1b3SAdrian Hunter		self.column_name = column_name
34371c3ca1b3SAdrian Hunter
34381c3ca1b3SAdrian Hunter		self.last_id = 0
34391c3ca1b3SAdrian Hunter		self.first_time = 0
34401c3ca1b3SAdrian Hunter		self.last_time = 2 ** 64
34411c3ca1b3SAdrian Hunter
34421c3ca1b3SAdrian Hunter		query = QSqlQuery(glb.db)
34431c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
34441c3ca1b3SAdrian Hunter		if query.next():
34451c3ca1b3SAdrian Hunter			self.last_id = int(query.value(0))
34469a9dae36SAdrian Hunter		self.first_time = int(glb.HostStartTime())
34479a9dae36SAdrian Hunter		self.last_time = int(glb.HostFinishTime())
34481c3ca1b3SAdrian Hunter		if placeholder_text:
34491c3ca1b3SAdrian Hunter			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
34501c3ca1b3SAdrian Hunter
34511c3ca1b3SAdrian Hunter		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
34521c3ca1b3SAdrian Hunter
34531c3ca1b3SAdrian Hunter	def IdBetween(self, query, lower_id, higher_id, order):
34541c3ca1b3SAdrian Hunter		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
34551c3ca1b3SAdrian Hunter		if query.next():
34561c3ca1b3SAdrian Hunter			return True, int(query.value(0))
34571c3ca1b3SAdrian Hunter		else:
34581c3ca1b3SAdrian Hunter			return False, 0
34591c3ca1b3SAdrian Hunter
34601c3ca1b3SAdrian Hunter	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
34611c3ca1b3SAdrian Hunter		query = QSqlQuery(self.glb.db)
34621c3ca1b3SAdrian Hunter		while True:
34631c3ca1b3SAdrian Hunter			next_id = int((lower_id + higher_id) / 2)
34641c3ca1b3SAdrian Hunter			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
34651c3ca1b3SAdrian Hunter			if not query.next():
34661c3ca1b3SAdrian Hunter				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
34671c3ca1b3SAdrian Hunter				if not ok:
34681c3ca1b3SAdrian Hunter					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
34691c3ca1b3SAdrian Hunter					if not ok:
34701c3ca1b3SAdrian Hunter						return str(higher_id)
34711c3ca1b3SAdrian Hunter				next_id = dbid
34721c3ca1b3SAdrian Hunter				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
34731c3ca1b3SAdrian Hunter			next_time = int(query.value(0))
34741c3ca1b3SAdrian Hunter			if get_floor:
34751c3ca1b3SAdrian Hunter				if target_time > next_time:
34761c3ca1b3SAdrian Hunter					lower_id = next_id
34771c3ca1b3SAdrian Hunter				else:
34781c3ca1b3SAdrian Hunter					higher_id = next_id
34791c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
34801c3ca1b3SAdrian Hunter					return str(higher_id)
34811c3ca1b3SAdrian Hunter			else:
34821c3ca1b3SAdrian Hunter				if target_time >= next_time:
34831c3ca1b3SAdrian Hunter					lower_id = next_id
34841c3ca1b3SAdrian Hunter				else:
34851c3ca1b3SAdrian Hunter					higher_id = next_id
34861c3ca1b3SAdrian Hunter				if higher_id <= lower_id + 1:
34871c3ca1b3SAdrian Hunter					return str(lower_id)
34881c3ca1b3SAdrian Hunter
34891c3ca1b3SAdrian Hunter	def ConvertRelativeTime(self, val):
34901c3ca1b3SAdrian Hunter		mult = 1
34911c3ca1b3SAdrian Hunter		suffix = val[-2:]
34921c3ca1b3SAdrian Hunter		if suffix == "ms":
34931c3ca1b3SAdrian Hunter			mult = 1000000
34941c3ca1b3SAdrian Hunter		elif suffix == "us":
34951c3ca1b3SAdrian Hunter			mult = 1000
34961c3ca1b3SAdrian Hunter		elif suffix == "ns":
34971c3ca1b3SAdrian Hunter			mult = 1
34981c3ca1b3SAdrian Hunter		else:
34991c3ca1b3SAdrian Hunter			return val
35001c3ca1b3SAdrian Hunter		val = val[:-2].strip()
35011c3ca1b3SAdrian Hunter		if not self.IsNumber(val):
35021c3ca1b3SAdrian Hunter			return val
35031c3ca1b3SAdrian Hunter		val = int(val) * mult
35041c3ca1b3SAdrian Hunter		if val >= 0:
35051c3ca1b3SAdrian Hunter			val += self.first_time
35061c3ca1b3SAdrian Hunter		else:
35071c3ca1b3SAdrian Hunter			val += self.last_time
35081c3ca1b3SAdrian Hunter		return str(val)
35091c3ca1b3SAdrian Hunter
35101c3ca1b3SAdrian Hunter	def ConvertTimeRange(self, vrange):
35111c3ca1b3SAdrian Hunter		if vrange[0] == "":
35121c3ca1b3SAdrian Hunter			vrange[0] = str(self.first_time)
35131c3ca1b3SAdrian Hunter		if vrange[1] == "":
35141c3ca1b3SAdrian Hunter			vrange[1] = str(self.last_time)
35151c3ca1b3SAdrian Hunter		vrange[0] = self.ConvertRelativeTime(vrange[0])
35161c3ca1b3SAdrian Hunter		vrange[1] = self.ConvertRelativeTime(vrange[1])
35171c3ca1b3SAdrian Hunter		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
35181c3ca1b3SAdrian Hunter			return False
35191c3ca1b3SAdrian Hunter		beg_range = max(int(vrange[0]), self.first_time)
35201c3ca1b3SAdrian Hunter		end_range = min(int(vrange[1]), self.last_time)
35211c3ca1b3SAdrian Hunter		if beg_range > self.last_time or end_range < self.first_time:
35221c3ca1b3SAdrian Hunter			return False
35231c3ca1b3SAdrian Hunter		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
35241c3ca1b3SAdrian Hunter		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
35251c3ca1b3SAdrian Hunter		return True
35261c3ca1b3SAdrian Hunter
35271c3ca1b3SAdrian Hunter	def AddTimeRange(self, value, ranges):
35281c3ca1b3SAdrian Hunter		n = value.count("-")
35291c3ca1b3SAdrian Hunter		if n == 1:
35301c3ca1b3SAdrian Hunter			pass
35311c3ca1b3SAdrian Hunter		elif n == 2:
35321c3ca1b3SAdrian Hunter			if value.split("-")[1].strip() == "":
35331c3ca1b3SAdrian Hunter				n = 1
35341c3ca1b3SAdrian Hunter		elif n == 3:
35351c3ca1b3SAdrian Hunter			n = 2
35361c3ca1b3SAdrian Hunter		else:
35371c3ca1b3SAdrian Hunter			return False
35381c3ca1b3SAdrian Hunter		pos = findnth(value, "-", n)
35391c3ca1b3SAdrian Hunter		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
35401c3ca1b3SAdrian Hunter		if self.ConvertTimeRange(vrange):
35411c3ca1b3SAdrian Hunter			ranges.append(vrange)
35421c3ca1b3SAdrian Hunter			return True
35431c3ca1b3SAdrian Hunter		return False
35441c3ca1b3SAdrian Hunter
35451c3ca1b3SAdrian Hunter	def DoValidate(self, input_string):
35461c3ca1b3SAdrian Hunter		ranges = []
35471c3ca1b3SAdrian Hunter		for value in [x.strip() for x in input_string.split(",")]:
35481c3ca1b3SAdrian Hunter			if not self.AddTimeRange(value, ranges):
35491c3ca1b3SAdrian Hunter				return self.InvalidValue(value)
35501c3ca1b3SAdrian Hunter		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
35511c3ca1b3SAdrian Hunter		self.value = " OR ".join(ranges)
35521c3ca1b3SAdrian Hunter
35530924cd68SAdrian Hunter# Report Dialog Base
3554210cf1f9SAdrian Hunter
35550924cd68SAdrian Hunterclass ReportDialogBase(QDialog):
3556210cf1f9SAdrian Hunter
35570924cd68SAdrian Hunter	def __init__(self, glb, title, items, partial, parent=None):
35580924cd68SAdrian Hunter		super(ReportDialogBase, self).__init__(parent)
3559210cf1f9SAdrian Hunter
3560210cf1f9SAdrian Hunter		self.glb = glb
3561210cf1f9SAdrian Hunter
35620bf0947aSAdrian Hunter		self.report_vars = ReportVars()
3563210cf1f9SAdrian Hunter
35640924cd68SAdrian Hunter		self.setWindowTitle(title)
3565210cf1f9SAdrian Hunter		self.setMinimumWidth(600)
3566210cf1f9SAdrian Hunter
35671c3ca1b3SAdrian Hunter		self.data_items = [x(glb, self) for x in items]
3568210cf1f9SAdrian Hunter
35690924cd68SAdrian Hunter		self.partial = partial
35700924cd68SAdrian Hunter
3571210cf1f9SAdrian Hunter		self.grid = QGridLayout()
3572210cf1f9SAdrian Hunter
3573210cf1f9SAdrian Hunter		for row in xrange(len(self.data_items)):
3574210cf1f9SAdrian Hunter			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
3575210cf1f9SAdrian Hunter			self.grid.addWidget(self.data_items[row].widget, row, 1)
3576210cf1f9SAdrian Hunter
3577210cf1f9SAdrian Hunter		self.status = QLabel()
3578210cf1f9SAdrian Hunter
3579210cf1f9SAdrian Hunter		self.ok_button = QPushButton("Ok", self)
3580210cf1f9SAdrian Hunter		self.ok_button.setDefault(True)
3581210cf1f9SAdrian Hunter		self.ok_button.released.connect(self.Ok)
3582210cf1f9SAdrian Hunter		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
3583210cf1f9SAdrian Hunter
3584210cf1f9SAdrian Hunter		self.cancel_button = QPushButton("Cancel", self)
3585210cf1f9SAdrian Hunter		self.cancel_button.released.connect(self.reject)
3586210cf1f9SAdrian Hunter		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
3587210cf1f9SAdrian Hunter
3588210cf1f9SAdrian Hunter		self.hbox = QHBoxLayout()
3589210cf1f9SAdrian Hunter		#self.hbox.addStretch()
3590210cf1f9SAdrian Hunter		self.hbox.addWidget(self.status)
3591210cf1f9SAdrian Hunter		self.hbox.addWidget(self.ok_button)
3592210cf1f9SAdrian Hunter		self.hbox.addWidget(self.cancel_button)
3593210cf1f9SAdrian Hunter
3594210cf1f9SAdrian Hunter		self.vbox = QVBoxLayout()
3595210cf1f9SAdrian Hunter		self.vbox.addLayout(self.grid)
3596210cf1f9SAdrian Hunter		self.vbox.addLayout(self.hbox)
3597210cf1f9SAdrian Hunter
359826688729SAdrian Hunter		self.setLayout(self.vbox)
3599210cf1f9SAdrian Hunter
3600210cf1f9SAdrian Hunter	def Ok(self):
36010bf0947aSAdrian Hunter		vars = self.report_vars
36021c3ca1b3SAdrian Hunter		for d in self.data_items:
36031c3ca1b3SAdrian Hunter			if d.id == "REPORTNAME":
36041c3ca1b3SAdrian Hunter				vars.name = d.value
3605947cc38dSAdrian Hunter		if not vars.name:
3606210cf1f9SAdrian Hunter			self.ShowMessage("Report name is required")
3607210cf1f9SAdrian Hunter			return
3608210cf1f9SAdrian Hunter		for d in self.data_items:
3609210cf1f9SAdrian Hunter			if not d.IsValid():
3610210cf1f9SAdrian Hunter				return
3611210cf1f9SAdrian Hunter		for d in self.data_items[1:]:
3612cd358012SAdrian Hunter			if d.id == "LIMIT":
3613cd358012SAdrian Hunter				vars.limit = d.value
3614cd358012SAdrian Hunter			elif len(d.value):
36150bf0947aSAdrian Hunter				if len(vars.where_clause):
36160bf0947aSAdrian Hunter					vars.where_clause += " AND "
36170bf0947aSAdrian Hunter				vars.where_clause += d.value
36180bf0947aSAdrian Hunter		if len(vars.where_clause):
36190924cd68SAdrian Hunter			if self.partial:
36200bf0947aSAdrian Hunter				vars.where_clause = " AND ( " + vars.where_clause + " ) "
3621210cf1f9SAdrian Hunter			else:
36220bf0947aSAdrian Hunter				vars.where_clause = " WHERE " + vars.where_clause + " "
3623210cf1f9SAdrian Hunter		self.accept()
3624210cf1f9SAdrian Hunter
3625210cf1f9SAdrian Hunter	def ShowMessage(self, msg):
3626210cf1f9SAdrian Hunter		self.status.setText("<font color=#FF0000>" + msg)
3627210cf1f9SAdrian Hunter
3628210cf1f9SAdrian Hunter	def ClearMessage(self):
3629210cf1f9SAdrian Hunter		self.status.setText("")
3630210cf1f9SAdrian Hunter
36310924cd68SAdrian Hunter# Selected branch report creation dialog
36320924cd68SAdrian Hunter
36330924cd68SAdrian Hunterclass SelectedBranchDialog(ReportDialogBase):
36340924cd68SAdrian Hunter
36350924cd68SAdrian Hunter	def __init__(self, glb, parent=None):
36360924cd68SAdrian Hunter		title = "Selected Branches"
36371c3ca1b3SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
36381c3ca1b3SAdrian Hunter			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
36391c3ca1b3SAdrian Hunter			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
36401c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
36411c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
36421c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
36431c3ca1b3SAdrian 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),
36441c3ca1b3SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
36451c3ca1b3SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
36460924cd68SAdrian Hunter		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
36470924cd68SAdrian Hunter
364876099f98SAdrian Hunter# Event list
364976099f98SAdrian Hunter
365076099f98SAdrian Hunterdef GetEventList(db):
365176099f98SAdrian Hunter	events = []
365276099f98SAdrian Hunter	query = QSqlQuery(db)
365376099f98SAdrian Hunter	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
365476099f98SAdrian Hunter	while query.next():
365576099f98SAdrian Hunter		events.append(query.value(0))
365676099f98SAdrian Hunter	return events
365776099f98SAdrian Hunter
3658655cb952SAdrian Hunter# Is a table selectable
3659655cb952SAdrian Hunter
3660530e22fdSAdrian Hunterdef IsSelectable(db, table, sql = "", columns = "*"):
3661655cb952SAdrian Hunter	query = QSqlQuery(db)
3662655cb952SAdrian Hunter	try:
3663530e22fdSAdrian Hunter		QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
3664655cb952SAdrian Hunter	except:
3665655cb952SAdrian Hunter		return False
3666655cb952SAdrian Hunter	return True
3667655cb952SAdrian Hunter
36688392b74bSAdrian Hunter# SQL table data model item
36698392b74bSAdrian Hunter
36708392b74bSAdrian Hunterclass SQLTableItem():
36718392b74bSAdrian Hunter
36728392b74bSAdrian Hunter	def __init__(self, row, data):
36738392b74bSAdrian Hunter		self.row = row
36748392b74bSAdrian Hunter		self.data = data
36758392b74bSAdrian Hunter
36768392b74bSAdrian Hunter	def getData(self, column):
36778392b74bSAdrian Hunter		return self.data[column]
36788392b74bSAdrian Hunter
36798392b74bSAdrian Hunter# SQL table data model
36808392b74bSAdrian Hunter
36818392b74bSAdrian Hunterclass SQLTableModel(TableModel):
36828392b74bSAdrian Hunter
36838392b74bSAdrian Hunter	progress = Signal(object)
36848392b74bSAdrian Hunter
36858c90fef9SAdrian Hunter	def __init__(self, glb, sql, column_headers, parent=None):
36868392b74bSAdrian Hunter		super(SQLTableModel, self).__init__(parent)
36878392b74bSAdrian Hunter		self.glb = glb
36888392b74bSAdrian Hunter		self.more = True
36898392b74bSAdrian Hunter		self.populated = 0
36908c90fef9SAdrian Hunter		self.column_headers = column_headers
36918453c936SAdrian Hunter		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
36928392b74bSAdrian Hunter		self.fetcher.done.connect(self.Update)
36938392b74bSAdrian Hunter		self.fetcher.Fetch(glb_chunk_sz)
36948392b74bSAdrian Hunter
36958392b74bSAdrian Hunter	def DisplayData(self, item, index):
36968392b74bSAdrian Hunter		self.FetchIfNeeded(item.row)
36978392b74bSAdrian Hunter		return item.getData(index.column())
36988392b74bSAdrian Hunter
36998392b74bSAdrian Hunter	def AddSample(self, data):
37008392b74bSAdrian Hunter		child = SQLTableItem(self.populated, data)
37018392b74bSAdrian Hunter		self.child_items.append(child)
37028392b74bSAdrian Hunter		self.populated += 1
37038392b74bSAdrian Hunter
37048392b74bSAdrian Hunter	def Update(self, fetched):
37058392b74bSAdrian Hunter		if not fetched:
37068392b74bSAdrian Hunter			self.more = False
37078392b74bSAdrian Hunter			self.progress.emit(0)
37088392b74bSAdrian Hunter		child_count = self.child_count
37098392b74bSAdrian Hunter		count = self.populated - child_count
37108392b74bSAdrian Hunter		if count > 0:
37118392b74bSAdrian Hunter			parent = QModelIndex()
37128392b74bSAdrian Hunter			self.beginInsertRows(parent, child_count, child_count + count - 1)
37138392b74bSAdrian Hunter			self.insertRows(child_count, count, parent)
37148392b74bSAdrian Hunter			self.child_count += count
37158392b74bSAdrian Hunter			self.endInsertRows()
37168392b74bSAdrian Hunter			self.progress.emit(self.child_count)
37178392b74bSAdrian Hunter
37188392b74bSAdrian Hunter	def FetchMoreRecords(self, count):
37198392b74bSAdrian Hunter		current = self.child_count
37208392b74bSAdrian Hunter		if self.more:
37218392b74bSAdrian Hunter			self.fetcher.Fetch(count)
37228392b74bSAdrian Hunter		else:
37238392b74bSAdrian Hunter			self.progress.emit(0)
37248392b74bSAdrian Hunter		return current
37258392b74bSAdrian Hunter
37268392b74bSAdrian Hunter	def HasMoreRecords(self):
37278392b74bSAdrian Hunter		return self.more
37288392b74bSAdrian Hunter
37298c90fef9SAdrian Hunter	def columnCount(self, parent=None):
37308c90fef9SAdrian Hunter		return len(self.column_headers)
37318c90fef9SAdrian Hunter
37328c90fef9SAdrian Hunter	def columnHeader(self, column):
37338c90fef9SAdrian Hunter		return self.column_headers[column]
37348c90fef9SAdrian Hunter
37358453c936SAdrian Hunter	def SQLTableDataPrep(self, query, count):
37368453c936SAdrian Hunter		data = []
37378453c936SAdrian Hunter		for i in xrange(count):
37388453c936SAdrian Hunter			data.append(query.value(i))
37398453c936SAdrian Hunter		return data
37408453c936SAdrian Hunter
37418392b74bSAdrian Hunter# SQL automatic table data model
37428392b74bSAdrian Hunter
37438392b74bSAdrian Hunterclass SQLAutoTableModel(SQLTableModel):
37448392b74bSAdrian Hunter
37458392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
37468392b74bSAdrian Hunter		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
37478392b74bSAdrian Hunter		if table_name == "comm_threads_view":
37488392b74bSAdrian Hunter			# For now, comm_threads_view has no id column
37498392b74bSAdrian Hunter			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
37508c90fef9SAdrian Hunter		column_headers = []
37518392b74bSAdrian Hunter		query = QSqlQuery(glb.db)
37528392b74bSAdrian Hunter		if glb.dbref.is_sqlite3:
37538392b74bSAdrian Hunter			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
37548392b74bSAdrian Hunter			while query.next():
37558c90fef9SAdrian Hunter				column_headers.append(query.value(1))
37568392b74bSAdrian Hunter			if table_name == "sqlite_master":
37578392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
37588392b74bSAdrian Hunter		else:
37598392b74bSAdrian Hunter			if table_name[:19] == "information_schema.":
37608392b74bSAdrian Hunter				sql = "SELECT * FROM " + table_name
37618392b74bSAdrian Hunter				select_table_name = table_name[19:]
37628392b74bSAdrian Hunter				schema = "information_schema"
37638392b74bSAdrian Hunter			else:
37648392b74bSAdrian Hunter				select_table_name = table_name
37658392b74bSAdrian Hunter				schema = "public"
37668392b74bSAdrian Hunter			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
37678392b74bSAdrian Hunter			while query.next():
37688c90fef9SAdrian Hunter				column_headers.append(query.value(0))
37698453c936SAdrian Hunter		if pyside_version_1 and sys.version_info[0] == 3:
37708453c936SAdrian Hunter			if table_name == "samples_view":
37718453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_view_DataPrep
37728453c936SAdrian Hunter			if table_name == "samples":
37738453c936SAdrian Hunter				self.SQLTableDataPrep = self.samples_DataPrep
37748c90fef9SAdrian Hunter		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
37758392b74bSAdrian Hunter
37768453c936SAdrian Hunter	def samples_view_DataPrep(self, query, count):
37778453c936SAdrian Hunter		data = []
37788453c936SAdrian Hunter		data.append(query.value(0))
37798453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
37808453c936SAdrian Hunter		data.append("{:>19}".format(query.value(1)))
37818453c936SAdrian Hunter		for i in xrange(2, count):
37828453c936SAdrian Hunter			data.append(query.value(i))
37838453c936SAdrian Hunter		return data
37848453c936SAdrian Hunter
37858453c936SAdrian Hunter	def samples_DataPrep(self, query, count):
37868453c936SAdrian Hunter		data = []
37878453c936SAdrian Hunter		for i in xrange(9):
37888453c936SAdrian Hunter			data.append(query.value(i))
37898453c936SAdrian Hunter		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
37908453c936SAdrian Hunter		data.append("{:>19}".format(query.value(9)))
37918453c936SAdrian Hunter		for i in xrange(10, count):
37928453c936SAdrian Hunter			data.append(query.value(i))
37938453c936SAdrian Hunter		return data
37948453c936SAdrian Hunter
37958392b74bSAdrian Hunter# Base class for custom ResizeColumnsToContents
37968392b74bSAdrian Hunter
37978392b74bSAdrian Hunterclass ResizeColumnsToContentsBase(QObject):
37988392b74bSAdrian Hunter
37998392b74bSAdrian Hunter	def __init__(self, parent=None):
38008392b74bSAdrian Hunter		super(ResizeColumnsToContentsBase, self).__init__(parent)
38018392b74bSAdrian Hunter
38028392b74bSAdrian Hunter	def ResizeColumnToContents(self, column, n):
38038392b74bSAdrian Hunter		# Using the view's resizeColumnToContents() here is extrememly slow
38048392b74bSAdrian Hunter		# so implement a crude alternative
38058392b74bSAdrian Hunter		font = self.view.font()
38068392b74bSAdrian Hunter		metrics = QFontMetrics(font)
38078392b74bSAdrian Hunter		max = 0
38088392b74bSAdrian Hunter		for row in xrange(n):
38098392b74bSAdrian Hunter			val = self.data_model.child_items[row].data[column]
38108392b74bSAdrian Hunter			len = metrics.width(str(val) + "MM")
38118392b74bSAdrian Hunter			max = len if len > max else max
38128392b74bSAdrian Hunter		val = self.data_model.columnHeader(column)
38138392b74bSAdrian Hunter		len = metrics.width(str(val) + "MM")
38148392b74bSAdrian Hunter		max = len if len > max else max
38158392b74bSAdrian Hunter		self.view.setColumnWidth(column, max)
38168392b74bSAdrian Hunter
38178392b74bSAdrian Hunter	def ResizeColumnsToContents(self):
38188392b74bSAdrian Hunter		n = min(self.data_model.child_count, 100)
38198392b74bSAdrian Hunter		if n < 1:
38208392b74bSAdrian Hunter			# No data yet, so connect a signal to notify when there is
38218392b74bSAdrian Hunter			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
38228392b74bSAdrian Hunter			return
38238392b74bSAdrian Hunter		columns = self.data_model.columnCount()
38248392b74bSAdrian Hunter		for i in xrange(columns):
38258392b74bSAdrian Hunter			self.ResizeColumnToContents(i, n)
38268392b74bSAdrian Hunter
38278392b74bSAdrian Hunter	def UpdateColumnWidths(self, *x):
38288392b74bSAdrian Hunter		# This only needs to be done once, so disconnect the signal now
38298392b74bSAdrian Hunter		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
38308392b74bSAdrian Hunter		self.ResizeColumnsToContents()
38318392b74bSAdrian Hunter
383296c43b9aSAdrian Hunter# Convert value to CSV
383396c43b9aSAdrian Hunter
383496c43b9aSAdrian Hunterdef ToCSValue(val):
383596c43b9aSAdrian Hunter	if '"' in val:
383696c43b9aSAdrian Hunter		val = val.replace('"', '""')
383796c43b9aSAdrian Hunter	if "," in val or '"' in val:
383896c43b9aSAdrian Hunter		val = '"' + val + '"'
383996c43b9aSAdrian Hunter	return val
384096c43b9aSAdrian Hunter
384196c43b9aSAdrian Hunter# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
384296c43b9aSAdrian Hunter
384396c43b9aSAdrian Hunterglb_max_cols = 1000
384496c43b9aSAdrian Hunter
384596c43b9aSAdrian Hunterdef RowColumnKey(a):
384696c43b9aSAdrian Hunter	return a.row() * glb_max_cols + a.column()
384796c43b9aSAdrian Hunter
384896c43b9aSAdrian Hunter# Copy selected table cells to clipboard
384996c43b9aSAdrian Hunter
385096c43b9aSAdrian Hunterdef CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
385196c43b9aSAdrian Hunter	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
385296c43b9aSAdrian Hunter	idx_cnt = len(indexes)
385396c43b9aSAdrian Hunter	if not idx_cnt:
385496c43b9aSAdrian Hunter		return
385596c43b9aSAdrian Hunter	if idx_cnt == 1:
385696c43b9aSAdrian Hunter		with_hdr=False
385796c43b9aSAdrian Hunter	min_row = indexes[0].row()
385896c43b9aSAdrian Hunter	max_row = indexes[0].row()
385996c43b9aSAdrian Hunter	min_col = indexes[0].column()
386096c43b9aSAdrian Hunter	max_col = indexes[0].column()
386196c43b9aSAdrian Hunter	for i in indexes:
386296c43b9aSAdrian Hunter		min_row = min(min_row, i.row())
386396c43b9aSAdrian Hunter		max_row = max(max_row, i.row())
386496c43b9aSAdrian Hunter		min_col = min(min_col, i.column())
386596c43b9aSAdrian Hunter		max_col = max(max_col, i.column())
386696c43b9aSAdrian Hunter	if max_col > glb_max_cols:
386796c43b9aSAdrian Hunter		raise RuntimeError("glb_max_cols is too low")
386896c43b9aSAdrian Hunter	max_width = [0] * (1 + max_col - min_col)
386996c43b9aSAdrian Hunter	for i in indexes:
387096c43b9aSAdrian Hunter		c = i.column() - min_col
387196c43b9aSAdrian Hunter		max_width[c] = max(max_width[c], len(str(i.data())))
387296c43b9aSAdrian Hunter	text = ""
387396c43b9aSAdrian Hunter	pad = ""
387496c43b9aSAdrian Hunter	sep = ""
387596c43b9aSAdrian Hunter	if with_hdr:
387696c43b9aSAdrian Hunter		model = indexes[0].model()
387796c43b9aSAdrian Hunter		for col in range(min_col, max_col + 1):
3878a6172059SAdrian Hunter			val = model.headerData(col, Qt.Horizontal, Qt.DisplayRole)
387996c43b9aSAdrian Hunter			if as_csv:
388096c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
388196c43b9aSAdrian Hunter				sep = ","
388296c43b9aSAdrian Hunter			else:
388396c43b9aSAdrian Hunter				c = col - min_col
388496c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
388596c43b9aSAdrian Hunter				width = max_width[c]
388696c43b9aSAdrian Hunter				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
388796c43b9aSAdrian Hunter				if align & Qt.AlignRight:
388896c43b9aSAdrian Hunter					val = val.rjust(width)
388996c43b9aSAdrian Hunter				text += pad + sep + val
389096c43b9aSAdrian Hunter				pad = " " * (width - len(val))
389196c43b9aSAdrian Hunter				sep = "  "
389296c43b9aSAdrian Hunter		text += "\n"
389396c43b9aSAdrian Hunter		pad = ""
389496c43b9aSAdrian Hunter		sep = ""
389596c43b9aSAdrian Hunter	last_row = min_row
389696c43b9aSAdrian Hunter	for i in indexes:
389796c43b9aSAdrian Hunter		if i.row() > last_row:
389896c43b9aSAdrian Hunter			last_row = i.row()
389996c43b9aSAdrian Hunter			text += "\n"
390096c43b9aSAdrian Hunter			pad = ""
390196c43b9aSAdrian Hunter			sep = ""
390296c43b9aSAdrian Hunter		if as_csv:
390396c43b9aSAdrian Hunter			text += sep + ToCSValue(str(i.data()))
390496c43b9aSAdrian Hunter			sep = ","
390596c43b9aSAdrian Hunter		else:
390696c43b9aSAdrian Hunter			width = max_width[i.column() - min_col]
390796c43b9aSAdrian Hunter			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
390896c43b9aSAdrian Hunter				val = str(i.data()).rjust(width)
390996c43b9aSAdrian Hunter			else:
391096c43b9aSAdrian Hunter				val = str(i.data())
391196c43b9aSAdrian Hunter			text += pad + sep + val
391296c43b9aSAdrian Hunter			pad = " " * (width - len(val))
391396c43b9aSAdrian Hunter			sep = "  "
391496c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
391596c43b9aSAdrian Hunter
391696c43b9aSAdrian Hunterdef CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
391796c43b9aSAdrian Hunter	indexes = view.selectedIndexes()
391896c43b9aSAdrian Hunter	if not len(indexes):
391996c43b9aSAdrian Hunter		return
392096c43b9aSAdrian Hunter
392196c43b9aSAdrian Hunter	selection = view.selectionModel()
392296c43b9aSAdrian Hunter
392396c43b9aSAdrian Hunter	first = None
392496c43b9aSAdrian Hunter	for i in indexes:
392596c43b9aSAdrian Hunter		above = view.indexAbove(i)
392696c43b9aSAdrian Hunter		if not selection.isSelected(above):
392796c43b9aSAdrian Hunter			first = i
392896c43b9aSAdrian Hunter			break
392996c43b9aSAdrian Hunter
393096c43b9aSAdrian Hunter	if first is None:
393196c43b9aSAdrian Hunter		raise RuntimeError("CopyTreeCellsToClipboard internal error")
393296c43b9aSAdrian Hunter
393396c43b9aSAdrian Hunter	model = first.model()
393496c43b9aSAdrian Hunter	row_cnt = 0
393596c43b9aSAdrian Hunter	col_cnt = model.columnCount(first)
393696c43b9aSAdrian Hunter	max_width = [0] * col_cnt
393796c43b9aSAdrian Hunter
393896c43b9aSAdrian Hunter	indent_sz = 2
393996c43b9aSAdrian Hunter	indent_str = " " * indent_sz
394096c43b9aSAdrian Hunter
394196c43b9aSAdrian Hunter	expanded_mark_sz = 2
394296c43b9aSAdrian Hunter	if sys.version_info[0] == 3:
394396c43b9aSAdrian Hunter		expanded_mark = "\u25BC "
394496c43b9aSAdrian Hunter		not_expanded_mark = "\u25B6 "
394596c43b9aSAdrian Hunter	else:
394696c43b9aSAdrian Hunter		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
394796c43b9aSAdrian Hunter		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
394896c43b9aSAdrian Hunter	leaf_mark = "  "
394996c43b9aSAdrian Hunter
395096c43b9aSAdrian Hunter	if not as_csv:
395196c43b9aSAdrian Hunter		pos = first
395296c43b9aSAdrian Hunter		while True:
395396c43b9aSAdrian Hunter			row_cnt += 1
395496c43b9aSAdrian Hunter			row = pos.row()
395596c43b9aSAdrian Hunter			for c in range(col_cnt):
395696c43b9aSAdrian Hunter				i = pos.sibling(row, c)
395796c43b9aSAdrian Hunter				if c:
395896c43b9aSAdrian Hunter					n = len(str(i.data()))
395996c43b9aSAdrian Hunter				else:
396096c43b9aSAdrian Hunter					n = len(str(i.data()).strip())
396196c43b9aSAdrian Hunter					n += (i.internalPointer().level - 1) * indent_sz
396296c43b9aSAdrian Hunter					n += expanded_mark_sz
396396c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], n)
396496c43b9aSAdrian Hunter			pos = view.indexBelow(pos)
396596c43b9aSAdrian Hunter			if not selection.isSelected(pos):
396696c43b9aSAdrian Hunter				break
396796c43b9aSAdrian Hunter
396896c43b9aSAdrian Hunter	text = ""
396996c43b9aSAdrian Hunter	pad = ""
397096c43b9aSAdrian Hunter	sep = ""
397196c43b9aSAdrian Hunter	if with_hdr:
397296c43b9aSAdrian Hunter		for c in range(col_cnt):
397396c43b9aSAdrian Hunter			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
397496c43b9aSAdrian Hunter			if as_csv:
397596c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
397696c43b9aSAdrian Hunter				sep = ","
397796c43b9aSAdrian Hunter			else:
397896c43b9aSAdrian Hunter				max_width[c] = max(max_width[c], len(val))
397996c43b9aSAdrian Hunter				width = max_width[c]
398096c43b9aSAdrian Hunter				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
398196c43b9aSAdrian Hunter				if align & Qt.AlignRight:
398296c43b9aSAdrian Hunter					val = val.rjust(width)
398396c43b9aSAdrian Hunter				text += pad + sep + val
398496c43b9aSAdrian Hunter				pad = " " * (width - len(val))
398596c43b9aSAdrian Hunter				sep = "   "
398696c43b9aSAdrian Hunter		text += "\n"
398796c43b9aSAdrian Hunter		pad = ""
398896c43b9aSAdrian Hunter		sep = ""
398996c43b9aSAdrian Hunter
399096c43b9aSAdrian Hunter	pos = first
399196c43b9aSAdrian Hunter	while True:
399296c43b9aSAdrian Hunter		row = pos.row()
399396c43b9aSAdrian Hunter		for c in range(col_cnt):
399496c43b9aSAdrian Hunter			i = pos.sibling(row, c)
399596c43b9aSAdrian Hunter			val = str(i.data())
399696c43b9aSAdrian Hunter			if not c:
399796c43b9aSAdrian Hunter				if model.hasChildren(i):
399896c43b9aSAdrian Hunter					if view.isExpanded(i):
399996c43b9aSAdrian Hunter						mark = expanded_mark
400096c43b9aSAdrian Hunter					else:
400196c43b9aSAdrian Hunter						mark = not_expanded_mark
400296c43b9aSAdrian Hunter				else:
400396c43b9aSAdrian Hunter					mark = leaf_mark
400496c43b9aSAdrian Hunter				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
400596c43b9aSAdrian Hunter			if as_csv:
400696c43b9aSAdrian Hunter				text += sep + ToCSValue(val)
400796c43b9aSAdrian Hunter				sep = ","
400896c43b9aSAdrian Hunter			else:
400996c43b9aSAdrian Hunter				width = max_width[c]
401096c43b9aSAdrian Hunter				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
401196c43b9aSAdrian Hunter					val = val.rjust(width)
401296c43b9aSAdrian Hunter				text += pad + sep + val
401396c43b9aSAdrian Hunter				pad = " " * (width - len(val))
401496c43b9aSAdrian Hunter				sep = "   "
401596c43b9aSAdrian Hunter		pos = view.indexBelow(pos)
401696c43b9aSAdrian Hunter		if not selection.isSelected(pos):
401796c43b9aSAdrian Hunter			break
401896c43b9aSAdrian Hunter		text = text.rstrip() + "\n"
401996c43b9aSAdrian Hunter		pad = ""
402096c43b9aSAdrian Hunter		sep = ""
402196c43b9aSAdrian Hunter
402296c43b9aSAdrian Hunter	QApplication.clipboard().setText(text)
402396c43b9aSAdrian Hunter
402496c43b9aSAdrian Hunterdef CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
402596c43b9aSAdrian Hunter	view.CopyCellsToClipboard(view, as_csv, with_hdr)
402696c43b9aSAdrian Hunter
402796c43b9aSAdrian Hunterdef CopyCellsToClipboardHdr(view):
402896c43b9aSAdrian Hunter	CopyCellsToClipboard(view, False, True)
402996c43b9aSAdrian Hunter
403096c43b9aSAdrian Hunterdef CopyCellsToClipboardCSV(view):
403196c43b9aSAdrian Hunter	CopyCellsToClipboard(view, True, True)
403296c43b9aSAdrian Hunter
40339bc4e4bfSAdrian Hunter# Context menu
40349bc4e4bfSAdrian Hunter
40359bc4e4bfSAdrian Hunterclass ContextMenu(object):
40369bc4e4bfSAdrian Hunter
40379bc4e4bfSAdrian Hunter	def __init__(self, view):
40389bc4e4bfSAdrian Hunter		self.view = view
40399bc4e4bfSAdrian Hunter		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
40409bc4e4bfSAdrian Hunter		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
40419bc4e4bfSAdrian Hunter
40429bc4e4bfSAdrian Hunter	def ShowContextMenu(self, pos):
40439bc4e4bfSAdrian Hunter		menu = QMenu(self.view)
40449bc4e4bfSAdrian Hunter		self.AddActions(menu)
40459bc4e4bfSAdrian Hunter		menu.exec_(self.view.mapToGlobal(pos))
40469bc4e4bfSAdrian Hunter
40479bc4e4bfSAdrian Hunter	def AddCopy(self, menu):
40489bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
40499bc4e4bfSAdrian Hunter		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
40509bc4e4bfSAdrian Hunter
40519bc4e4bfSAdrian Hunter	def AddActions(self, menu):
40529bc4e4bfSAdrian Hunter		self.AddCopy(menu)
40539bc4e4bfSAdrian Hunter
40549bc4e4bfSAdrian Hunterclass TreeContextMenu(ContextMenu):
40559bc4e4bfSAdrian Hunter
40569bc4e4bfSAdrian Hunter	def __init__(self, view):
40579bc4e4bfSAdrian Hunter		super(TreeContextMenu, self).__init__(view)
40589bc4e4bfSAdrian Hunter
40599bc4e4bfSAdrian Hunter	def AddActions(self, menu):
40609bc4e4bfSAdrian Hunter		i = self.view.currentIndex()
40619bc4e4bfSAdrian Hunter		text = str(i.data()).strip()
40629bc4e4bfSAdrian Hunter		if len(text):
40639bc4e4bfSAdrian Hunter			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
40649bc4e4bfSAdrian Hunter		self.AddCopy(menu)
40659bc4e4bfSAdrian Hunter
40668392b74bSAdrian Hunter# Table window
40678392b74bSAdrian Hunter
40688392b74bSAdrian Hunterclass TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
40698392b74bSAdrian Hunter
40708392b74bSAdrian Hunter	def __init__(self, glb, table_name, parent=None):
40718392b74bSAdrian Hunter		super(TableWindow, self).__init__(parent)
40728392b74bSAdrian Hunter
40738392b74bSAdrian Hunter		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
40748392b74bSAdrian Hunter
40758392b74bSAdrian Hunter		self.model = QSortFilterProxyModel()
40768392b74bSAdrian Hunter		self.model.setSourceModel(self.data_model)
40778392b74bSAdrian Hunter
40788392b74bSAdrian Hunter		self.view = QTableView()
40798392b74bSAdrian Hunter		self.view.setModel(self.model)
40808392b74bSAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
40818392b74bSAdrian Hunter		self.view.verticalHeader().setVisible(False)
40828392b74bSAdrian Hunter		self.view.sortByColumn(-1, Qt.AscendingOrder)
40838392b74bSAdrian Hunter		self.view.setSortingEnabled(True)
408496c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
408596c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
40868392b74bSAdrian Hunter
40878392b74bSAdrian Hunter		self.ResizeColumnsToContents()
40888392b74bSAdrian Hunter
40899bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
40909bc4e4bfSAdrian Hunter
40918392b74bSAdrian Hunter		self.find_bar = FindBar(self, self, True)
40928392b74bSAdrian Hunter
40938392b74bSAdrian Hunter		self.finder = ChildDataItemFinder(self.data_model)
40948392b74bSAdrian Hunter
40958392b74bSAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
40968392b74bSAdrian Hunter
40978392b74bSAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
40988392b74bSAdrian Hunter
40998392b74bSAdrian Hunter		self.setWidget(self.vbox.Widget())
41008392b74bSAdrian Hunter
41018392b74bSAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
41028392b74bSAdrian Hunter
41038392b74bSAdrian Hunter	def Find(self, value, direction, pattern, context):
41048392b74bSAdrian Hunter		self.view.setFocus()
41058392b74bSAdrian Hunter		self.find_bar.Busy()
41068392b74bSAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
41078392b74bSAdrian Hunter
41088392b74bSAdrian Hunter	def FindDone(self, row):
41098392b74bSAdrian Hunter		self.find_bar.Idle()
41108392b74bSAdrian Hunter		if row >= 0:
411135fa1ceeSAdrian Hunter			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
41128392b74bSAdrian Hunter		else:
41138392b74bSAdrian Hunter			self.find_bar.NotFound()
41148392b74bSAdrian Hunter
41158392b74bSAdrian Hunter# Table list
41168392b74bSAdrian Hunter
41178392b74bSAdrian Hunterdef GetTableList(glb):
41188392b74bSAdrian Hunter	tables = []
41198392b74bSAdrian Hunter	query = QSqlQuery(glb.db)
41208392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
41218392b74bSAdrian Hunter		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
41228392b74bSAdrian Hunter	else:
41238392b74bSAdrian 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")
41248392b74bSAdrian Hunter	while query.next():
41258392b74bSAdrian Hunter		tables.append(query.value(0))
41268392b74bSAdrian Hunter	if glb.dbref.is_sqlite3:
41278392b74bSAdrian Hunter		tables.append("sqlite_master")
41288392b74bSAdrian Hunter	else:
41298392b74bSAdrian Hunter		tables.append("information_schema.tables")
41308392b74bSAdrian Hunter		tables.append("information_schema.views")
41318392b74bSAdrian Hunter		tables.append("information_schema.columns")
41328392b74bSAdrian Hunter	return tables
41338392b74bSAdrian Hunter
4134cd358012SAdrian Hunter# Top Calls data model
4135cd358012SAdrian Hunter
4136cd358012SAdrian Hunterclass TopCallsModel(SQLTableModel):
4137cd358012SAdrian Hunter
4138cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
4139cd358012SAdrian Hunter		text = ""
4140cd358012SAdrian Hunter		if not glb.dbref.is_sqlite3:
4141cd358012SAdrian Hunter			text = "::text"
4142cd358012SAdrian Hunter		limit = ""
4143cd358012SAdrian Hunter		if len(report_vars.limit):
4144cd358012SAdrian Hunter			limit = " LIMIT " + report_vars.limit
4145cd358012SAdrian Hunter		sql = ("SELECT comm, pid, tid, name,"
4146cd358012SAdrian Hunter			" CASE"
4147cd358012SAdrian Hunter			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
4148cd358012SAdrian Hunter			" ELSE short_name"
4149cd358012SAdrian Hunter			" END AS dso,"
4150cd358012SAdrian Hunter			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
4151cd358012SAdrian Hunter			" CASE"
4152cd358012SAdrian Hunter			" WHEN (calls.flags = 1) THEN 'no call'" + text +
4153cd358012SAdrian Hunter			" WHEN (calls.flags = 2) THEN 'no return'" + text +
4154cd358012SAdrian Hunter			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
4155cd358012SAdrian Hunter			" ELSE ''" + text +
4156cd358012SAdrian Hunter			" END AS flags"
4157cd358012SAdrian Hunter			" FROM calls"
4158cd358012SAdrian Hunter			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
4159cd358012SAdrian Hunter			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
4160cd358012SAdrian Hunter			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
4161cd358012SAdrian Hunter			" INNER JOIN comms ON calls.comm_id = comms.id"
4162cd358012SAdrian Hunter			" INNER JOIN threads ON calls.thread_id = threads.id" +
4163cd358012SAdrian Hunter			report_vars.where_clause +
4164cd358012SAdrian Hunter			" ORDER BY elapsed_time DESC" +
4165cd358012SAdrian Hunter			limit
4166cd358012SAdrian Hunter			)
4167cd358012SAdrian Hunter		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
4168cd358012SAdrian Hunter		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
4169cd358012SAdrian Hunter		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
4170cd358012SAdrian Hunter
4171cd358012SAdrian Hunter	def columnAlignment(self, column):
4172cd358012SAdrian Hunter		return self.alignment[column]
4173cd358012SAdrian Hunter
4174cd358012SAdrian Hunter# Top Calls report creation dialog
4175cd358012SAdrian Hunter
4176cd358012SAdrian Hunterclass TopCallsDialog(ReportDialogBase):
4177cd358012SAdrian Hunter
4178cd358012SAdrian Hunter	def __init__(self, glb, parent=None):
4179cd358012SAdrian Hunter		title = "Top Calls by Elapsed Time"
4180cd358012SAdrian Hunter		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
4181cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
4182cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
4183cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
4184cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
4185cd358012SAdrian Hunter			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
4186cd358012SAdrian Hunter			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
4187cd358012SAdrian Hunter			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
4188cd358012SAdrian Hunter		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
4189cd358012SAdrian Hunter
4190cd358012SAdrian Hunter# Top Calls window
4191cd358012SAdrian Hunter
4192cd358012SAdrian Hunterclass TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
4193cd358012SAdrian Hunter
4194cd358012SAdrian Hunter	def __init__(self, glb, report_vars, parent=None):
4195cd358012SAdrian Hunter		super(TopCallsWindow, self).__init__(parent)
4196cd358012SAdrian Hunter
4197cd358012SAdrian Hunter		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
4198cd358012SAdrian Hunter		self.model = self.data_model
4199cd358012SAdrian Hunter
4200cd358012SAdrian Hunter		self.view = QTableView()
4201cd358012SAdrian Hunter		self.view.setModel(self.model)
4202cd358012SAdrian Hunter		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
4203cd358012SAdrian Hunter		self.view.verticalHeader().setVisible(False)
420496c43b9aSAdrian Hunter		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
420596c43b9aSAdrian Hunter		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
4206cd358012SAdrian Hunter
42079bc4e4bfSAdrian Hunter		self.context_menu = ContextMenu(self.view)
42089bc4e4bfSAdrian Hunter
4209cd358012SAdrian Hunter		self.ResizeColumnsToContents()
4210cd358012SAdrian Hunter
4211cd358012SAdrian Hunter		self.find_bar = FindBar(self, self, True)
4212cd358012SAdrian Hunter
4213cd358012SAdrian Hunter		self.finder = ChildDataItemFinder(self.model)
4214cd358012SAdrian Hunter
4215cd358012SAdrian Hunter		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
4216cd358012SAdrian Hunter
4217cd358012SAdrian Hunter		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
4218cd358012SAdrian Hunter
4219cd358012SAdrian Hunter		self.setWidget(self.vbox.Widget())
4220cd358012SAdrian Hunter
4221cd358012SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
4222cd358012SAdrian Hunter
4223cd358012SAdrian Hunter	def Find(self, value, direction, pattern, context):
4224cd358012SAdrian Hunter		self.view.setFocus()
4225cd358012SAdrian Hunter		self.find_bar.Busy()
4226cd358012SAdrian Hunter		self.finder.Find(value, direction, pattern, context, self.FindDone)
4227cd358012SAdrian Hunter
4228cd358012SAdrian Hunter	def FindDone(self, row):
4229cd358012SAdrian Hunter		self.find_bar.Idle()
4230cd358012SAdrian Hunter		if row >= 0:
4231cd358012SAdrian Hunter			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
4232cd358012SAdrian Hunter		else:
4233cd358012SAdrian Hunter			self.find_bar.NotFound()
4234cd358012SAdrian Hunter
42351beb5c7bSAdrian Hunter# Action Definition
42361beb5c7bSAdrian Hunter
42371beb5c7bSAdrian Hunterdef CreateAction(label, tip, callback, parent=None, shortcut=None):
42381beb5c7bSAdrian Hunter	action = QAction(label, parent)
42391beb5c7bSAdrian Hunter	if shortcut != None:
42401beb5c7bSAdrian Hunter		action.setShortcuts(shortcut)
42411beb5c7bSAdrian Hunter	action.setStatusTip(tip)
42421beb5c7bSAdrian Hunter	action.triggered.connect(callback)
42431beb5c7bSAdrian Hunter	return action
42441beb5c7bSAdrian Hunter
42451beb5c7bSAdrian Hunter# Typical application actions
42461beb5c7bSAdrian Hunter
42471beb5c7bSAdrian Hunterdef CreateExitAction(app, parent=None):
42481beb5c7bSAdrian Hunter	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
42491beb5c7bSAdrian Hunter
42501beb5c7bSAdrian Hunter# Typical MDI actions
42511beb5c7bSAdrian Hunter
42521beb5c7bSAdrian Hunterdef CreateCloseActiveWindowAction(mdi_area):
42531beb5c7bSAdrian Hunter	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
42541beb5c7bSAdrian Hunter
42551beb5c7bSAdrian Hunterdef CreateCloseAllWindowsAction(mdi_area):
42561beb5c7bSAdrian Hunter	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
42571beb5c7bSAdrian Hunter
42581beb5c7bSAdrian Hunterdef CreateTileWindowsAction(mdi_area):
42591beb5c7bSAdrian Hunter	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
42601beb5c7bSAdrian Hunter
42611beb5c7bSAdrian Hunterdef CreateCascadeWindowsAction(mdi_area):
42621beb5c7bSAdrian Hunter	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
42631beb5c7bSAdrian Hunter
42641beb5c7bSAdrian Hunterdef CreateNextWindowAction(mdi_area):
42651beb5c7bSAdrian Hunter	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
42661beb5c7bSAdrian Hunter
42671beb5c7bSAdrian Hunterdef CreatePreviousWindowAction(mdi_area):
42681beb5c7bSAdrian Hunter	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
42691beb5c7bSAdrian Hunter
42701beb5c7bSAdrian Hunter# Typical MDI window menu
42711beb5c7bSAdrian Hunter
42721beb5c7bSAdrian Hunterclass WindowMenu():
42731beb5c7bSAdrian Hunter
42741beb5c7bSAdrian Hunter	def __init__(self, mdi_area, menu):
42751beb5c7bSAdrian Hunter		self.mdi_area = mdi_area
42761beb5c7bSAdrian Hunter		self.window_menu = menu.addMenu("&Windows")
42771beb5c7bSAdrian Hunter		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
42781beb5c7bSAdrian Hunter		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
42791beb5c7bSAdrian Hunter		self.tile_windows = CreateTileWindowsAction(mdi_area)
42801beb5c7bSAdrian Hunter		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
42811beb5c7bSAdrian Hunter		self.next_window = CreateNextWindowAction(mdi_area)
42821beb5c7bSAdrian Hunter		self.previous_window = CreatePreviousWindowAction(mdi_area)
42831beb5c7bSAdrian Hunter		self.window_menu.aboutToShow.connect(self.Update)
42841beb5c7bSAdrian Hunter
42851beb5c7bSAdrian Hunter	def Update(self):
42861beb5c7bSAdrian Hunter		self.window_menu.clear()
42871beb5c7bSAdrian Hunter		sub_window_count = len(self.mdi_area.subWindowList())
42881beb5c7bSAdrian Hunter		have_sub_windows = sub_window_count != 0
42891beb5c7bSAdrian Hunter		self.close_active_window.setEnabled(have_sub_windows)
42901beb5c7bSAdrian Hunter		self.close_all_windows.setEnabled(have_sub_windows)
42911beb5c7bSAdrian Hunter		self.tile_windows.setEnabled(have_sub_windows)
42921beb5c7bSAdrian Hunter		self.cascade_windows.setEnabled(have_sub_windows)
42931beb5c7bSAdrian Hunter		self.next_window.setEnabled(have_sub_windows)
42941beb5c7bSAdrian Hunter		self.previous_window.setEnabled(have_sub_windows)
42951beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_active_window)
42961beb5c7bSAdrian Hunter		self.window_menu.addAction(self.close_all_windows)
42971beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
42981beb5c7bSAdrian Hunter		self.window_menu.addAction(self.tile_windows)
42991beb5c7bSAdrian Hunter		self.window_menu.addAction(self.cascade_windows)
43001beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
43011beb5c7bSAdrian Hunter		self.window_menu.addAction(self.next_window)
43021beb5c7bSAdrian Hunter		self.window_menu.addAction(self.previous_window)
43031beb5c7bSAdrian Hunter		if sub_window_count == 0:
43041beb5c7bSAdrian Hunter			return
43051beb5c7bSAdrian Hunter		self.window_menu.addSeparator()
43061beb5c7bSAdrian Hunter		nr = 1
43071beb5c7bSAdrian Hunter		for sub_window in self.mdi_area.subWindowList():
43081beb5c7bSAdrian Hunter			label = str(nr) + " " + sub_window.name
43091beb5c7bSAdrian Hunter			if nr < 10:
43101beb5c7bSAdrian Hunter				label = "&" + label
43111beb5c7bSAdrian Hunter			action = self.window_menu.addAction(label)
43121beb5c7bSAdrian Hunter			action.setCheckable(True)
43131beb5c7bSAdrian Hunter			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
4314df8ea22aSAdrian Hunter			action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
43151beb5c7bSAdrian Hunter			self.window_menu.addAction(action)
43161beb5c7bSAdrian Hunter			nr += 1
43171beb5c7bSAdrian Hunter
43181beb5c7bSAdrian Hunter	def setActiveSubWindow(self, nr):
43191beb5c7bSAdrian Hunter		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
43201beb5c7bSAdrian Hunter
432165b24292SAdrian Hunter# Help text
432265b24292SAdrian Hunter
432365b24292SAdrian Hunterglb_help_text = """
432465b24292SAdrian Hunter<h1>Contents</h1>
432565b24292SAdrian Hunter<style>
432665b24292SAdrian Hunterp.c1 {
432765b24292SAdrian Hunter    text-indent: 40px;
432865b24292SAdrian Hunter}
432965b24292SAdrian Hunterp.c2 {
433065b24292SAdrian Hunter    text-indent: 80px;
433165b24292SAdrian Hunter}
433265b24292SAdrian Hunter}
433365b24292SAdrian Hunter</style>
433465b24292SAdrian Hunter<p class=c1><a href=#reports>1. Reports</a></p>
433565b24292SAdrian Hunter<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
4336ae8b887cSAdrian Hunter<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
4337ae8b887cSAdrian Hunter<p class=c2><a href=#allbranches>1.3 All branches</a></p>
4338ae8b887cSAdrian Hunter<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
4339ae8b887cSAdrian Hunter<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
4340b3700f21SAdrian Hunter<p class=c1><a href=#charts>2. Charts</a></p>
4341b3700f21SAdrian Hunter<p class=c2><a href=#timechartbycpu>2.1 Time chart by CPU</a></p>
4342b3700f21SAdrian Hunter<p class=c1><a href=#tables>3. Tables</a></p>
434365b24292SAdrian Hunter<h1 id=reports>1. Reports</h1>
434465b24292SAdrian Hunter<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
434565b24292SAdrian HunterThe result is a GUI window with a tree representing a context-sensitive
434665b24292SAdrian Huntercall-graph. Expanding a couple of levels of the tree and adjusting column
434765b24292SAdrian Hunterwidths to suit will display something like:
434865b24292SAdrian Hunter<pre>
434965b24292SAdrian Hunter                                         Call Graph: pt_example
435065b24292SAdrian HunterCall Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
435165b24292SAdrian Hunterv- ls
435265b24292SAdrian Hunter    v- 2638:2638
435365b24292SAdrian Hunter        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
435465b24292SAdrian Hunter          |- unknown               unknown       1        13198     0.1              1              0.0
435565b24292SAdrian Hunter          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
435665b24292SAdrian Hunter          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
435765b24292SAdrian Hunter          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
435865b24292SAdrian Hunter             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
435965b24292SAdrian Hunter             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
436065b24292SAdrian Hunter             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
436165b24292SAdrian Hunter             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
436265b24292SAdrian Hunter             v- main               ls            1      8182043    99.6         180254             99.9
436365b24292SAdrian Hunter</pre>
436465b24292SAdrian Hunter<h3>Points to note:</h3>
436565b24292SAdrian Hunter<ul>
436665b24292SAdrian Hunter<li>The top level is a command name (comm)</li>
436765b24292SAdrian Hunter<li>The next level is a thread (pid:tid)</li>
436865b24292SAdrian Hunter<li>Subsequent levels are functions</li>
436965b24292SAdrian Hunter<li>'Count' is the number of calls</li>
437065b24292SAdrian Hunter<li>'Time' is the elapsed time until the function returns</li>
437165b24292SAdrian Hunter<li>Percentages are relative to the level above</li>
437265b24292SAdrian Hunter<li>'Branch Count' is the total number of branches for that function and all functions that it calls
437365b24292SAdrian Hunter</ul>
437465b24292SAdrian Hunter<h3>Find</h3>
437565b24292SAdrian HunterCtrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
437665b24292SAdrian HunterThe pattern matching symbols are ? for any character and * for zero or more characters.
4377ae8b887cSAdrian Hunter<h2 id=calltree>1.2 Call Tree</h2>
4378ae8b887cSAdrian HunterThe Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
4379ae8b887cSAdrian HunterAlso the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
4380ae8b887cSAdrian Hunter<h2 id=allbranches>1.3 All branches</h2>
438165b24292SAdrian HunterThe All branches report displays all branches in chronological order.
438265b24292SAdrian HunterNot all data is fetched immediately. More records can be fetched using the Fetch bar provided.
438365b24292SAdrian Hunter<h3>Disassembly</h3>
438465b24292SAdrian HunterOpen a branch to display disassembly. This only works if:
438565b24292SAdrian Hunter<ol>
438665b24292SAdrian Hunter<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
438765b24292SAdrian Hunter<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
438865b24292SAdrian HunterThe default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
438965b24292SAdrian HunterOne exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
439065b24292SAdrian Hunteror alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
439165b24292SAdrian Hunter</ol>
439265b24292SAdrian Hunter<h4 id=xed>Intel XED Setup</h4>
439365b24292SAdrian HunterTo use Intel XED, libxed.so must be present.  To build and install libxed.so:
439465b24292SAdrian Hunter<pre>
439565b24292SAdrian Huntergit clone https://github.com/intelxed/mbuild.git mbuild
439665b24292SAdrian Huntergit clone https://github.com/intelxed/xed
439765b24292SAdrian Huntercd xed
439865b24292SAdrian Hunter./mfile.py --share
439965b24292SAdrian Huntersudo ./mfile.py --prefix=/usr/local install
440065b24292SAdrian Huntersudo ldconfig
440165b24292SAdrian Hunter</pre>
4402530e22fdSAdrian Hunter<h3>Instructions per Cycle (IPC)</h3>
4403530e22fdSAdrian HunterIf available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
4404530e22fdSAdrian Hunter<p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
4405530e22fdSAdrian HunterDue to the granularity of timing information, the number of cycles for some code blocks will not be known.
4406530e22fdSAdrian HunterIn that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
4407530e22fdSAdrian Huntersince the previous displayed 'IPC'.
440865b24292SAdrian Hunter<h3>Find</h3>
440965b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
441065b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
441165b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
4412ae8b887cSAdrian Hunter<h2 id=selectedbranches>1.4 Selected branches</h2>
441365b24292SAdrian HunterThis is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
441465b24292SAdrian Hunterby various selection criteria. A dialog box displays available criteria which are AND'ed together.
4415ae8b887cSAdrian Hunter<h3>1.4.1 Time ranges</h3>
441665b24292SAdrian HunterThe time ranges hint text shows the total time range. Relative time ranges can also be entered in
441765b24292SAdrian Hunterms, us or ns. Also, negative values are relative to the end of trace.  Examples:
441865b24292SAdrian Hunter<pre>
441965b24292SAdrian Hunter	81073085947329-81073085958238	From 81073085947329 to 81073085958238
442065b24292SAdrian Hunter	100us-200us		From 100us to 200us
442165b24292SAdrian Hunter	10ms-			From 10ms to the end
442265b24292SAdrian Hunter	-100ns			The first 100ns
442365b24292SAdrian Hunter	-10ms-			The last 10ms
442465b24292SAdrian Hunter</pre>
442565b24292SAdrian HunterN.B. Due to the granularity of timestamps, there could be no branches in any given time range.
4426ae8b887cSAdrian Hunter<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
4427cd358012SAdrian 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.
4428cd358012SAdrian HunterThe data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
4429cd358012SAdrian HunterIf not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
4430b3700f21SAdrian Hunter<h1 id=charts>2. Charts</h1>
4431b3700f21SAdrian Hunter<h2 id=timechartbycpu>2.1 Time chart by CPU</h2>
4432b3700f21SAdrian HunterThis chart displays context switch information when that data is available. Refer to context_switches_view on the Tables menu.
4433b3700f21SAdrian Hunter<h3>Features</h3>
4434b3700f21SAdrian Hunter<ol>
4435b3700f21SAdrian Hunter<li>Mouse over to highight the task and show the time</li>
4436b3700f21SAdrian Hunter<li>Drag the mouse to select a region and zoom by pushing the Zoom button</li>
4437b3700f21SAdrian Hunter<li>Go back and forward by pressing the arrow buttons</li>
4438b3700f21SAdrian Hunter<li>If call information is available, right-click to show a call tree opened to that task and time.
4439b3700f21SAdrian HunterNote, the call tree may take some time to appear, and there may not be call information for the task or time selected.
4440b3700f21SAdrian Hunter</li>
4441b3700f21SAdrian Hunter</ol>
4442b3700f21SAdrian Hunter<h3>Important</h3>
4443b3700f21SAdrian HunterThe graph can be misleading in the following respects:
4444b3700f21SAdrian Hunter<ol>
4445b3700f21SAdrian Hunter<li>The graph shows the first task on each CPU as running from the beginning of the time range.
4446b3700f21SAdrian HunterBecause tracing might start on different CPUs at different times, that is not necessarily the case.
4447b3700f21SAdrian HunterRefer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4448b3700f21SAdrian Hunter<li>Similarly, the last task on each CPU can be showing running longer than it really was.
4449b3700f21SAdrian HunterAgain, refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4450b3700f21SAdrian 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>
4451b3700f21SAdrian Hunter</ol>
4452b3700f21SAdrian Hunter<h1 id=tables>3. Tables</h1>
445365b24292SAdrian HunterThe Tables menu shows all tables and views in the database. Most tables have an associated view
445465b24292SAdrian Hunterwhich displays the information in a more friendly way. Not all data for large tables is fetched
445565b24292SAdrian Hunterimmediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
445665b24292SAdrian Hunterbut that can be slow for large tables.
445765b24292SAdrian Hunter<p>There are also tables of database meta-information.
445865b24292SAdrian HunterFor SQLite3 databases, the sqlite_master table is included.
445965b24292SAdrian HunterFor PostgreSQL databases, information_schema.tables/views/columns are included.
446065b24292SAdrian Hunter<h3>Find</h3>
446165b24292SAdrian HunterCtrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
446265b24292SAdrian HunterRefer to Python documentation for the regular expression syntax.
446365b24292SAdrian HunterAll columns are searched, but only currently fetched rows are searched.
446435fa1ceeSAdrian Hunter<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
446535fa1ceeSAdrian Hunterwill go to the next/previous result in id order, instead of display order.
446665b24292SAdrian Hunter"""
446765b24292SAdrian Hunter
446865b24292SAdrian Hunter# Help window
446965b24292SAdrian Hunter
447065b24292SAdrian Hunterclass HelpWindow(QMdiSubWindow):
447165b24292SAdrian Hunter
447265b24292SAdrian Hunter	def __init__(self, glb, parent=None):
447365b24292SAdrian Hunter		super(HelpWindow, self).__init__(parent)
447465b24292SAdrian Hunter
447565b24292SAdrian Hunter		self.text = QTextBrowser()
447665b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
447765b24292SAdrian Hunter		self.text.setReadOnly(True)
447865b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
447965b24292SAdrian Hunter
448065b24292SAdrian Hunter		self.setWidget(self.text)
448165b24292SAdrian Hunter
448265b24292SAdrian Hunter		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
448365b24292SAdrian Hunter
448465b24292SAdrian Hunter# Main window that only displays the help text
448565b24292SAdrian Hunter
448665b24292SAdrian Hunterclass HelpOnlyWindow(QMainWindow):
448765b24292SAdrian Hunter
448865b24292SAdrian Hunter	def __init__(self, parent=None):
448965b24292SAdrian Hunter		super(HelpOnlyWindow, self).__init__(parent)
449065b24292SAdrian Hunter
449165b24292SAdrian Hunter		self.setMinimumSize(200, 100)
449265b24292SAdrian Hunter		self.resize(800, 600)
449365b24292SAdrian Hunter		self.setWindowTitle("Exported SQL Viewer Help")
449465b24292SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
449565b24292SAdrian Hunter
449665b24292SAdrian Hunter		self.text = QTextBrowser()
449765b24292SAdrian Hunter		self.text.setHtml(glb_help_text)
449865b24292SAdrian Hunter		self.text.setReadOnly(True)
449965b24292SAdrian Hunter		self.text.setOpenExternalLinks(True)
450065b24292SAdrian Hunter
450165b24292SAdrian Hunter		self.setCentralWidget(self.text)
450265b24292SAdrian Hunter
4503b62d18abSAdrian Hunter# PostqreSQL server version
4504b62d18abSAdrian Hunter
4505b62d18abSAdrian Hunterdef PostqreSQLServerVersion(db):
4506b62d18abSAdrian Hunter	query = QSqlQuery(db)
4507b62d18abSAdrian Hunter	QueryExec(query, "SELECT VERSION()")
4508b62d18abSAdrian Hunter	if query.next():
4509b62d18abSAdrian Hunter		v_str = query.value(0)
4510b62d18abSAdrian Hunter		v_list = v_str.strip().split(" ")
4511b62d18abSAdrian Hunter		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
4512b62d18abSAdrian Hunter			return v_list[1]
4513b62d18abSAdrian Hunter		return v_str
4514b62d18abSAdrian Hunter	return "Unknown"
4515b62d18abSAdrian Hunter
4516b62d18abSAdrian Hunter# SQLite version
4517b62d18abSAdrian Hunter
4518b62d18abSAdrian Hunterdef SQLiteVersion(db):
4519b62d18abSAdrian Hunter	query = QSqlQuery(db)
4520b62d18abSAdrian Hunter	QueryExec(query, "SELECT sqlite_version()")
4521b62d18abSAdrian Hunter	if query.next():
4522b62d18abSAdrian Hunter		return query.value(0)
4523b62d18abSAdrian Hunter	return "Unknown"
4524b62d18abSAdrian Hunter
4525b62d18abSAdrian Hunter# About dialog
4526b62d18abSAdrian Hunter
4527b62d18abSAdrian Hunterclass AboutDialog(QDialog):
4528b62d18abSAdrian Hunter
4529b62d18abSAdrian Hunter	def __init__(self, glb, parent=None):
4530b62d18abSAdrian Hunter		super(AboutDialog, self).__init__(parent)
4531b62d18abSAdrian Hunter
4532b62d18abSAdrian Hunter		self.setWindowTitle("About Exported SQL Viewer")
4533b62d18abSAdrian Hunter		self.setMinimumWidth(300)
4534b62d18abSAdrian Hunter
4535b62d18abSAdrian Hunter		pyside_version = "1" if pyside_version_1 else "2"
4536b62d18abSAdrian Hunter
4537b62d18abSAdrian Hunter		text = "<pre>"
4538b62d18abSAdrian Hunter		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
4539b62d18abSAdrian Hunter		text += "PySide version:     " + pyside_version + "\n"
4540b62d18abSAdrian Hunter		text += "Qt version:         " + qVersion() + "\n"
4541b62d18abSAdrian Hunter		if glb.dbref.is_sqlite3:
4542b62d18abSAdrian Hunter			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
4543b62d18abSAdrian Hunter		else:
4544b62d18abSAdrian Hunter			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
4545b62d18abSAdrian Hunter		text += "</pre>"
4546b62d18abSAdrian Hunter
4547b62d18abSAdrian Hunter		self.text = QTextBrowser()
4548b62d18abSAdrian Hunter		self.text.setHtml(text)
4549b62d18abSAdrian Hunter		self.text.setReadOnly(True)
4550b62d18abSAdrian Hunter		self.text.setOpenExternalLinks(True)
4551b62d18abSAdrian Hunter
4552b62d18abSAdrian Hunter		self.vbox = QVBoxLayout()
4553b62d18abSAdrian Hunter		self.vbox.addWidget(self.text)
4554b62d18abSAdrian Hunter
455526688729SAdrian Hunter		self.setLayout(self.vbox)
4556b62d18abSAdrian Hunter
455782f68e28SAdrian Hunter# Font resize
455882f68e28SAdrian Hunter
455982f68e28SAdrian Hunterdef ResizeFont(widget, diff):
456082f68e28SAdrian Hunter	font = widget.font()
456182f68e28SAdrian Hunter	sz = font.pointSize()
456282f68e28SAdrian Hunter	font.setPointSize(sz + diff)
456382f68e28SAdrian Hunter	widget.setFont(font)
456482f68e28SAdrian Hunter
456582f68e28SAdrian Hunterdef ShrinkFont(widget):
456682f68e28SAdrian Hunter	ResizeFont(widget, -1)
456782f68e28SAdrian Hunter
456882f68e28SAdrian Hunterdef EnlargeFont(widget):
456982f68e28SAdrian Hunter	ResizeFont(widget, 1)
457082f68e28SAdrian Hunter
45711beb5c7bSAdrian Hunter# Unique name for sub-windows
45721beb5c7bSAdrian Hunter
45731beb5c7bSAdrian Hunterdef NumberedWindowName(name, nr):
45741beb5c7bSAdrian Hunter	if nr > 1:
45751beb5c7bSAdrian Hunter		name += " <" + str(nr) + ">"
45761beb5c7bSAdrian Hunter	return name
45771beb5c7bSAdrian Hunter
45781beb5c7bSAdrian Hunterdef UniqueSubWindowName(mdi_area, name):
45791beb5c7bSAdrian Hunter	nr = 1
45801beb5c7bSAdrian Hunter	while True:
45811beb5c7bSAdrian Hunter		unique_name = NumberedWindowName(name, nr)
45821beb5c7bSAdrian Hunter		ok = True
45831beb5c7bSAdrian Hunter		for sub_window in mdi_area.subWindowList():
45841beb5c7bSAdrian Hunter			if sub_window.name == unique_name:
45851beb5c7bSAdrian Hunter				ok = False
45861beb5c7bSAdrian Hunter				break
45871beb5c7bSAdrian Hunter		if ok:
45881beb5c7bSAdrian Hunter			return unique_name
45891beb5c7bSAdrian Hunter		nr += 1
45901beb5c7bSAdrian Hunter
45911beb5c7bSAdrian Hunter# Add a sub-window
45921beb5c7bSAdrian Hunter
45931beb5c7bSAdrian Hunterdef AddSubWindow(mdi_area, sub_window, name):
45941beb5c7bSAdrian Hunter	unique_name = UniqueSubWindowName(mdi_area, name)
45951beb5c7bSAdrian Hunter	sub_window.setMinimumSize(200, 100)
45961beb5c7bSAdrian Hunter	sub_window.resize(800, 600)
45971beb5c7bSAdrian Hunter	sub_window.setWindowTitle(unique_name)
45981beb5c7bSAdrian Hunter	sub_window.setAttribute(Qt.WA_DeleteOnClose)
45991beb5c7bSAdrian Hunter	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
46001beb5c7bSAdrian Hunter	sub_window.name = unique_name
46011beb5c7bSAdrian Hunter	mdi_area.addSubWindow(sub_window)
46021beb5c7bSAdrian Hunter	sub_window.show()
46031beb5c7bSAdrian Hunter
4604031c2a00SAdrian Hunter# Main window
4605031c2a00SAdrian Hunter
4606031c2a00SAdrian Hunterclass MainWindow(QMainWindow):
4607031c2a00SAdrian Hunter
4608031c2a00SAdrian Hunter	def __init__(self, glb, parent=None):
4609031c2a00SAdrian Hunter		super(MainWindow, self).__init__(parent)
4610031c2a00SAdrian Hunter
4611031c2a00SAdrian Hunter		self.glb = glb
4612031c2a00SAdrian Hunter
46131beb5c7bSAdrian Hunter		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
4614031c2a00SAdrian Hunter		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
4615031c2a00SAdrian Hunter		self.setMinimumSize(200, 100)
4616031c2a00SAdrian Hunter
46171beb5c7bSAdrian Hunter		self.mdi_area = QMdiArea()
46181beb5c7bSAdrian Hunter		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
46191beb5c7bSAdrian Hunter		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
4620031c2a00SAdrian Hunter
46211beb5c7bSAdrian Hunter		self.setCentralWidget(self.mdi_area)
4622031c2a00SAdrian Hunter
46231beb5c7bSAdrian Hunter		menu = self.menuBar()
4624031c2a00SAdrian Hunter
46251beb5c7bSAdrian Hunter		file_menu = menu.addMenu("&File")
46261beb5c7bSAdrian Hunter		file_menu.addAction(CreateExitAction(glb.app, self))
46271beb5c7bSAdrian Hunter
4628ebd70c7dSAdrian Hunter		edit_menu = menu.addMenu("&Edit")
462996c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
463096c43b9aSAdrian Hunter		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
4631ebd70c7dSAdrian Hunter		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
46328392b74bSAdrian Hunter		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
463382f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
463482f68e28SAdrian Hunter		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
4635ebd70c7dSAdrian Hunter
46361beb5c7bSAdrian Hunter		reports_menu = menu.addMenu("&Reports")
4637655cb952SAdrian Hunter		if IsSelectable(glb.db, "calls"):
46381beb5c7bSAdrian Hunter			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
46391beb5c7bSAdrian Hunter
4640ae8b887cSAdrian Hunter		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
4641ae8b887cSAdrian Hunter			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
4642ae8b887cSAdrian Hunter
464376099f98SAdrian Hunter		self.EventMenu(GetEventList(glb.db), reports_menu)
464476099f98SAdrian Hunter
4645cd358012SAdrian Hunter		if IsSelectable(glb.db, "calls"):
4646cd358012SAdrian Hunter			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
4647cd358012SAdrian Hunter
4648b3700f21SAdrian Hunter		if IsSelectable(glb.db, "context_switches"):
4649b3700f21SAdrian Hunter			charts_menu = menu.addMenu("&Charts")
4650b3700f21SAdrian Hunter			charts_menu.addAction(CreateAction("&Time chart by CPU", "Create a new window displaying time charts by CPU", self.TimeChartByCPU, self))
4651b3700f21SAdrian Hunter
46528392b74bSAdrian Hunter		self.TableMenu(GetTableList(glb), menu)
46538392b74bSAdrian Hunter
46541beb5c7bSAdrian Hunter		self.window_menu = WindowMenu(self.mdi_area, menu)
46551beb5c7bSAdrian Hunter
465665b24292SAdrian Hunter		help_menu = menu.addMenu("&Help")
465765b24292SAdrian Hunter		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
4658b62d18abSAdrian Hunter		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
465965b24292SAdrian Hunter
46604b208453SAdrian Hunter	def Try(self, fn):
46614b208453SAdrian Hunter		win = self.mdi_area.activeSubWindow()
46624b208453SAdrian Hunter		if win:
46634b208453SAdrian Hunter			try:
46644b208453SAdrian Hunter				fn(win.view)
46654b208453SAdrian Hunter			except:
46664b208453SAdrian Hunter				pass
46674b208453SAdrian Hunter
466896c43b9aSAdrian Hunter	def CopyToClipboard(self):
466996c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardHdr)
467096c43b9aSAdrian Hunter
467196c43b9aSAdrian Hunter	def CopyToClipboardCSV(self):
467296c43b9aSAdrian Hunter		self.Try(CopyCellsToClipboardCSV)
467396c43b9aSAdrian Hunter
4674ebd70c7dSAdrian Hunter	def Find(self):
4675ebd70c7dSAdrian Hunter		win = self.mdi_area.activeSubWindow()
4676ebd70c7dSAdrian Hunter		if win:
4677ebd70c7dSAdrian Hunter			try:
4678ebd70c7dSAdrian Hunter				win.find_bar.Activate()
4679ebd70c7dSAdrian Hunter			except:
4680ebd70c7dSAdrian Hunter				pass
4681ebd70c7dSAdrian Hunter
46828392b74bSAdrian Hunter	def FetchMoreRecords(self):
46838392b74bSAdrian Hunter		win = self.mdi_area.activeSubWindow()
46848392b74bSAdrian Hunter		if win:
46858392b74bSAdrian Hunter			try:
46868392b74bSAdrian Hunter				win.fetch_bar.Activate()
46878392b74bSAdrian Hunter			except:
46888392b74bSAdrian Hunter				pass
46898392b74bSAdrian Hunter
469082f68e28SAdrian Hunter	def ShrinkFont(self):
46914b208453SAdrian Hunter		self.Try(ShrinkFont)
469282f68e28SAdrian Hunter
469382f68e28SAdrian Hunter	def EnlargeFont(self):
46944b208453SAdrian Hunter		self.Try(EnlargeFont)
469582f68e28SAdrian Hunter
469676099f98SAdrian Hunter	def EventMenu(self, events, reports_menu):
469776099f98SAdrian Hunter		branches_events = 0
469876099f98SAdrian Hunter		for event in events:
469976099f98SAdrian Hunter			event = event.split(":")[0]
470076099f98SAdrian Hunter			if event == "branches":
470176099f98SAdrian Hunter				branches_events += 1
470276099f98SAdrian Hunter		dbid = 0
470376099f98SAdrian Hunter		for event in events:
470476099f98SAdrian Hunter			dbid += 1
470576099f98SAdrian Hunter			event = event.split(":")[0]
470676099f98SAdrian Hunter			if event == "branches":
470776099f98SAdrian Hunter				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
4708df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
4709210cf1f9SAdrian Hunter				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
4710df8ea22aSAdrian Hunter				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
471176099f98SAdrian Hunter
4712b3700f21SAdrian Hunter	def TimeChartByCPU(self):
4713b3700f21SAdrian Hunter		TimeChartByCPUWindow(self.glb, self)
4714b3700f21SAdrian Hunter
47158392b74bSAdrian Hunter	def TableMenu(self, tables, menu):
47168392b74bSAdrian Hunter		table_menu = menu.addMenu("&Tables")
47178392b74bSAdrian Hunter		for table in tables:
4718df8ea22aSAdrian Hunter			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
47198392b74bSAdrian Hunter
47201beb5c7bSAdrian Hunter	def NewCallGraph(self):
47211beb5c7bSAdrian Hunter		CallGraphWindow(self.glb, self)
4722031c2a00SAdrian Hunter
4723ae8b887cSAdrian Hunter	def NewCallTree(self):
4724ae8b887cSAdrian Hunter		CallTreeWindow(self.glb, self)
4725ae8b887cSAdrian Hunter
4726cd358012SAdrian Hunter	def NewTopCalls(self):
4727cd358012SAdrian Hunter		dialog = TopCallsDialog(self.glb, self)
4728cd358012SAdrian Hunter		ret = dialog.exec_()
4729cd358012SAdrian Hunter		if ret:
4730cd358012SAdrian Hunter			TopCallsWindow(self.glb, dialog.report_vars, self)
4731cd358012SAdrian Hunter
473276099f98SAdrian Hunter	def NewBranchView(self, event_id):
4733947cc38dSAdrian Hunter		BranchWindow(self.glb, event_id, ReportVars(), self)
473476099f98SAdrian Hunter
4735210cf1f9SAdrian Hunter	def NewSelectedBranchView(self, event_id):
4736210cf1f9SAdrian Hunter		dialog = SelectedBranchDialog(self.glb, self)
4737210cf1f9SAdrian Hunter		ret = dialog.exec_()
4738210cf1f9SAdrian Hunter		if ret:
4739947cc38dSAdrian Hunter			BranchWindow(self.glb, event_id, dialog.report_vars, self)
4740210cf1f9SAdrian Hunter
47418392b74bSAdrian Hunter	def NewTableView(self, table_name):
47428392b74bSAdrian Hunter		TableWindow(self.glb, table_name, self)
47438392b74bSAdrian Hunter
474465b24292SAdrian Hunter	def Help(self):
474565b24292SAdrian Hunter		HelpWindow(self.glb, self)
474665b24292SAdrian Hunter
4747b62d18abSAdrian Hunter	def About(self):
4748b62d18abSAdrian Hunter		dialog = AboutDialog(self.glb, self)
4749b62d18abSAdrian Hunter		dialog.exec_()
4750b62d18abSAdrian Hunter
475176099f98SAdrian Hunterdef TryOpen(file_name):
475276099f98SAdrian Hunter	try:
475376099f98SAdrian Hunter		return open(file_name, "rb")
475476099f98SAdrian Hunter	except:
475576099f98SAdrian Hunter		return None
475676099f98SAdrian Hunter
475776099f98SAdrian Hunterdef Is64Bit(f):
475876099f98SAdrian Hunter	result = sizeof(c_void_p)
475976099f98SAdrian Hunter	# ELF support only
476076099f98SAdrian Hunter	pos = f.tell()
476176099f98SAdrian Hunter	f.seek(0)
476276099f98SAdrian Hunter	header = f.read(7)
476376099f98SAdrian Hunter	f.seek(pos)
476476099f98SAdrian Hunter	magic = header[0:4]
4765606bd60aSAdrian Hunter	if sys.version_info[0] == 2:
476676099f98SAdrian Hunter		eclass = ord(header[4])
476776099f98SAdrian Hunter		encoding = ord(header[5])
476876099f98SAdrian Hunter		version = ord(header[6])
4769606bd60aSAdrian Hunter	else:
4770606bd60aSAdrian Hunter		eclass = header[4]
4771606bd60aSAdrian Hunter		encoding = header[5]
4772606bd60aSAdrian Hunter		version = header[6]
477376099f98SAdrian Hunter	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
477476099f98SAdrian Hunter		result = True if eclass == 2 else False
477576099f98SAdrian Hunter	return result
477676099f98SAdrian Hunter
4777031c2a00SAdrian Hunter# Global data
4778031c2a00SAdrian Hunter
4779031c2a00SAdrian Hunterclass Glb():
4780031c2a00SAdrian Hunter
4781031c2a00SAdrian Hunter	def __init__(self, dbref, db, dbname):
4782031c2a00SAdrian Hunter		self.dbref = dbref
4783031c2a00SAdrian Hunter		self.db = db
4784031c2a00SAdrian Hunter		self.dbname = dbname
478576099f98SAdrian Hunter		self.home_dir = os.path.expanduser("~")
478676099f98SAdrian Hunter		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
478776099f98SAdrian Hunter		if self.buildid_dir:
478876099f98SAdrian Hunter			self.buildid_dir += "/.build-id/"
478976099f98SAdrian Hunter		else:
479076099f98SAdrian Hunter			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
4791031c2a00SAdrian Hunter		self.app = None
4792031c2a00SAdrian Hunter		self.mainwindow = None
47938392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit = weakref.WeakSet()
479476099f98SAdrian Hunter		try:
479576099f98SAdrian Hunter			self.disassembler = LibXED()
479676099f98SAdrian Hunter			self.have_disassembler = True
479776099f98SAdrian Hunter		except:
479876099f98SAdrian Hunter			self.have_disassembler = False
47999a9dae36SAdrian Hunter		self.host_machine_id = 0
48009a9dae36SAdrian Hunter		self.host_start_time = 0
48019a9dae36SAdrian Hunter		self.host_finish_time = 0
480276099f98SAdrian Hunter
480376099f98SAdrian Hunter	def FileFromBuildId(self, build_id):
480476099f98SAdrian Hunter		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
480576099f98SAdrian Hunter		return TryOpen(file_name)
480676099f98SAdrian Hunter
480776099f98SAdrian Hunter	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
480876099f98SAdrian Hunter		# Assume current machine i.e. no support for virtualization
480976099f98SAdrian Hunter		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
481076099f98SAdrian Hunter			file_name = os.getenv("PERF_KCORE")
481176099f98SAdrian Hunter			f = TryOpen(file_name) if file_name else None
481276099f98SAdrian Hunter			if f:
481376099f98SAdrian Hunter				return f
481476099f98SAdrian Hunter			# For now, no special handling if long_name is /proc/kcore
481576099f98SAdrian Hunter			f = TryOpen(long_name)
481676099f98SAdrian Hunter			if f:
481776099f98SAdrian Hunter				return f
481876099f98SAdrian Hunter		f = self.FileFromBuildId(build_id)
481976099f98SAdrian Hunter		if f:
482076099f98SAdrian Hunter			return f
482176099f98SAdrian Hunter		return None
48228392b74bSAdrian Hunter
48238392b74bSAdrian Hunter	def AddInstanceToShutdownOnExit(self, instance):
48248392b74bSAdrian Hunter		self.instances_to_shutdown_on_exit.add(instance)
48258392b74bSAdrian Hunter
48268392b74bSAdrian Hunter	# Shutdown any background processes or threads
48278392b74bSAdrian Hunter	def ShutdownInstances(self):
48288392b74bSAdrian Hunter		for x in self.instances_to_shutdown_on_exit:
48298392b74bSAdrian Hunter			try:
48308392b74bSAdrian Hunter				x.Shutdown()
48318392b74bSAdrian Hunter			except:
48328392b74bSAdrian Hunter				pass
4833031c2a00SAdrian Hunter
48349a9dae36SAdrian Hunter	def GetHostMachineId(self):
48359a9dae36SAdrian Hunter		query = QSqlQuery(self.db)
48369a9dae36SAdrian Hunter		QueryExec(query, "SELECT id FROM machines WHERE pid = -1")
48379a9dae36SAdrian Hunter		if query.next():
48389a9dae36SAdrian Hunter			self.host_machine_id = query.value(0)
48399a9dae36SAdrian Hunter		else:
48409a9dae36SAdrian Hunter			self.host_machine_id = 0
48419a9dae36SAdrian Hunter		return self.host_machine_id
48429a9dae36SAdrian Hunter
48439a9dae36SAdrian Hunter	def HostMachineId(self):
48449a9dae36SAdrian Hunter		if self.host_machine_id:
48459a9dae36SAdrian Hunter			return self.host_machine_id
48469a9dae36SAdrian Hunter		return self.GetHostMachineId()
48479a9dae36SAdrian Hunter
48489a9dae36SAdrian Hunter	def SelectValue(self, sql):
48499a9dae36SAdrian Hunter		query = QSqlQuery(self.db)
48509a9dae36SAdrian Hunter		try:
48519a9dae36SAdrian Hunter			QueryExec(query, sql)
48529a9dae36SAdrian Hunter		except:
48539a9dae36SAdrian Hunter			return None
48549a9dae36SAdrian Hunter		if query.next():
48559a9dae36SAdrian Hunter			return Decimal(query.value(0))
48569a9dae36SAdrian Hunter		return None
48579a9dae36SAdrian Hunter
48589a9dae36SAdrian Hunter	def SwitchesMinTime(self, machine_id):
48599a9dae36SAdrian Hunter		return self.SelectValue("SELECT time"
48609a9dae36SAdrian Hunter					" FROM context_switches"
48619a9dae36SAdrian Hunter					" WHERE time != 0 AND machine_id = " + str(machine_id) +
48629a9dae36SAdrian Hunter					" ORDER BY id LIMIT 1")
48639a9dae36SAdrian Hunter
48649a9dae36SAdrian Hunter	def SwitchesMaxTime(self, machine_id):
48659a9dae36SAdrian Hunter		return self.SelectValue("SELECT time"
48669a9dae36SAdrian Hunter					" FROM context_switches"
48679a9dae36SAdrian Hunter					" WHERE time != 0 AND machine_id = " + str(machine_id) +
48689a9dae36SAdrian Hunter					" ORDER BY id DESC LIMIT 1")
48699a9dae36SAdrian Hunter
48709a9dae36SAdrian Hunter	def SamplesMinTime(self, machine_id):
48719a9dae36SAdrian Hunter		return self.SelectValue("SELECT time"
48729a9dae36SAdrian Hunter					" FROM samples"
48739a9dae36SAdrian Hunter					" WHERE time != 0 AND machine_id = " + str(machine_id) +
48749a9dae36SAdrian Hunter					" ORDER BY id LIMIT 1")
48759a9dae36SAdrian Hunter
48769a9dae36SAdrian Hunter	def SamplesMaxTime(self, machine_id):
48779a9dae36SAdrian Hunter		return self.SelectValue("SELECT time"
48789a9dae36SAdrian Hunter					" FROM samples"
48799a9dae36SAdrian Hunter					" WHERE time != 0 AND machine_id = " + str(machine_id) +
48809a9dae36SAdrian Hunter					" ORDER BY id DESC LIMIT 1")
48819a9dae36SAdrian Hunter
48829a9dae36SAdrian Hunter	def CallsMinTime(self, machine_id):
48839a9dae36SAdrian Hunter		return self.SelectValue("SELECT calls.call_time"
48849a9dae36SAdrian Hunter					" FROM calls"
48859a9dae36SAdrian Hunter					" INNER JOIN threads ON threads.thread_id = calls.thread_id"
48869a9dae36SAdrian Hunter					" WHERE calls.call_time != 0 AND threads.machine_id = " + str(machine_id) +
48879a9dae36SAdrian Hunter					" ORDER BY calls.id LIMIT 1")
48889a9dae36SAdrian Hunter
48899a9dae36SAdrian Hunter	def CallsMaxTime(self, machine_id):
48909a9dae36SAdrian Hunter		return self.SelectValue("SELECT calls.return_time"
48919a9dae36SAdrian Hunter					" FROM calls"
48929a9dae36SAdrian Hunter					" INNER JOIN threads ON threads.thread_id = calls.thread_id"
48939a9dae36SAdrian Hunter					" WHERE calls.return_time != 0 AND threads.machine_id = " + str(machine_id) +
48949a9dae36SAdrian Hunter					" ORDER BY calls.return_time DESC LIMIT 1")
48959a9dae36SAdrian Hunter
48969a9dae36SAdrian Hunter	def GetStartTime(self, machine_id):
48979a9dae36SAdrian Hunter		t0 = self.SwitchesMinTime(machine_id)
48989a9dae36SAdrian Hunter		t1 = self.SamplesMinTime(machine_id)
48999a9dae36SAdrian Hunter		t2 = self.CallsMinTime(machine_id)
49009a9dae36SAdrian Hunter		if t0 is None or (not(t1 is None) and t1 < t0):
49019a9dae36SAdrian Hunter			t0 = t1
49029a9dae36SAdrian Hunter		if t0 is None or (not(t2 is None) and t2 < t0):
49039a9dae36SAdrian Hunter			t0 = t2
49049a9dae36SAdrian Hunter		return t0
49059a9dae36SAdrian Hunter
49069a9dae36SAdrian Hunter	def GetFinishTime(self, machine_id):
49079a9dae36SAdrian Hunter		t0 = self.SwitchesMaxTime(machine_id)
49089a9dae36SAdrian Hunter		t1 = self.SamplesMaxTime(machine_id)
49099a9dae36SAdrian Hunter		t2 = self.CallsMaxTime(machine_id)
49109a9dae36SAdrian Hunter		if t0 is None or (not(t1 is None) and t1 > t0):
49119a9dae36SAdrian Hunter			t0 = t1
49129a9dae36SAdrian Hunter		if t0 is None or (not(t2 is None) and t2 > t0):
49139a9dae36SAdrian Hunter			t0 = t2
49149a9dae36SAdrian Hunter		return t0
49159a9dae36SAdrian Hunter
49169a9dae36SAdrian Hunter	def HostStartTime(self):
49179a9dae36SAdrian Hunter		if self.host_start_time:
49189a9dae36SAdrian Hunter			return self.host_start_time
49199a9dae36SAdrian Hunter		self.host_start_time = self.GetStartTime(self.HostMachineId())
49209a9dae36SAdrian Hunter		return self.host_start_time
49219a9dae36SAdrian Hunter
49229a9dae36SAdrian Hunter	def HostFinishTime(self):
49239a9dae36SAdrian Hunter		if self.host_finish_time:
49249a9dae36SAdrian Hunter			return self.host_finish_time
49259a9dae36SAdrian Hunter		self.host_finish_time = self.GetFinishTime(self.HostMachineId())
49269a9dae36SAdrian Hunter		return self.host_finish_time
49279a9dae36SAdrian Hunter
49289a9dae36SAdrian Hunter	def StartTime(self, machine_id):
49299a9dae36SAdrian Hunter		if machine_id == self.HostMachineId():
49309a9dae36SAdrian Hunter			return self.HostStartTime()
49319a9dae36SAdrian Hunter		return self.GetStartTime(machine_id)
49329a9dae36SAdrian Hunter
49339a9dae36SAdrian Hunter	def FinishTime(self, machine_id):
49349a9dae36SAdrian Hunter		if machine_id == self.HostMachineId():
49359a9dae36SAdrian Hunter			return self.HostFinishTime()
49369a9dae36SAdrian Hunter		return self.GetFinishTime(machine_id)
49379a9dae36SAdrian Hunter
4938031c2a00SAdrian Hunter# Database reference
4939031c2a00SAdrian Hunter
4940031c2a00SAdrian Hunterclass DBRef():
4941031c2a00SAdrian Hunter
4942031c2a00SAdrian Hunter	def __init__(self, is_sqlite3, dbname):
4943031c2a00SAdrian Hunter		self.is_sqlite3 = is_sqlite3
4944031c2a00SAdrian Hunter		self.dbname = dbname
4945af833988SAdrian Hunter		self.TRUE = "TRUE"
4946af833988SAdrian Hunter		self.FALSE = "FALSE"
4947af833988SAdrian Hunter		# SQLite prior to version 3.23 does not support TRUE and FALSE
4948af833988SAdrian Hunter		if self.is_sqlite3:
4949af833988SAdrian Hunter			self.TRUE = "1"
4950af833988SAdrian Hunter			self.FALSE = "0"
4951031c2a00SAdrian Hunter
4952031c2a00SAdrian Hunter	def Open(self, connection_name):
4953031c2a00SAdrian Hunter		dbname = self.dbname
4954031c2a00SAdrian Hunter		if self.is_sqlite3:
4955031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
4956031c2a00SAdrian Hunter		else:
4957031c2a00SAdrian Hunter			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
4958031c2a00SAdrian Hunter			opts = dbname.split()
4959031c2a00SAdrian Hunter			for opt in opts:
4960031c2a00SAdrian Hunter				if "=" in opt:
4961031c2a00SAdrian Hunter					opt = opt.split("=")
4962031c2a00SAdrian Hunter					if opt[0] == "hostname":
4963031c2a00SAdrian Hunter						db.setHostName(opt[1])
4964031c2a00SAdrian Hunter					elif opt[0] == "port":
4965031c2a00SAdrian Hunter						db.setPort(int(opt[1]))
4966031c2a00SAdrian Hunter					elif opt[0] == "username":
4967031c2a00SAdrian Hunter						db.setUserName(opt[1])
4968031c2a00SAdrian Hunter					elif opt[0] == "password":
4969031c2a00SAdrian Hunter						db.setPassword(opt[1])
4970031c2a00SAdrian Hunter					elif opt[0] == "dbname":
4971031c2a00SAdrian Hunter						dbname = opt[1]
4972031c2a00SAdrian Hunter				else:
4973031c2a00SAdrian Hunter					dbname = opt
4974031c2a00SAdrian Hunter
4975031c2a00SAdrian Hunter		db.setDatabaseName(dbname)
4976031c2a00SAdrian Hunter		if not db.open():
4977031c2a00SAdrian Hunter			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
4978031c2a00SAdrian Hunter		return db, dbname
4979031c2a00SAdrian Hunter
4980031c2a00SAdrian Hunter# Main
4981031c2a00SAdrian Hunter
4982031c2a00SAdrian Hunterdef Main():
49831ed7f47fSAdrian Hunter	usage_str =	"exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
49841ed7f47fSAdrian Hunter			"   or: exported-sql-viewer.py --help-only"
49851ed7f47fSAdrian Hunter	ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
4986df8ea22aSAdrian Hunter	ap.add_argument("--pyside-version-1", action='store_true')
49871ed7f47fSAdrian Hunter	ap.add_argument("dbname", nargs="?")
49881ed7f47fSAdrian Hunter	ap.add_argument("--help-only", action='store_true')
49891ed7f47fSAdrian Hunter	args = ap.parse_args()
4990031c2a00SAdrian Hunter
49911ed7f47fSAdrian Hunter	if args.help_only:
499265b24292SAdrian Hunter		app = QApplication(sys.argv)
499365b24292SAdrian Hunter		mainwindow = HelpOnlyWindow()
499465b24292SAdrian Hunter		mainwindow.show()
499565b24292SAdrian Hunter		err = app.exec_()
499665b24292SAdrian Hunter		sys.exit(err)
4997031c2a00SAdrian Hunter
49981ed7f47fSAdrian Hunter	dbname = args.dbname
49991ed7f47fSAdrian Hunter	if dbname is None:
50001ed7f47fSAdrian Hunter		ap.print_usage()
50011ed7f47fSAdrian Hunter		print("Too few arguments")
50021ed7f47fSAdrian Hunter		sys.exit(1)
50031ed7f47fSAdrian Hunter
5004031c2a00SAdrian Hunter	is_sqlite3 = False
5005031c2a00SAdrian Hunter	try:
5006beda0e72STony Jones		f = open(dbname, "rb")
5007beda0e72STony Jones		if f.read(15) == b'SQLite format 3':
5008031c2a00SAdrian Hunter			is_sqlite3 = True
5009031c2a00SAdrian Hunter		f.close()
5010031c2a00SAdrian Hunter	except:
5011031c2a00SAdrian Hunter		pass
5012031c2a00SAdrian Hunter
5013031c2a00SAdrian Hunter	dbref = DBRef(is_sqlite3, dbname)
5014031c2a00SAdrian Hunter	db, dbname = dbref.Open("main")
5015031c2a00SAdrian Hunter	glb = Glb(dbref, db, dbname)
5016031c2a00SAdrian Hunter	app = QApplication(sys.argv)
5017031c2a00SAdrian Hunter	glb.app = app
5018031c2a00SAdrian Hunter	mainwindow = MainWindow(glb)
5019031c2a00SAdrian Hunter	glb.mainwindow = mainwindow
5020031c2a00SAdrian Hunter	mainwindow.show()
5021031c2a00SAdrian Hunter	err = app.exec_()
50228392b74bSAdrian Hunter	glb.ShutdownInstances()
5023031c2a00SAdrian Hunter	db.close()
5024031c2a00SAdrian Hunter	sys.exit(err)
5025031c2a00SAdrian Hunter
5026031c2a00SAdrian Hunterif __name__ == "__main__":
5027031c2a00SAdrian Hunter	Main()
5028