cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

exported-sql-viewer.py (157284B)


      1#!/usr/bin/env python
      2# SPDX-License-Identifier: GPL-2.0
      3# exported-sql-viewer.py: view data from sql database
      4# Copyright (c) 2014-2018, Intel Corporation.
      5
      6# To use this script you will need to have exported data using either the
      7# export-to-sqlite.py or the export-to-postgresql.py script.  Refer to those
      8# scripts for details.
      9#
     10# Following on from the example in the export scripts, a
     11# call-graph can be displayed for the pt_example database like this:
     12#
     13#	python tools/perf/scripts/python/exported-sql-viewer.py pt_example
     14#
     15# Note that for PostgreSQL, this script supports connecting to remote databases
     16# by setting hostname, port, username, password, and dbname e.g.
     17#
     18#	python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
     19#
     20# The result is a GUI window with a tree representing a context-sensitive
     21# call-graph.  Expanding a couple of levels of the tree and adjusting column
     22# widths to suit will display something like:
     23#
     24#                                         Call Graph: pt_example
     25# Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
     26# v- ls
     27#     v- 2638:2638
     28#         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
     29#           |- unknown               unknown       1        13198     0.1              1              0.0
     30#           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
     31#           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
     32#           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
     33#              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
     34#              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
     35#              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
     36#              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
     37#              v- main               ls            1      8182043    99.6         180254             99.9
     38#
     39# Points to note:
     40#	The top level is a command name (comm)
     41#	The next level is a thread (pid:tid)
     42#	Subsequent levels are functions
     43#	'Count' is the number of calls
     44#	'Time' is the elapsed time until the function returns
     45#	Percentages are relative to the level above
     46#	'Branch Count' is the total number of branches for that function and all
     47#       functions that it calls
     48
     49# There is also a "All branches" report, which displays branches and
     50# possibly disassembly.  However, presently, the only supported disassembler is
     51# Intel XED, and additionally the object code must be present in perf build ID
     52# cache. To use Intel XED, libxed.so must be present. To build and install
     53# libxed.so:
     54#            git clone https://github.com/intelxed/mbuild.git mbuild
     55#            git clone https://github.com/intelxed/xed
     56#            cd xed
     57#            ./mfile.py --share
     58#            sudo ./mfile.py --prefix=/usr/local install
     59#            sudo ldconfig
     60#
     61# Example report:
     62#
     63# Time           CPU  Command  PID    TID    Branch Type            In Tx  Branch
     64# 8107675239590  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
     65#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
     66# 8107675239899  2    ls       22011  22011  hardware interrupt     No         7fab593ea260 _start (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
     67# 8107675241900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea260 _start (ld-2.19.so)
     68#                                                                              7fab593ea260 48 89 e7                                        mov %rsp, %rdi
     69#                                                                              7fab593ea263 e8 c8 06 00 00                                  callq  0x7fab593ea930
     70# 8107675241900  2    ls       22011  22011  call                   No         7fab593ea263 _start+0x3 (ld-2.19.so) -> 7fab593ea930 _dl_start (ld-2.19.so)
     71#                                                                              7fab593ea930 55                                              pushq  %rbp
     72#                                                                              7fab593ea931 48 89 e5                                        mov %rsp, %rbp
     73#                                                                              7fab593ea934 41 57                                           pushq  %r15
     74#                                                                              7fab593ea936 41 56                                           pushq  %r14
     75#                                                                              7fab593ea938 41 55                                           pushq  %r13
     76#                                                                              7fab593ea93a 41 54                                           pushq  %r12
     77#                                                                              7fab593ea93c 53                                              pushq  %rbx
     78#                                                                              7fab593ea93d 48 89 fb                                        mov %rdi, %rbx
     79#                                                                              7fab593ea940 48 83 ec 68                                     sub $0x68, %rsp
     80#                                                                              7fab593ea944 0f 31                                           rdtsc
     81#                                                                              7fab593ea946 48 c1 e2 20                                     shl $0x20, %rdx
     82#                                                                              7fab593ea94a 89 c0                                           mov %eax, %eax
     83#                                                                              7fab593ea94c 48 09 c2                                        or %rax, %rdx
     84#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
     85# 8107675242232  2    ls       22011  22011  hardware interrupt     No         7fab593ea94f _dl_start+0x1f (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
     86# 8107675242900  2    ls       22011  22011  return from interrupt  No     ffffffff86a00a67 native_irq_return_iret ([kernel]) -> 7fab593ea94f _dl_start+0x1f (ld-2.19.so)
     87#                                                                              7fab593ea94f 48 8b 05 1a 15 22 00                            movq  0x22151a(%rip), %rax
     88#                                                                              7fab593ea956 48 89 15 3b 13 22 00                            movq  %rdx, 0x22133b(%rip)
     89# 8107675243232  2    ls       22011  22011  hardware interrupt     No         7fab593ea956 _dl_start+0x26 (ld-2.19.so) -> ffffffff86a012e0 page_fault ([kernel])
     90
     91from __future__ import print_function
     92
     93import sys
     94# Only change warnings if the python -W option was not used
     95if not sys.warnoptions:
     96	import warnings
     97	# PySide2 causes deprecation warnings, ignore them.
     98	warnings.filterwarnings("ignore", category=DeprecationWarning)
     99import argparse
    100import weakref
    101import threading
    102import string
    103try:
    104	# Python2
    105	import cPickle as pickle
    106	# size of pickled integer big enough for record size
    107	glb_nsz = 8
    108except ImportError:
    109	import pickle
    110	glb_nsz = 16
    111import re
    112import os
    113import random
    114import copy
    115import math
    116from libxed import LibXED
    117
    118pyside_version_1 = True
    119if not "--pyside-version-1" in sys.argv:
    120	try:
    121		from PySide2.QtCore import *
    122		from PySide2.QtGui import *
    123		from PySide2.QtSql import *
    124		from PySide2.QtWidgets import *
    125		pyside_version_1 = False
    126	except:
    127		pass
    128
    129if pyside_version_1:
    130	from PySide.QtCore import *
    131	from PySide.QtGui import *
    132	from PySide.QtSql import *
    133
    134from decimal import Decimal, ROUND_HALF_UP
    135from ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \
    136		   c_void_p, c_bool, c_byte, c_char, c_int, c_uint, c_longlong, c_ulonglong
    137from multiprocessing import Process, Array, Value, Event
    138
    139# xrange is range in Python3
    140try:
    141	xrange
    142except NameError:
    143	xrange = range
    144
    145def printerr(*args, **keyword_args):
    146	print(*args, file=sys.stderr, **keyword_args)
    147
    148# Data formatting helpers
    149
    150def tohex(ip):
    151	if ip < 0:
    152		ip += 1 << 64
    153	return "%x" % ip
    154
    155def offstr(offset):
    156	if offset:
    157		return "+0x%x" % offset
    158	return ""
    159
    160def dsoname(name):
    161	if name == "[kernel.kallsyms]":
    162		return "[kernel]"
    163	return name
    164
    165def findnth(s, sub, n, offs=0):
    166	pos = s.find(sub)
    167	if pos < 0:
    168		return pos
    169	if n <= 1:
    170		return offs + pos
    171	return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
    172
    173# Percent to one decimal place
    174
    175def PercentToOneDP(n, d):
    176	if not d:
    177		return "0.0"
    178	x = (n * Decimal(100)) / d
    179	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
    180
    181# Helper for queries that must not fail
    182
    183def QueryExec(query, stmt):
    184	ret = query.exec_(stmt)
    185	if not ret:
    186		raise Exception("Query failed: " + query.lastError().text())
    187
    188# Background thread
    189
    190class Thread(QThread):
    191
    192	done = Signal(object)
    193
    194	def __init__(self, task, param=None, parent=None):
    195		super(Thread, self).__init__(parent)
    196		self.task = task
    197		self.param = param
    198
    199	def run(self):
    200		while True:
    201			if self.param is None:
    202				done, result = self.task()
    203			else:
    204				done, result = self.task(self.param)
    205			self.done.emit(result)
    206			if done:
    207				break
    208
    209# Tree data model
    210
    211class TreeModel(QAbstractItemModel):
    212
    213	def __init__(self, glb, params, parent=None):
    214		super(TreeModel, self).__init__(parent)
    215		self.glb = glb
    216		self.params = params
    217		self.root = self.GetRoot()
    218		self.last_row_read = 0
    219
    220	def Item(self, parent):
    221		if parent.isValid():
    222			return parent.internalPointer()
    223		else:
    224			return self.root
    225
    226	def rowCount(self, parent):
    227		result = self.Item(parent).childCount()
    228		if result < 0:
    229			result = 0
    230			self.dataChanged.emit(parent, parent)
    231		return result
    232
    233	def hasChildren(self, parent):
    234		return self.Item(parent).hasChildren()
    235
    236	def headerData(self, section, orientation, role):
    237		if role == Qt.TextAlignmentRole:
    238			return self.columnAlignment(section)
    239		if role != Qt.DisplayRole:
    240			return None
    241		if orientation != Qt.Horizontal:
    242			return None
    243		return self.columnHeader(section)
    244
    245	def parent(self, child):
    246		child_item = child.internalPointer()
    247		if child_item is self.root:
    248			return QModelIndex()
    249		parent_item = child_item.getParentItem()
    250		return self.createIndex(parent_item.getRow(), 0, parent_item)
    251
    252	def index(self, row, column, parent):
    253		child_item = self.Item(parent).getChildItem(row)
    254		return self.createIndex(row, column, child_item)
    255
    256	def DisplayData(self, item, index):
    257		return item.getData(index.column())
    258
    259	def FetchIfNeeded(self, row):
    260		if row > self.last_row_read:
    261			self.last_row_read = row
    262			if row + 10 >= self.root.child_count:
    263				self.fetcher.Fetch(glb_chunk_sz)
    264
    265	def columnAlignment(self, column):
    266		return Qt.AlignLeft
    267
    268	def columnFont(self, column):
    269		return None
    270
    271	def data(self, index, role):
    272		if role == Qt.TextAlignmentRole:
    273			return self.columnAlignment(index.column())
    274		if role == Qt.FontRole:
    275			return self.columnFont(index.column())
    276		if role != Qt.DisplayRole:
    277			return None
    278		item = index.internalPointer()
    279		return self.DisplayData(item, index)
    280
    281# Table data model
    282
    283class TableModel(QAbstractTableModel):
    284
    285	def __init__(self, parent=None):
    286		super(TableModel, self).__init__(parent)
    287		self.child_count = 0
    288		self.child_items = []
    289		self.last_row_read = 0
    290
    291	def Item(self, parent):
    292		if parent.isValid():
    293			return parent.internalPointer()
    294		else:
    295			return self
    296
    297	def rowCount(self, parent):
    298		return self.child_count
    299
    300	def headerData(self, section, orientation, role):
    301		if role == Qt.TextAlignmentRole:
    302			return self.columnAlignment(section)
    303		if role != Qt.DisplayRole:
    304			return None
    305		if orientation != Qt.Horizontal:
    306			return None
    307		return self.columnHeader(section)
    308
    309	def index(self, row, column, parent):
    310		return self.createIndex(row, column, self.child_items[row])
    311
    312	def DisplayData(self, item, index):
    313		return item.getData(index.column())
    314
    315	def FetchIfNeeded(self, row):
    316		if row > self.last_row_read:
    317			self.last_row_read = row
    318			if row + 10 >= self.child_count:
    319				self.fetcher.Fetch(glb_chunk_sz)
    320
    321	def columnAlignment(self, column):
    322		return Qt.AlignLeft
    323
    324	def columnFont(self, column):
    325		return None
    326
    327	def data(self, index, role):
    328		if role == Qt.TextAlignmentRole:
    329			return self.columnAlignment(index.column())
    330		if role == Qt.FontRole:
    331			return self.columnFont(index.column())
    332		if role != Qt.DisplayRole:
    333			return None
    334		item = index.internalPointer()
    335		return self.DisplayData(item, index)
    336
    337# Model cache
    338
    339model_cache = weakref.WeakValueDictionary()
    340model_cache_lock = threading.Lock()
    341
    342def LookupCreateModel(model_name, create_fn):
    343	model_cache_lock.acquire()
    344	try:
    345		model = model_cache[model_name]
    346	except:
    347		model = None
    348	if model is None:
    349		model = create_fn()
    350		model_cache[model_name] = model
    351	model_cache_lock.release()
    352	return model
    353
    354def LookupModel(model_name):
    355	model_cache_lock.acquire()
    356	try:
    357		model = model_cache[model_name]
    358	except:
    359		model = None
    360	model_cache_lock.release()
    361	return model
    362
    363# Find bar
    364
    365class FindBar():
    366
    367	def __init__(self, parent, finder, is_reg_expr=False):
    368		self.finder = finder
    369		self.context = []
    370		self.last_value = None
    371		self.last_pattern = None
    372
    373		label = QLabel("Find:")
    374		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
    375
    376		self.textbox = QComboBox()
    377		self.textbox.setEditable(True)
    378		self.textbox.currentIndexChanged.connect(self.ValueChanged)
    379
    380		self.progress = QProgressBar()
    381		self.progress.setRange(0, 0)
    382		self.progress.hide()
    383
    384		if is_reg_expr:
    385			self.pattern = QCheckBox("Regular Expression")
    386		else:
    387			self.pattern = QCheckBox("Pattern")
    388		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
    389
    390		self.next_button = QToolButton()
    391		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
    392		self.next_button.released.connect(lambda: self.NextPrev(1))
    393
    394		self.prev_button = QToolButton()
    395		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
    396		self.prev_button.released.connect(lambda: self.NextPrev(-1))
    397
    398		self.close_button = QToolButton()
    399		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
    400		self.close_button.released.connect(self.Deactivate)
    401
    402		self.hbox = QHBoxLayout()
    403		self.hbox.setContentsMargins(0, 0, 0, 0)
    404
    405		self.hbox.addWidget(label)
    406		self.hbox.addWidget(self.textbox)
    407		self.hbox.addWidget(self.progress)
    408		self.hbox.addWidget(self.pattern)
    409		self.hbox.addWidget(self.next_button)
    410		self.hbox.addWidget(self.prev_button)
    411		self.hbox.addWidget(self.close_button)
    412
    413		self.bar = QWidget()
    414		self.bar.setLayout(self.hbox)
    415		self.bar.hide()
    416
    417	def Widget(self):
    418		return self.bar
    419
    420	def Activate(self):
    421		self.bar.show()
    422		self.textbox.lineEdit().selectAll()
    423		self.textbox.setFocus()
    424
    425	def Deactivate(self):
    426		self.bar.hide()
    427
    428	def Busy(self):
    429		self.textbox.setEnabled(False)
    430		self.pattern.hide()
    431		self.next_button.hide()
    432		self.prev_button.hide()
    433		self.progress.show()
    434
    435	def Idle(self):
    436		self.textbox.setEnabled(True)
    437		self.progress.hide()
    438		self.pattern.show()
    439		self.next_button.show()
    440		self.prev_button.show()
    441
    442	def Find(self, direction):
    443		value = self.textbox.currentText()
    444		pattern = self.pattern.isChecked()
    445		self.last_value = value
    446		self.last_pattern = pattern
    447		self.finder.Find(value, direction, pattern, self.context)
    448
    449	def ValueChanged(self):
    450		value = self.textbox.currentText()
    451		pattern = self.pattern.isChecked()
    452		index = self.textbox.currentIndex()
    453		data = self.textbox.itemData(index)
    454		# Store the pattern in the combo box to keep it with the text value
    455		if data == None:
    456			self.textbox.setItemData(index, pattern)
    457		else:
    458			self.pattern.setChecked(data)
    459		self.Find(0)
    460
    461	def NextPrev(self, direction):
    462		value = self.textbox.currentText()
    463		pattern = self.pattern.isChecked()
    464		if value != self.last_value:
    465			index = self.textbox.findText(value)
    466			# Allow for a button press before the value has been added to the combo box
    467			if index < 0:
    468				index = self.textbox.count()
    469				self.textbox.addItem(value, pattern)
    470				self.textbox.setCurrentIndex(index)
    471				return
    472			else:
    473				self.textbox.setItemData(index, pattern)
    474		elif pattern != self.last_pattern:
    475			# Keep the pattern recorded in the combo box up to date
    476			index = self.textbox.currentIndex()
    477			self.textbox.setItemData(index, pattern)
    478		self.Find(direction)
    479
    480	def NotFound(self):
    481		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
    482
    483# Context-sensitive call graph data model item base
    484
    485class CallGraphLevelItemBase(object):
    486
    487	def __init__(self, glb, params, row, parent_item):
    488		self.glb = glb
    489		self.params = params
    490		self.row = row
    491		self.parent_item = parent_item
    492		self.query_done = False
    493		self.child_count = 0
    494		self.child_items = []
    495		if parent_item:
    496			self.level = parent_item.level + 1
    497		else:
    498			self.level = 0
    499
    500	def getChildItem(self, row):
    501		return self.child_items[row]
    502
    503	def getParentItem(self):
    504		return self.parent_item
    505
    506	def getRow(self):
    507		return self.row
    508
    509	def childCount(self):
    510		if not self.query_done:
    511			self.Select()
    512			if not self.child_count:
    513				return -1
    514		return self.child_count
    515
    516	def hasChildren(self):
    517		if not self.query_done:
    518			return True
    519		return self.child_count > 0
    520
    521	def getData(self, column):
    522		return self.data[column]
    523
    524# Context-sensitive call graph data model level 2+ item base
    525
    526class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
    527
    528	def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
    529		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
    530		self.comm_id = comm_id
    531		self.thread_id = thread_id
    532		self.call_path_id = call_path_id
    533		self.insn_cnt = insn_cnt
    534		self.cyc_cnt = cyc_cnt
    535		self.branch_count = branch_count
    536		self.time = time
    537
    538	def Select(self):
    539		self.query_done = True
    540		query = QSqlQuery(self.glb.db)
    541		if self.params.have_ipc:
    542			ipc_str = ", SUM(insn_count), SUM(cyc_count)"
    543		else:
    544			ipc_str = ""
    545		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
    546					" FROM calls"
    547					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
    548					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
    549					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
    550					" WHERE parent_call_path_id = " + str(self.call_path_id) +
    551					" AND comm_id = " + str(self.comm_id) +
    552					" AND thread_id = " + str(self.thread_id) +
    553					" GROUP BY call_path_id, name, short_name"
    554					" ORDER BY call_path_id")
    555		while query.next():
    556			if self.params.have_ipc:
    557				insn_cnt = int(query.value(5))
    558				cyc_cnt = int(query.value(6))
    559				branch_count = int(query.value(7))
    560			else:
    561				insn_cnt = 0
    562				cyc_cnt = 0
    563				branch_count = int(query.value(5))
    564			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)
    565			self.child_items.append(child_item)
    566			self.child_count += 1
    567
    568# Context-sensitive call graph data model level three item
    569
    570class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
    571
    572	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):
    573		super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
    574		dso = dsoname(dso)
    575		if self.params.have_ipc:
    576			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
    577			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
    578			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
    579			ipc = CalcIPC(cyc_cnt, insn_cnt)
    580			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 ]
    581		else:
    582			self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
    583		self.dbid = call_path_id
    584
    585# Context-sensitive call graph data model level two item
    586
    587class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
    588
    589	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
    590		super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
    591		if self.params.have_ipc:
    592			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
    593		else:
    594			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
    595		self.dbid = thread_id
    596
    597	def Select(self):
    598		super(CallGraphLevelTwoItem, self).Select()
    599		for child_item in self.child_items:
    600			self.time += child_item.time
    601			self.insn_cnt += child_item.insn_cnt
    602			self.cyc_cnt += child_item.cyc_cnt
    603			self.branch_count += child_item.branch_count
    604		for child_item in self.child_items:
    605			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
    606			if self.params.have_ipc:
    607				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
    608				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
    609				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
    610			else:
    611				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
    612
    613# Context-sensitive call graph data model level one item
    614
    615class CallGraphLevelOneItem(CallGraphLevelItemBase):
    616
    617	def __init__(self, glb, params, row, comm_id, comm, parent_item):
    618		super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
    619		if self.params.have_ipc:
    620			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
    621		else:
    622			self.data = [comm, "", "", "", "", "", ""]
    623		self.dbid = comm_id
    624
    625	def Select(self):
    626		self.query_done = True
    627		query = QSqlQuery(self.glb.db)
    628		QueryExec(query, "SELECT thread_id, pid, tid"
    629					" FROM comm_threads"
    630					" INNER JOIN threads ON thread_id = threads.id"
    631					" WHERE comm_id = " + str(self.dbid))
    632		while query.next():
    633			child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
    634			self.child_items.append(child_item)
    635			self.child_count += 1
    636
    637# Context-sensitive call graph data model root item
    638
    639class CallGraphRootItem(CallGraphLevelItemBase):
    640
    641	def __init__(self, glb, params):
    642		super(CallGraphRootItem, self).__init__(glb, params, 0, None)
    643		self.dbid = 0
    644		self.query_done = True
    645		if_has_calls = ""
    646		if IsSelectable(glb.db, "comms", columns = "has_calls"):
    647			if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
    648		query = QSqlQuery(glb.db)
    649		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
    650		while query.next():
    651			if not query.value(0):
    652				continue
    653			child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
    654			self.child_items.append(child_item)
    655			self.child_count += 1
    656
    657# Call graph model parameters
    658
    659class CallGraphModelParams():
    660
    661	def __init__(self, glb, parent=None):
    662		self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
    663
    664# Context-sensitive call graph data model base
    665
    666class CallGraphModelBase(TreeModel):
    667
    668	def __init__(self, glb, parent=None):
    669		super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
    670
    671	def FindSelect(self, value, pattern, query):
    672		if pattern:
    673			# postgresql and sqlite pattern patching differences:
    674			#   postgresql LIKE is case sensitive but sqlite LIKE is not
    675			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
    676			#   postgresql supports ILIKE which is case insensitive
    677			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
    678			if not self.glb.dbref.is_sqlite3:
    679				# Escape % and _
    680				s = value.replace("%", "\%")
    681				s = s.replace("_", "\_")
    682				# Translate * and ? into SQL LIKE pattern characters % and _
    683				trans = string.maketrans("*?", "%_")
    684				match = " LIKE '" + str(s).translate(trans) + "'"
    685			else:
    686				match = " GLOB '" + str(value) + "'"
    687		else:
    688			match = " = '" + str(value) + "'"
    689		self.DoFindSelect(query, match)
    690
    691	def Found(self, query, found):
    692		if found:
    693			return self.FindPath(query)
    694		return []
    695
    696	def FindValue(self, value, pattern, query, last_value, last_pattern):
    697		if last_value == value and pattern == last_pattern:
    698			found = query.first()
    699		else:
    700			self.FindSelect(value, pattern, query)
    701			found = query.next()
    702		return self.Found(query, found)
    703
    704	def FindNext(self, query):
    705		found = query.next()
    706		if not found:
    707			found = query.first()
    708		return self.Found(query, found)
    709
    710	def FindPrev(self, query):
    711		found = query.previous()
    712		if not found:
    713			found = query.last()
    714		return self.Found(query, found)
    715
    716	def FindThread(self, c):
    717		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
    718			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
    719		elif c.direction > 0:
    720			ids = self.FindNext(c.query)
    721		else:
    722			ids = self.FindPrev(c.query)
    723		return (True, ids)
    724
    725	def Find(self, value, direction, pattern, context, callback):
    726		class Context():
    727			def __init__(self, *x):
    728				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
    729			def Update(self, *x):
    730				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
    731		if len(context):
    732			context[0].Update(value, direction, pattern)
    733		else:
    734			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
    735		# Use a thread so the UI is not blocked during the SELECT
    736		thread = Thread(self.FindThread, context[0])
    737		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
    738		thread.start()
    739
    740	def FindDone(self, thread, callback, ids):
    741		callback(ids)
    742
    743# Context-sensitive call graph data model
    744
    745class CallGraphModel(CallGraphModelBase):
    746
    747	def __init__(self, glb, parent=None):
    748		super(CallGraphModel, self).__init__(glb, parent)
    749
    750	def GetRoot(self):
    751		return CallGraphRootItem(self.glb, self.params)
    752
    753	def columnCount(self, parent=None):
    754		if self.params.have_ipc:
    755			return 12
    756		else:
    757			return 7
    758
    759	def columnHeader(self, column):
    760		if self.params.have_ipc:
    761			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
    762		else:
    763			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
    764		return headers[column]
    765
    766	def columnAlignment(self, column):
    767		if self.params.have_ipc:
    768			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 ]
    769		else:
    770			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
    771		return alignment[column]
    772
    773	def DoFindSelect(self, query, match):
    774		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
    775						" FROM calls"
    776						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
    777						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
    778						" WHERE calls.id <> 0"
    779						" AND symbols.name" + match +
    780						" GROUP BY comm_id, thread_id, call_path_id"
    781						" ORDER BY comm_id, thread_id, call_path_id")
    782
    783	def FindPath(self, query):
    784		# Turn the query result into a list of ids that the tree view can walk
    785		# to open the tree at the right place.
    786		ids = []
    787		parent_id = query.value(0)
    788		while parent_id:
    789			ids.insert(0, parent_id)
    790			q2 = QSqlQuery(self.glb.db)
    791			QueryExec(q2, "SELECT parent_id"
    792					" FROM call_paths"
    793					" WHERE id = " + str(parent_id))
    794			if not q2.next():
    795				break
    796			parent_id = q2.value(0)
    797		# The call path root is not used
    798		if ids[0] == 1:
    799			del ids[0]
    800		ids.insert(0, query.value(2))
    801		ids.insert(0, query.value(1))
    802		return ids
    803
    804# Call tree data model level 2+ item base
    805
    806class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
    807
    808	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
    809		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
    810		self.comm_id = comm_id
    811		self.thread_id = thread_id
    812		self.calls_id = calls_id
    813		self.call_time = call_time
    814		self.time = time
    815		self.insn_cnt = insn_cnt
    816		self.cyc_cnt = cyc_cnt
    817		self.branch_count = branch_count
    818
    819	def Select(self):
    820		self.query_done = True
    821		if self.calls_id == 0:
    822			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
    823		else:
    824			comm_thread = ""
    825		if self.params.have_ipc:
    826			ipc_str = ", insn_count, cyc_count"
    827		else:
    828			ipc_str = ""
    829		query = QSqlQuery(self.glb.db)
    830		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
    831					" FROM calls"
    832					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
    833					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
    834					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
    835					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
    836					" ORDER BY call_time, calls.id")
    837		while query.next():
    838			if self.params.have_ipc:
    839				insn_cnt = int(query.value(5))
    840				cyc_cnt = int(query.value(6))
    841				branch_count = int(query.value(7))
    842			else:
    843				insn_cnt = 0
    844				cyc_cnt = 0
    845				branch_count = int(query.value(5))
    846			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)
    847			self.child_items.append(child_item)
    848			self.child_count += 1
    849
    850# Call tree data model level three item
    851
    852class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
    853
    854	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):
    855		super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item)
    856		dso = dsoname(dso)
    857		if self.params.have_ipc:
    858			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
    859			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
    860			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
    861			ipc = CalcIPC(cyc_cnt, insn_cnt)
    862			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 ]
    863		else:
    864			self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
    865		self.dbid = calls_id
    866
    867# Call tree data model level two item
    868
    869class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
    870
    871	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
    872		super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, 0, parent_item)
    873		if self.params.have_ipc:
    874			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
    875		else:
    876			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
    877		self.dbid = thread_id
    878
    879	def Select(self):
    880		super(CallTreeLevelTwoItem, self).Select()
    881		for child_item in self.child_items:
    882			self.time += child_item.time
    883			self.insn_cnt += child_item.insn_cnt
    884			self.cyc_cnt += child_item.cyc_cnt
    885			self.branch_count += child_item.branch_count
    886		for child_item in self.child_items:
    887			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
    888			if self.params.have_ipc:
    889				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
    890				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
    891				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
    892			else:
    893				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
    894
    895# Call tree data model level one item
    896
    897class CallTreeLevelOneItem(CallGraphLevelItemBase):
    898
    899	def __init__(self, glb, params, row, comm_id, comm, parent_item):
    900		super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
    901		if self.params.have_ipc:
    902			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
    903		else:
    904			self.data = [comm, "", "", "", "", "", ""]
    905		self.dbid = comm_id
    906
    907	def Select(self):
    908		self.query_done = True
    909		query = QSqlQuery(self.glb.db)
    910		QueryExec(query, "SELECT thread_id, pid, tid"
    911					" FROM comm_threads"
    912					" INNER JOIN threads ON thread_id = threads.id"
    913					" WHERE comm_id = " + str(self.dbid))
    914		while query.next():
    915			child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
    916			self.child_items.append(child_item)
    917			self.child_count += 1
    918
    919# Call tree data model root item
    920
    921class CallTreeRootItem(CallGraphLevelItemBase):
    922
    923	def __init__(self, glb, params):
    924		super(CallTreeRootItem, self).__init__(glb, params, 0, None)
    925		self.dbid = 0
    926		self.query_done = True
    927		if_has_calls = ""
    928		if IsSelectable(glb.db, "comms", columns = "has_calls"):
    929			if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
    930		query = QSqlQuery(glb.db)
    931		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
    932		while query.next():
    933			if not query.value(0):
    934				continue
    935			child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
    936			self.child_items.append(child_item)
    937			self.child_count += 1
    938
    939# Call Tree data model
    940
    941class CallTreeModel(CallGraphModelBase):
    942
    943	def __init__(self, glb, parent=None):
    944		super(CallTreeModel, self).__init__(glb, parent)
    945
    946	def GetRoot(self):
    947		return CallTreeRootItem(self.glb, self.params)
    948
    949	def columnCount(self, parent=None):
    950		if self.params.have_ipc:
    951			return 12
    952		else:
    953			return 7
    954
    955	def columnHeader(self, column):
    956		if self.params.have_ipc:
    957			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
    958		else:
    959			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
    960		return headers[column]
    961
    962	def columnAlignment(self, column):
    963		if self.params.have_ipc:
    964			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 ]
    965		else:
    966			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
    967		return alignment[column]
    968
    969	def DoFindSelect(self, query, match):
    970		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
    971						" FROM calls"
    972						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
    973						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
    974						" WHERE calls.id <> 0"
    975						" AND symbols.name" + match +
    976						" ORDER BY comm_id, thread_id, call_time, calls.id")
    977
    978	def FindPath(self, query):
    979		# Turn the query result into a list of ids that the tree view can walk
    980		# to open the tree at the right place.
    981		ids = []
    982		parent_id = query.value(0)
    983		while parent_id:
    984			ids.insert(0, parent_id)
    985			q2 = QSqlQuery(self.glb.db)
    986			QueryExec(q2, "SELECT parent_id"
    987					" FROM calls"
    988					" WHERE id = " + str(parent_id))
    989			if not q2.next():
    990				break
    991			parent_id = q2.value(0)
    992		ids.insert(0, query.value(2))
    993		ids.insert(0, query.value(1))
    994		return ids
    995
    996# Vertical layout
    997
    998class HBoxLayout(QHBoxLayout):
    999
   1000	def __init__(self, *children):
   1001		super(HBoxLayout, self).__init__()
   1002
   1003		self.layout().setContentsMargins(0, 0, 0, 0)
   1004		for child in children:
   1005			if child.isWidgetType():
   1006				self.layout().addWidget(child)
   1007			else:
   1008				self.layout().addLayout(child)
   1009
   1010# Horizontal layout
   1011
   1012class VBoxLayout(QVBoxLayout):
   1013
   1014	def __init__(self, *children):
   1015		super(VBoxLayout, self).__init__()
   1016
   1017		self.layout().setContentsMargins(0, 0, 0, 0)
   1018		for child in children:
   1019			if child.isWidgetType():
   1020				self.layout().addWidget(child)
   1021			else:
   1022				self.layout().addLayout(child)
   1023
   1024# Vertical layout widget
   1025
   1026class VBox():
   1027
   1028	def __init__(self, *children):
   1029		self.vbox = QWidget()
   1030		self.vbox.setLayout(VBoxLayout(*children))
   1031
   1032	def Widget(self):
   1033		return self.vbox
   1034
   1035# Tree window base
   1036
   1037class TreeWindowBase(QMdiSubWindow):
   1038
   1039	def __init__(self, parent=None):
   1040		super(TreeWindowBase, self).__init__(parent)
   1041
   1042		self.model = None
   1043		self.find_bar = None
   1044
   1045		self.view = QTreeView()
   1046		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
   1047		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
   1048
   1049		self.context_menu = TreeContextMenu(self.view)
   1050
   1051	def DisplayFound(self, ids):
   1052		if not len(ids):
   1053			return False
   1054		parent = QModelIndex()
   1055		for dbid in ids:
   1056			found = False
   1057			n = self.model.rowCount(parent)
   1058			for row in xrange(n):
   1059				child = self.model.index(row, 0, parent)
   1060				if child.internalPointer().dbid == dbid:
   1061					found = True
   1062					self.view.setExpanded(parent, True)
   1063					self.view.setCurrentIndex(child)
   1064					parent = child
   1065					break
   1066			if not found:
   1067				break
   1068		return found
   1069
   1070	def Find(self, value, direction, pattern, context):
   1071		self.view.setFocus()
   1072		self.find_bar.Busy()
   1073		self.model.Find(value, direction, pattern, context, self.FindDone)
   1074
   1075	def FindDone(self, ids):
   1076		found = True
   1077		if not self.DisplayFound(ids):
   1078			found = False
   1079		self.find_bar.Idle()
   1080		if not found:
   1081			self.find_bar.NotFound()
   1082
   1083
   1084# Context-sensitive call graph window
   1085
   1086class CallGraphWindow(TreeWindowBase):
   1087
   1088	def __init__(self, glb, parent=None):
   1089		super(CallGraphWindow, self).__init__(parent)
   1090
   1091		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
   1092
   1093		self.view.setModel(self.model)
   1094
   1095		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
   1096			self.view.setColumnWidth(c, w)
   1097
   1098		self.find_bar = FindBar(self, self)
   1099
   1100		self.vbox = VBox(self.view, self.find_bar.Widget())
   1101
   1102		self.setWidget(self.vbox.Widget())
   1103
   1104		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
   1105
   1106# Call tree window
   1107
   1108class CallTreeWindow(TreeWindowBase):
   1109
   1110	def __init__(self, glb, parent=None, thread_at_time=None):
   1111		super(CallTreeWindow, self).__init__(parent)
   1112
   1113		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
   1114
   1115		self.view.setModel(self.model)
   1116
   1117		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
   1118			self.view.setColumnWidth(c, w)
   1119
   1120		self.find_bar = FindBar(self, self)
   1121
   1122		self.vbox = VBox(self.view, self.find_bar.Widget())
   1123
   1124		self.setWidget(self.vbox.Widget())
   1125
   1126		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
   1127
   1128		if thread_at_time:
   1129			self.DisplayThreadAtTime(*thread_at_time)
   1130
   1131	def DisplayThreadAtTime(self, comm_id, thread_id, time):
   1132		parent = QModelIndex()
   1133		for dbid in (comm_id, thread_id):
   1134			found = False
   1135			n = self.model.rowCount(parent)
   1136			for row in xrange(n):
   1137				child = self.model.index(row, 0, parent)
   1138				if child.internalPointer().dbid == dbid:
   1139					found = True
   1140					self.view.setExpanded(parent, True)
   1141					self.view.setCurrentIndex(child)
   1142					parent = child
   1143					break
   1144			if not found:
   1145				return
   1146		found = False
   1147		while True:
   1148			n = self.model.rowCount(parent)
   1149			if not n:
   1150				return
   1151			last_child = None
   1152			for row in xrange(n):
   1153				self.view.setExpanded(parent, True)
   1154				child = self.model.index(row, 0, parent)
   1155				child_call_time = child.internalPointer().call_time
   1156				if child_call_time < time:
   1157					last_child = child
   1158				elif child_call_time == time:
   1159					self.view.setCurrentIndex(child)
   1160					return
   1161				elif child_call_time > time:
   1162					break
   1163			if not last_child:
   1164				if not found:
   1165					child = self.model.index(0, 0, parent)
   1166					self.view.setExpanded(parent, True)
   1167					self.view.setCurrentIndex(child)
   1168				return
   1169			found = True
   1170			self.view.setExpanded(parent, True)
   1171			self.view.setCurrentIndex(last_child)
   1172			parent = last_child
   1173
   1174# ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name
   1175
   1176def ExecComm(db, thread_id, time):
   1177	query = QSqlQuery(db)
   1178	QueryExec(query, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag"
   1179				" FROM comm_threads"
   1180				" INNER JOIN comms ON comms.id = comm_threads.comm_id"
   1181				" WHERE comm_threads.thread_id = " + str(thread_id) +
   1182				" ORDER BY comms.c_time, comms.id")
   1183	first = None
   1184	last = None
   1185	while query.next():
   1186		if first is None:
   1187			first = query.value(0)
   1188		if query.value(2) and Decimal(query.value(1)) <= Decimal(time):
   1189			last = query.value(0)
   1190	if not(last is None):
   1191		return last
   1192	return first
   1193
   1194# Container for (x, y) data
   1195
   1196class XY():
   1197	def __init__(self, x=0, y=0):
   1198		self.x = x
   1199		self.y = y
   1200
   1201	def __str__(self):
   1202		return "XY({}, {})".format(str(self.x), str(self.y))
   1203
   1204# Container for sub-range data
   1205
   1206class Subrange():
   1207	def __init__(self, lo=0, hi=0):
   1208		self.lo = lo
   1209		self.hi = hi
   1210
   1211	def __str__(self):
   1212		return "Subrange({}, {})".format(str(self.lo), str(self.hi))
   1213
   1214# Graph data region base class
   1215
   1216class GraphDataRegion(object):
   1217
   1218	def __init__(self, key, title = "", ordinal = ""):
   1219		self.key = key
   1220		self.title = title
   1221		self.ordinal = ordinal
   1222
   1223# Function to sort GraphDataRegion
   1224
   1225def GraphDataRegionOrdinal(data_region):
   1226	return data_region.ordinal
   1227
   1228# Attributes for a graph region
   1229
   1230class GraphRegionAttribute():
   1231
   1232	def __init__(self, colour):
   1233		self.colour = colour
   1234
   1235# Switch graph data region represents a task
   1236
   1237class SwitchGraphDataRegion(GraphDataRegion):
   1238
   1239	def __init__(self, key, exec_comm_id, pid, tid, comm, thread_id, comm_id):
   1240		super(SwitchGraphDataRegion, self).__init__(key)
   1241
   1242		self.title = str(pid) + " / " + str(tid) + " " + comm
   1243		# Order graph legend within exec comm by pid / tid / time
   1244		self.ordinal = str(pid).rjust(16) + str(exec_comm_id).rjust(8) + str(tid).rjust(16)
   1245		self.exec_comm_id = exec_comm_id
   1246		self.pid = pid
   1247		self.tid = tid
   1248		self.comm = comm
   1249		self.thread_id = thread_id
   1250		self.comm_id = comm_id
   1251
   1252# Graph data point
   1253
   1254class GraphDataPoint():
   1255
   1256	def __init__(self, data, index, x, y, altx=None, alty=None, hregion=None, vregion=None):
   1257		self.data = data
   1258		self.index = index
   1259		self.x = x
   1260		self.y = y
   1261		self.altx = altx
   1262		self.alty = alty
   1263		self.hregion = hregion
   1264		self.vregion = vregion
   1265
   1266# Graph data (single graph) base class
   1267
   1268class GraphData(object):
   1269
   1270	def __init__(self, collection, xbase=Decimal(0), ybase=Decimal(0)):
   1271		self.collection = collection
   1272		self.points = []
   1273		self.xbase = xbase
   1274		self.ybase = ybase
   1275		self.title = ""
   1276
   1277	def AddPoint(self, x, y, altx=None, alty=None, hregion=None, vregion=None):
   1278		index = len(self.points)
   1279
   1280		x = float(Decimal(x) - self.xbase)
   1281		y = float(Decimal(y) - self.ybase)
   1282
   1283		self.points.append(GraphDataPoint(self, index, x, y, altx, alty, hregion, vregion))
   1284
   1285	def XToData(self, x):
   1286		return Decimal(x) + self.xbase
   1287
   1288	def YToData(self, y):
   1289		return Decimal(y) + self.ybase
   1290
   1291# Switch graph data (for one CPU)
   1292
   1293class SwitchGraphData(GraphData):
   1294
   1295	def __init__(self, db, collection, cpu, xbase):
   1296		super(SwitchGraphData, self).__init__(collection, xbase)
   1297
   1298		self.cpu = cpu
   1299		self.title = "CPU " + str(cpu)
   1300		self.SelectSwitches(db)
   1301
   1302	def SelectComms(self, db, thread_id, last_comm_id, start_time, end_time):
   1303		query = QSqlQuery(db)
   1304		QueryExec(query, "SELECT id, c_time"
   1305					" FROM comms"
   1306					" WHERE c_thread_id = " + str(thread_id) +
   1307					"   AND exec_flag = " + self.collection.glb.dbref.TRUE +
   1308					"   AND c_time >= " + str(start_time) +
   1309					"   AND c_time <= " + str(end_time) +
   1310					" ORDER BY c_time, id")
   1311		while query.next():
   1312			comm_id = query.value(0)
   1313			if comm_id == last_comm_id:
   1314				continue
   1315			time = query.value(1)
   1316			hregion = self.HRegion(db, thread_id, comm_id, time)
   1317			self.AddPoint(time, 1000, None, None, hregion)
   1318
   1319	def SelectSwitches(self, db):
   1320		last_time = None
   1321		last_comm_id = None
   1322		last_thread_id = None
   1323		query = QSqlQuery(db)
   1324		QueryExec(query, "SELECT time, thread_out_id, thread_in_id, comm_out_id, comm_in_id, flags"
   1325					" FROM context_switches"
   1326					" WHERE machine_id = " + str(self.collection.machine_id) +
   1327					"   AND cpu = " + str(self.cpu) +
   1328					" ORDER BY time, id")
   1329		while query.next():
   1330			flags = int(query.value(5))
   1331			if flags & 1:
   1332				# Schedule-out: detect and add exec's
   1333				if last_thread_id == query.value(1) and last_comm_id is not None and last_comm_id != query.value(3):
   1334					self.SelectComms(db, last_thread_id, last_comm_id, last_time, query.value(0))
   1335				continue
   1336			# Schedule-in: add data point
   1337			if len(self.points) == 0:
   1338				start_time = self.collection.glb.StartTime(self.collection.machine_id)
   1339				hregion = self.HRegion(db, query.value(1), query.value(3), start_time)
   1340				self.AddPoint(start_time, 1000, None, None, hregion)
   1341			time = query.value(0)
   1342			comm_id = query.value(4)
   1343			thread_id = query.value(2)
   1344			hregion = self.HRegion(db, thread_id, comm_id, time)
   1345			self.AddPoint(time, 1000, None, None, hregion)
   1346			last_time = time
   1347			last_comm_id = comm_id
   1348			last_thread_id = thread_id
   1349
   1350	def NewHRegion(self, db, key, thread_id, comm_id, time):
   1351		exec_comm_id = ExecComm(db, thread_id, time)
   1352		query = QSqlQuery(db)
   1353		QueryExec(query, "SELECT pid, tid FROM threads WHERE id = " + str(thread_id))
   1354		if query.next():
   1355			pid = query.value(0)
   1356			tid = query.value(1)
   1357		else:
   1358			pid = -1
   1359			tid = -1
   1360		query = QSqlQuery(db)
   1361		QueryExec(query, "SELECT comm FROM comms WHERE id = " + str(comm_id))
   1362		if query.next():
   1363			comm = query.value(0)
   1364		else:
   1365			comm = ""
   1366		return SwitchGraphDataRegion(key, exec_comm_id, pid, tid, comm, thread_id, comm_id)
   1367
   1368	def HRegion(self, db, thread_id, comm_id, time):
   1369		key = str(thread_id) + ":" + str(comm_id)
   1370		hregion = self.collection.LookupHRegion(key)
   1371		if hregion is None:
   1372			hregion = self.NewHRegion(db, key, thread_id, comm_id, time)
   1373			self.collection.AddHRegion(key, hregion)
   1374		return hregion
   1375
   1376# Graph data collection (multiple related graphs) base class
   1377
   1378class GraphDataCollection(object):
   1379
   1380	def __init__(self, glb):
   1381		self.glb = glb
   1382		self.data = []
   1383		self.hregions = {}
   1384		self.xrangelo = None
   1385		self.xrangehi = None
   1386		self.yrangelo = None
   1387		self.yrangehi = None
   1388		self.dp = XY(0, 0)
   1389
   1390	def AddGraphData(self, data):
   1391		self.data.append(data)
   1392
   1393	def LookupHRegion(self, key):
   1394		if key in self.hregions:
   1395			return self.hregions[key]
   1396		return None
   1397
   1398	def AddHRegion(self, key, hregion):
   1399		self.hregions[key] = hregion
   1400
   1401# Switch graph data collection (SwitchGraphData for each CPU)
   1402
   1403class SwitchGraphDataCollection(GraphDataCollection):
   1404
   1405	def __init__(self, glb, db, machine_id):
   1406		super(SwitchGraphDataCollection, self).__init__(glb)
   1407
   1408		self.machine_id = machine_id
   1409		self.cpus = self.SelectCPUs(db)
   1410
   1411		self.xrangelo = glb.StartTime(machine_id)
   1412		self.xrangehi = glb.FinishTime(machine_id)
   1413
   1414		self.yrangelo = Decimal(0)
   1415		self.yrangehi = Decimal(1000)
   1416
   1417		for cpu in self.cpus:
   1418			self.AddGraphData(SwitchGraphData(db, self, cpu, self.xrangelo))
   1419
   1420	def SelectCPUs(self, db):
   1421		cpus = []
   1422		query = QSqlQuery(db)
   1423		QueryExec(query, "SELECT DISTINCT cpu"
   1424					" FROM context_switches"
   1425					" WHERE machine_id = " + str(self.machine_id))
   1426		while query.next():
   1427			cpus.append(int(query.value(0)))
   1428		return sorted(cpus)
   1429
   1430# Switch graph data graphics item displays the graphed data
   1431
   1432class SwitchGraphDataGraphicsItem(QGraphicsItem):
   1433
   1434	def __init__(self, data, graph_width, graph_height, attrs, event_handler, parent=None):
   1435		super(SwitchGraphDataGraphicsItem, self).__init__(parent)
   1436
   1437		self.data = data
   1438		self.graph_width = graph_width
   1439		self.graph_height = graph_height
   1440		self.attrs = attrs
   1441		self.event_handler = event_handler
   1442		self.setAcceptHoverEvents(True)
   1443
   1444	def boundingRect(self):
   1445		return QRectF(0, 0, self.graph_width, self.graph_height)
   1446
   1447	def PaintPoint(self, painter, last, x):
   1448		if not(last is None or last.hregion.pid == 0 or x < self.attrs.subrange.x.lo):
   1449			if last.x < self.attrs.subrange.x.lo:
   1450				x0 = self.attrs.subrange.x.lo
   1451			else:
   1452				x0 = last.x
   1453			if x > self.attrs.subrange.x.hi:
   1454				x1 = self.attrs.subrange.x.hi
   1455			else:
   1456				x1 = x - 1
   1457			x0 = self.attrs.XToPixel(x0)
   1458			x1 = self.attrs.XToPixel(x1)
   1459
   1460			y0 = self.attrs.YToPixel(last.y)
   1461
   1462			colour = self.attrs.region_attributes[last.hregion.key].colour
   1463
   1464			width = x1 - x0 + 1
   1465			if width < 2:
   1466				painter.setPen(colour)
   1467				painter.drawLine(x0, self.graph_height - y0, x0, self.graph_height)
   1468			else:
   1469				painter.fillRect(x0, self.graph_height - y0, width, self.graph_height - 1, colour)
   1470
   1471	def paint(self, painter, option, widget):
   1472		last = None
   1473		for point in self.data.points:
   1474			self.PaintPoint(painter, last, point.x)
   1475			if point.x > self.attrs.subrange.x.hi:
   1476				break;
   1477			last = point
   1478		self.PaintPoint(painter, last, self.attrs.subrange.x.hi + 1)
   1479
   1480	def BinarySearchPoint(self, target):
   1481		lower_pos = 0
   1482		higher_pos = len(self.data.points)
   1483		while True:
   1484			pos = int((lower_pos + higher_pos) / 2)
   1485			val = self.data.points[pos].x
   1486			if target >= val:
   1487				lower_pos = pos
   1488			else:
   1489				higher_pos = pos
   1490			if higher_pos <= lower_pos + 1:
   1491				return lower_pos
   1492
   1493	def XPixelToData(self, x):
   1494		x = self.attrs.PixelToX(x)
   1495		if x < self.data.points[0].x:
   1496			x = 0
   1497			pos = 0
   1498			low = True
   1499		else:
   1500			pos = self.BinarySearchPoint(x)
   1501			low = False
   1502		return (low, pos, self.data.XToData(x))
   1503
   1504	def EventToData(self, event):
   1505		no_data = (None,) * 4
   1506		if len(self.data.points) < 1:
   1507			return no_data
   1508		x = event.pos().x()
   1509		if x < 0:
   1510			return no_data
   1511		low0, pos0, time_from = self.XPixelToData(x)
   1512		low1, pos1, time_to = self.XPixelToData(x + 1)
   1513		hregions = set()
   1514		hregion_times = []
   1515		if not low1:
   1516			for i in xrange(pos0, pos1 + 1):
   1517				hregion = self.data.points[i].hregion
   1518				hregions.add(hregion)
   1519				if i == pos0:
   1520					time = time_from
   1521				else:
   1522					time = self.data.XToData(self.data.points[i].x)
   1523				hregion_times.append((hregion, time))
   1524		return (time_from, time_to, hregions, hregion_times)
   1525
   1526	def hoverMoveEvent(self, event):
   1527		time_from, time_to, hregions, hregion_times = self.EventToData(event)
   1528		if time_from is not None:
   1529			self.event_handler.PointEvent(self.data.cpu, time_from, time_to, hregions)
   1530
   1531	def hoverLeaveEvent(self, event):
   1532		self.event_handler.NoPointEvent()
   1533
   1534	def mousePressEvent(self, event):
   1535		if event.button() != Qt.RightButton:
   1536			super(SwitchGraphDataGraphicsItem, self).mousePressEvent(event)
   1537			return
   1538		time_from, time_to, hregions, hregion_times = self.EventToData(event)
   1539		if hregion_times:
   1540			self.event_handler.RightClickEvent(self.data.cpu, hregion_times, event.screenPos())
   1541
   1542# X-axis graphics item
   1543
   1544class XAxisGraphicsItem(QGraphicsItem):
   1545
   1546	def __init__(self, width, parent=None):
   1547		super(XAxisGraphicsItem, self).__init__(parent)
   1548
   1549		self.width = width
   1550		self.max_mark_sz = 4
   1551		self.height = self.max_mark_sz + 1
   1552
   1553	def boundingRect(self):
   1554		return QRectF(0, 0, self.width, self.height)
   1555
   1556	def Step(self):
   1557		attrs = self.parentItem().attrs
   1558		subrange = attrs.subrange.x
   1559		t = subrange.hi - subrange.lo
   1560		s = (3.0 * t) / self.width
   1561		n = 1.0
   1562		while s > n:
   1563			n = n * 10.0
   1564		return n
   1565
   1566	def PaintMarks(self, painter, at_y, lo, hi, step, i):
   1567		attrs = self.parentItem().attrs
   1568		x = lo
   1569		while x <= hi:
   1570			xp = attrs.XToPixel(x)
   1571			if i % 10:
   1572				if i % 5:
   1573					sz = 1
   1574				else:
   1575					sz = 2
   1576			else:
   1577				sz = self.max_mark_sz
   1578				i = 0
   1579			painter.drawLine(xp, at_y, xp, at_y + sz)
   1580			x += step
   1581			i += 1
   1582
   1583	def paint(self, painter, option, widget):
   1584		# Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1
   1585		painter.drawLine(0, 0, self.width - 1, 0)
   1586		n = self.Step()
   1587		attrs = self.parentItem().attrs
   1588		subrange = attrs.subrange.x
   1589		if subrange.lo:
   1590			x_offset = n - (subrange.lo % n)
   1591		else:
   1592			x_offset = 0.0
   1593		x = subrange.lo + x_offset
   1594		i = (x / n) % 10
   1595		self.PaintMarks(painter, 0, x, subrange.hi, n, i)
   1596
   1597	def ScaleDimensions(self):
   1598		n = self.Step()
   1599		attrs = self.parentItem().attrs
   1600		lo = attrs.subrange.x.lo
   1601		hi = (n * 10.0) + lo
   1602		width = attrs.XToPixel(hi)
   1603		if width > 500:
   1604			width = 0
   1605		return (n, lo, hi, width)
   1606
   1607	def PaintScale(self, painter, at_x, at_y):
   1608		n, lo, hi, width = self.ScaleDimensions()
   1609		if not width:
   1610			return
   1611		painter.drawLine(at_x, at_y, at_x + width, at_y)
   1612		self.PaintMarks(painter, at_y, lo, hi, n, 0)
   1613
   1614	def ScaleWidth(self):
   1615		n, lo, hi, width = self.ScaleDimensions()
   1616		return width
   1617
   1618	def ScaleHeight(self):
   1619		return self.height
   1620
   1621	def ScaleUnit(self):
   1622		return self.Step() * 10
   1623
   1624# Scale graphics item base class
   1625
   1626class ScaleGraphicsItem(QGraphicsItem):
   1627
   1628	def __init__(self, axis, parent=None):
   1629		super(ScaleGraphicsItem, self).__init__(parent)
   1630		self.axis = axis
   1631
   1632	def boundingRect(self):
   1633		scale_width = self.axis.ScaleWidth()
   1634		if not scale_width:
   1635			return QRectF()
   1636		return QRectF(0, 0, self.axis.ScaleWidth() + 100, self.axis.ScaleHeight())
   1637
   1638	def paint(self, painter, option, widget):
   1639		scale_width = self.axis.ScaleWidth()
   1640		if not scale_width:
   1641			return
   1642		self.axis.PaintScale(painter, 0, 5)
   1643		x = scale_width + 4
   1644		painter.drawText(QPointF(x, 10), self.Text())
   1645
   1646	def Unit(self):
   1647		return self.axis.ScaleUnit()
   1648
   1649	def Text(self):
   1650		return ""
   1651
   1652# Switch graph scale graphics item
   1653
   1654class SwitchScaleGraphicsItem(ScaleGraphicsItem):
   1655
   1656	def __init__(self, axis, parent=None):
   1657		super(SwitchScaleGraphicsItem, self).__init__(axis, parent)
   1658
   1659	def Text(self):
   1660		unit = self.Unit()
   1661		if unit >= 1000000000:
   1662			unit = int(unit / 1000000000)
   1663			us = "s"
   1664		elif unit >= 1000000:
   1665			unit = int(unit / 1000000)
   1666			us = "ms"
   1667		elif unit >= 1000:
   1668			unit = int(unit / 1000)
   1669			us = "us"
   1670		else:
   1671			unit = int(unit)
   1672			us = "ns"
   1673		return " = " + str(unit) + " " + us
   1674
   1675# Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data
   1676
   1677class SwitchGraphGraphicsItem(QGraphicsItem):
   1678
   1679	def __init__(self, collection, data, attrs, event_handler, first, parent=None):
   1680		super(SwitchGraphGraphicsItem, self).__init__(parent)
   1681		self.collection = collection
   1682		self.data = data
   1683		self.attrs = attrs
   1684		self.event_handler = event_handler
   1685
   1686		margin = 20
   1687		title_width = 50
   1688
   1689		self.title_graphics = QGraphicsSimpleTextItem(data.title, self)
   1690
   1691		self.title_graphics.setPos(margin, margin)
   1692		graph_width = attrs.XToPixel(attrs.subrange.x.hi) + 1
   1693		graph_height = attrs.YToPixel(attrs.subrange.y.hi) + 1
   1694
   1695		self.graph_origin_x = margin + title_width + margin
   1696		self.graph_origin_y = graph_height + margin
   1697
   1698		x_axis_size = 1
   1699		y_axis_size = 1
   1700		self.yline = QGraphicsLineItem(0, 0, 0, graph_height, self)
   1701
   1702		self.x_axis = XAxisGraphicsItem(graph_width, self)
   1703		self.x_axis.setPos(self.graph_origin_x, self.graph_origin_y + 1)
   1704
   1705		if first:
   1706			self.scale_item = SwitchScaleGraphicsItem(self.x_axis, self)
   1707			self.scale_item.setPos(self.graph_origin_x, self.graph_origin_y + 10)
   1708
   1709		self.yline.setPos(self.graph_origin_x - y_axis_size, self.graph_origin_y - graph_height)
   1710
   1711		self.axis_point = QGraphicsLineItem(0, 0, 0, 0, self)
   1712		self.axis_point.setPos(self.graph_origin_x - 1, self.graph_origin_y +1)
   1713
   1714		self.width = self.graph_origin_x + graph_width + margin
   1715		self.height = self.graph_origin_y + margin
   1716
   1717		self.graph = SwitchGraphDataGraphicsItem(data, graph_width, graph_height, attrs, event_handler, self)
   1718		self.graph.setPos(self.graph_origin_x, self.graph_origin_y - graph_height)
   1719
   1720		if parent and 'EnableRubberBand' in dir(parent):
   1721			parent.EnableRubberBand(self.graph_origin_x, self.graph_origin_x + graph_width - 1, self)
   1722
   1723	def boundingRect(self):
   1724		return QRectF(0, 0, self.width, self.height)
   1725
   1726	def paint(self, painter, option, widget):
   1727		pass
   1728
   1729	def RBXToPixel(self, x):
   1730		return self.attrs.PixelToX(x - self.graph_origin_x)
   1731
   1732	def RBXRangeToPixel(self, x0, x1):
   1733		return (self.RBXToPixel(x0), self.RBXToPixel(x1 + 1))
   1734
   1735	def RBPixelToTime(self, x):
   1736		if x < self.data.points[0].x:
   1737			return self.data.XToData(0)
   1738		return self.data.XToData(x)
   1739
   1740	def RBEventTimes(self, x0, x1):
   1741		x0, x1 = self.RBXRangeToPixel(x0, x1)
   1742		time_from = self.RBPixelToTime(x0)
   1743		time_to = self.RBPixelToTime(x1)
   1744		return (time_from, time_to)
   1745
   1746	def RBEvent(self, x0, x1):
   1747		time_from, time_to = self.RBEventTimes(x0, x1)
   1748		self.event_handler.RangeEvent(time_from, time_to)
   1749
   1750	def RBMoveEvent(self, x0, x1):
   1751		if x1 < x0:
   1752			x0, x1 = x1, x0
   1753		self.RBEvent(x0, x1)
   1754
   1755	def RBReleaseEvent(self, x0, x1, selection_state):
   1756		if x1 < x0:
   1757			x0, x1 = x1, x0
   1758		x0, x1 = self.RBXRangeToPixel(x0, x1)
   1759		self.event_handler.SelectEvent(x0, x1, selection_state)
   1760
   1761# Graphics item to draw a vertical bracket (used to highlight "forward" sub-range)
   1762
   1763class VerticalBracketGraphicsItem(QGraphicsItem):
   1764
   1765	def __init__(self, parent=None):
   1766		super(VerticalBracketGraphicsItem, self).__init__(parent)
   1767
   1768		self.width = 0
   1769		self.height = 0
   1770		self.hide()
   1771
   1772	def SetSize(self, width, height):
   1773		self.width = width + 1
   1774		self.height = height + 1
   1775
   1776	def boundingRect(self):
   1777		return QRectF(0, 0, self.width, self.height)
   1778
   1779	def paint(self, painter, option, widget):
   1780		colour = QColor(255, 255, 0, 32)
   1781		painter.fillRect(0, 0, self.width, self.height, colour)
   1782		x1 = self.width - 1
   1783		y1 = self.height - 1
   1784		painter.drawLine(0, 0, x1, 0)
   1785		painter.drawLine(0, 0, 0, 3)
   1786		painter.drawLine(x1, 0, x1, 3)
   1787		painter.drawLine(0, y1, x1, y1)
   1788		painter.drawLine(0, y1, 0, y1 - 3)
   1789		painter.drawLine(x1, y1, x1, y1 - 3)
   1790
   1791# Graphics item to contain graphs arranged vertically
   1792
   1793class VertcalGraphSetGraphicsItem(QGraphicsItem):
   1794
   1795	def __init__(self, collection, attrs, event_handler, child_class, parent=None):
   1796		super(VertcalGraphSetGraphicsItem, self).__init__(parent)
   1797
   1798		self.collection = collection
   1799
   1800		self.top = 10
   1801
   1802		self.width = 0
   1803		self.height = self.top
   1804
   1805		self.rubber_band = None
   1806		self.rb_enabled = False
   1807
   1808		first = True
   1809		for data in collection.data:
   1810			child = child_class(collection, data, attrs, event_handler, first, self)
   1811			child.setPos(0, self.height + 1)
   1812			rect = child.boundingRect()
   1813			if rect.right() > self.width:
   1814				self.width = rect.right()
   1815			self.height = self.height + rect.bottom() + 1
   1816			first = False
   1817
   1818		self.bracket = VerticalBracketGraphicsItem(self)
   1819
   1820	def EnableRubberBand(self, xlo, xhi, rb_event_handler):
   1821		if self.rb_enabled:
   1822			return
   1823		self.rb_enabled = True
   1824		self.rb_in_view = False
   1825		self.setAcceptedMouseButtons(Qt.LeftButton)
   1826		self.rb_xlo = xlo
   1827		self.rb_xhi = xhi
   1828		self.rb_event_handler = rb_event_handler
   1829		self.mousePressEvent = self.MousePressEvent
   1830		self.mouseMoveEvent = self.MouseMoveEvent
   1831		self.mouseReleaseEvent = self.MouseReleaseEvent
   1832
   1833	def boundingRect(self):
   1834		return QRectF(0, 0, self.width, self.height)
   1835
   1836	def paint(self, painter, option, widget):
   1837		pass
   1838
   1839	def RubberBandParent(self):
   1840		scene = self.scene()
   1841		view = scene.views()[0]
   1842		viewport = view.viewport()
   1843		return viewport
   1844
   1845	def RubberBandSetGeometry(self, rect):
   1846		scene_rectf = self.mapRectToScene(QRectF(rect))
   1847		scene = self.scene()
   1848		view = scene.views()[0]
   1849		poly = view.mapFromScene(scene_rectf)
   1850		self.rubber_band.setGeometry(poly.boundingRect())
   1851
   1852	def SetSelection(self, selection_state):
   1853		if self.rubber_band:
   1854			if selection_state:
   1855				self.RubberBandSetGeometry(selection_state)
   1856				self.rubber_band.show()
   1857			else:
   1858				self.rubber_band.hide()
   1859
   1860	def SetBracket(self, rect):
   1861		if rect:
   1862			x, y, width, height = rect.x(), rect.y(), rect.width(), rect.height()
   1863			self.bracket.setPos(x, y)
   1864			self.bracket.SetSize(width, height)
   1865			self.bracket.show()
   1866		else:
   1867			self.bracket.hide()
   1868
   1869	def RubberBandX(self, event):
   1870		x = event.pos().toPoint().x()
   1871		if x < self.rb_xlo:
   1872			x = self.rb_xlo
   1873		elif x > self.rb_xhi:
   1874			x = self.rb_xhi
   1875		else:
   1876			self.rb_in_view = True
   1877		return x
   1878
   1879	def RubberBandRect(self, x):
   1880		if self.rb_origin.x() <= x:
   1881			width = x - self.rb_origin.x()
   1882			rect = QRect(self.rb_origin, QSize(width, self.height))
   1883		else:
   1884			width = self.rb_origin.x() - x
   1885			top_left = QPoint(self.rb_origin.x() - width, self.rb_origin.y())
   1886			rect = QRect(top_left, QSize(width, self.height))
   1887		return rect
   1888
   1889	def MousePressEvent(self, event):
   1890		self.rb_in_view = False
   1891		x = self.RubberBandX(event)
   1892		self.rb_origin = QPoint(x, self.top)
   1893		if self.rubber_band is None:
   1894			self.rubber_band = QRubberBand(QRubberBand.Rectangle, self.RubberBandParent())
   1895		self.RubberBandSetGeometry(QRect(self.rb_origin, QSize(0, self.height)))
   1896		if self.rb_in_view:
   1897			self.rubber_band.show()
   1898			self.rb_event_handler.RBMoveEvent(x, x)
   1899		else:
   1900			self.rubber_band.hide()
   1901
   1902	def MouseMoveEvent(self, event):
   1903		x = self.RubberBandX(event)
   1904		rect = self.RubberBandRect(x)
   1905		self.RubberBandSetGeometry(rect)
   1906		if self.rb_in_view:
   1907			self.rubber_band.show()
   1908			self.rb_event_handler.RBMoveEvent(self.rb_origin.x(), x)
   1909
   1910	def MouseReleaseEvent(self, event):
   1911		x = self.RubberBandX(event)
   1912		if self.rb_in_view:
   1913			selection_state = self.RubberBandRect(x)
   1914		else:
   1915			selection_state = None
   1916		self.rb_event_handler.RBReleaseEvent(self.rb_origin.x(), x, selection_state)
   1917
   1918# Switch graph legend data model
   1919
   1920class SwitchGraphLegendModel(QAbstractTableModel):
   1921
   1922	def __init__(self, collection, region_attributes, parent=None):
   1923		super(SwitchGraphLegendModel, self).__init__(parent)
   1924
   1925		self.region_attributes = region_attributes
   1926
   1927		self.child_items = sorted(collection.hregions.values(), key=GraphDataRegionOrdinal)
   1928		self.child_count = len(self.child_items)
   1929
   1930		self.highlight_set = set()
   1931
   1932		self.column_headers = ("pid", "tid", "comm")
   1933
   1934	def rowCount(self, parent):
   1935		return self.child_count
   1936
   1937	def headerData(self, section, orientation, role):
   1938		if role != Qt.DisplayRole:
   1939			return None
   1940		if orientation != Qt.Horizontal:
   1941			return None
   1942		return self.columnHeader(section)
   1943
   1944	def index(self, row, column, parent):
   1945		return self.createIndex(row, column, self.child_items[row])
   1946
   1947	def columnCount(self, parent=None):
   1948		return len(self.column_headers)
   1949
   1950	def columnHeader(self, column):
   1951		return self.column_headers[column]
   1952
   1953	def data(self, index, role):
   1954		if role == Qt.BackgroundRole:
   1955			child = self.child_items[index.row()]
   1956			if child in self.highlight_set:
   1957				return self.region_attributes[child.key].colour
   1958			return None
   1959		if role == Qt.ForegroundRole:
   1960			child = self.child_items[index.row()]
   1961			if child in self.highlight_set:
   1962				return QColor(255, 255, 255)
   1963			return self.region_attributes[child.key].colour
   1964		if role != Qt.DisplayRole:
   1965			return None
   1966		hregion = self.child_items[index.row()]
   1967		col = index.column()
   1968		if col == 0:
   1969			return hregion.pid
   1970		if col == 1:
   1971			return hregion.tid
   1972		if col == 2:
   1973			return hregion.comm
   1974		return None
   1975
   1976	def SetHighlight(self, row, set_highlight):
   1977		child = self.child_items[row]
   1978		top_left = self.createIndex(row, 0, child)
   1979		bottom_right = self.createIndex(row, len(self.column_headers) - 1, child)
   1980		self.dataChanged.emit(top_left, bottom_right)
   1981
   1982	def Highlight(self, highlight_set):
   1983		for row in xrange(self.child_count):
   1984			child = self.child_items[row]
   1985			if child in self.highlight_set:
   1986				if child not in highlight_set:
   1987					self.SetHighlight(row, False)
   1988			elif child in highlight_set:
   1989				self.SetHighlight(row, True)
   1990		self.highlight_set = highlight_set
   1991
   1992# Switch graph legend is a table
   1993
   1994class SwitchGraphLegend(QWidget):
   1995
   1996	def __init__(self, collection, region_attributes, parent=None):
   1997		super(SwitchGraphLegend, self).__init__(parent)
   1998
   1999		self.data_model = SwitchGraphLegendModel(collection, region_attributes)
   2000
   2001		self.model = QSortFilterProxyModel()
   2002		self.model.setSourceModel(self.data_model)
   2003
   2004		self.view = QTableView()
   2005		self.view.setModel(self.model)
   2006		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
   2007		self.view.verticalHeader().setVisible(False)
   2008		self.view.sortByColumn(-1, Qt.AscendingOrder)
   2009		self.view.setSortingEnabled(True)
   2010		self.view.resizeColumnsToContents()
   2011		self.view.resizeRowsToContents()
   2012
   2013		self.vbox = VBoxLayout(self.view)
   2014		self.setLayout(self.vbox)
   2015
   2016		sz1 = self.view.columnWidth(0) + self.view.columnWidth(1) + self.view.columnWidth(2) + 2
   2017		sz1 = sz1 + self.view.verticalScrollBar().sizeHint().width()
   2018		self.saved_size = sz1
   2019
   2020	def resizeEvent(self, event):
   2021		self.saved_size = self.size().width()
   2022		super(SwitchGraphLegend, self).resizeEvent(event)
   2023
   2024	def Highlight(self, highlight_set):
   2025		self.data_model.Highlight(highlight_set)
   2026		self.update()
   2027
   2028	def changeEvent(self, event):
   2029		if event.type() == QEvent.FontChange:
   2030			self.view.resizeRowsToContents()
   2031			self.view.resizeColumnsToContents()
   2032			# Need to resize rows again after column resize
   2033			self.view.resizeRowsToContents()
   2034		super(SwitchGraphLegend, self).changeEvent(event)
   2035
   2036# Random colour generation
   2037
   2038def RGBColourTooLight(r, g, b):
   2039	if g > 230:
   2040		return True
   2041	if g <= 160:
   2042		return False
   2043	if r <= 180 and g <= 180:
   2044		return False
   2045	if r < 60:
   2046		return False
   2047	return True
   2048
   2049def GenerateColours(x):
   2050	cs = [0]
   2051	for i in xrange(1, x):
   2052		cs.append(int((255.0 / i) + 0.5))
   2053	colours = []
   2054	for r in cs:
   2055		for g in cs:
   2056			for b in cs:
   2057				# Exclude black and colours that look too light against a white background
   2058				if (r, g, b) == (0, 0, 0) or RGBColourTooLight(r, g, b):
   2059					continue
   2060				colours.append(QColor(r, g, b))
   2061	return colours
   2062
   2063def GenerateNColours(n):
   2064	for x in xrange(2, n + 2):
   2065		colours = GenerateColours(x)
   2066		if len(colours) >= n:
   2067			return colours
   2068	return []
   2069
   2070def GenerateNRandomColours(n, seed):
   2071	colours = GenerateNColours(n)
   2072	random.seed(seed)
   2073	random.shuffle(colours)
   2074	return colours
   2075
   2076# Graph attributes, in particular the scale and subrange that change when zooming
   2077
   2078class GraphAttributes():
   2079
   2080	def __init__(self, scale, subrange, region_attributes, dp):
   2081		self.scale = scale
   2082		self.subrange = subrange
   2083		self.region_attributes = region_attributes
   2084		# Rounding avoids errors due to finite floating point precision
   2085		self.dp = dp	# data decimal places
   2086		self.Update()
   2087
   2088	def XToPixel(self, x):
   2089		return int(round((x - self.subrange.x.lo) * self.scale.x, self.pdp.x))
   2090
   2091	def YToPixel(self, y):
   2092		return int(round((y - self.subrange.y.lo) * self.scale.y, self.pdp.y))
   2093
   2094	def PixelToXRounded(self, px):
   2095		return round((round(px, 0) / self.scale.x), self.dp.x) + self.subrange.x.lo
   2096
   2097	def PixelToYRounded(self, py):
   2098		return round((round(py, 0) / self.scale.y), self.dp.y) + self.subrange.y.lo
   2099
   2100	def PixelToX(self, px):
   2101		x = self.PixelToXRounded(px)
   2102		if self.pdp.x == 0:
   2103			rt = self.XToPixel(x)
   2104			if rt > px:
   2105				return x - 1
   2106		return x
   2107
   2108	def PixelToY(self, py):
   2109		y = self.PixelToYRounded(py)
   2110		if self.pdp.y == 0:
   2111			rt = self.YToPixel(y)
   2112			if rt > py:
   2113				return y - 1
   2114		return y
   2115
   2116	def ToPDP(self, dp, scale):
   2117		# Calculate pixel decimal places:
   2118		#    (10 ** dp) is the minimum delta in the data
   2119		#    scale it to get the minimum delta in pixels
   2120		#    log10 gives the number of decimals places negatively
   2121		#    subtrace 1 to divide by 10
   2122		#    round to the lower negative number
   2123		#    change the sign to get the number of decimals positively
   2124		x = math.log10((10 ** dp) * scale)
   2125		if x < 0:
   2126			x -= 1
   2127			x = -int(math.floor(x) - 0.1)
   2128		else:
   2129			x = 0
   2130		return x
   2131
   2132	def Update(self):
   2133		x = self.ToPDP(self.dp.x, self.scale.x)
   2134		y = self.ToPDP(self.dp.y, self.scale.y)
   2135		self.pdp = XY(x, y) # pixel decimal places
   2136
   2137# Switch graph splitter which divides the CPU graphs from the legend
   2138
   2139class SwitchGraphSplitter(QSplitter):
   2140
   2141	def __init__(self, parent=None):
   2142		super(SwitchGraphSplitter, self).__init__(parent)
   2143
   2144		self.first_time = False
   2145
   2146	def resizeEvent(self, ev):
   2147		if self.first_time:
   2148			self.first_time = False
   2149			sz1 = self.widget(1).view.columnWidth(0) + self.widget(1).view.columnWidth(1) + self.widget(1).view.columnWidth(2) + 2
   2150			sz1 = sz1 + self.widget(1).view.verticalScrollBar().sizeHint().width()
   2151			sz0 = self.size().width() - self.handleWidth() - sz1
   2152			self.setSizes([sz0, sz1])
   2153		elif not(self.widget(1).saved_size is None):
   2154			sz1 = self.widget(1).saved_size
   2155			sz0 = self.size().width() - self.handleWidth() - sz1
   2156			self.setSizes([sz0, sz1])
   2157		super(SwitchGraphSplitter, self).resizeEvent(ev)
   2158
   2159# Graph widget base class
   2160
   2161class GraphWidget(QWidget):
   2162
   2163	graph_title_changed = Signal(object)
   2164
   2165	def __init__(self, parent=None):
   2166		super(GraphWidget, self).__init__(parent)
   2167
   2168	def GraphTitleChanged(self, title):
   2169		self.graph_title_changed.emit(title)
   2170
   2171	def Title(self):
   2172		return ""
   2173
   2174# Display time in s, ms, us or ns
   2175
   2176def ToTimeStr(val):
   2177	val = Decimal(val)
   2178	if val >= 1000000000:
   2179		return "{} s".format((val / 1000000000).quantize(Decimal("0.000000001")))
   2180	if val >= 1000000:
   2181		return "{} ms".format((val / 1000000).quantize(Decimal("0.000001")))
   2182	if val >= 1000:
   2183		return "{} us".format((val / 1000).quantize(Decimal("0.001")))
   2184	return "{} ns".format(val.quantize(Decimal("1")))
   2185
   2186# Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons
   2187
   2188class SwitchGraphWidget(GraphWidget):
   2189
   2190	def __init__(self, glb, collection, parent=None):
   2191		super(SwitchGraphWidget, self).__init__(parent)
   2192
   2193		self.glb = glb
   2194		self.collection = collection
   2195
   2196		self.back_state = []
   2197		self.forward_state = []
   2198		self.selection_state = (None, None)
   2199		self.fwd_rect = None
   2200		self.start_time = self.glb.StartTime(collection.machine_id)
   2201
   2202		i = 0
   2203		hregions = collection.hregions.values()
   2204		colours = GenerateNRandomColours(len(hregions), 1013)
   2205		region_attributes = {}
   2206		for hregion in hregions:
   2207			if hregion.pid == 0 and hregion.tid == 0:
   2208				region_attributes[hregion.key] = GraphRegionAttribute(QColor(0, 0, 0))
   2209			else:
   2210				region_attributes[hregion.key] = GraphRegionAttribute(colours[i])
   2211				i = i + 1
   2212
   2213		# Default to entire range
   2214		xsubrange = Subrange(0.0, float(collection.xrangehi - collection.xrangelo) + 1.0)
   2215		ysubrange = Subrange(0.0, float(collection.yrangehi - collection.yrangelo) + 1.0)
   2216		subrange = XY(xsubrange, ysubrange)
   2217
   2218		scale = self.GetScaleForRange(subrange)
   2219
   2220		self.attrs = GraphAttributes(scale, subrange, region_attributes, collection.dp)
   2221
   2222		self.item = VertcalGraphSetGraphicsItem(collection, self.attrs, self, SwitchGraphGraphicsItem)
   2223
   2224		self.scene = QGraphicsScene()
   2225		self.scene.addItem(self.item)
   2226
   2227		self.view = QGraphicsView(self.scene)
   2228		self.view.centerOn(0, 0)
   2229		self.view.setAlignment(Qt.AlignLeft | Qt.AlignTop)
   2230
   2231		self.legend = SwitchGraphLegend(collection, region_attributes)
   2232
   2233		self.splitter = SwitchGraphSplitter()
   2234		self.splitter.addWidget(self.view)
   2235		self.splitter.addWidget(self.legend)
   2236
   2237		self.point_label = QLabel("")
   2238		self.point_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
   2239
   2240		self.back_button = QToolButton()
   2241		self.back_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowLeft))
   2242		self.back_button.setDisabled(True)
   2243		self.back_button.released.connect(lambda: self.Back())
   2244
   2245		self.forward_button = QToolButton()
   2246		self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowRight))
   2247		self.forward_button.setDisabled(True)
   2248		self.forward_button.released.connect(lambda: self.Forward())
   2249
   2250		self.zoom_button = QToolButton()
   2251		self.zoom_button.setText("Zoom")
   2252		self.zoom_button.setDisabled(True)
   2253		self.zoom_button.released.connect(lambda: self.Zoom())
   2254
   2255		self.hbox = HBoxLayout(self.back_button, self.forward_button, self.zoom_button, self.point_label)
   2256
   2257		self.vbox = VBoxLayout(self.splitter, self.hbox)
   2258
   2259		self.setLayout(self.vbox)
   2260
   2261	def GetScaleForRangeX(self, xsubrange):
   2262		# Default graph 1000 pixels wide
   2263		dflt = 1000.0
   2264		r = xsubrange.hi - xsubrange.lo
   2265		return dflt / r
   2266
   2267	def GetScaleForRangeY(self, ysubrange):
   2268		# Default graph 50 pixels high
   2269		dflt = 50.0
   2270		r = ysubrange.hi - ysubrange.lo
   2271		return dflt / r
   2272
   2273	def GetScaleForRange(self, subrange):
   2274		# Default graph 1000 pixels wide, 50 pixels high
   2275		xscale = self.GetScaleForRangeX(subrange.x)
   2276		yscale = self.GetScaleForRangeY(subrange.y)
   2277		return XY(xscale, yscale)
   2278
   2279	def PointEvent(self, cpu, time_from, time_to, hregions):
   2280		text = "CPU: " + str(cpu)
   2281		time_from = time_from.quantize(Decimal(1))
   2282		rel_time_from = time_from - self.glb.StartTime(self.collection.machine_id)
   2283		text = text + " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ")"
   2284		self.point_label.setText(text)
   2285		self.legend.Highlight(hregions)
   2286
   2287	def RightClickEvent(self, cpu, hregion_times, pos):
   2288		if not IsSelectable(self.glb.db, "calls", "WHERE parent_id >= 0"):
   2289			return
   2290		menu = QMenu(self.view)
   2291		for hregion, time in hregion_times:
   2292			thread_at_time = (hregion.exec_comm_id, hregion.thread_id, time)
   2293			menu_text = "Show Call Tree for {} {}:{} at {}".format(hregion.comm, hregion.pid, hregion.tid, time)
   2294			menu.addAction(CreateAction(menu_text, "Show Call Tree", lambda a=None, args=thread_at_time: self.RightClickSelect(args), self.view))
   2295		menu.exec_(pos)
   2296
   2297	def RightClickSelect(self, args):
   2298		CallTreeWindow(self.glb, self.glb.mainwindow, thread_at_time=args)
   2299
   2300	def NoPointEvent(self):
   2301		self.point_label.setText("")
   2302		self.legend.Highlight({})
   2303
   2304	def RangeEvent(self, time_from, time_to):
   2305		time_from = time_from.quantize(Decimal(1))
   2306		time_to = time_to.quantize(Decimal(1))
   2307		if time_to <= time_from:
   2308			self.point_label.setText("")
   2309			return
   2310		rel_time_from = time_from - self.start_time
   2311		rel_time_to = time_to - self.start_time
   2312		text = " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ") to: " + str(time_to) + " (+" + ToTimeStr(rel_time_to) + ")"
   2313		text = text + " duration: " + ToTimeStr(time_to - time_from)
   2314		self.point_label.setText(text)
   2315
   2316	def BackState(self):
   2317		return (self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect)
   2318
   2319	def PushBackState(self):
   2320		state = copy.deepcopy(self.BackState())
   2321		self.back_state.append(state)
   2322		self.back_button.setEnabled(True)
   2323
   2324	def PopBackState(self):
   2325		self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.back_state.pop()
   2326		self.attrs.Update()
   2327		if not self.back_state:
   2328			self.back_button.setDisabled(True)
   2329
   2330	def PushForwardState(self):
   2331		state = copy.deepcopy(self.BackState())
   2332		self.forward_state.append(state)
   2333		self.forward_button.setEnabled(True)
   2334
   2335	def PopForwardState(self):
   2336		self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.forward_state.pop()
   2337		self.attrs.Update()
   2338		if not self.forward_state:
   2339			self.forward_button.setDisabled(True)
   2340
   2341	def Title(self):
   2342		time_from = self.collection.xrangelo + Decimal(self.attrs.subrange.x.lo)
   2343		time_to = self.collection.xrangelo + Decimal(self.attrs.subrange.x.hi)
   2344		rel_time_from = time_from - self.start_time
   2345		rel_time_to = time_to - self.start_time
   2346		title = "+" + ToTimeStr(rel_time_from) + " to +" + ToTimeStr(rel_time_to)
   2347		title = title + " (" + ToTimeStr(time_to - time_from) + ")"
   2348		return title
   2349
   2350	def Update(self):
   2351		selected_subrange, selection_state = self.selection_state
   2352		self.item.SetSelection(selection_state)
   2353		self.item.SetBracket(self.fwd_rect)
   2354		self.zoom_button.setDisabled(selected_subrange is None)
   2355		self.GraphTitleChanged(self.Title())
   2356		self.item.update(self.item.boundingRect())
   2357
   2358	def Back(self):
   2359		if not self.back_state:
   2360			return
   2361		self.PushForwardState()
   2362		self.PopBackState()
   2363		self.Update()
   2364
   2365	def Forward(self):
   2366		if not self.forward_state:
   2367			return
   2368		self.PushBackState()
   2369		self.PopForwardState()
   2370		self.Update()
   2371
   2372	def SelectEvent(self, x0, x1, selection_state):
   2373		if selection_state is None:
   2374			selected_subrange = None
   2375		else:
   2376			if x1 - x0 < 1.0:
   2377				x1 += 1.0
   2378			selected_subrange = Subrange(x0, x1)
   2379		self.selection_state = (selected_subrange, selection_state)
   2380		self.zoom_button.setDisabled(selected_subrange is None)
   2381
   2382	def Zoom(self):
   2383		selected_subrange, selection_state = self.selection_state
   2384		if selected_subrange is None:
   2385			return
   2386		self.fwd_rect = selection_state
   2387		self.item.SetSelection(None)
   2388		self.PushBackState()
   2389		self.attrs.subrange.x = selected_subrange
   2390		self.forward_state = []
   2391		self.forward_button.setDisabled(True)
   2392		self.selection_state = (None, None)
   2393		self.fwd_rect = None
   2394		self.attrs.scale.x = self.GetScaleForRangeX(self.attrs.subrange.x)
   2395		self.attrs.Update()
   2396		self.Update()
   2397
   2398# Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting
   2399
   2400class SlowInitClass():
   2401
   2402	def __init__(self, glb, title, init_fn):
   2403		self.init_fn = init_fn
   2404		self.done = False
   2405		self.result = None
   2406
   2407		self.msg_box = QMessageBox(glb.mainwindow)
   2408		self.msg_box.setText("Initializing " + title + ". Please wait.")
   2409		self.msg_box.setWindowTitle("Initializing " + title)
   2410		self.msg_box.setWindowIcon(glb.mainwindow.style().standardIcon(QStyle.SP_MessageBoxInformation))
   2411
   2412		self.init_thread = Thread(self.ThreadFn, glb)
   2413		self.init_thread.done.connect(lambda: self.Done(), Qt.QueuedConnection)
   2414
   2415		self.init_thread.start()
   2416
   2417	def Done(self):
   2418		self.msg_box.done(0)
   2419
   2420	def ThreadFn(self, glb):
   2421		conn_name = "SlowInitClass" + str(os.getpid())
   2422		db, dbname = glb.dbref.Open(conn_name)
   2423		self.result = self.init_fn(db)
   2424		self.done = True
   2425		return (True, 0)
   2426
   2427	def Result(self):
   2428		while not self.done:
   2429			self.msg_box.exec_()
   2430		self.init_thread.wait()
   2431		return self.result
   2432
   2433def SlowInit(glb, title, init_fn):
   2434	init = SlowInitClass(glb, title, init_fn)
   2435	return init.Result()
   2436
   2437# Time chart by CPU window
   2438
   2439class TimeChartByCPUWindow(QMdiSubWindow):
   2440
   2441	def __init__(self, glb, parent=None):
   2442		super(TimeChartByCPUWindow, self).__init__(parent)
   2443
   2444		self.glb = glb
   2445		self.machine_id = glb.HostMachineId()
   2446		self.collection_name = "SwitchGraphDataCollection " + str(self.machine_id)
   2447
   2448		collection = LookupModel(self.collection_name)
   2449		if collection is None:
   2450			collection = SlowInit(glb, "Time Chart", self.Init)
   2451
   2452		self.widget = SwitchGraphWidget(glb, collection, self)
   2453		self.view = self.widget
   2454
   2455		self.base_title = "Time Chart by CPU"
   2456		self.setWindowTitle(self.base_title + self.widget.Title())
   2457		self.widget.graph_title_changed.connect(self.GraphTitleChanged)
   2458
   2459		self.setWidget(self.widget)
   2460
   2461		AddSubWindow(glb.mainwindow.mdi_area, self, self.windowTitle())
   2462
   2463	def Init(self, db):
   2464		return LookupCreateModel(self.collection_name, lambda : SwitchGraphDataCollection(self.glb, db, self.machine_id))
   2465
   2466	def GraphTitleChanged(self, title):
   2467		self.setWindowTitle(self.base_title + " : " + title)
   2468
   2469# Child data item  finder
   2470
   2471class ChildDataItemFinder():
   2472
   2473	def __init__(self, root):
   2474		self.root = root
   2475		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
   2476		self.rows = []
   2477		self.pos = 0
   2478
   2479	def FindSelect(self):
   2480		self.rows = []
   2481		if self.pattern:
   2482			pattern = re.compile(self.value)
   2483			for child in self.root.child_items:
   2484				for column_data in child.data:
   2485					if re.search(pattern, str(column_data)) is not None:
   2486						self.rows.append(child.row)
   2487						break
   2488		else:
   2489			for child in self.root.child_items:
   2490				for column_data in child.data:
   2491					if self.value in str(column_data):
   2492						self.rows.append(child.row)
   2493						break
   2494
   2495	def FindValue(self):
   2496		self.pos = 0
   2497		if self.last_value != self.value or self.pattern != self.last_pattern:
   2498			self.FindSelect()
   2499		if not len(self.rows):
   2500			return -1
   2501		return self.rows[self.pos]
   2502
   2503	def FindThread(self):
   2504		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
   2505			row = self.FindValue()
   2506		elif len(self.rows):
   2507			if self.direction > 0:
   2508				self.pos += 1
   2509				if self.pos >= len(self.rows):
   2510					self.pos = 0
   2511			else:
   2512				self.pos -= 1
   2513				if self.pos < 0:
   2514					self.pos = len(self.rows) - 1
   2515			row = self.rows[self.pos]
   2516		else:
   2517			row = -1
   2518		return (True, row)
   2519
   2520	def Find(self, value, direction, pattern, context, callback):
   2521		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
   2522		# Use a thread so the UI is not blocked
   2523		thread = Thread(self.FindThread)
   2524		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
   2525		thread.start()
   2526
   2527	def FindDone(self, thread, callback, row):
   2528		callback(row)
   2529
   2530# Number of database records to fetch in one go
   2531
   2532glb_chunk_sz = 10000
   2533
   2534# Background process for SQL data fetcher
   2535
   2536class SQLFetcherProcess():
   2537
   2538	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
   2539		# Need a unique connection name
   2540		conn_name = "SQLFetcher" + str(os.getpid())
   2541		self.db, dbname = dbref.Open(conn_name)
   2542		self.sql = sql
   2543		self.buffer = buffer
   2544		self.head = head
   2545		self.tail = tail
   2546		self.fetch_count = fetch_count
   2547		self.fetching_done = fetching_done
   2548		self.process_target = process_target
   2549		self.wait_event = wait_event
   2550		self.fetched_event = fetched_event
   2551		self.prep = prep
   2552		self.query = QSqlQuery(self.db)
   2553		self.query_limit = 0 if "$$last_id$$" in sql else 2
   2554		self.last_id = -1
   2555		self.fetched = 0
   2556		self.more = True
   2557		self.local_head = self.head.value
   2558		self.local_tail = self.tail.value
   2559
   2560	def Select(self):
   2561		if self.query_limit:
   2562			if self.query_limit == 1:
   2563				return
   2564			self.query_limit -= 1
   2565		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
   2566		QueryExec(self.query, stmt)
   2567
   2568	def Next(self):
   2569		if not self.query.next():
   2570			self.Select()
   2571			if not self.query.next():
   2572				return None
   2573		self.last_id = self.query.value(0)
   2574		return self.prep(self.query)
   2575
   2576	def WaitForTarget(self):
   2577		while True:
   2578			self.wait_event.clear()
   2579			target = self.process_target.value
   2580			if target > self.fetched or target < 0:
   2581				break
   2582			self.wait_event.wait()
   2583		return target
   2584
   2585	def HasSpace(self, sz):
   2586		if self.local_tail <= self.local_head:
   2587			space = len(self.buffer) - self.local_head
   2588			if space > sz:
   2589				return True
   2590			if space >= glb_nsz:
   2591				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
   2592				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
   2593				self.buffer[self.local_head : self.local_head + len(nd)] = nd
   2594			self.local_head = 0
   2595		if self.local_tail - self.local_head > sz:
   2596			return True
   2597		return False
   2598
   2599	def WaitForSpace(self, sz):
   2600		if self.HasSpace(sz):
   2601			return
   2602		while True:
   2603			self.wait_event.clear()
   2604			self.local_tail = self.tail.value
   2605			if self.HasSpace(sz):
   2606				return
   2607			self.wait_event.wait()
   2608
   2609	def AddToBuffer(self, obj):
   2610		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
   2611		n = len(d)
   2612		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
   2613		sz = n + glb_nsz
   2614		self.WaitForSpace(sz)
   2615		pos = self.local_head
   2616		self.buffer[pos : pos + len(nd)] = nd
   2617		self.buffer[pos + glb_nsz : pos + sz] = d
   2618		self.local_head += sz
   2619
   2620	def FetchBatch(self, batch_size):
   2621		fetched = 0
   2622		while batch_size > fetched:
   2623			obj = self.Next()
   2624			if obj is None:
   2625				self.more = False
   2626				break
   2627			self.AddToBuffer(obj)
   2628			fetched += 1
   2629		if fetched:
   2630			self.fetched += fetched
   2631			with self.fetch_count.get_lock():
   2632				self.fetch_count.value += fetched
   2633			self.head.value = self.local_head
   2634			self.fetched_event.set()
   2635
   2636	def Run(self):
   2637		while self.more:
   2638			target = self.WaitForTarget()
   2639			if target < 0:
   2640				break
   2641			batch_size = min(glb_chunk_sz, target - self.fetched)
   2642			self.FetchBatch(batch_size)
   2643		self.fetching_done.value = True
   2644		self.fetched_event.set()
   2645
   2646def SQLFetcherFn(*x):
   2647	process = SQLFetcherProcess(*x)
   2648	process.Run()
   2649
   2650# SQL data fetcher
   2651
   2652class SQLFetcher(QObject):
   2653
   2654	done = Signal(object)
   2655
   2656	def __init__(self, glb, sql, prep, process_data, parent=None):
   2657		super(SQLFetcher, self).__init__(parent)
   2658		self.process_data = process_data
   2659		self.more = True
   2660		self.target = 0
   2661		self.last_target = 0
   2662		self.fetched = 0
   2663		self.buffer_size = 16 * 1024 * 1024
   2664		self.buffer = Array(c_char, self.buffer_size, lock=False)
   2665		self.head = Value(c_longlong)
   2666		self.tail = Value(c_longlong)
   2667		self.local_tail = 0
   2668		self.fetch_count = Value(c_longlong)
   2669		self.fetching_done = Value(c_bool)
   2670		self.last_count = 0
   2671		self.process_target = Value(c_longlong)
   2672		self.wait_event = Event()
   2673		self.fetched_event = Event()
   2674		glb.AddInstanceToShutdownOnExit(self)
   2675		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))
   2676		self.process.start()
   2677		self.thread = Thread(self.Thread)
   2678		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
   2679		self.thread.start()
   2680
   2681	def Shutdown(self):
   2682		# Tell the thread and process to exit
   2683		self.process_target.value = -1
   2684		self.wait_event.set()
   2685		self.more = False
   2686		self.fetching_done.value = True
   2687		self.fetched_event.set()
   2688
   2689	def Thread(self):
   2690		if not self.more:
   2691			return True, 0
   2692		while True:
   2693			self.fetched_event.clear()
   2694			fetch_count = self.fetch_count.value
   2695			if fetch_count != self.last_count:
   2696				break
   2697			if self.fetching_done.value:
   2698				self.more = False
   2699				return True, 0
   2700			self.fetched_event.wait()
   2701		count = fetch_count - self.last_count
   2702		self.last_count = fetch_count
   2703		self.fetched += count
   2704		return False, count
   2705
   2706	def Fetch(self, nr):
   2707		if not self.more:
   2708			# -1 inidcates there are no more
   2709			return -1
   2710		result = self.fetched
   2711		extra = result + nr - self.target
   2712		if extra > 0:
   2713			self.target += extra
   2714			# process_target < 0 indicates shutting down
   2715			if self.process_target.value >= 0:
   2716				self.process_target.value = self.target
   2717			self.wait_event.set()
   2718		return result
   2719
   2720	def RemoveFromBuffer(self):
   2721		pos = self.local_tail
   2722		if len(self.buffer) - pos < glb_nsz:
   2723			pos = 0
   2724		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
   2725		if n == 0:
   2726			pos = 0
   2727			n = pickle.loads(self.buffer[0 : glb_nsz])
   2728		pos += glb_nsz
   2729		obj = pickle.loads(self.buffer[pos : pos + n])
   2730		self.local_tail = pos + n
   2731		return obj
   2732
   2733	def ProcessData(self, count):
   2734		for i in xrange(count):
   2735			obj = self.RemoveFromBuffer()
   2736			self.process_data(obj)
   2737		self.tail.value = self.local_tail
   2738		self.wait_event.set()
   2739		self.done.emit(count)
   2740
   2741# Fetch more records bar
   2742
   2743class FetchMoreRecordsBar():
   2744
   2745	def __init__(self, model, parent):
   2746		self.model = model
   2747
   2748		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
   2749		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
   2750
   2751		self.fetch_count = QSpinBox()
   2752		self.fetch_count.setRange(1, 1000000)
   2753		self.fetch_count.setValue(10)
   2754		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
   2755
   2756		self.fetch = QPushButton("Go!")
   2757		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
   2758		self.fetch.released.connect(self.FetchMoreRecords)
   2759
   2760		self.progress = QProgressBar()
   2761		self.progress.setRange(0, 100)
   2762		self.progress.hide()
   2763
   2764		self.done_label = QLabel("All records fetched")
   2765		self.done_label.hide()
   2766
   2767		self.spacer = QLabel("")
   2768
   2769		self.close_button = QToolButton()
   2770		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
   2771		self.close_button.released.connect(self.Deactivate)
   2772
   2773		self.hbox = QHBoxLayout()
   2774		self.hbox.setContentsMargins(0, 0, 0, 0)
   2775
   2776		self.hbox.addWidget(self.label)
   2777		self.hbox.addWidget(self.fetch_count)
   2778		self.hbox.addWidget(self.fetch)
   2779		self.hbox.addWidget(self.spacer)
   2780		self.hbox.addWidget(self.progress)
   2781		self.hbox.addWidget(self.done_label)
   2782		self.hbox.addWidget(self.close_button)
   2783
   2784		self.bar = QWidget()
   2785		self.bar.setLayout(self.hbox)
   2786		self.bar.show()
   2787
   2788		self.in_progress = False
   2789		self.model.progress.connect(self.Progress)
   2790
   2791		self.done = False
   2792
   2793		if not model.HasMoreRecords():
   2794			self.Done()
   2795
   2796	def Widget(self):
   2797		return self.bar
   2798
   2799	def Activate(self):
   2800		self.bar.show()
   2801		self.fetch.setFocus()
   2802
   2803	def Deactivate(self):
   2804		self.bar.hide()
   2805
   2806	def Enable(self, enable):
   2807		self.fetch.setEnabled(enable)
   2808		self.fetch_count.setEnabled(enable)
   2809
   2810	def Busy(self):
   2811		self.Enable(False)
   2812		self.fetch.hide()
   2813		self.spacer.hide()
   2814		self.progress.show()
   2815
   2816	def Idle(self):
   2817		self.in_progress = False
   2818		self.Enable(True)
   2819		self.progress.hide()
   2820		self.fetch.show()
   2821		self.spacer.show()
   2822
   2823	def Target(self):
   2824		return self.fetch_count.value() * glb_chunk_sz
   2825
   2826	def Done(self):
   2827		self.done = True
   2828		self.Idle()
   2829		self.label.hide()
   2830		self.fetch_count.hide()
   2831		self.fetch.hide()
   2832		self.spacer.hide()
   2833		self.done_label.show()
   2834
   2835	def Progress(self, count):
   2836		if self.in_progress:
   2837			if count:
   2838				percent = ((count - self.start) * 100) / self.Target()
   2839				if percent >= 100:
   2840					self.Idle()
   2841				else:
   2842					self.progress.setValue(percent)
   2843		if not count:
   2844			# Count value of zero means no more records
   2845			self.Done()
   2846
   2847	def FetchMoreRecords(self):
   2848		if self.done:
   2849			return
   2850		self.progress.setValue(0)
   2851		self.Busy()
   2852		self.in_progress = True
   2853		self.start = self.model.FetchMoreRecords(self.Target())
   2854
   2855# Brance data model level two item
   2856
   2857class BranchLevelTwoItem():
   2858
   2859	def __init__(self, row, col, text, parent_item):
   2860		self.row = row
   2861		self.parent_item = parent_item
   2862		self.data = [""] * (col + 1)
   2863		self.data[col] = text
   2864		self.level = 2
   2865
   2866	def getParentItem(self):
   2867		return self.parent_item
   2868
   2869	def getRow(self):
   2870		return self.row
   2871
   2872	def childCount(self):
   2873		return 0
   2874
   2875	def hasChildren(self):
   2876		return False
   2877
   2878	def getData(self, column):
   2879		return self.data[column]
   2880
   2881# Brance data model level one item
   2882
   2883class BranchLevelOneItem():
   2884
   2885	def __init__(self, glb, row, data, parent_item):
   2886		self.glb = glb
   2887		self.row = row
   2888		self.parent_item = parent_item
   2889		self.child_count = 0
   2890		self.child_items = []
   2891		self.data = data[1:]
   2892		self.dbid = data[0]
   2893		self.level = 1
   2894		self.query_done = False
   2895		self.br_col = len(self.data) - 1
   2896
   2897	def getChildItem(self, row):
   2898		return self.child_items[row]
   2899
   2900	def getParentItem(self):
   2901		return self.parent_item
   2902
   2903	def getRow(self):
   2904		return self.row
   2905
   2906	def Select(self):
   2907		self.query_done = True
   2908
   2909		if not self.glb.have_disassembler:
   2910			return
   2911
   2912		query = QSqlQuery(self.glb.db)
   2913
   2914		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
   2915				  " FROM samples"
   2916				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
   2917				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
   2918				  " WHERE samples.id = " + str(self.dbid))
   2919		if not query.next():
   2920			return
   2921		cpu = query.value(0)
   2922		dso = query.value(1)
   2923		sym = query.value(2)
   2924		if dso == 0 or sym == 0:
   2925			return
   2926		off = query.value(3)
   2927		short_name = query.value(4)
   2928		long_name = query.value(5)
   2929		build_id = query.value(6)
   2930		sym_start = query.value(7)
   2931		ip = query.value(8)
   2932
   2933		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
   2934				  " FROM samples"
   2935				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
   2936				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
   2937				  " ORDER BY samples.id"
   2938				  " LIMIT 1")
   2939		if not query.next():
   2940			return
   2941		if query.value(0) != dso:
   2942			# Cannot disassemble from one dso to another
   2943			return
   2944		bsym = query.value(1)
   2945		boff = query.value(2)
   2946		bsym_start = query.value(3)
   2947		if bsym == 0:
   2948			return
   2949		tot = bsym_start + boff + 1 - sym_start - off
   2950		if tot <= 0 or tot > 16384:
   2951			return
   2952
   2953		inst = self.glb.disassembler.Instruction()
   2954		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
   2955		if not f:
   2956			return
   2957		mode = 0 if Is64Bit(f) else 1
   2958		self.glb.disassembler.SetMode(inst, mode)
   2959
   2960		buf_sz = tot + 16
   2961		buf = create_string_buffer(tot + 16)
   2962		f.seek(sym_start + off)
   2963		buf.value = f.read(buf_sz)
   2964		buf_ptr = addressof(buf)
   2965		i = 0
   2966		while tot > 0:
   2967			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
   2968			if cnt:
   2969				byte_str = tohex(ip).rjust(16)
   2970				for k in xrange(cnt):
   2971					byte_str += " %02x" % ord(buf[i])
   2972					i += 1
   2973				while k < 15:
   2974					byte_str += "   "
   2975					k += 1
   2976				self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
   2977				self.child_count += 1
   2978			else:
   2979				return
   2980			buf_ptr += cnt
   2981			tot -= cnt
   2982			buf_sz -= cnt
   2983			ip += cnt
   2984
   2985	def childCount(self):
   2986		if not self.query_done:
   2987			self.Select()
   2988			if not self.child_count:
   2989				return -1
   2990		return self.child_count
   2991
   2992	def hasChildren(self):
   2993		if not self.query_done:
   2994			return True
   2995		return self.child_count > 0
   2996
   2997	def getData(self, column):
   2998		return self.data[column]
   2999
   3000# Brance data model root item
   3001
   3002class BranchRootItem():
   3003
   3004	def __init__(self):
   3005		self.child_count = 0
   3006		self.child_items = []
   3007		self.level = 0
   3008
   3009	def getChildItem(self, row):
   3010		return self.child_items[row]
   3011
   3012	def getParentItem(self):
   3013		return None
   3014
   3015	def getRow(self):
   3016		return 0
   3017
   3018	def childCount(self):
   3019		return self.child_count
   3020
   3021	def hasChildren(self):
   3022		return self.child_count > 0
   3023
   3024	def getData(self, column):
   3025		return ""
   3026
   3027# Calculate instructions per cycle
   3028
   3029def CalcIPC(cyc_cnt, insn_cnt):
   3030	if cyc_cnt and insn_cnt:
   3031		ipc = Decimal(float(insn_cnt) / cyc_cnt)
   3032		ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
   3033	else:
   3034		ipc = "0"
   3035	return ipc
   3036
   3037# Branch data preparation
   3038
   3039def BranchDataPrepBr(query, data):
   3040	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
   3041			" (" + dsoname(query.value(11)) + ")" + " -> " +
   3042			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
   3043			" (" + dsoname(query.value(15)) + ")")
   3044
   3045def BranchDataPrepIPC(query, data):
   3046	insn_cnt = query.value(16)
   3047	cyc_cnt = query.value(17)
   3048	ipc = CalcIPC(cyc_cnt, insn_cnt)
   3049	data.append(insn_cnt)
   3050	data.append(cyc_cnt)
   3051	data.append(ipc)
   3052
   3053def BranchDataPrep(query):
   3054	data = []
   3055	for i in xrange(0, 8):
   3056		data.append(query.value(i))
   3057	BranchDataPrepBr(query, data)
   3058	return data
   3059
   3060def BranchDataPrepWA(query):
   3061	data = []
   3062	data.append(query.value(0))
   3063	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
   3064	data.append("{:>19}".format(query.value(1)))
   3065	for i in xrange(2, 8):
   3066		data.append(query.value(i))
   3067	BranchDataPrepBr(query, data)
   3068	return data
   3069
   3070def BranchDataWithIPCPrep(query):
   3071	data = []
   3072	for i in xrange(0, 8):
   3073		data.append(query.value(i))
   3074	BranchDataPrepIPC(query, data)
   3075	BranchDataPrepBr(query, data)
   3076	return data
   3077
   3078def BranchDataWithIPCPrepWA(query):
   3079	data = []
   3080	data.append(query.value(0))
   3081	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
   3082	data.append("{:>19}".format(query.value(1)))
   3083	for i in xrange(2, 8):
   3084		data.append(query.value(i))
   3085	BranchDataPrepIPC(query, data)
   3086	BranchDataPrepBr(query, data)
   3087	return data
   3088
   3089# Branch data model
   3090
   3091class BranchModel(TreeModel):
   3092
   3093	progress = Signal(object)
   3094
   3095	def __init__(self, glb, event_id, where_clause, parent=None):
   3096		super(BranchModel, self).__init__(glb, None, parent)
   3097		self.event_id = event_id
   3098		self.more = True
   3099		self.populated = 0
   3100		self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
   3101		if self.have_ipc:
   3102			select_ipc = ", insn_count, cyc_count"
   3103			prep_fn = BranchDataWithIPCPrep
   3104			prep_wa_fn = BranchDataWithIPCPrepWA
   3105		else:
   3106			select_ipc = ""
   3107			prep_fn = BranchDataPrep
   3108			prep_wa_fn = BranchDataPrepWA
   3109		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
   3110			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
   3111			" ip, symbols.name, sym_offset, dsos.short_name,"
   3112			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
   3113			+ select_ipc +
   3114			" FROM samples"
   3115			" INNER JOIN comms ON comm_id = comms.id"
   3116			" INNER JOIN threads ON thread_id = threads.id"
   3117			" INNER JOIN branch_types ON branch_type = branch_types.id"
   3118			" INNER JOIN symbols ON symbol_id = symbols.id"
   3119			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
   3120			" INNER JOIN dsos ON samples.dso_id = dsos.id"
   3121			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
   3122			" WHERE samples.id > $$last_id$$" + where_clause +
   3123			" AND evsel_id = " + str(self.event_id) +
   3124			" ORDER BY samples.id"
   3125			" LIMIT " + str(glb_chunk_sz))
   3126		if pyside_version_1 and sys.version_info[0] == 3:
   3127			prep = prep_fn
   3128		else:
   3129			prep = prep_wa_fn
   3130		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
   3131		self.fetcher.done.connect(self.Update)
   3132		self.fetcher.Fetch(glb_chunk_sz)
   3133
   3134	def GetRoot(self):
   3135		return BranchRootItem()
   3136
   3137	def columnCount(self, parent=None):
   3138		if self.have_ipc:
   3139			return 11
   3140		else:
   3141			return 8
   3142
   3143	def columnHeader(self, column):
   3144		if self.have_ipc:
   3145			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
   3146		else:
   3147			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
   3148
   3149	def columnFont(self, column):
   3150		if self.have_ipc:
   3151			br_col = 10
   3152		else:
   3153			br_col = 7
   3154		if column != br_col:
   3155			return None
   3156		return QFont("Monospace")
   3157
   3158	def DisplayData(self, item, index):
   3159		if item.level == 1:
   3160			self.FetchIfNeeded(item.row)
   3161		return item.getData(index.column())
   3162
   3163	def AddSample(self, data):
   3164		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
   3165		self.root.child_items.append(child)
   3166		self.populated += 1
   3167
   3168	def Update(self, fetched):
   3169		if not fetched:
   3170			self.more = False
   3171			self.progress.emit(0)
   3172		child_count = self.root.child_count
   3173		count = self.populated - child_count
   3174		if count > 0:
   3175			parent = QModelIndex()
   3176			self.beginInsertRows(parent, child_count, child_count + count - 1)
   3177			self.insertRows(child_count, count, parent)
   3178			self.root.child_count += count
   3179			self.endInsertRows()
   3180			self.progress.emit(self.root.child_count)
   3181
   3182	def FetchMoreRecords(self, count):
   3183		current = self.root.child_count
   3184		if self.more:
   3185			self.fetcher.Fetch(count)
   3186		else:
   3187			self.progress.emit(0)
   3188		return current
   3189
   3190	def HasMoreRecords(self):
   3191		return self.more
   3192
   3193# Report Variables
   3194
   3195class ReportVars():
   3196
   3197	def __init__(self, name = "", where_clause = "", limit = ""):
   3198		self.name = name
   3199		self.where_clause = where_clause
   3200		self.limit = limit
   3201
   3202	def UniqueId(self):
   3203		return str(self.where_clause + ";" + self.limit)
   3204
   3205# Branch window
   3206
   3207class BranchWindow(QMdiSubWindow):
   3208
   3209	def __init__(self, glb, event_id, report_vars, parent=None):
   3210		super(BranchWindow, self).__init__(parent)
   3211
   3212		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
   3213
   3214		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
   3215
   3216		self.view = QTreeView()
   3217		self.view.setUniformRowHeights(True)
   3218		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
   3219		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
   3220		self.view.setModel(self.model)
   3221
   3222		self.ResizeColumnsToContents()
   3223
   3224		self.context_menu = TreeContextMenu(self.view)
   3225
   3226		self.find_bar = FindBar(self, self, True)
   3227
   3228		self.finder = ChildDataItemFinder(self.model.root)
   3229
   3230		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
   3231
   3232		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
   3233
   3234		self.setWidget(self.vbox.Widget())
   3235
   3236		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
   3237
   3238	def ResizeColumnToContents(self, column, n):
   3239		# Using the view's resizeColumnToContents() here is extrememly slow
   3240		# so implement a crude alternative
   3241		mm = "MM" if column else "MMMM"
   3242		font = self.view.font()
   3243		metrics = QFontMetrics(font)
   3244		max = 0
   3245		for row in xrange(n):
   3246			val = self.model.root.child_items[row].data[column]
   3247			len = metrics.width(str(val) + mm)
   3248			max = len if len > max else max
   3249		val = self.model.columnHeader(column)
   3250		len = metrics.width(str(val) + mm)
   3251		max = len if len > max else max
   3252		self.view.setColumnWidth(column, max)
   3253
   3254	def ResizeColumnsToContents(self):
   3255		n = min(self.model.root.child_count, 100)
   3256		if n < 1:
   3257			# No data yet, so connect a signal to notify when there is
   3258			self.model.rowsInserted.connect(self.UpdateColumnWidths)
   3259			return
   3260		columns = self.model.columnCount()
   3261		for i in xrange(columns):
   3262			self.ResizeColumnToContents(i, n)
   3263
   3264	def UpdateColumnWidths(self, *x):
   3265		# This only needs to be done once, so disconnect the signal now
   3266		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
   3267		self.ResizeColumnsToContents()
   3268
   3269	def Find(self, value, direction, pattern, context):
   3270		self.view.setFocus()
   3271		self.find_bar.Busy()
   3272		self.finder.Find(value, direction, pattern, context, self.FindDone)
   3273
   3274	def FindDone(self, row):
   3275		self.find_bar.Idle()
   3276		if row >= 0:
   3277			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
   3278		else:
   3279			self.find_bar.NotFound()
   3280
   3281# Line edit data item
   3282
   3283class LineEditDataItem(object):
   3284
   3285	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
   3286		self.glb = glb
   3287		self.label = label
   3288		self.placeholder_text = placeholder_text
   3289		self.parent = parent
   3290		self.id = id
   3291
   3292		self.value = default
   3293
   3294		self.widget = QLineEdit(default)
   3295		self.widget.editingFinished.connect(self.Validate)
   3296		self.widget.textChanged.connect(self.Invalidate)
   3297		self.red = False
   3298		self.error = ""
   3299		self.validated = True
   3300
   3301		if placeholder_text:
   3302			self.widget.setPlaceholderText(placeholder_text)
   3303
   3304	def TurnTextRed(self):
   3305		if not self.red:
   3306			palette = QPalette()
   3307			palette.setColor(QPalette.Text,Qt.red)
   3308			self.widget.setPalette(palette)
   3309			self.red = True
   3310
   3311	def TurnTextNormal(self):
   3312		if self.red:
   3313			palette = QPalette()
   3314			self.widget.setPalette(palette)
   3315			self.red = False
   3316
   3317	def InvalidValue(self, value):
   3318		self.value = ""
   3319		self.TurnTextRed()
   3320		self.error = self.label + " invalid value '" + value + "'"
   3321		self.parent.ShowMessage(self.error)
   3322
   3323	def Invalidate(self):
   3324		self.validated = False
   3325
   3326	def DoValidate(self, input_string):
   3327		self.value = input_string.strip()
   3328
   3329	def Validate(self):
   3330		self.validated = True
   3331		self.error = ""
   3332		self.TurnTextNormal()
   3333		self.parent.ClearMessage()
   3334		input_string = self.widget.text()
   3335		if not len(input_string.strip()):
   3336			self.value = ""
   3337			return
   3338		self.DoValidate(input_string)
   3339
   3340	def IsValid(self):
   3341		if not self.validated:
   3342			self.Validate()
   3343		if len(self.error):
   3344			self.parent.ShowMessage(self.error)
   3345			return False
   3346		return True
   3347
   3348	def IsNumber(self, value):
   3349		try:
   3350			x = int(value)
   3351		except:
   3352			x = 0
   3353		return str(x) == value
   3354
   3355# Non-negative integer ranges dialog data item
   3356
   3357class NonNegativeIntegerRangesDataItem(LineEditDataItem):
   3358
   3359	def __init__(self, glb, label, placeholder_text, column_name, parent):
   3360		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
   3361
   3362		self.column_name = column_name
   3363
   3364	def DoValidate(self, input_string):
   3365		singles = []
   3366		ranges = []
   3367		for value in [x.strip() for x in input_string.split(",")]:
   3368			if "-" in value:
   3369				vrange = value.split("-")
   3370				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
   3371					return self.InvalidValue(value)
   3372				ranges.append(vrange)
   3373			else:
   3374				if not self.IsNumber(value):
   3375					return self.InvalidValue(value)
   3376				singles.append(value)
   3377		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
   3378		if len(singles):
   3379			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
   3380		self.value = " OR ".join(ranges)
   3381
   3382# Positive integer dialog data item
   3383
   3384class PositiveIntegerDataItem(LineEditDataItem):
   3385
   3386	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
   3387		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
   3388
   3389	def DoValidate(self, input_string):
   3390		if not self.IsNumber(input_string.strip()):
   3391			return self.InvalidValue(input_string)
   3392		value = int(input_string.strip())
   3393		if value <= 0:
   3394			return self.InvalidValue(input_string)
   3395		self.value = str(value)
   3396
   3397# Dialog data item converted and validated using a SQL table
   3398
   3399class SQLTableDataItem(LineEditDataItem):
   3400
   3401	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
   3402		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
   3403
   3404		self.table_name = table_name
   3405		self.match_column = match_column
   3406		self.column_name1 = column_name1
   3407		self.column_name2 = column_name2
   3408
   3409	def ValueToIds(self, value):
   3410		ids = []
   3411		query = QSqlQuery(self.glb.db)
   3412		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
   3413		ret = query.exec_(stmt)
   3414		if ret:
   3415			while query.next():
   3416				ids.append(str(query.value(0)))
   3417		return ids
   3418
   3419	def DoValidate(self, input_string):
   3420		all_ids = []
   3421		for value in [x.strip() for x in input_string.split(",")]:
   3422			ids = self.ValueToIds(value)
   3423			if len(ids):
   3424				all_ids.extend(ids)
   3425			else:
   3426				return self.InvalidValue(value)
   3427		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
   3428		if self.column_name2:
   3429			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
   3430
   3431# Sample time ranges dialog data item converted and validated using 'samples' SQL table
   3432
   3433class SampleTimeRangesDataItem(LineEditDataItem):
   3434
   3435	def __init__(self, glb, label, placeholder_text, column_name, parent):
   3436		self.column_name = column_name
   3437
   3438		self.last_id = 0
   3439		self.first_time = 0
   3440		self.last_time = 2 ** 64
   3441
   3442		query = QSqlQuery(glb.db)
   3443		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
   3444		if query.next():
   3445			self.last_id = int(query.value(0))
   3446		self.first_time = int(glb.HostStartTime())
   3447		self.last_time = int(glb.HostFinishTime())
   3448		if placeholder_text:
   3449			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
   3450
   3451		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
   3452
   3453	def IdBetween(self, query, lower_id, higher_id, order):
   3454		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
   3455		if query.next():
   3456			return True, int(query.value(0))
   3457		else:
   3458			return False, 0
   3459
   3460	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
   3461		query = QSqlQuery(self.glb.db)
   3462		while True:
   3463			next_id = int((lower_id + higher_id) / 2)
   3464			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
   3465			if not query.next():
   3466				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
   3467				if not ok:
   3468					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
   3469					if not ok:
   3470						return str(higher_id)
   3471				next_id = dbid
   3472				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
   3473			next_time = int(query.value(0))
   3474			if get_floor:
   3475				if target_time > next_time:
   3476					lower_id = next_id
   3477				else:
   3478					higher_id = next_id
   3479				if higher_id <= lower_id + 1:
   3480					return str(higher_id)
   3481			else:
   3482				if target_time >= next_time:
   3483					lower_id = next_id
   3484				else:
   3485					higher_id = next_id
   3486				if higher_id <= lower_id + 1:
   3487					return str(lower_id)
   3488
   3489	def ConvertRelativeTime(self, val):
   3490		mult = 1
   3491		suffix = val[-2:]
   3492		if suffix == "ms":
   3493			mult = 1000000
   3494		elif suffix == "us":
   3495			mult = 1000
   3496		elif suffix == "ns":
   3497			mult = 1
   3498		else:
   3499			return val
   3500		val = val[:-2].strip()
   3501		if not self.IsNumber(val):
   3502			return val
   3503		val = int(val) * mult
   3504		if val >= 0:
   3505			val += self.first_time
   3506		else:
   3507			val += self.last_time
   3508		return str(val)
   3509
   3510	def ConvertTimeRange(self, vrange):
   3511		if vrange[0] == "":
   3512			vrange[0] = str(self.first_time)
   3513		if vrange[1] == "":
   3514			vrange[1] = str(self.last_time)
   3515		vrange[0] = self.ConvertRelativeTime(vrange[0])
   3516		vrange[1] = self.ConvertRelativeTime(vrange[1])
   3517		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
   3518			return False
   3519		beg_range = max(int(vrange[0]), self.first_time)
   3520		end_range = min(int(vrange[1]), self.last_time)
   3521		if beg_range > self.last_time or end_range < self.first_time:
   3522			return False
   3523		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
   3524		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
   3525		return True
   3526
   3527	def AddTimeRange(self, value, ranges):
   3528		n = value.count("-")
   3529		if n == 1:
   3530			pass
   3531		elif n == 2:
   3532			if value.split("-")[1].strip() == "":
   3533				n = 1
   3534		elif n == 3:
   3535			n = 2
   3536		else:
   3537			return False
   3538		pos = findnth(value, "-", n)
   3539		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
   3540		if self.ConvertTimeRange(vrange):
   3541			ranges.append(vrange)
   3542			return True
   3543		return False
   3544
   3545	def DoValidate(self, input_string):
   3546		ranges = []
   3547		for value in [x.strip() for x in input_string.split(",")]:
   3548			if not self.AddTimeRange(value, ranges):
   3549				return self.InvalidValue(value)
   3550		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
   3551		self.value = " OR ".join(ranges)
   3552
   3553# Report Dialog Base
   3554
   3555class ReportDialogBase(QDialog):
   3556
   3557	def __init__(self, glb, title, items, partial, parent=None):
   3558		super(ReportDialogBase, self).__init__(parent)
   3559
   3560		self.glb = glb
   3561
   3562		self.report_vars = ReportVars()
   3563
   3564		self.setWindowTitle(title)
   3565		self.setMinimumWidth(600)
   3566
   3567		self.data_items = [x(glb, self) for x in items]
   3568
   3569		self.partial = partial
   3570
   3571		self.grid = QGridLayout()
   3572
   3573		for row in xrange(len(self.data_items)):
   3574			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
   3575			self.grid.addWidget(self.data_items[row].widget, row, 1)
   3576
   3577		self.status = QLabel()
   3578
   3579		self.ok_button = QPushButton("Ok", self)
   3580		self.ok_button.setDefault(True)
   3581		self.ok_button.released.connect(self.Ok)
   3582		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
   3583
   3584		self.cancel_button = QPushButton("Cancel", self)
   3585		self.cancel_button.released.connect(self.reject)
   3586		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
   3587
   3588		self.hbox = QHBoxLayout()
   3589		#self.hbox.addStretch()
   3590		self.hbox.addWidget(self.status)
   3591		self.hbox.addWidget(self.ok_button)
   3592		self.hbox.addWidget(self.cancel_button)
   3593
   3594		self.vbox = QVBoxLayout()
   3595		self.vbox.addLayout(self.grid)
   3596		self.vbox.addLayout(self.hbox)
   3597
   3598		self.setLayout(self.vbox)
   3599
   3600	def Ok(self):
   3601		vars = self.report_vars
   3602		for d in self.data_items:
   3603			if d.id == "REPORTNAME":
   3604				vars.name = d.value
   3605		if not vars.name:
   3606			self.ShowMessage("Report name is required")
   3607			return
   3608		for d in self.data_items:
   3609			if not d.IsValid():
   3610				return
   3611		for d in self.data_items[1:]:
   3612			if d.id == "LIMIT":
   3613				vars.limit = d.value
   3614			elif len(d.value):
   3615				if len(vars.where_clause):
   3616					vars.where_clause += " AND "
   3617				vars.where_clause += d.value
   3618		if len(vars.where_clause):
   3619			if self.partial:
   3620				vars.where_clause = " AND ( " + vars.where_clause + " ) "
   3621			else:
   3622				vars.where_clause = " WHERE " + vars.where_clause + " "
   3623		self.accept()
   3624
   3625	def ShowMessage(self, msg):
   3626		self.status.setText("<font color=#FF0000>" + msg)
   3627
   3628	def ClearMessage(self):
   3629		self.status.setText("")
   3630
   3631# Selected branch report creation dialog
   3632
   3633class SelectedBranchDialog(ReportDialogBase):
   3634
   3635	def __init__(self, glb, parent=None):
   3636		title = "Selected Branches"
   3637		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
   3638			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
   3639			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
   3640			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
   3641			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
   3642			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
   3643			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
   3644			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
   3645			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
   3646		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
   3647
   3648# Event list
   3649
   3650def GetEventList(db):
   3651	events = []
   3652	query = QSqlQuery(db)
   3653	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
   3654	while query.next():
   3655		events.append(query.value(0))
   3656	return events
   3657
   3658# Is a table selectable
   3659
   3660def IsSelectable(db, table, sql = "", columns = "*"):
   3661	query = QSqlQuery(db)
   3662	try:
   3663		QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
   3664	except:
   3665		return False
   3666	return True
   3667
   3668# SQL table data model item
   3669
   3670class SQLTableItem():
   3671
   3672	def __init__(self, row, data):
   3673		self.row = row
   3674		self.data = data
   3675
   3676	def getData(self, column):
   3677		return self.data[column]
   3678
   3679# SQL table data model
   3680
   3681class SQLTableModel(TableModel):
   3682
   3683	progress = Signal(object)
   3684
   3685	def __init__(self, glb, sql, column_headers, parent=None):
   3686		super(SQLTableModel, self).__init__(parent)
   3687		self.glb = glb
   3688		self.more = True
   3689		self.populated = 0
   3690		self.column_headers = column_headers
   3691		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
   3692		self.fetcher.done.connect(self.Update)
   3693		self.fetcher.Fetch(glb_chunk_sz)
   3694
   3695	def DisplayData(self, item, index):
   3696		self.FetchIfNeeded(item.row)
   3697		return item.getData(index.column())
   3698
   3699	def AddSample(self, data):
   3700		child = SQLTableItem(self.populated, data)
   3701		self.child_items.append(child)
   3702		self.populated += 1
   3703
   3704	def Update(self, fetched):
   3705		if not fetched:
   3706			self.more = False
   3707			self.progress.emit(0)
   3708		child_count = self.child_count
   3709		count = self.populated - child_count
   3710		if count > 0:
   3711			parent = QModelIndex()
   3712			self.beginInsertRows(parent, child_count, child_count + count - 1)
   3713			self.insertRows(child_count, count, parent)
   3714			self.child_count += count
   3715			self.endInsertRows()
   3716			self.progress.emit(self.child_count)
   3717
   3718	def FetchMoreRecords(self, count):
   3719		current = self.child_count
   3720		if self.more:
   3721			self.fetcher.Fetch(count)
   3722		else:
   3723			self.progress.emit(0)
   3724		return current
   3725
   3726	def HasMoreRecords(self):
   3727		return self.more
   3728
   3729	def columnCount(self, parent=None):
   3730		return len(self.column_headers)
   3731
   3732	def columnHeader(self, column):
   3733		return self.column_headers[column]
   3734
   3735	def SQLTableDataPrep(self, query, count):
   3736		data = []
   3737		for i in xrange(count):
   3738			data.append(query.value(i))
   3739		return data
   3740
   3741# SQL automatic table data model
   3742
   3743class SQLAutoTableModel(SQLTableModel):
   3744
   3745	def __init__(self, glb, table_name, parent=None):
   3746		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
   3747		if table_name == "comm_threads_view":
   3748			# For now, comm_threads_view has no id column
   3749			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
   3750		column_headers = []
   3751		query = QSqlQuery(glb.db)
   3752		if glb.dbref.is_sqlite3:
   3753			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
   3754			while query.next():
   3755				column_headers.append(query.value(1))
   3756			if table_name == "sqlite_master":
   3757				sql = "SELECT * FROM " + table_name
   3758		else:
   3759			if table_name[:19] == "information_schema.":
   3760				sql = "SELECT * FROM " + table_name
   3761				select_table_name = table_name[19:]
   3762				schema = "information_schema"
   3763			else:
   3764				select_table_name = table_name
   3765				schema = "public"
   3766			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
   3767			while query.next():
   3768				column_headers.append(query.value(0))
   3769		if pyside_version_1 and sys.version_info[0] == 3:
   3770			if table_name == "samples_view":
   3771				self.SQLTableDataPrep = self.samples_view_DataPrep
   3772			if table_name == "samples":
   3773				self.SQLTableDataPrep = self.samples_DataPrep
   3774		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
   3775
   3776	def samples_view_DataPrep(self, query, count):
   3777		data = []
   3778		data.append(query.value(0))
   3779		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
   3780		data.append("{:>19}".format(query.value(1)))
   3781		for i in xrange(2, count):
   3782			data.append(query.value(i))
   3783		return data
   3784
   3785	def samples_DataPrep(self, query, count):
   3786		data = []
   3787		for i in xrange(9):
   3788			data.append(query.value(i))
   3789		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
   3790		data.append("{:>19}".format(query.value(9)))
   3791		for i in xrange(10, count):
   3792			data.append(query.value(i))
   3793		return data
   3794
   3795# Base class for custom ResizeColumnsToContents
   3796
   3797class ResizeColumnsToContentsBase(QObject):
   3798
   3799	def __init__(self, parent=None):
   3800		super(ResizeColumnsToContentsBase, self).__init__(parent)
   3801
   3802	def ResizeColumnToContents(self, column, n):
   3803		# Using the view's resizeColumnToContents() here is extrememly slow
   3804		# so implement a crude alternative
   3805		font = self.view.font()
   3806		metrics = QFontMetrics(font)
   3807		max = 0
   3808		for row in xrange(n):
   3809			val = self.data_model.child_items[row].data[column]
   3810			len = metrics.width(str(val) + "MM")
   3811			max = len if len > max else max
   3812		val = self.data_model.columnHeader(column)
   3813		len = metrics.width(str(val) + "MM")
   3814		max = len if len > max else max
   3815		self.view.setColumnWidth(column, max)
   3816
   3817	def ResizeColumnsToContents(self):
   3818		n = min(self.data_model.child_count, 100)
   3819		if n < 1:
   3820			# No data yet, so connect a signal to notify when there is
   3821			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
   3822			return
   3823		columns = self.data_model.columnCount()
   3824		for i in xrange(columns):
   3825			self.ResizeColumnToContents(i, n)
   3826
   3827	def UpdateColumnWidths(self, *x):
   3828		# This only needs to be done once, so disconnect the signal now
   3829		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
   3830		self.ResizeColumnsToContents()
   3831
   3832# Convert value to CSV
   3833
   3834def ToCSValue(val):
   3835	if '"' in val:
   3836		val = val.replace('"', '""')
   3837	if "," in val or '"' in val:
   3838		val = '"' + val + '"'
   3839	return val
   3840
   3841# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
   3842
   3843glb_max_cols = 1000
   3844
   3845def RowColumnKey(a):
   3846	return a.row() * glb_max_cols + a.column()
   3847
   3848# Copy selected table cells to clipboard
   3849
   3850def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
   3851	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
   3852	idx_cnt = len(indexes)
   3853	if not idx_cnt:
   3854		return
   3855	if idx_cnt == 1:
   3856		with_hdr=False
   3857	min_row = indexes[0].row()
   3858	max_row = indexes[0].row()
   3859	min_col = indexes[0].column()
   3860	max_col = indexes[0].column()
   3861	for i in indexes:
   3862		min_row = min(min_row, i.row())
   3863		max_row = max(max_row, i.row())
   3864		min_col = min(min_col, i.column())
   3865		max_col = max(max_col, i.column())
   3866	if max_col > glb_max_cols:
   3867		raise RuntimeError("glb_max_cols is too low")
   3868	max_width = [0] * (1 + max_col - min_col)
   3869	for i in indexes:
   3870		c = i.column() - min_col
   3871		max_width[c] = max(max_width[c], len(str(i.data())))
   3872	text = ""
   3873	pad = ""
   3874	sep = ""
   3875	if with_hdr:
   3876		model = indexes[0].model()
   3877		for col in range(min_col, max_col + 1):
   3878			val = model.headerData(col, Qt.Horizontal, Qt.DisplayRole)
   3879			if as_csv:
   3880				text += sep + ToCSValue(val)
   3881				sep = ","
   3882			else:
   3883				c = col - min_col
   3884				max_width[c] = max(max_width[c], len(val))
   3885				width = max_width[c]
   3886				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
   3887				if align & Qt.AlignRight:
   3888					val = val.rjust(width)
   3889				text += pad + sep + val
   3890				pad = " " * (width - len(val))
   3891				sep = "  "
   3892		text += "\n"
   3893		pad = ""
   3894		sep = ""
   3895	last_row = min_row
   3896	for i in indexes:
   3897		if i.row() > last_row:
   3898			last_row = i.row()
   3899			text += "\n"
   3900			pad = ""
   3901			sep = ""
   3902		if as_csv:
   3903			text += sep + ToCSValue(str(i.data()))
   3904			sep = ","
   3905		else:
   3906			width = max_width[i.column() - min_col]
   3907			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
   3908				val = str(i.data()).rjust(width)
   3909			else:
   3910				val = str(i.data())
   3911			text += pad + sep + val
   3912			pad = " " * (width - len(val))
   3913			sep = "  "
   3914	QApplication.clipboard().setText(text)
   3915
   3916def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
   3917	indexes = view.selectedIndexes()
   3918	if not len(indexes):
   3919		return
   3920
   3921	selection = view.selectionModel()
   3922
   3923	first = None
   3924	for i in indexes:
   3925		above = view.indexAbove(i)
   3926		if not selection.isSelected(above):
   3927			first = i
   3928			break
   3929
   3930	if first is None:
   3931		raise RuntimeError("CopyTreeCellsToClipboard internal error")
   3932
   3933	model = first.model()
   3934	row_cnt = 0
   3935	col_cnt = model.columnCount(first)
   3936	max_width = [0] * col_cnt
   3937
   3938	indent_sz = 2
   3939	indent_str = " " * indent_sz
   3940
   3941	expanded_mark_sz = 2
   3942	if sys.version_info[0] == 3:
   3943		expanded_mark = "\u25BC "
   3944		not_expanded_mark = "\u25B6 "
   3945	else:
   3946		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
   3947		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
   3948	leaf_mark = "  "
   3949
   3950	if not as_csv:
   3951		pos = first
   3952		while True:
   3953			row_cnt += 1
   3954			row = pos.row()
   3955			for c in range(col_cnt):
   3956				i = pos.sibling(row, c)
   3957				if c:
   3958					n = len(str(i.data()))
   3959				else:
   3960					n = len(str(i.data()).strip())
   3961					n += (i.internalPointer().level - 1) * indent_sz
   3962					n += expanded_mark_sz
   3963				max_width[c] = max(max_width[c], n)
   3964			pos = view.indexBelow(pos)
   3965			if not selection.isSelected(pos):
   3966				break
   3967
   3968	text = ""
   3969	pad = ""
   3970	sep = ""
   3971	if with_hdr:
   3972		for c in range(col_cnt):
   3973			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
   3974			if as_csv:
   3975				text += sep + ToCSValue(val)
   3976				sep = ","
   3977			else:
   3978				max_width[c] = max(max_width[c], len(val))
   3979				width = max_width[c]
   3980				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
   3981				if align & Qt.AlignRight:
   3982					val = val.rjust(width)
   3983				text += pad + sep + val
   3984				pad = " " * (width - len(val))
   3985				sep = "   "
   3986		text += "\n"
   3987		pad = ""
   3988		sep = ""
   3989
   3990	pos = first
   3991	while True:
   3992		row = pos.row()
   3993		for c in range(col_cnt):
   3994			i = pos.sibling(row, c)
   3995			val = str(i.data())
   3996			if not c:
   3997				if model.hasChildren(i):
   3998					if view.isExpanded(i):
   3999						mark = expanded_mark
   4000					else:
   4001						mark = not_expanded_mark
   4002				else:
   4003					mark = leaf_mark
   4004				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
   4005			if as_csv:
   4006				text += sep + ToCSValue(val)
   4007				sep = ","
   4008			else:
   4009				width = max_width[c]
   4010				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
   4011					val = val.rjust(width)
   4012				text += pad + sep + val
   4013				pad = " " * (width - len(val))
   4014				sep = "   "
   4015		pos = view.indexBelow(pos)
   4016		if not selection.isSelected(pos):
   4017			break
   4018		text = text.rstrip() + "\n"
   4019		pad = ""
   4020		sep = ""
   4021
   4022	QApplication.clipboard().setText(text)
   4023
   4024def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
   4025	view.CopyCellsToClipboard(view, as_csv, with_hdr)
   4026
   4027def CopyCellsToClipboardHdr(view):
   4028	CopyCellsToClipboard(view, False, True)
   4029
   4030def CopyCellsToClipboardCSV(view):
   4031	CopyCellsToClipboard(view, True, True)
   4032
   4033# Context menu
   4034
   4035class ContextMenu(object):
   4036
   4037	def __init__(self, view):
   4038		self.view = view
   4039		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
   4040		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
   4041
   4042	def ShowContextMenu(self, pos):
   4043		menu = QMenu(self.view)
   4044		self.AddActions(menu)
   4045		menu.exec_(self.view.mapToGlobal(pos))
   4046
   4047	def AddCopy(self, menu):
   4048		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
   4049		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
   4050
   4051	def AddActions(self, menu):
   4052		self.AddCopy(menu)
   4053
   4054class TreeContextMenu(ContextMenu):
   4055
   4056	def __init__(self, view):
   4057		super(TreeContextMenu, self).__init__(view)
   4058
   4059	def AddActions(self, menu):
   4060		i = self.view.currentIndex()
   4061		text = str(i.data()).strip()
   4062		if len(text):
   4063			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
   4064		self.AddCopy(menu)
   4065
   4066# Table window
   4067
   4068class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
   4069
   4070	def __init__(self, glb, table_name, parent=None):
   4071		super(TableWindow, self).__init__(parent)
   4072
   4073		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
   4074
   4075		self.model = QSortFilterProxyModel()
   4076		self.model.setSourceModel(self.data_model)
   4077
   4078		self.view = QTableView()
   4079		self.view.setModel(self.model)
   4080		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
   4081		self.view.verticalHeader().setVisible(False)
   4082		self.view.sortByColumn(-1, Qt.AscendingOrder)
   4083		self.view.setSortingEnabled(True)
   4084		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
   4085		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
   4086
   4087		self.ResizeColumnsToContents()
   4088
   4089		self.context_menu = ContextMenu(self.view)
   4090
   4091		self.find_bar = FindBar(self, self, True)
   4092
   4093		self.finder = ChildDataItemFinder(self.data_model)
   4094
   4095		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
   4096
   4097		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
   4098
   4099		self.setWidget(self.vbox.Widget())
   4100
   4101		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
   4102
   4103	def Find(self, value, direction, pattern, context):
   4104		self.view.setFocus()
   4105		self.find_bar.Busy()
   4106		self.finder.Find(value, direction, pattern, context, self.FindDone)
   4107
   4108	def FindDone(self, row):
   4109		self.find_bar.Idle()
   4110		if row >= 0:
   4111			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
   4112		else:
   4113			self.find_bar.NotFound()
   4114
   4115# Table list
   4116
   4117def GetTableList(glb):
   4118	tables = []
   4119	query = QSqlQuery(glb.db)
   4120	if glb.dbref.is_sqlite3:
   4121		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
   4122	else:
   4123		QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
   4124	while query.next():
   4125		tables.append(query.value(0))
   4126	if glb.dbref.is_sqlite3:
   4127		tables.append("sqlite_master")
   4128	else:
   4129		tables.append("information_schema.tables")
   4130		tables.append("information_schema.views")
   4131		tables.append("information_schema.columns")
   4132	return tables
   4133
   4134# Top Calls data model
   4135
   4136class TopCallsModel(SQLTableModel):
   4137
   4138	def __init__(self, glb, report_vars, parent=None):
   4139		text = ""
   4140		if not glb.dbref.is_sqlite3:
   4141			text = "::text"
   4142		limit = ""
   4143		if len(report_vars.limit):
   4144			limit = " LIMIT " + report_vars.limit
   4145		sql = ("SELECT comm, pid, tid, name,"
   4146			" CASE"
   4147			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
   4148			" ELSE short_name"
   4149			" END AS dso,"
   4150			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
   4151			" CASE"
   4152			" WHEN (calls.flags = 1) THEN 'no call'" + text +
   4153			" WHEN (calls.flags = 2) THEN 'no return'" + text +
   4154			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
   4155			" ELSE ''" + text +
   4156			" END AS flags"
   4157			" FROM calls"
   4158			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
   4159			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
   4160			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
   4161			" INNER JOIN comms ON calls.comm_id = comms.id"
   4162			" INNER JOIN threads ON calls.thread_id = threads.id" +
   4163			report_vars.where_clause +
   4164			" ORDER BY elapsed_time DESC" +
   4165			limit
   4166			)
   4167		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
   4168		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
   4169		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
   4170
   4171	def columnAlignment(self, column):
   4172		return self.alignment[column]
   4173
   4174# Top Calls report creation dialog
   4175
   4176class TopCallsDialog(ReportDialogBase):
   4177
   4178	def __init__(self, glb, parent=None):
   4179		title = "Top Calls by Elapsed Time"
   4180		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
   4181			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
   4182			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
   4183			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
   4184			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
   4185			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
   4186			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
   4187			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
   4188		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
   4189
   4190# Top Calls window
   4191
   4192class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
   4193
   4194	def __init__(self, glb, report_vars, parent=None):
   4195		super(TopCallsWindow, self).__init__(parent)
   4196
   4197		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
   4198		self.model = self.data_model
   4199
   4200		self.view = QTableView()
   4201		self.view.setModel(self.model)
   4202		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
   4203		self.view.verticalHeader().setVisible(False)
   4204		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
   4205		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
   4206
   4207		self.context_menu = ContextMenu(self.view)
   4208
   4209		self.ResizeColumnsToContents()
   4210
   4211		self.find_bar = FindBar(self, self, True)
   4212
   4213		self.finder = ChildDataItemFinder(self.model)
   4214
   4215		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
   4216
   4217		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
   4218
   4219		self.setWidget(self.vbox.Widget())
   4220
   4221		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
   4222
   4223	def Find(self, value, direction, pattern, context):
   4224		self.view.setFocus()
   4225		self.find_bar.Busy()
   4226		self.finder.Find(value, direction, pattern, context, self.FindDone)
   4227
   4228	def FindDone(self, row):
   4229		self.find_bar.Idle()
   4230		if row >= 0:
   4231			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
   4232		else:
   4233			self.find_bar.NotFound()
   4234
   4235# Action Definition
   4236
   4237def CreateAction(label, tip, callback, parent=None, shortcut=None):
   4238	action = QAction(label, parent)
   4239	if shortcut != None:
   4240		action.setShortcuts(shortcut)
   4241	action.setStatusTip(tip)
   4242	action.triggered.connect(callback)
   4243	return action
   4244
   4245# Typical application actions
   4246
   4247def CreateExitAction(app, parent=None):
   4248	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
   4249
   4250# Typical MDI actions
   4251
   4252def CreateCloseActiveWindowAction(mdi_area):
   4253	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
   4254
   4255def CreateCloseAllWindowsAction(mdi_area):
   4256	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
   4257
   4258def CreateTileWindowsAction(mdi_area):
   4259	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
   4260
   4261def CreateCascadeWindowsAction(mdi_area):
   4262	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
   4263
   4264def CreateNextWindowAction(mdi_area):
   4265	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
   4266
   4267def CreatePreviousWindowAction(mdi_area):
   4268	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
   4269
   4270# Typical MDI window menu
   4271
   4272class WindowMenu():
   4273
   4274	def __init__(self, mdi_area, menu):
   4275		self.mdi_area = mdi_area
   4276		self.window_menu = menu.addMenu("&Windows")
   4277		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
   4278		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
   4279		self.tile_windows = CreateTileWindowsAction(mdi_area)
   4280		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
   4281		self.next_window = CreateNextWindowAction(mdi_area)
   4282		self.previous_window = CreatePreviousWindowAction(mdi_area)
   4283		self.window_menu.aboutToShow.connect(self.Update)
   4284
   4285	def Update(self):
   4286		self.window_menu.clear()
   4287		sub_window_count = len(self.mdi_area.subWindowList())
   4288		have_sub_windows = sub_window_count != 0
   4289		self.close_active_window.setEnabled(have_sub_windows)
   4290		self.close_all_windows.setEnabled(have_sub_windows)
   4291		self.tile_windows.setEnabled(have_sub_windows)
   4292		self.cascade_windows.setEnabled(have_sub_windows)
   4293		self.next_window.setEnabled(have_sub_windows)
   4294		self.previous_window.setEnabled(have_sub_windows)
   4295		self.window_menu.addAction(self.close_active_window)
   4296		self.window_menu.addAction(self.close_all_windows)
   4297		self.window_menu.addSeparator()
   4298		self.window_menu.addAction(self.tile_windows)
   4299		self.window_menu.addAction(self.cascade_windows)
   4300		self.window_menu.addSeparator()
   4301		self.window_menu.addAction(self.next_window)
   4302		self.window_menu.addAction(self.previous_window)
   4303		if sub_window_count == 0:
   4304			return
   4305		self.window_menu.addSeparator()
   4306		nr = 1
   4307		for sub_window in self.mdi_area.subWindowList():
   4308			label = str(nr) + " " + sub_window.name
   4309			if nr < 10:
   4310				label = "&" + label
   4311			action = self.window_menu.addAction(label)
   4312			action.setCheckable(True)
   4313			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
   4314			action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
   4315			self.window_menu.addAction(action)
   4316			nr += 1
   4317
   4318	def setActiveSubWindow(self, nr):
   4319		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
   4320
   4321# Help text
   4322
   4323glb_help_text = """
   4324<h1>Contents</h1>
   4325<style>
   4326p.c1 {
   4327    text-indent: 40px;
   4328}
   4329p.c2 {
   4330    text-indent: 80px;
   4331}
   4332}
   4333</style>
   4334<p class=c1><a href=#reports>1. Reports</a></p>
   4335<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
   4336<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
   4337<p class=c2><a href=#allbranches>1.3 All branches</a></p>
   4338<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
   4339<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
   4340<p class=c1><a href=#charts>2. Charts</a></p>
   4341<p class=c2><a href=#timechartbycpu>2.1 Time chart by CPU</a></p>
   4342<p class=c1><a href=#tables>3. Tables</a></p>
   4343<h1 id=reports>1. Reports</h1>
   4344<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
   4345The result is a GUI window with a tree representing a context-sensitive
   4346call-graph. Expanding a couple of levels of the tree and adjusting column
   4347widths to suit will display something like:
   4348<pre>
   4349                                         Call Graph: pt_example
   4350Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
   4351v- ls
   4352    v- 2638:2638
   4353        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
   4354          |- unknown               unknown       1        13198     0.1              1              0.0
   4355          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
   4356          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
   4357          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
   4358             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
   4359             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
   4360             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
   4361             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
   4362             v- main               ls            1      8182043    99.6         180254             99.9
   4363</pre>
   4364<h3>Points to note:</h3>
   4365<ul>
   4366<li>The top level is a command name (comm)</li>
   4367<li>The next level is a thread (pid:tid)</li>
   4368<li>Subsequent levels are functions</li>
   4369<li>'Count' is the number of calls</li>
   4370<li>'Time' is the elapsed time until the function returns</li>
   4371<li>Percentages are relative to the level above</li>
   4372<li>'Branch Count' is the total number of branches for that function and all functions that it calls
   4373</ul>
   4374<h3>Find</h3>
   4375Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
   4376The pattern matching symbols are ? for any character and * for zero or more characters.
   4377<h2 id=calltree>1.2 Call Tree</h2>
   4378The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
   4379Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
   4380<h2 id=allbranches>1.3 All branches</h2>
   4381The All branches report displays all branches in chronological order.
   4382Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
   4383<h3>Disassembly</h3>
   4384Open a branch to display disassembly. This only works if:
   4385<ol>
   4386<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
   4387<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
   4388The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
   4389One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
   4390or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
   4391</ol>
   4392<h4 id=xed>Intel XED Setup</h4>
   4393To use Intel XED, libxed.so must be present.  To build and install libxed.so:
   4394<pre>
   4395git clone https://github.com/intelxed/mbuild.git mbuild
   4396git clone https://github.com/intelxed/xed
   4397cd xed
   4398./mfile.py --share
   4399sudo ./mfile.py --prefix=/usr/local install
   4400sudo ldconfig
   4401</pre>
   4402<h3>Instructions per Cycle (IPC)</h3>
   4403If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
   4404<p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
   4405Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
   4406In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
   4407since the previous displayed 'IPC'.
   4408<h3>Find</h3>
   4409Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
   4410Refer to Python documentation for the regular expression syntax.
   4411All columns are searched, but only currently fetched rows are searched.
   4412<h2 id=selectedbranches>1.4 Selected branches</h2>
   4413This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
   4414by various selection criteria. A dialog box displays available criteria which are AND'ed together.
   4415<h3>1.4.1 Time ranges</h3>
   4416The time ranges hint text shows the total time range. Relative time ranges can also be entered in
   4417ms, us or ns. Also, negative values are relative to the end of trace.  Examples:
   4418<pre>
   4419	81073085947329-81073085958238	From 81073085947329 to 81073085958238
   4420	100us-200us		From 100us to 200us
   4421	10ms-			From 10ms to the end
   4422	-100ns			The first 100ns
   4423	-10ms-			The last 10ms
   4424</pre>
   4425N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
   4426<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
   4427The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
   4428The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
   4429If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
   4430<h1 id=charts>2. Charts</h1>
   4431<h2 id=timechartbycpu>2.1 Time chart by CPU</h2>
   4432This chart displays context switch information when that data is available. Refer to context_switches_view on the Tables menu.
   4433<h3>Features</h3>
   4434<ol>
   4435<li>Mouse over to highight the task and show the time</li>
   4436<li>Drag the mouse to select a region and zoom by pushing the Zoom button</li>
   4437<li>Go back and forward by pressing the arrow buttons</li>
   4438<li>If call information is available, right-click to show a call tree opened to that task and time.
   4439Note, the call tree may take some time to appear, and there may not be call information for the task or time selected.
   4440</li>
   4441</ol>
   4442<h3>Important</h3>
   4443The graph can be misleading in the following respects:
   4444<ol>
   4445<li>The graph shows the first task on each CPU as running from the beginning of the time range.
   4446Because tracing might start on different CPUs at different times, that is not necessarily the case.
   4447Refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
   4448<li>Similarly, the last task on each CPU can be showing running longer than it really was.
   4449Again, refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
   4450<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>
   4451</ol>
   4452<h1 id=tables>3. Tables</h1>
   4453The Tables menu shows all tables and views in the database. Most tables have an associated view
   4454which displays the information in a more friendly way. Not all data for large tables is fetched
   4455immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
   4456but that can be slow for large tables.
   4457<p>There are also tables of database meta-information.
   4458For SQLite3 databases, the sqlite_master table is included.
   4459For PostgreSQL databases, information_schema.tables/views/columns are included.
   4460<h3>Find</h3>
   4461Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
   4462Refer to Python documentation for the regular expression syntax.
   4463All columns are searched, but only currently fetched rows are searched.
   4464<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
   4465will go to the next/previous result in id order, instead of display order.
   4466"""
   4467
   4468# Help window
   4469
   4470class HelpWindow(QMdiSubWindow):
   4471
   4472	def __init__(self, glb, parent=None):
   4473		super(HelpWindow, self).__init__(parent)
   4474
   4475		self.text = QTextBrowser()
   4476		self.text.setHtml(glb_help_text)
   4477		self.text.setReadOnly(True)
   4478		self.text.setOpenExternalLinks(True)
   4479
   4480		self.setWidget(self.text)
   4481
   4482		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
   4483
   4484# Main window that only displays the help text
   4485
   4486class HelpOnlyWindow(QMainWindow):
   4487
   4488	def __init__(self, parent=None):
   4489		super(HelpOnlyWindow, self).__init__(parent)
   4490
   4491		self.setMinimumSize(200, 100)
   4492		self.resize(800, 600)
   4493		self.setWindowTitle("Exported SQL Viewer Help")
   4494		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
   4495
   4496		self.text = QTextBrowser()
   4497		self.text.setHtml(glb_help_text)
   4498		self.text.setReadOnly(True)
   4499		self.text.setOpenExternalLinks(True)
   4500
   4501		self.setCentralWidget(self.text)
   4502
   4503# PostqreSQL server version
   4504
   4505def PostqreSQLServerVersion(db):
   4506	query = QSqlQuery(db)
   4507	QueryExec(query, "SELECT VERSION()")
   4508	if query.next():
   4509		v_str = query.value(0)
   4510		v_list = v_str.strip().split(" ")
   4511		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
   4512			return v_list[1]
   4513		return v_str
   4514	return "Unknown"
   4515
   4516# SQLite version
   4517
   4518def SQLiteVersion(db):
   4519	query = QSqlQuery(db)
   4520	QueryExec(query, "SELECT sqlite_version()")
   4521	if query.next():
   4522		return query.value(0)
   4523	return "Unknown"
   4524
   4525# About dialog
   4526
   4527class AboutDialog(QDialog):
   4528
   4529	def __init__(self, glb, parent=None):
   4530		super(AboutDialog, self).__init__(parent)
   4531
   4532		self.setWindowTitle("About Exported SQL Viewer")
   4533		self.setMinimumWidth(300)
   4534
   4535		pyside_version = "1" if pyside_version_1 else "2"
   4536
   4537		text = "<pre>"
   4538		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
   4539		text += "PySide version:     " + pyside_version + "\n"
   4540		text += "Qt version:         " + qVersion() + "\n"
   4541		if glb.dbref.is_sqlite3:
   4542			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
   4543		else:
   4544			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
   4545		text += "</pre>"
   4546
   4547		self.text = QTextBrowser()
   4548		self.text.setHtml(text)
   4549		self.text.setReadOnly(True)
   4550		self.text.setOpenExternalLinks(True)
   4551
   4552		self.vbox = QVBoxLayout()
   4553		self.vbox.addWidget(self.text)
   4554
   4555		self.setLayout(self.vbox)
   4556
   4557# Font resize
   4558
   4559def ResizeFont(widget, diff):
   4560	font = widget.font()
   4561	sz = font.pointSize()
   4562	font.setPointSize(sz + diff)
   4563	widget.setFont(font)
   4564
   4565def ShrinkFont(widget):
   4566	ResizeFont(widget, -1)
   4567
   4568def EnlargeFont(widget):
   4569	ResizeFont(widget, 1)
   4570
   4571# Unique name for sub-windows
   4572
   4573def NumberedWindowName(name, nr):
   4574	if nr > 1:
   4575		name += " <" + str(nr) + ">"
   4576	return name
   4577
   4578def UniqueSubWindowName(mdi_area, name):
   4579	nr = 1
   4580	while True:
   4581		unique_name = NumberedWindowName(name, nr)
   4582		ok = True
   4583		for sub_window in mdi_area.subWindowList():
   4584			if sub_window.name == unique_name:
   4585				ok = False
   4586				break
   4587		if ok:
   4588			return unique_name
   4589		nr += 1
   4590
   4591# Add a sub-window
   4592
   4593def AddSubWindow(mdi_area, sub_window, name):
   4594	unique_name = UniqueSubWindowName(mdi_area, name)
   4595	sub_window.setMinimumSize(200, 100)
   4596	sub_window.resize(800, 600)
   4597	sub_window.setWindowTitle(unique_name)
   4598	sub_window.setAttribute(Qt.WA_DeleteOnClose)
   4599	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
   4600	sub_window.name = unique_name
   4601	mdi_area.addSubWindow(sub_window)
   4602	sub_window.show()
   4603
   4604# Main window
   4605
   4606class MainWindow(QMainWindow):
   4607
   4608	def __init__(self, glb, parent=None):
   4609		super(MainWindow, self).__init__(parent)
   4610
   4611		self.glb = glb
   4612
   4613		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
   4614		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
   4615		self.setMinimumSize(200, 100)
   4616
   4617		self.mdi_area = QMdiArea()
   4618		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
   4619		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
   4620
   4621		self.setCentralWidget(self.mdi_area)
   4622
   4623		menu = self.menuBar()
   4624
   4625		file_menu = menu.addMenu("&File")
   4626		file_menu.addAction(CreateExitAction(glb.app, self))
   4627
   4628		edit_menu = menu.addMenu("&Edit")
   4629		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
   4630		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
   4631		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
   4632		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
   4633		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
   4634		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
   4635
   4636		reports_menu = menu.addMenu("&Reports")
   4637		if IsSelectable(glb.db, "calls"):
   4638			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
   4639
   4640		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
   4641			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
   4642
   4643		self.EventMenu(GetEventList(glb.db), reports_menu)
   4644
   4645		if IsSelectable(glb.db, "calls"):
   4646			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
   4647
   4648		if IsSelectable(glb.db, "context_switches"):
   4649			charts_menu = menu.addMenu("&Charts")
   4650			charts_menu.addAction(CreateAction("&Time chart by CPU", "Create a new window displaying time charts by CPU", self.TimeChartByCPU, self))
   4651
   4652		self.TableMenu(GetTableList(glb), menu)
   4653
   4654		self.window_menu = WindowMenu(self.mdi_area, menu)
   4655
   4656		help_menu = menu.addMenu("&Help")
   4657		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
   4658		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
   4659
   4660	def Try(self, fn):
   4661		win = self.mdi_area.activeSubWindow()
   4662		if win:
   4663			try:
   4664				fn(win.view)
   4665			except:
   4666				pass
   4667
   4668	def CopyToClipboard(self):
   4669		self.Try(CopyCellsToClipboardHdr)
   4670
   4671	def CopyToClipboardCSV(self):
   4672		self.Try(CopyCellsToClipboardCSV)
   4673
   4674	def Find(self):
   4675		win = self.mdi_area.activeSubWindow()
   4676		if win:
   4677			try:
   4678				win.find_bar.Activate()
   4679			except:
   4680				pass
   4681
   4682	def FetchMoreRecords(self):
   4683		win = self.mdi_area.activeSubWindow()
   4684		if win:
   4685			try:
   4686				win.fetch_bar.Activate()
   4687			except:
   4688				pass
   4689
   4690	def ShrinkFont(self):
   4691		self.Try(ShrinkFont)
   4692
   4693	def EnlargeFont(self):
   4694		self.Try(EnlargeFont)
   4695
   4696	def EventMenu(self, events, reports_menu):
   4697		branches_events = 0
   4698		for event in events:
   4699			event = event.split(":")[0]
   4700			if event == "branches":
   4701				branches_events += 1
   4702		dbid = 0
   4703		for event in events:
   4704			dbid += 1
   4705			event = event.split(":")[0]
   4706			if event == "branches":
   4707				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
   4708				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
   4709				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
   4710				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
   4711
   4712	def TimeChartByCPU(self):
   4713		TimeChartByCPUWindow(self.glb, self)
   4714
   4715	def TableMenu(self, tables, menu):
   4716		table_menu = menu.addMenu("&Tables")
   4717		for table in tables:
   4718			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
   4719
   4720	def NewCallGraph(self):
   4721		CallGraphWindow(self.glb, self)
   4722
   4723	def NewCallTree(self):
   4724		CallTreeWindow(self.glb, self)
   4725
   4726	def NewTopCalls(self):
   4727		dialog = TopCallsDialog(self.glb, self)
   4728		ret = dialog.exec_()
   4729		if ret:
   4730			TopCallsWindow(self.glb, dialog.report_vars, self)
   4731
   4732	def NewBranchView(self, event_id):
   4733		BranchWindow(self.glb, event_id, ReportVars(), self)
   4734
   4735	def NewSelectedBranchView(self, event_id):
   4736		dialog = SelectedBranchDialog(self.glb, self)
   4737		ret = dialog.exec_()
   4738		if ret:
   4739			BranchWindow(self.glb, event_id, dialog.report_vars, self)
   4740
   4741	def NewTableView(self, table_name):
   4742		TableWindow(self.glb, table_name, self)
   4743
   4744	def Help(self):
   4745		HelpWindow(self.glb, self)
   4746
   4747	def About(self):
   4748		dialog = AboutDialog(self.glb, self)
   4749		dialog.exec_()
   4750
   4751def TryOpen(file_name):
   4752	try:
   4753		return open(file_name, "rb")
   4754	except:
   4755		return None
   4756
   4757def Is64Bit(f):
   4758	result = sizeof(c_void_p)
   4759	# ELF support only
   4760	pos = f.tell()
   4761	f.seek(0)
   4762	header = f.read(7)
   4763	f.seek(pos)
   4764	magic = header[0:4]
   4765	if sys.version_info[0] == 2:
   4766		eclass = ord(header[4])
   4767		encoding = ord(header[5])
   4768		version = ord(header[6])
   4769	else:
   4770		eclass = header[4]
   4771		encoding = header[5]
   4772		version = header[6]
   4773	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
   4774		result = True if eclass == 2 else False
   4775	return result
   4776
   4777# Global data
   4778
   4779class Glb():
   4780
   4781	def __init__(self, dbref, db, dbname):
   4782		self.dbref = dbref
   4783		self.db = db
   4784		self.dbname = dbname
   4785		self.home_dir = os.path.expanduser("~")
   4786		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
   4787		if self.buildid_dir:
   4788			self.buildid_dir += "/.build-id/"
   4789		else:
   4790			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
   4791		self.app = None
   4792		self.mainwindow = None
   4793		self.instances_to_shutdown_on_exit = weakref.WeakSet()
   4794		try:
   4795			self.disassembler = LibXED()
   4796			self.have_disassembler = True
   4797		except:
   4798			self.have_disassembler = False
   4799		self.host_machine_id = 0
   4800		self.host_start_time = 0
   4801		self.host_finish_time = 0
   4802
   4803	def FileFromBuildId(self, build_id):
   4804		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
   4805		return TryOpen(file_name)
   4806
   4807	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
   4808		# Assume current machine i.e. no support for virtualization
   4809		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
   4810			file_name = os.getenv("PERF_KCORE")
   4811			f = TryOpen(file_name) if file_name else None
   4812			if f:
   4813				return f
   4814			# For now, no special handling if long_name is /proc/kcore
   4815			f = TryOpen(long_name)
   4816			if f:
   4817				return f
   4818		f = self.FileFromBuildId(build_id)
   4819		if f:
   4820			return f
   4821		return None
   4822
   4823	def AddInstanceToShutdownOnExit(self, instance):
   4824		self.instances_to_shutdown_on_exit.add(instance)
   4825
   4826	# Shutdown any background processes or threads
   4827	def ShutdownInstances(self):
   4828		for x in self.instances_to_shutdown_on_exit:
   4829			try:
   4830				x.Shutdown()
   4831			except:
   4832				pass
   4833
   4834	def GetHostMachineId(self):
   4835		query = QSqlQuery(self.db)
   4836		QueryExec(query, "SELECT id FROM machines WHERE pid = -1")
   4837		if query.next():
   4838			self.host_machine_id = query.value(0)
   4839		else:
   4840			self.host_machine_id = 0
   4841		return self.host_machine_id
   4842
   4843	def HostMachineId(self):
   4844		if self.host_machine_id:
   4845			return self.host_machine_id
   4846		return self.GetHostMachineId()
   4847
   4848	def SelectValue(self, sql):
   4849		query = QSqlQuery(self.db)
   4850		try:
   4851			QueryExec(query, sql)
   4852		except:
   4853			return None
   4854		if query.next():
   4855			return Decimal(query.value(0))
   4856		return None
   4857
   4858	def SwitchesMinTime(self, machine_id):
   4859		return self.SelectValue("SELECT time"
   4860					" FROM context_switches"
   4861					" WHERE time != 0 AND machine_id = " + str(machine_id) +
   4862					" ORDER BY id LIMIT 1")
   4863
   4864	def SwitchesMaxTime(self, machine_id):
   4865		return self.SelectValue("SELECT time"
   4866					" FROM context_switches"
   4867					" WHERE time != 0 AND machine_id = " + str(machine_id) +
   4868					" ORDER BY id DESC LIMIT 1")
   4869
   4870	def SamplesMinTime(self, machine_id):
   4871		return self.SelectValue("SELECT time"
   4872					" FROM samples"
   4873					" WHERE time != 0 AND machine_id = " + str(machine_id) +
   4874					" ORDER BY id LIMIT 1")
   4875
   4876	def SamplesMaxTime(self, machine_id):
   4877		return self.SelectValue("SELECT time"
   4878					" FROM samples"
   4879					" WHERE time != 0 AND machine_id = " + str(machine_id) +
   4880					" ORDER BY id DESC LIMIT 1")
   4881
   4882	def CallsMinTime(self, machine_id):
   4883		return self.SelectValue("SELECT calls.call_time"
   4884					" FROM calls"
   4885					" INNER JOIN threads ON threads.thread_id = calls.thread_id"
   4886					" WHERE calls.call_time != 0 AND threads.machine_id = " + str(machine_id) +
   4887					" ORDER BY calls.id LIMIT 1")
   4888
   4889	def CallsMaxTime(self, machine_id):
   4890		return self.SelectValue("SELECT calls.return_time"
   4891					" FROM calls"
   4892					" INNER JOIN threads ON threads.thread_id = calls.thread_id"
   4893					" WHERE calls.return_time != 0 AND threads.machine_id = " + str(machine_id) +
   4894					" ORDER BY calls.return_time DESC LIMIT 1")
   4895
   4896	def GetStartTime(self, machine_id):
   4897		t0 = self.SwitchesMinTime(machine_id)
   4898		t1 = self.SamplesMinTime(machine_id)
   4899		t2 = self.CallsMinTime(machine_id)
   4900		if t0 is None or (not(t1 is None) and t1 < t0):
   4901			t0 = t1
   4902		if t0 is None or (not(t2 is None) and t2 < t0):
   4903			t0 = t2
   4904		return t0
   4905
   4906	def GetFinishTime(self, machine_id):
   4907		t0 = self.SwitchesMaxTime(machine_id)
   4908		t1 = self.SamplesMaxTime(machine_id)
   4909		t2 = self.CallsMaxTime(machine_id)
   4910		if t0 is None or (not(t1 is None) and t1 > t0):
   4911			t0 = t1
   4912		if t0 is None or (not(t2 is None) and t2 > t0):
   4913			t0 = t2
   4914		return t0
   4915
   4916	def HostStartTime(self):
   4917		if self.host_start_time:
   4918			return self.host_start_time
   4919		self.host_start_time = self.GetStartTime(self.HostMachineId())
   4920		return self.host_start_time
   4921
   4922	def HostFinishTime(self):
   4923		if self.host_finish_time:
   4924			return self.host_finish_time
   4925		self.host_finish_time = self.GetFinishTime(self.HostMachineId())
   4926		return self.host_finish_time
   4927
   4928	def StartTime(self, machine_id):
   4929		if machine_id == self.HostMachineId():
   4930			return self.HostStartTime()
   4931		return self.GetStartTime(machine_id)
   4932
   4933	def FinishTime(self, machine_id):
   4934		if machine_id == self.HostMachineId():
   4935			return self.HostFinishTime()
   4936		return self.GetFinishTime(machine_id)
   4937
   4938# Database reference
   4939
   4940class DBRef():
   4941
   4942	def __init__(self, is_sqlite3, dbname):
   4943		self.is_sqlite3 = is_sqlite3
   4944		self.dbname = dbname
   4945		self.TRUE = "TRUE"
   4946		self.FALSE = "FALSE"
   4947		# SQLite prior to version 3.23 does not support TRUE and FALSE
   4948		if self.is_sqlite3:
   4949			self.TRUE = "1"
   4950			self.FALSE = "0"
   4951
   4952	def Open(self, connection_name):
   4953		dbname = self.dbname
   4954		if self.is_sqlite3:
   4955			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
   4956		else:
   4957			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
   4958			opts = dbname.split()
   4959			for opt in opts:
   4960				if "=" in opt:
   4961					opt = opt.split("=")
   4962					if opt[0] == "hostname":
   4963						db.setHostName(opt[1])
   4964					elif opt[0] == "port":
   4965						db.setPort(int(opt[1]))
   4966					elif opt[0] == "username":
   4967						db.setUserName(opt[1])
   4968					elif opt[0] == "password":
   4969						db.setPassword(opt[1])
   4970					elif opt[0] == "dbname":
   4971						dbname = opt[1]
   4972				else:
   4973					dbname = opt
   4974
   4975		db.setDatabaseName(dbname)
   4976		if not db.open():
   4977			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
   4978		return db, dbname
   4979
   4980# Main
   4981
   4982def Main():
   4983	usage_str =	"exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
   4984			"   or: exported-sql-viewer.py --help-only"
   4985	ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
   4986	ap.add_argument("--pyside-version-1", action='store_true')
   4987	ap.add_argument("dbname", nargs="?")
   4988	ap.add_argument("--help-only", action='store_true')
   4989	args = ap.parse_args()
   4990
   4991	if args.help_only:
   4992		app = QApplication(sys.argv)
   4993		mainwindow = HelpOnlyWindow()
   4994		mainwindow.show()
   4995		err = app.exec_()
   4996		sys.exit(err)
   4997
   4998	dbname = args.dbname
   4999	if dbname is None:
   5000		ap.print_usage()
   5001		print("Too few arguments")
   5002		sys.exit(1)
   5003
   5004	is_sqlite3 = False
   5005	try:
   5006		f = open(dbname, "rb")
   5007		if f.read(15) == b'SQLite format 3':
   5008			is_sqlite3 = True
   5009		f.close()
   5010	except:
   5011		pass
   5012
   5013	dbref = DBRef(is_sqlite3, dbname)
   5014	db, dbname = dbref.Open("main")
   5015	glb = Glb(dbref, db, dbname)
   5016	app = QApplication(sys.argv)
   5017	glb.app = app
   5018	mainwindow = MainWindow(glb)
   5019	glb.mainwindow = mainwindow
   5020	mainwindow.show()
   5021	err = app.exec_()
   5022	glb.ShutdownInstances()
   5023	db.close()
   5024	sys.exit(err)
   5025
   5026if __name__ == "__main__":
   5027	Main()