Linux Audio

Check our new training course

Linux kernel drivers training

Mar 31-Apr 9, 2025, special US time zones
Register
Loading...
v5.4
   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
 
 
 
 
 
  94import argparse
  95import weakref
  96import threading
  97import string
  98try:
  99	# Python2
 100	import cPickle as pickle
 101	# size of pickled integer big enough for record size
 102	glb_nsz = 8
 103except ImportError:
 104	import pickle
 105	glb_nsz = 16
 106import re
 107import os
 
 
 
 
 108
 109pyside_version_1 = True
 110if not "--pyside-version-1" in sys.argv:
 111	try:
 112		from PySide2.QtCore import *
 113		from PySide2.QtGui import *
 114		from PySide2.QtSql import *
 115		from PySide2.QtWidgets import *
 116		pyside_version_1 = False
 117	except:
 118		pass
 119
 120if pyside_version_1:
 121	from PySide.QtCore import *
 122	from PySide.QtGui import *
 123	from PySide.QtSql import *
 124
 125from decimal import *
 126from ctypes import *
 
 127from multiprocessing import Process, Array, Value, Event
 128
 129# xrange is range in Python3
 130try:
 131	xrange
 132except NameError:
 133	xrange = range
 134
 135def printerr(*args, **keyword_args):
 136	print(*args, file=sys.stderr, **keyword_args)
 137
 138# Data formatting helpers
 139
 140def tohex(ip):
 141	if ip < 0:
 142		ip += 1 << 64
 143	return "%x" % ip
 144
 145def offstr(offset):
 146	if offset:
 147		return "+0x%x" % offset
 148	return ""
 149
 150def dsoname(name):
 151	if name == "[kernel.kallsyms]":
 152		return "[kernel]"
 153	return name
 154
 155def findnth(s, sub, n, offs=0):
 156	pos = s.find(sub)
 157	if pos < 0:
 158		return pos
 159	if n <= 1:
 160		return offs + pos
 161	return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
 162
 163# Percent to one decimal place
 164
 165def PercentToOneDP(n, d):
 166	if not d:
 167		return "0.0"
 168	x = (n * Decimal(100)) / d
 169	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
 170
 171# Helper for queries that must not fail
 172
 173def QueryExec(query, stmt):
 174	ret = query.exec_(stmt)
 175	if not ret:
 176		raise Exception("Query failed: " + query.lastError().text())
 177
 178# Background thread
 179
 180class Thread(QThread):
 181
 182	done = Signal(object)
 183
 184	def __init__(self, task, param=None, parent=None):
 185		super(Thread, self).__init__(parent)
 186		self.task = task
 187		self.param = param
 188
 189	def run(self):
 190		while True:
 191			if self.param is None:
 192				done, result = self.task()
 193			else:
 194				done, result = self.task(self.param)
 195			self.done.emit(result)
 196			if done:
 197				break
 198
 199# Tree data model
 200
 201class TreeModel(QAbstractItemModel):
 202
 203	def __init__(self, glb, params, parent=None):
 204		super(TreeModel, self).__init__(parent)
 205		self.glb = glb
 206		self.params = params
 207		self.root = self.GetRoot()
 208		self.last_row_read = 0
 209
 210	def Item(self, parent):
 211		if parent.isValid():
 212			return parent.internalPointer()
 213		else:
 214			return self.root
 215
 216	def rowCount(self, parent):
 217		result = self.Item(parent).childCount()
 218		if result < 0:
 219			result = 0
 220			self.dataChanged.emit(parent, parent)
 221		return result
 222
 223	def hasChildren(self, parent):
 224		return self.Item(parent).hasChildren()
 225
 226	def headerData(self, section, orientation, role):
 227		if role == Qt.TextAlignmentRole:
 228			return self.columnAlignment(section)
 229		if role != Qt.DisplayRole:
 230			return None
 231		if orientation != Qt.Horizontal:
 232			return None
 233		return self.columnHeader(section)
 234
 235	def parent(self, child):
 236		child_item = child.internalPointer()
 237		if child_item is self.root:
 238			return QModelIndex()
 239		parent_item = child_item.getParentItem()
 240		return self.createIndex(parent_item.getRow(), 0, parent_item)
 241
 242	def index(self, row, column, parent):
 243		child_item = self.Item(parent).getChildItem(row)
 244		return self.createIndex(row, column, child_item)
 245
 246	def DisplayData(self, item, index):
 247		return item.getData(index.column())
 248
 249	def FetchIfNeeded(self, row):
 250		if row > self.last_row_read:
 251			self.last_row_read = row
 252			if row + 10 >= self.root.child_count:
 253				self.fetcher.Fetch(glb_chunk_sz)
 254
 255	def columnAlignment(self, column):
 256		return Qt.AlignLeft
 257
 258	def columnFont(self, column):
 259		return None
 260
 261	def data(self, index, role):
 262		if role == Qt.TextAlignmentRole:
 263			return self.columnAlignment(index.column())
 264		if role == Qt.FontRole:
 265			return self.columnFont(index.column())
 266		if role != Qt.DisplayRole:
 267			return None
 268		item = index.internalPointer()
 269		return self.DisplayData(item, index)
 270
 271# Table data model
 272
 273class TableModel(QAbstractTableModel):
 274
 275	def __init__(self, parent=None):
 276		super(TableModel, self).__init__(parent)
 277		self.child_count = 0
 278		self.child_items = []
 279		self.last_row_read = 0
 280
 281	def Item(self, parent):
 282		if parent.isValid():
 283			return parent.internalPointer()
 284		else:
 285			return self
 286
 287	def rowCount(self, parent):
 288		return self.child_count
 289
 290	def headerData(self, section, orientation, role):
 291		if role == Qt.TextAlignmentRole:
 292			return self.columnAlignment(section)
 293		if role != Qt.DisplayRole:
 294			return None
 295		if orientation != Qt.Horizontal:
 296			return None
 297		return self.columnHeader(section)
 298
 299	def index(self, row, column, parent):
 300		return self.createIndex(row, column, self.child_items[row])
 301
 302	def DisplayData(self, item, index):
 303		return item.getData(index.column())
 304
 305	def FetchIfNeeded(self, row):
 306		if row > self.last_row_read:
 307			self.last_row_read = row
 308			if row + 10 >= self.child_count:
 309				self.fetcher.Fetch(glb_chunk_sz)
 310
 311	def columnAlignment(self, column):
 312		return Qt.AlignLeft
 313
 314	def columnFont(self, column):
 315		return None
 316
 317	def data(self, index, role):
 318		if role == Qt.TextAlignmentRole:
 319			return self.columnAlignment(index.column())
 320		if role == Qt.FontRole:
 321			return self.columnFont(index.column())
 322		if role != Qt.DisplayRole:
 323			return None
 324		item = index.internalPointer()
 325		return self.DisplayData(item, index)
 326
 327# Model cache
 328
 329model_cache = weakref.WeakValueDictionary()
 330model_cache_lock = threading.Lock()
 331
 332def LookupCreateModel(model_name, create_fn):
 333	model_cache_lock.acquire()
 334	try:
 335		model = model_cache[model_name]
 336	except:
 337		model = None
 338	if model is None:
 339		model = create_fn()
 340		model_cache[model_name] = model
 341	model_cache_lock.release()
 342	return model
 343
 
 
 
 
 
 
 
 
 
 344# Find bar
 345
 346class FindBar():
 347
 348	def __init__(self, parent, finder, is_reg_expr=False):
 349		self.finder = finder
 350		self.context = []
 351		self.last_value = None
 352		self.last_pattern = None
 353
 354		label = QLabel("Find:")
 355		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
 356
 357		self.textbox = QComboBox()
 358		self.textbox.setEditable(True)
 359		self.textbox.currentIndexChanged.connect(self.ValueChanged)
 360
 361		self.progress = QProgressBar()
 362		self.progress.setRange(0, 0)
 363		self.progress.hide()
 364
 365		if is_reg_expr:
 366			self.pattern = QCheckBox("Regular Expression")
 367		else:
 368			self.pattern = QCheckBox("Pattern")
 369		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
 370
 371		self.next_button = QToolButton()
 372		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
 373		self.next_button.released.connect(lambda: self.NextPrev(1))
 374
 375		self.prev_button = QToolButton()
 376		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
 377		self.prev_button.released.connect(lambda: self.NextPrev(-1))
 378
 379		self.close_button = QToolButton()
 380		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
 381		self.close_button.released.connect(self.Deactivate)
 382
 383		self.hbox = QHBoxLayout()
 384		self.hbox.setContentsMargins(0, 0, 0, 0)
 385
 386		self.hbox.addWidget(label)
 387		self.hbox.addWidget(self.textbox)
 388		self.hbox.addWidget(self.progress)
 389		self.hbox.addWidget(self.pattern)
 390		self.hbox.addWidget(self.next_button)
 391		self.hbox.addWidget(self.prev_button)
 392		self.hbox.addWidget(self.close_button)
 393
 394		self.bar = QWidget()
 395		self.bar.setLayout(self.hbox)
 396		self.bar.hide()
 397
 398	def Widget(self):
 399		return self.bar
 400
 401	def Activate(self):
 402		self.bar.show()
 403		self.textbox.lineEdit().selectAll()
 404		self.textbox.setFocus()
 405
 406	def Deactivate(self):
 407		self.bar.hide()
 408
 409	def Busy(self):
 410		self.textbox.setEnabled(False)
 411		self.pattern.hide()
 412		self.next_button.hide()
 413		self.prev_button.hide()
 414		self.progress.show()
 415
 416	def Idle(self):
 417		self.textbox.setEnabled(True)
 418		self.progress.hide()
 419		self.pattern.show()
 420		self.next_button.show()
 421		self.prev_button.show()
 422
 423	def Find(self, direction):
 424		value = self.textbox.currentText()
 425		pattern = self.pattern.isChecked()
 426		self.last_value = value
 427		self.last_pattern = pattern
 428		self.finder.Find(value, direction, pattern, self.context)
 429
 430	def ValueChanged(self):
 431		value = self.textbox.currentText()
 432		pattern = self.pattern.isChecked()
 433		index = self.textbox.currentIndex()
 434		data = self.textbox.itemData(index)
 435		# Store the pattern in the combo box to keep it with the text value
 436		if data == None:
 437			self.textbox.setItemData(index, pattern)
 438		else:
 439			self.pattern.setChecked(data)
 440		self.Find(0)
 441
 442	def NextPrev(self, direction):
 443		value = self.textbox.currentText()
 444		pattern = self.pattern.isChecked()
 445		if value != self.last_value:
 446			index = self.textbox.findText(value)
 447			# Allow for a button press before the value has been added to the combo box
 448			if index < 0:
 449				index = self.textbox.count()
 450				self.textbox.addItem(value, pattern)
 451				self.textbox.setCurrentIndex(index)
 452				return
 453			else:
 454				self.textbox.setItemData(index, pattern)
 455		elif pattern != self.last_pattern:
 456			# Keep the pattern recorded in the combo box up to date
 457			index = self.textbox.currentIndex()
 458			self.textbox.setItemData(index, pattern)
 459		self.Find(direction)
 460
 461	def NotFound(self):
 462		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
 463
 464# Context-sensitive call graph data model item base
 465
 466class CallGraphLevelItemBase(object):
 467
 468	def __init__(self, glb, params, row, parent_item):
 469		self.glb = glb
 470		self.params = params
 471		self.row = row
 472		self.parent_item = parent_item
 473		self.query_done = False
 474		self.child_count = 0
 475		self.child_items = []
 476		if parent_item:
 477			self.level = parent_item.level + 1
 478		else:
 479			self.level = 0
 480
 481	def getChildItem(self, row):
 482		return self.child_items[row]
 483
 484	def getParentItem(self):
 485		return self.parent_item
 486
 487	def getRow(self):
 488		return self.row
 489
 490	def childCount(self):
 491		if not self.query_done:
 492			self.Select()
 493			if not self.child_count:
 494				return -1
 495		return self.child_count
 496
 497	def hasChildren(self):
 498		if not self.query_done:
 499			return True
 500		return self.child_count > 0
 501
 502	def getData(self, column):
 503		return self.data[column]
 504
 505# Context-sensitive call graph data model level 2+ item base
 506
 507class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
 508
 509	def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
 510		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
 511		self.comm_id = comm_id
 512		self.thread_id = thread_id
 513		self.call_path_id = call_path_id
 514		self.insn_cnt = insn_cnt
 515		self.cyc_cnt = cyc_cnt
 516		self.branch_count = branch_count
 517		self.time = time
 518
 519	def Select(self):
 520		self.query_done = True
 521		query = QSqlQuery(self.glb.db)
 522		if self.params.have_ipc:
 523			ipc_str = ", SUM(insn_count), SUM(cyc_count)"
 524		else:
 525			ipc_str = ""
 526		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
 527					" FROM calls"
 528					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
 529					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
 530					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
 531					" WHERE parent_call_path_id = " + str(self.call_path_id) +
 532					" AND comm_id = " + str(self.comm_id) +
 533					" AND thread_id = " + str(self.thread_id) +
 534					" GROUP BY call_path_id, name, short_name"
 535					" ORDER BY call_path_id")
 536		while query.next():
 537			if self.params.have_ipc:
 538				insn_cnt = int(query.value(5))
 539				cyc_cnt = int(query.value(6))
 540				branch_count = int(query.value(7))
 541			else:
 542				insn_cnt = 0
 543				cyc_cnt = 0
 544				branch_count = int(query.value(5))
 545			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)
 546			self.child_items.append(child_item)
 547			self.child_count += 1
 548
 549# Context-sensitive call graph data model level three item
 550
 551class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
 552
 553	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):
 554		super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
 555		dso = dsoname(dso)
 556		if self.params.have_ipc:
 557			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
 558			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
 559			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
 560			ipc = CalcIPC(cyc_cnt, insn_cnt)
 561			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 ]
 562		else:
 563			self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
 564		self.dbid = call_path_id
 565
 566# Context-sensitive call graph data model level two item
 567
 568class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
 569
 570	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
 571		super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
 572		if self.params.have_ipc:
 573			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
 574		else:
 575			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
 576		self.dbid = thread_id
 577
 578	def Select(self):
 579		super(CallGraphLevelTwoItem, self).Select()
 580		for child_item in self.child_items:
 581			self.time += child_item.time
 582			self.insn_cnt += child_item.insn_cnt
 583			self.cyc_cnt += child_item.cyc_cnt
 584			self.branch_count += child_item.branch_count
 585		for child_item in self.child_items:
 586			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
 587			if self.params.have_ipc:
 588				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
 589				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
 590				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
 591			else:
 592				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
 593
 594# Context-sensitive call graph data model level one item
 595
 596class CallGraphLevelOneItem(CallGraphLevelItemBase):
 597
 598	def __init__(self, glb, params, row, comm_id, comm, parent_item):
 599		super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
 600		if self.params.have_ipc:
 601			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
 602		else:
 603			self.data = [comm, "", "", "", "", "", ""]
 604		self.dbid = comm_id
 605
 606	def Select(self):
 607		self.query_done = True
 608		query = QSqlQuery(self.glb.db)
 609		QueryExec(query, "SELECT thread_id, pid, tid"
 610					" FROM comm_threads"
 611					" INNER JOIN threads ON thread_id = threads.id"
 612					" WHERE comm_id = " + str(self.dbid))
 613		while query.next():
 614			child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
 615			self.child_items.append(child_item)
 616			self.child_count += 1
 617
 618# Context-sensitive call graph data model root item
 619
 620class CallGraphRootItem(CallGraphLevelItemBase):
 621
 622	def __init__(self, glb, params):
 623		super(CallGraphRootItem, self).__init__(glb, params, 0, None)
 624		self.dbid = 0
 625		self.query_done = True
 626		if_has_calls = ""
 627		if IsSelectable(glb.db, "comms", columns = "has_calls"):
 628			if_has_calls = " WHERE has_calls = TRUE"
 629		query = QSqlQuery(glb.db)
 630		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
 631		while query.next():
 632			if not query.value(0):
 633				continue
 634			child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
 635			self.child_items.append(child_item)
 636			self.child_count += 1
 637
 638# Call graph model parameters
 639
 640class CallGraphModelParams():
 641
 642	def __init__(self, glb, parent=None):
 643		self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
 644
 645# Context-sensitive call graph data model base
 646
 647class CallGraphModelBase(TreeModel):
 648
 649	def __init__(self, glb, parent=None):
 650		super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
 651
 652	def FindSelect(self, value, pattern, query):
 653		if pattern:
 654			# postgresql and sqlite pattern patching differences:
 655			#   postgresql LIKE is case sensitive but sqlite LIKE is not
 656			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
 657			#   postgresql supports ILIKE which is case insensitive
 658			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
 659			if not self.glb.dbref.is_sqlite3:
 660				# Escape % and _
 661				s = value.replace("%", "\%")
 662				s = s.replace("_", "\_")
 663				# Translate * and ? into SQL LIKE pattern characters % and _
 664				trans = string.maketrans("*?", "%_")
 665				match = " LIKE '" + str(s).translate(trans) + "'"
 666			else:
 667				match = " GLOB '" + str(value) + "'"
 668		else:
 669			match = " = '" + str(value) + "'"
 670		self.DoFindSelect(query, match)
 671
 672	def Found(self, query, found):
 673		if found:
 674			return self.FindPath(query)
 675		return []
 676
 677	def FindValue(self, value, pattern, query, last_value, last_pattern):
 678		if last_value == value and pattern == last_pattern:
 679			found = query.first()
 680		else:
 681			self.FindSelect(value, pattern, query)
 682			found = query.next()
 683		return self.Found(query, found)
 684
 685	def FindNext(self, query):
 686		found = query.next()
 687		if not found:
 688			found = query.first()
 689		return self.Found(query, found)
 690
 691	def FindPrev(self, query):
 692		found = query.previous()
 693		if not found:
 694			found = query.last()
 695		return self.Found(query, found)
 696
 697	def FindThread(self, c):
 698		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
 699			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
 700		elif c.direction > 0:
 701			ids = self.FindNext(c.query)
 702		else:
 703			ids = self.FindPrev(c.query)
 704		return (True, ids)
 705
 706	def Find(self, value, direction, pattern, context, callback):
 707		class Context():
 708			def __init__(self, *x):
 709				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
 710			def Update(self, *x):
 711				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
 712		if len(context):
 713			context[0].Update(value, direction, pattern)
 714		else:
 715			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
 716		# Use a thread so the UI is not blocked during the SELECT
 717		thread = Thread(self.FindThread, context[0])
 718		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
 719		thread.start()
 720
 721	def FindDone(self, thread, callback, ids):
 722		callback(ids)
 723
 724# Context-sensitive call graph data model
 725
 726class CallGraphModel(CallGraphModelBase):
 727
 728	def __init__(self, glb, parent=None):
 729		super(CallGraphModel, self).__init__(glb, parent)
 730
 731	def GetRoot(self):
 732		return CallGraphRootItem(self.glb, self.params)
 733
 734	def columnCount(self, parent=None):
 735		if self.params.have_ipc:
 736			return 12
 737		else:
 738			return 7
 739
 740	def columnHeader(self, column):
 741		if self.params.have_ipc:
 742			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
 743		else:
 744			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
 745		return headers[column]
 746
 747	def columnAlignment(self, column):
 748		if self.params.have_ipc:
 749			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 ]
 750		else:
 751			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
 752		return alignment[column]
 753
 754	def DoFindSelect(self, query, match):
 755		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
 756						" FROM calls"
 757						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
 758						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
 759						" WHERE symbols.name" + match +
 
 760						" GROUP BY comm_id, thread_id, call_path_id"
 761						" ORDER BY comm_id, thread_id, call_path_id")
 762
 763	def FindPath(self, query):
 764		# Turn the query result into a list of ids that the tree view can walk
 765		# to open the tree at the right place.
 766		ids = []
 767		parent_id = query.value(0)
 768		while parent_id:
 769			ids.insert(0, parent_id)
 770			q2 = QSqlQuery(self.glb.db)
 771			QueryExec(q2, "SELECT parent_id"
 772					" FROM call_paths"
 773					" WHERE id = " + str(parent_id))
 774			if not q2.next():
 775				break
 776			parent_id = q2.value(0)
 777		# The call path root is not used
 778		if ids[0] == 1:
 779			del ids[0]
 780		ids.insert(0, query.value(2))
 781		ids.insert(0, query.value(1))
 782		return ids
 783
 784# Call tree data model level 2+ item base
 785
 786class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
 787
 788	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
 789		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
 790		self.comm_id = comm_id
 791		self.thread_id = thread_id
 792		self.calls_id = calls_id
 
 
 793		self.insn_cnt = insn_cnt
 794		self.cyc_cnt = cyc_cnt
 795		self.branch_count = branch_count
 796		self.time = time
 797
 798	def Select(self):
 799		self.query_done = True
 800		if self.calls_id == 0:
 801			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
 802		else:
 803			comm_thread = ""
 804		if self.params.have_ipc:
 805			ipc_str = ", insn_count, cyc_count"
 806		else:
 807			ipc_str = ""
 808		query = QSqlQuery(self.glb.db)
 809		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
 810					" FROM calls"
 811					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
 812					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
 813					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
 814					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
 815					" ORDER BY call_time, calls.id")
 816		while query.next():
 817			if self.params.have_ipc:
 818				insn_cnt = int(query.value(5))
 819				cyc_cnt = int(query.value(6))
 820				branch_count = int(query.value(7))
 821			else:
 822				insn_cnt = 0
 823				cyc_cnt = 0
 824				branch_count = int(query.value(5))
 825			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)
 826			self.child_items.append(child_item)
 827			self.child_count += 1
 828
 829# Call tree data model level three item
 830
 831class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
 832
 833	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, count, time, insn_cnt, cyc_cnt, branch_count, parent_item):
 834		super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
 835		dso = dsoname(dso)
 836		if self.params.have_ipc:
 837			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
 838			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
 839			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
 840			ipc = CalcIPC(cyc_cnt, insn_cnt)
 841			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 ]
 842		else:
 843			self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
 844		self.dbid = calls_id
 845
 846# Call tree data model level two item
 847
 848class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
 849
 850	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
 851		super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, parent_item)
 852		if self.params.have_ipc:
 853			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
 854		else:
 855			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
 856		self.dbid = thread_id
 857
 858	def Select(self):
 859		super(CallTreeLevelTwoItem, self).Select()
 860		for child_item in self.child_items:
 861			self.time += child_item.time
 862			self.insn_cnt += child_item.insn_cnt
 863			self.cyc_cnt += child_item.cyc_cnt
 864			self.branch_count += child_item.branch_count
 865		for child_item in self.child_items:
 866			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
 867			if self.params.have_ipc:
 868				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
 869				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
 870				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
 871			else:
 872				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
 873
 874# Call tree data model level one item
 875
 876class CallTreeLevelOneItem(CallGraphLevelItemBase):
 877
 878	def __init__(self, glb, params, row, comm_id, comm, parent_item):
 879		super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
 880		if self.params.have_ipc:
 881			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
 882		else:
 883			self.data = [comm, "", "", "", "", "", ""]
 884		self.dbid = comm_id
 885
 886	def Select(self):
 887		self.query_done = True
 888		query = QSqlQuery(self.glb.db)
 889		QueryExec(query, "SELECT thread_id, pid, tid"
 890					" FROM comm_threads"
 891					" INNER JOIN threads ON thread_id = threads.id"
 892					" WHERE comm_id = " + str(self.dbid))
 893		while query.next():
 894			child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
 895			self.child_items.append(child_item)
 896			self.child_count += 1
 897
 898# Call tree data model root item
 899
 900class CallTreeRootItem(CallGraphLevelItemBase):
 901
 902	def __init__(self, glb, params):
 903		super(CallTreeRootItem, self).__init__(glb, params, 0, None)
 904		self.dbid = 0
 905		self.query_done = True
 906		if_has_calls = ""
 907		if IsSelectable(glb.db, "comms", columns = "has_calls"):
 908			if_has_calls = " WHERE has_calls = TRUE"
 909		query = QSqlQuery(glb.db)
 910		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
 911		while query.next():
 912			if not query.value(0):
 913				continue
 914			child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
 915			self.child_items.append(child_item)
 916			self.child_count += 1
 917
 918# Call Tree data model
 919
 920class CallTreeModel(CallGraphModelBase):
 921
 922	def __init__(self, glb, parent=None):
 923		super(CallTreeModel, self).__init__(glb, parent)
 924
 925	def GetRoot(self):
 926		return CallTreeRootItem(self.glb, self.params)
 927
 928	def columnCount(self, parent=None):
 929		if self.params.have_ipc:
 930			return 12
 931		else:
 932			return 7
 933
 934	def columnHeader(self, column):
 935		if self.params.have_ipc:
 936			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
 937		else:
 938			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
 939		return headers[column]
 940
 941	def columnAlignment(self, column):
 942		if self.params.have_ipc:
 943			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 ]
 944		else:
 945			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
 946		return alignment[column]
 947
 948	def DoFindSelect(self, query, match):
 949		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
 950						" FROM calls"
 951						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
 952						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
 953						" WHERE symbols.name" + match +
 
 954						" ORDER BY comm_id, thread_id, call_time, calls.id")
 955
 956	def FindPath(self, query):
 957		# Turn the query result into a list of ids that the tree view can walk
 958		# to open the tree at the right place.
 959		ids = []
 960		parent_id = query.value(0)
 961		while parent_id:
 962			ids.insert(0, parent_id)
 963			q2 = QSqlQuery(self.glb.db)
 964			QueryExec(q2, "SELECT parent_id"
 965					" FROM calls"
 966					" WHERE id = " + str(parent_id))
 967			if not q2.next():
 968				break
 969			parent_id = q2.value(0)
 970		ids.insert(0, query.value(2))
 971		ids.insert(0, query.value(1))
 972		return ids
 973
 974# Vertical widget layout
 975
 976class VBox():
 977
 978	def __init__(self, w1, w2, w3=None):
 979		self.vbox = QWidget()
 980		self.vbox.setLayout(QVBoxLayout())
 
 
 
 
 
 
 
 
 981
 982		self.vbox.layout().setContentsMargins(0, 0, 0, 0)
 983
 984		self.vbox.layout().addWidget(w1)
 985		self.vbox.layout().addWidget(w2)
 986		if w3:
 987			self.vbox.layout().addWidget(w3)
 
 
 
 
 
 
 
 
 
 
 
 
 
 988
 989	def Widget(self):
 990		return self.vbox
 991
 992# Tree window base
 993
 994class TreeWindowBase(QMdiSubWindow):
 995
 996	def __init__(self, parent=None):
 997		super(TreeWindowBase, self).__init__(parent)
 998
 999		self.model = None
1000		self.find_bar = None
1001
1002		self.view = QTreeView()
1003		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1004		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1005
1006		self.context_menu = TreeContextMenu(self.view)
1007
1008	def DisplayFound(self, ids):
1009		if not len(ids):
1010			return False
1011		parent = QModelIndex()
1012		for dbid in ids:
1013			found = False
1014			n = self.model.rowCount(parent)
1015			for row in xrange(n):
1016				child = self.model.index(row, 0, parent)
1017				if child.internalPointer().dbid == dbid:
1018					found = True
 
1019					self.view.setCurrentIndex(child)
1020					parent = child
1021					break
1022			if not found:
1023				break
1024		return found
1025
1026	def Find(self, value, direction, pattern, context):
1027		self.view.setFocus()
1028		self.find_bar.Busy()
1029		self.model.Find(value, direction, pattern, context, self.FindDone)
1030
1031	def FindDone(self, ids):
1032		found = True
1033		if not self.DisplayFound(ids):
1034			found = False
1035		self.find_bar.Idle()
1036		if not found:
1037			self.find_bar.NotFound()
1038
1039
1040# Context-sensitive call graph window
1041
1042class CallGraphWindow(TreeWindowBase):
1043
1044	def __init__(self, glb, parent=None):
1045		super(CallGraphWindow, self).__init__(parent)
1046
1047		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
1048
1049		self.view.setModel(self.model)
1050
1051		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1052			self.view.setColumnWidth(c, w)
1053
1054		self.find_bar = FindBar(self, self)
1055
1056		self.vbox = VBox(self.view, self.find_bar.Widget())
1057
1058		self.setWidget(self.vbox.Widget())
1059
1060		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
1061
1062# Call tree window
1063
1064class CallTreeWindow(TreeWindowBase):
1065
1066	def __init__(self, glb, parent=None):
1067		super(CallTreeWindow, self).__init__(parent)
1068
1069		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
1070
1071		self.view.setModel(self.model)
1072
1073		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1074			self.view.setColumnWidth(c, w)
1075
1076		self.find_bar = FindBar(self, self)
1077
1078		self.vbox = VBox(self.view, self.find_bar.Widget())
1079
1080		self.setWidget(self.vbox.Widget())
1081
1082		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
1083
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1084# Child data item  finder
1085
1086class ChildDataItemFinder():
1087
1088	def __init__(self, root):
1089		self.root = root
1090		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
1091		self.rows = []
1092		self.pos = 0
1093
1094	def FindSelect(self):
1095		self.rows = []
1096		if self.pattern:
1097			pattern = re.compile(self.value)
1098			for child in self.root.child_items:
1099				for column_data in child.data:
1100					if re.search(pattern, str(column_data)) is not None:
1101						self.rows.append(child.row)
1102						break
1103		else:
1104			for child in self.root.child_items:
1105				for column_data in child.data:
1106					if self.value in str(column_data):
1107						self.rows.append(child.row)
1108						break
1109
1110	def FindValue(self):
1111		self.pos = 0
1112		if self.last_value != self.value or self.pattern != self.last_pattern:
1113			self.FindSelect()
1114		if not len(self.rows):
1115			return -1
1116		return self.rows[self.pos]
1117
1118	def FindThread(self):
1119		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1120			row = self.FindValue()
1121		elif len(self.rows):
1122			if self.direction > 0:
1123				self.pos += 1
1124				if self.pos >= len(self.rows):
1125					self.pos = 0
1126			else:
1127				self.pos -= 1
1128				if self.pos < 0:
1129					self.pos = len(self.rows) - 1
1130			row = self.rows[self.pos]
1131		else:
1132			row = -1
1133		return (True, row)
1134
1135	def Find(self, value, direction, pattern, context, callback):
1136		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1137		# Use a thread so the UI is not blocked
1138		thread = Thread(self.FindThread)
1139		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1140		thread.start()
1141
1142	def FindDone(self, thread, callback, row):
1143		callback(row)
1144
1145# Number of database records to fetch in one go
1146
1147glb_chunk_sz = 10000
1148
1149# Background process for SQL data fetcher
1150
1151class SQLFetcherProcess():
1152
1153	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1154		# Need a unique connection name
1155		conn_name = "SQLFetcher" + str(os.getpid())
1156		self.db, dbname = dbref.Open(conn_name)
1157		self.sql = sql
1158		self.buffer = buffer
1159		self.head = head
1160		self.tail = tail
1161		self.fetch_count = fetch_count
1162		self.fetching_done = fetching_done
1163		self.process_target = process_target
1164		self.wait_event = wait_event
1165		self.fetched_event = fetched_event
1166		self.prep = prep
1167		self.query = QSqlQuery(self.db)
1168		self.query_limit = 0 if "$$last_id$$" in sql else 2
1169		self.last_id = -1
1170		self.fetched = 0
1171		self.more = True
1172		self.local_head = self.head.value
1173		self.local_tail = self.tail.value
1174
1175	def Select(self):
1176		if self.query_limit:
1177			if self.query_limit == 1:
1178				return
1179			self.query_limit -= 1
1180		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1181		QueryExec(self.query, stmt)
1182
1183	def Next(self):
1184		if not self.query.next():
1185			self.Select()
1186			if not self.query.next():
1187				return None
1188		self.last_id = self.query.value(0)
1189		return self.prep(self.query)
1190
1191	def WaitForTarget(self):
1192		while True:
1193			self.wait_event.clear()
1194			target = self.process_target.value
1195			if target > self.fetched or target < 0:
1196				break
1197			self.wait_event.wait()
1198		return target
1199
1200	def HasSpace(self, sz):
1201		if self.local_tail <= self.local_head:
1202			space = len(self.buffer) - self.local_head
1203			if space > sz:
1204				return True
1205			if space >= glb_nsz:
1206				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1207				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
1208				self.buffer[self.local_head : self.local_head + len(nd)] = nd
1209			self.local_head = 0
1210		if self.local_tail - self.local_head > sz:
1211			return True
1212		return False
1213
1214	def WaitForSpace(self, sz):
1215		if self.HasSpace(sz):
1216			return
1217		while True:
1218			self.wait_event.clear()
1219			self.local_tail = self.tail.value
1220			if self.HasSpace(sz):
1221				return
1222			self.wait_event.wait()
1223
1224	def AddToBuffer(self, obj):
1225		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
1226		n = len(d)
1227		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
1228		sz = n + glb_nsz
1229		self.WaitForSpace(sz)
1230		pos = self.local_head
1231		self.buffer[pos : pos + len(nd)] = nd
1232		self.buffer[pos + glb_nsz : pos + sz] = d
1233		self.local_head += sz
1234
1235	def FetchBatch(self, batch_size):
1236		fetched = 0
1237		while batch_size > fetched:
1238			obj = self.Next()
1239			if obj is None:
1240				self.more = False
1241				break
1242			self.AddToBuffer(obj)
1243			fetched += 1
1244		if fetched:
1245			self.fetched += fetched
1246			with self.fetch_count.get_lock():
1247				self.fetch_count.value += fetched
1248			self.head.value = self.local_head
1249			self.fetched_event.set()
1250
1251	def Run(self):
1252		while self.more:
1253			target = self.WaitForTarget()
1254			if target < 0:
1255				break
1256			batch_size = min(glb_chunk_sz, target - self.fetched)
1257			self.FetchBatch(batch_size)
1258		self.fetching_done.value = True
1259		self.fetched_event.set()
1260
1261def SQLFetcherFn(*x):
1262	process = SQLFetcherProcess(*x)
1263	process.Run()
1264
1265# SQL data fetcher
1266
1267class SQLFetcher(QObject):
1268
1269	done = Signal(object)
1270
1271	def __init__(self, glb, sql, prep, process_data, parent=None):
1272		super(SQLFetcher, self).__init__(parent)
1273		self.process_data = process_data
1274		self.more = True
1275		self.target = 0
1276		self.last_target = 0
1277		self.fetched = 0
1278		self.buffer_size = 16 * 1024 * 1024
1279		self.buffer = Array(c_char, self.buffer_size, lock=False)
1280		self.head = Value(c_longlong)
1281		self.tail = Value(c_longlong)
1282		self.local_tail = 0
1283		self.fetch_count = Value(c_longlong)
1284		self.fetching_done = Value(c_bool)
1285		self.last_count = 0
1286		self.process_target = Value(c_longlong)
1287		self.wait_event = Event()
1288		self.fetched_event = Event()
1289		glb.AddInstanceToShutdownOnExit(self)
1290		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))
1291		self.process.start()
1292		self.thread = Thread(self.Thread)
1293		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1294		self.thread.start()
1295
1296	def Shutdown(self):
1297		# Tell the thread and process to exit
1298		self.process_target.value = -1
1299		self.wait_event.set()
1300		self.more = False
1301		self.fetching_done.value = True
1302		self.fetched_event.set()
1303
1304	def Thread(self):
1305		if not self.more:
1306			return True, 0
1307		while True:
1308			self.fetched_event.clear()
1309			fetch_count = self.fetch_count.value
1310			if fetch_count != self.last_count:
1311				break
1312			if self.fetching_done.value:
1313				self.more = False
1314				return True, 0
1315			self.fetched_event.wait()
1316		count = fetch_count - self.last_count
1317		self.last_count = fetch_count
1318		self.fetched += count
1319		return False, count
1320
1321	def Fetch(self, nr):
1322		if not self.more:
1323			# -1 inidcates there are no more
1324			return -1
1325		result = self.fetched
1326		extra = result + nr - self.target
1327		if extra > 0:
1328			self.target += extra
1329			# process_target < 0 indicates shutting down
1330			if self.process_target.value >= 0:
1331				self.process_target.value = self.target
1332			self.wait_event.set()
1333		return result
1334
1335	def RemoveFromBuffer(self):
1336		pos = self.local_tail
1337		if len(self.buffer) - pos < glb_nsz:
1338			pos = 0
1339		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
1340		if n == 0:
1341			pos = 0
1342			n = pickle.loads(self.buffer[0 : glb_nsz])
1343		pos += glb_nsz
1344		obj = pickle.loads(self.buffer[pos : pos + n])
1345		self.local_tail = pos + n
1346		return obj
1347
1348	def ProcessData(self, count):
1349		for i in xrange(count):
1350			obj = self.RemoveFromBuffer()
1351			self.process_data(obj)
1352		self.tail.value = self.local_tail
1353		self.wait_event.set()
1354		self.done.emit(count)
1355
1356# Fetch more records bar
1357
1358class FetchMoreRecordsBar():
1359
1360	def __init__(self, model, parent):
1361		self.model = model
1362
1363		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1364		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1365
1366		self.fetch_count = QSpinBox()
1367		self.fetch_count.setRange(1, 1000000)
1368		self.fetch_count.setValue(10)
1369		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1370
1371		self.fetch = QPushButton("Go!")
1372		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1373		self.fetch.released.connect(self.FetchMoreRecords)
1374
1375		self.progress = QProgressBar()
1376		self.progress.setRange(0, 100)
1377		self.progress.hide()
1378
1379		self.done_label = QLabel("All records fetched")
1380		self.done_label.hide()
1381
1382		self.spacer = QLabel("")
1383
1384		self.close_button = QToolButton()
1385		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1386		self.close_button.released.connect(self.Deactivate)
1387
1388		self.hbox = QHBoxLayout()
1389		self.hbox.setContentsMargins(0, 0, 0, 0)
1390
1391		self.hbox.addWidget(self.label)
1392		self.hbox.addWidget(self.fetch_count)
1393		self.hbox.addWidget(self.fetch)
1394		self.hbox.addWidget(self.spacer)
1395		self.hbox.addWidget(self.progress)
1396		self.hbox.addWidget(self.done_label)
1397		self.hbox.addWidget(self.close_button)
1398
1399		self.bar = QWidget()
1400		self.bar.setLayout(self.hbox)
1401		self.bar.show()
1402
1403		self.in_progress = False
1404		self.model.progress.connect(self.Progress)
1405
1406		self.done = False
1407
1408		if not model.HasMoreRecords():
1409			self.Done()
1410
1411	def Widget(self):
1412		return self.bar
1413
1414	def Activate(self):
1415		self.bar.show()
1416		self.fetch.setFocus()
1417
1418	def Deactivate(self):
1419		self.bar.hide()
1420
1421	def Enable(self, enable):
1422		self.fetch.setEnabled(enable)
1423		self.fetch_count.setEnabled(enable)
1424
1425	def Busy(self):
1426		self.Enable(False)
1427		self.fetch.hide()
1428		self.spacer.hide()
1429		self.progress.show()
1430
1431	def Idle(self):
1432		self.in_progress = False
1433		self.Enable(True)
1434		self.progress.hide()
1435		self.fetch.show()
1436		self.spacer.show()
1437
1438	def Target(self):
1439		return self.fetch_count.value() * glb_chunk_sz
1440
1441	def Done(self):
1442		self.done = True
1443		self.Idle()
1444		self.label.hide()
1445		self.fetch_count.hide()
1446		self.fetch.hide()
1447		self.spacer.hide()
1448		self.done_label.show()
1449
1450	def Progress(self, count):
1451		if self.in_progress:
1452			if count:
1453				percent = ((count - self.start) * 100) / self.Target()
1454				if percent >= 100:
1455					self.Idle()
1456				else:
1457					self.progress.setValue(percent)
1458		if not count:
1459			# Count value of zero means no more records
1460			self.Done()
1461
1462	def FetchMoreRecords(self):
1463		if self.done:
1464			return
1465		self.progress.setValue(0)
1466		self.Busy()
1467		self.in_progress = True
1468		self.start = self.model.FetchMoreRecords(self.Target())
1469
1470# Brance data model level two item
1471
1472class BranchLevelTwoItem():
1473
1474	def __init__(self, row, col, text, parent_item):
1475		self.row = row
1476		self.parent_item = parent_item
1477		self.data = [""] * (col + 1)
1478		self.data[col] = text
1479		self.level = 2
1480
1481	def getParentItem(self):
1482		return self.parent_item
1483
1484	def getRow(self):
1485		return self.row
1486
1487	def childCount(self):
1488		return 0
1489
1490	def hasChildren(self):
1491		return False
1492
1493	def getData(self, column):
1494		return self.data[column]
1495
1496# Brance data model level one item
1497
1498class BranchLevelOneItem():
1499
1500	def __init__(self, glb, row, data, parent_item):
1501		self.glb = glb
1502		self.row = row
1503		self.parent_item = parent_item
1504		self.child_count = 0
1505		self.child_items = []
1506		self.data = data[1:]
1507		self.dbid = data[0]
1508		self.level = 1
1509		self.query_done = False
1510		self.br_col = len(self.data) - 1
1511
1512	def getChildItem(self, row):
1513		return self.child_items[row]
1514
1515	def getParentItem(self):
1516		return self.parent_item
1517
1518	def getRow(self):
1519		return self.row
1520
1521	def Select(self):
1522		self.query_done = True
1523
1524		if not self.glb.have_disassembler:
1525			return
1526
1527		query = QSqlQuery(self.glb.db)
1528
1529		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1530				  " FROM samples"
1531				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1532				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1533				  " WHERE samples.id = " + str(self.dbid))
1534		if not query.next():
1535			return
1536		cpu = query.value(0)
1537		dso = query.value(1)
1538		sym = query.value(2)
1539		if dso == 0 or sym == 0:
1540			return
1541		off = query.value(3)
1542		short_name = query.value(4)
1543		long_name = query.value(5)
1544		build_id = query.value(6)
1545		sym_start = query.value(7)
1546		ip = query.value(8)
1547
1548		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1549				  " FROM samples"
1550				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1551				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1552				  " ORDER BY samples.id"
1553				  " LIMIT 1")
1554		if not query.next():
1555			return
1556		if query.value(0) != dso:
1557			# Cannot disassemble from one dso to another
1558			return
1559		bsym = query.value(1)
1560		boff = query.value(2)
1561		bsym_start = query.value(3)
1562		if bsym == 0:
1563			return
1564		tot = bsym_start + boff + 1 - sym_start - off
1565		if tot <= 0 or tot > 16384:
1566			return
1567
1568		inst = self.glb.disassembler.Instruction()
1569		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1570		if not f:
1571			return
1572		mode = 0 if Is64Bit(f) else 1
1573		self.glb.disassembler.SetMode(inst, mode)
1574
1575		buf_sz = tot + 16
1576		buf = create_string_buffer(tot + 16)
1577		f.seek(sym_start + off)
1578		buf.value = f.read(buf_sz)
1579		buf_ptr = addressof(buf)
1580		i = 0
1581		while tot > 0:
1582			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1583			if cnt:
1584				byte_str = tohex(ip).rjust(16)
1585				for k in xrange(cnt):
1586					byte_str += " %02x" % ord(buf[i])
1587					i += 1
1588				while k < 15:
1589					byte_str += "   "
1590					k += 1
1591				self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
1592				self.child_count += 1
1593			else:
1594				return
1595			buf_ptr += cnt
1596			tot -= cnt
1597			buf_sz -= cnt
1598			ip += cnt
1599
1600	def childCount(self):
1601		if not self.query_done:
1602			self.Select()
1603			if not self.child_count:
1604				return -1
1605		return self.child_count
1606
1607	def hasChildren(self):
1608		if not self.query_done:
1609			return True
1610		return self.child_count > 0
1611
1612	def getData(self, column):
1613		return self.data[column]
1614
1615# Brance data model root item
1616
1617class BranchRootItem():
1618
1619	def __init__(self):
1620		self.child_count = 0
1621		self.child_items = []
1622		self.level = 0
1623
1624	def getChildItem(self, row):
1625		return self.child_items[row]
1626
1627	def getParentItem(self):
1628		return None
1629
1630	def getRow(self):
1631		return 0
1632
1633	def childCount(self):
1634		return self.child_count
1635
1636	def hasChildren(self):
1637		return self.child_count > 0
1638
1639	def getData(self, column):
1640		return ""
1641
1642# Calculate instructions per cycle
1643
1644def CalcIPC(cyc_cnt, insn_cnt):
1645	if cyc_cnt and insn_cnt:
1646		ipc = Decimal(float(insn_cnt) / cyc_cnt)
1647		ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
1648	else:
1649		ipc = "0"
1650	return ipc
1651
1652# Branch data preparation
1653
1654def BranchDataPrepBr(query, data):
1655	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1656			" (" + dsoname(query.value(11)) + ")" + " -> " +
1657			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1658			" (" + dsoname(query.value(15)) + ")")
1659
1660def BranchDataPrepIPC(query, data):
1661	insn_cnt = query.value(16)
1662	cyc_cnt = query.value(17)
1663	ipc = CalcIPC(cyc_cnt, insn_cnt)
1664	data.append(insn_cnt)
1665	data.append(cyc_cnt)
1666	data.append(ipc)
1667
1668def BranchDataPrep(query):
1669	data = []
1670	for i in xrange(0, 8):
1671		data.append(query.value(i))
1672	BranchDataPrepBr(query, data)
1673	return data
1674
1675def BranchDataPrepWA(query):
1676	data = []
1677	data.append(query.value(0))
1678	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1679	data.append("{:>19}".format(query.value(1)))
1680	for i in xrange(2, 8):
1681		data.append(query.value(i))
1682	BranchDataPrepBr(query, data)
1683	return data
1684
1685def BranchDataWithIPCPrep(query):
1686	data = []
1687	for i in xrange(0, 8):
1688		data.append(query.value(i))
1689	BranchDataPrepIPC(query, data)
1690	BranchDataPrepBr(query, data)
1691	return data
1692
1693def BranchDataWithIPCPrepWA(query):
1694	data = []
1695	data.append(query.value(0))
1696	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1697	data.append("{:>19}".format(query.value(1)))
1698	for i in xrange(2, 8):
1699		data.append(query.value(i))
1700	BranchDataPrepIPC(query, data)
1701	BranchDataPrepBr(query, data)
1702	return data
1703
1704# Branch data model
1705
1706class BranchModel(TreeModel):
1707
1708	progress = Signal(object)
1709
1710	def __init__(self, glb, event_id, where_clause, parent=None):
1711		super(BranchModel, self).__init__(glb, None, parent)
1712		self.event_id = event_id
1713		self.more = True
1714		self.populated = 0
1715		self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
1716		if self.have_ipc:
1717			select_ipc = ", insn_count, cyc_count"
1718			prep_fn = BranchDataWithIPCPrep
1719			prep_wa_fn = BranchDataWithIPCPrepWA
1720		else:
1721			select_ipc = ""
1722			prep_fn = BranchDataPrep
1723			prep_wa_fn = BranchDataPrepWA
1724		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1725			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1726			" ip, symbols.name, sym_offset, dsos.short_name,"
1727			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1728			+ select_ipc +
1729			" FROM samples"
1730			" INNER JOIN comms ON comm_id = comms.id"
1731			" INNER JOIN threads ON thread_id = threads.id"
1732			" INNER JOIN branch_types ON branch_type = branch_types.id"
1733			" INNER JOIN symbols ON symbol_id = symbols.id"
1734			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1735			" INNER JOIN dsos ON samples.dso_id = dsos.id"
1736			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1737			" WHERE samples.id > $$last_id$$" + where_clause +
1738			" AND evsel_id = " + str(self.event_id) +
1739			" ORDER BY samples.id"
1740			" LIMIT " + str(glb_chunk_sz))
1741		if pyside_version_1 and sys.version_info[0] == 3:
1742			prep = prep_fn
1743		else:
1744			prep = prep_wa_fn
1745		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
1746		self.fetcher.done.connect(self.Update)
1747		self.fetcher.Fetch(glb_chunk_sz)
1748
1749	def GetRoot(self):
1750		return BranchRootItem()
1751
1752	def columnCount(self, parent=None):
1753		if self.have_ipc:
1754			return 11
1755		else:
1756			return 8
1757
1758	def columnHeader(self, column):
1759		if self.have_ipc:
1760			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
1761		else:
1762			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1763
1764	def columnFont(self, column):
1765		if self.have_ipc:
1766			br_col = 10
1767		else:
1768			br_col = 7
1769		if column != br_col:
1770			return None
1771		return QFont("Monospace")
1772
1773	def DisplayData(self, item, index):
1774		if item.level == 1:
1775			self.FetchIfNeeded(item.row)
1776		return item.getData(index.column())
1777
1778	def AddSample(self, data):
1779		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1780		self.root.child_items.append(child)
1781		self.populated += 1
1782
1783	def Update(self, fetched):
1784		if not fetched:
1785			self.more = False
1786			self.progress.emit(0)
1787		child_count = self.root.child_count
1788		count = self.populated - child_count
1789		if count > 0:
1790			parent = QModelIndex()
1791			self.beginInsertRows(parent, child_count, child_count + count - 1)
1792			self.insertRows(child_count, count, parent)
1793			self.root.child_count += count
1794			self.endInsertRows()
1795			self.progress.emit(self.root.child_count)
1796
1797	def FetchMoreRecords(self, count):
1798		current = self.root.child_count
1799		if self.more:
1800			self.fetcher.Fetch(count)
1801		else:
1802			self.progress.emit(0)
1803		return current
1804
1805	def HasMoreRecords(self):
1806		return self.more
1807
1808# Report Variables
1809
1810class ReportVars():
1811
1812	def __init__(self, name = "", where_clause = "", limit = ""):
1813		self.name = name
1814		self.where_clause = where_clause
1815		self.limit = limit
1816
1817	def UniqueId(self):
1818		return str(self.where_clause + ";" + self.limit)
1819
1820# Branch window
1821
1822class BranchWindow(QMdiSubWindow):
1823
1824	def __init__(self, glb, event_id, report_vars, parent=None):
1825		super(BranchWindow, self).__init__(parent)
1826
1827		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
1828
1829		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1830
1831		self.view = QTreeView()
1832		self.view.setUniformRowHeights(True)
1833		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1834		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1835		self.view.setModel(self.model)
1836
1837		self.ResizeColumnsToContents()
1838
1839		self.context_menu = TreeContextMenu(self.view)
1840
1841		self.find_bar = FindBar(self, self, True)
1842
1843		self.finder = ChildDataItemFinder(self.model.root)
1844
1845		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1846
1847		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1848
1849		self.setWidget(self.vbox.Widget())
1850
1851		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1852
1853	def ResizeColumnToContents(self, column, n):
1854		# Using the view's resizeColumnToContents() here is extrememly slow
1855		# so implement a crude alternative
1856		mm = "MM" if column else "MMMM"
1857		font = self.view.font()
1858		metrics = QFontMetrics(font)
1859		max = 0
1860		for row in xrange(n):
1861			val = self.model.root.child_items[row].data[column]
1862			len = metrics.width(str(val) + mm)
1863			max = len if len > max else max
1864		val = self.model.columnHeader(column)
1865		len = metrics.width(str(val) + mm)
1866		max = len if len > max else max
1867		self.view.setColumnWidth(column, max)
1868
1869	def ResizeColumnsToContents(self):
1870		n = min(self.model.root.child_count, 100)
1871		if n < 1:
1872			# No data yet, so connect a signal to notify when there is
1873			self.model.rowsInserted.connect(self.UpdateColumnWidths)
1874			return
1875		columns = self.model.columnCount()
1876		for i in xrange(columns):
1877			self.ResizeColumnToContents(i, n)
1878
1879	def UpdateColumnWidths(self, *x):
1880		# This only needs to be done once, so disconnect the signal now
1881		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1882		self.ResizeColumnsToContents()
1883
1884	def Find(self, value, direction, pattern, context):
1885		self.view.setFocus()
1886		self.find_bar.Busy()
1887		self.finder.Find(value, direction, pattern, context, self.FindDone)
1888
1889	def FindDone(self, row):
1890		self.find_bar.Idle()
1891		if row >= 0:
1892			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1893		else:
1894			self.find_bar.NotFound()
1895
1896# Line edit data item
1897
1898class LineEditDataItem(object):
1899
1900	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1901		self.glb = glb
1902		self.label = label
1903		self.placeholder_text = placeholder_text
1904		self.parent = parent
1905		self.id = id
1906
1907		self.value = default
1908
1909		self.widget = QLineEdit(default)
1910		self.widget.editingFinished.connect(self.Validate)
1911		self.widget.textChanged.connect(self.Invalidate)
1912		self.red = False
1913		self.error = ""
1914		self.validated = True
1915
1916		if placeholder_text:
1917			self.widget.setPlaceholderText(placeholder_text)
1918
1919	def TurnTextRed(self):
1920		if not self.red:
1921			palette = QPalette()
1922			palette.setColor(QPalette.Text,Qt.red)
1923			self.widget.setPalette(palette)
1924			self.red = True
1925
1926	def TurnTextNormal(self):
1927		if self.red:
1928			palette = QPalette()
1929			self.widget.setPalette(palette)
1930			self.red = False
1931
1932	def InvalidValue(self, value):
1933		self.value = ""
1934		self.TurnTextRed()
1935		self.error = self.label + " invalid value '" + value + "'"
1936		self.parent.ShowMessage(self.error)
1937
1938	def Invalidate(self):
1939		self.validated = False
1940
1941	def DoValidate(self, input_string):
1942		self.value = input_string.strip()
1943
1944	def Validate(self):
1945		self.validated = True
1946		self.error = ""
1947		self.TurnTextNormal()
1948		self.parent.ClearMessage()
1949		input_string = self.widget.text()
1950		if not len(input_string.strip()):
1951			self.value = ""
1952			return
1953		self.DoValidate(input_string)
1954
1955	def IsValid(self):
1956		if not self.validated:
1957			self.Validate()
1958		if len(self.error):
1959			self.parent.ShowMessage(self.error)
1960			return False
1961		return True
1962
1963	def IsNumber(self, value):
1964		try:
1965			x = int(value)
1966		except:
1967			x = 0
1968		return str(x) == value
1969
1970# Non-negative integer ranges dialog data item
1971
1972class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1973
1974	def __init__(self, glb, label, placeholder_text, column_name, parent):
1975		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1976
1977		self.column_name = column_name
1978
1979	def DoValidate(self, input_string):
1980		singles = []
1981		ranges = []
1982		for value in [x.strip() for x in input_string.split(",")]:
1983			if "-" in value:
1984				vrange = value.split("-")
1985				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1986					return self.InvalidValue(value)
1987				ranges.append(vrange)
1988			else:
1989				if not self.IsNumber(value):
1990					return self.InvalidValue(value)
1991				singles.append(value)
1992		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1993		if len(singles):
1994			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1995		self.value = " OR ".join(ranges)
1996
1997# Positive integer dialog data item
1998
1999class PositiveIntegerDataItem(LineEditDataItem):
2000
2001	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
2002		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
2003
2004	def DoValidate(self, input_string):
2005		if not self.IsNumber(input_string.strip()):
2006			return self.InvalidValue(input_string)
2007		value = int(input_string.strip())
2008		if value <= 0:
2009			return self.InvalidValue(input_string)
2010		self.value = str(value)
2011
2012# Dialog data item converted and validated using a SQL table
2013
2014class SQLTableDataItem(LineEditDataItem):
2015
2016	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
2017		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
2018
2019		self.table_name = table_name
2020		self.match_column = match_column
2021		self.column_name1 = column_name1
2022		self.column_name2 = column_name2
2023
2024	def ValueToIds(self, value):
2025		ids = []
2026		query = QSqlQuery(self.glb.db)
2027		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
2028		ret = query.exec_(stmt)
2029		if ret:
2030			while query.next():
2031				ids.append(str(query.value(0)))
2032		return ids
2033
2034	def DoValidate(self, input_string):
2035		all_ids = []
2036		for value in [x.strip() for x in input_string.split(",")]:
2037			ids = self.ValueToIds(value)
2038			if len(ids):
2039				all_ids.extend(ids)
2040			else:
2041				return self.InvalidValue(value)
2042		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
2043		if self.column_name2:
2044			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
2045
2046# Sample time ranges dialog data item converted and validated using 'samples' SQL table
2047
2048class SampleTimeRangesDataItem(LineEditDataItem):
2049
2050	def __init__(self, glb, label, placeholder_text, column_name, parent):
2051		self.column_name = column_name
2052
2053		self.last_id = 0
2054		self.first_time = 0
2055		self.last_time = 2 ** 64
2056
2057		query = QSqlQuery(glb.db)
2058		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
2059		if query.next():
2060			self.last_id = int(query.value(0))
2061			self.last_time = int(query.value(1))
2062		QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
2063		if query.next():
2064			self.first_time = int(query.value(0))
2065		if placeholder_text:
2066			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
2067
2068		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
2069
2070	def IdBetween(self, query, lower_id, higher_id, order):
2071		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
2072		if query.next():
2073			return True, int(query.value(0))
2074		else:
2075			return False, 0
2076
2077	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
2078		query = QSqlQuery(self.glb.db)
2079		while True:
2080			next_id = int((lower_id + higher_id) / 2)
2081			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2082			if not query.next():
2083				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
2084				if not ok:
2085					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
2086					if not ok:
2087						return str(higher_id)
2088				next_id = dbid
2089				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
2090			next_time = int(query.value(0))
2091			if get_floor:
2092				if target_time > next_time:
2093					lower_id = next_id
2094				else:
2095					higher_id = next_id
2096				if higher_id <= lower_id + 1:
2097					return str(higher_id)
2098			else:
2099				if target_time >= next_time:
2100					lower_id = next_id
2101				else:
2102					higher_id = next_id
2103				if higher_id <= lower_id + 1:
2104					return str(lower_id)
2105
2106	def ConvertRelativeTime(self, val):
2107		mult = 1
2108		suffix = val[-2:]
2109		if suffix == "ms":
2110			mult = 1000000
2111		elif suffix == "us":
2112			mult = 1000
2113		elif suffix == "ns":
2114			mult = 1
2115		else:
2116			return val
2117		val = val[:-2].strip()
2118		if not self.IsNumber(val):
2119			return val
2120		val = int(val) * mult
2121		if val >= 0:
2122			val += self.first_time
2123		else:
2124			val += self.last_time
2125		return str(val)
2126
2127	def ConvertTimeRange(self, vrange):
2128		if vrange[0] == "":
2129			vrange[0] = str(self.first_time)
2130		if vrange[1] == "":
2131			vrange[1] = str(self.last_time)
2132		vrange[0] = self.ConvertRelativeTime(vrange[0])
2133		vrange[1] = self.ConvertRelativeTime(vrange[1])
2134		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
2135			return False
2136		beg_range = max(int(vrange[0]), self.first_time)
2137		end_range = min(int(vrange[1]), self.last_time)
2138		if beg_range > self.last_time or end_range < self.first_time:
2139			return False
2140		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
2141		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
2142		return True
2143
2144	def AddTimeRange(self, value, ranges):
2145		n = value.count("-")
2146		if n == 1:
2147			pass
2148		elif n == 2:
2149			if value.split("-")[1].strip() == "":
2150				n = 1
2151		elif n == 3:
2152			n = 2
2153		else:
2154			return False
2155		pos = findnth(value, "-", n)
2156		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
2157		if self.ConvertTimeRange(vrange):
2158			ranges.append(vrange)
2159			return True
2160		return False
2161
2162	def DoValidate(self, input_string):
2163		ranges = []
2164		for value in [x.strip() for x in input_string.split(",")]:
2165			if not self.AddTimeRange(value, ranges):
2166				return self.InvalidValue(value)
2167		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
2168		self.value = " OR ".join(ranges)
2169
2170# Report Dialog Base
2171
2172class ReportDialogBase(QDialog):
2173
2174	def __init__(self, glb, title, items, partial, parent=None):
2175		super(ReportDialogBase, self).__init__(parent)
2176
2177		self.glb = glb
2178
2179		self.report_vars = ReportVars()
2180
2181		self.setWindowTitle(title)
2182		self.setMinimumWidth(600)
2183
2184		self.data_items = [x(glb, self) for x in items]
2185
2186		self.partial = partial
2187
2188		self.grid = QGridLayout()
2189
2190		for row in xrange(len(self.data_items)):
2191			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2192			self.grid.addWidget(self.data_items[row].widget, row, 1)
2193
2194		self.status = QLabel()
2195
2196		self.ok_button = QPushButton("Ok", self)
2197		self.ok_button.setDefault(True)
2198		self.ok_button.released.connect(self.Ok)
2199		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2200
2201		self.cancel_button = QPushButton("Cancel", self)
2202		self.cancel_button.released.connect(self.reject)
2203		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2204
2205		self.hbox = QHBoxLayout()
2206		#self.hbox.addStretch()
2207		self.hbox.addWidget(self.status)
2208		self.hbox.addWidget(self.ok_button)
2209		self.hbox.addWidget(self.cancel_button)
2210
2211		self.vbox = QVBoxLayout()
2212		self.vbox.addLayout(self.grid)
2213		self.vbox.addLayout(self.hbox)
2214
2215		self.setLayout(self.vbox)
2216
2217	def Ok(self):
2218		vars = self.report_vars
2219		for d in self.data_items:
2220			if d.id == "REPORTNAME":
2221				vars.name = d.value
2222		if not vars.name:
2223			self.ShowMessage("Report name is required")
2224			return
2225		for d in self.data_items:
2226			if not d.IsValid():
2227				return
2228		for d in self.data_items[1:]:
2229			if d.id == "LIMIT":
2230				vars.limit = d.value
2231			elif len(d.value):
2232				if len(vars.where_clause):
2233					vars.where_clause += " AND "
2234				vars.where_clause += d.value
2235		if len(vars.where_clause):
2236			if self.partial:
2237				vars.where_clause = " AND ( " + vars.where_clause + " ) "
2238			else:
2239				vars.where_clause = " WHERE " + vars.where_clause + " "
2240		self.accept()
2241
2242	def ShowMessage(self, msg):
2243		self.status.setText("<font color=#FF0000>" + msg)
2244
2245	def ClearMessage(self):
2246		self.status.setText("")
2247
2248# Selected branch report creation dialog
2249
2250class SelectedBranchDialog(ReportDialogBase):
2251
2252	def __init__(self, glb, parent=None):
2253		title = "Selected Branches"
2254		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2255			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2256			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2257			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2258			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2259			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2260			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2261			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2262			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2263		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2264
2265# Event list
2266
2267def GetEventList(db):
2268	events = []
2269	query = QSqlQuery(db)
2270	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2271	while query.next():
2272		events.append(query.value(0))
2273	return events
2274
2275# Is a table selectable
2276
2277def IsSelectable(db, table, sql = "", columns = "*"):
2278	query = QSqlQuery(db)
2279	try:
2280		QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
2281	except:
2282		return False
2283	return True
2284
2285# SQL table data model item
2286
2287class SQLTableItem():
2288
2289	def __init__(self, row, data):
2290		self.row = row
2291		self.data = data
2292
2293	def getData(self, column):
2294		return self.data[column]
2295
2296# SQL table data model
2297
2298class SQLTableModel(TableModel):
2299
2300	progress = Signal(object)
2301
2302	def __init__(self, glb, sql, column_headers, parent=None):
2303		super(SQLTableModel, self).__init__(parent)
2304		self.glb = glb
2305		self.more = True
2306		self.populated = 0
2307		self.column_headers = column_headers
2308		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
2309		self.fetcher.done.connect(self.Update)
2310		self.fetcher.Fetch(glb_chunk_sz)
2311
2312	def DisplayData(self, item, index):
2313		self.FetchIfNeeded(item.row)
2314		return item.getData(index.column())
2315
2316	def AddSample(self, data):
2317		child = SQLTableItem(self.populated, data)
2318		self.child_items.append(child)
2319		self.populated += 1
2320
2321	def Update(self, fetched):
2322		if not fetched:
2323			self.more = False
2324			self.progress.emit(0)
2325		child_count = self.child_count
2326		count = self.populated - child_count
2327		if count > 0:
2328			parent = QModelIndex()
2329			self.beginInsertRows(parent, child_count, child_count + count - 1)
2330			self.insertRows(child_count, count, parent)
2331			self.child_count += count
2332			self.endInsertRows()
2333			self.progress.emit(self.child_count)
2334
2335	def FetchMoreRecords(self, count):
2336		current = self.child_count
2337		if self.more:
2338			self.fetcher.Fetch(count)
2339		else:
2340			self.progress.emit(0)
2341		return current
2342
2343	def HasMoreRecords(self):
2344		return self.more
2345
2346	def columnCount(self, parent=None):
2347		return len(self.column_headers)
2348
2349	def columnHeader(self, column):
2350		return self.column_headers[column]
2351
2352	def SQLTableDataPrep(self, query, count):
2353		data = []
2354		for i in xrange(count):
2355			data.append(query.value(i))
2356		return data
2357
2358# SQL automatic table data model
2359
2360class SQLAutoTableModel(SQLTableModel):
2361
2362	def __init__(self, glb, table_name, parent=None):
2363		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2364		if table_name == "comm_threads_view":
2365			# For now, comm_threads_view has no id column
2366			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2367		column_headers = []
2368		query = QSqlQuery(glb.db)
2369		if glb.dbref.is_sqlite3:
2370			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2371			while query.next():
2372				column_headers.append(query.value(1))
2373			if table_name == "sqlite_master":
2374				sql = "SELECT * FROM " + table_name
2375		else:
2376			if table_name[:19] == "information_schema.":
2377				sql = "SELECT * FROM " + table_name
2378				select_table_name = table_name[19:]
2379				schema = "information_schema"
2380			else:
2381				select_table_name = table_name
2382				schema = "public"
2383			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2384			while query.next():
2385				column_headers.append(query.value(0))
2386		if pyside_version_1 and sys.version_info[0] == 3:
2387			if table_name == "samples_view":
2388				self.SQLTableDataPrep = self.samples_view_DataPrep
2389			if table_name == "samples":
2390				self.SQLTableDataPrep = self.samples_DataPrep
2391		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2392
2393	def samples_view_DataPrep(self, query, count):
2394		data = []
2395		data.append(query.value(0))
2396		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2397		data.append("{:>19}".format(query.value(1)))
2398		for i in xrange(2, count):
2399			data.append(query.value(i))
2400		return data
2401
2402	def samples_DataPrep(self, query, count):
2403		data = []
2404		for i in xrange(9):
2405			data.append(query.value(i))
2406		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2407		data.append("{:>19}".format(query.value(9)))
2408		for i in xrange(10, count):
2409			data.append(query.value(i))
2410		return data
2411
2412# Base class for custom ResizeColumnsToContents
2413
2414class ResizeColumnsToContentsBase(QObject):
2415
2416	def __init__(self, parent=None):
2417		super(ResizeColumnsToContentsBase, self).__init__(parent)
2418
2419	def ResizeColumnToContents(self, column, n):
2420		# Using the view's resizeColumnToContents() here is extrememly slow
2421		# so implement a crude alternative
2422		font = self.view.font()
2423		metrics = QFontMetrics(font)
2424		max = 0
2425		for row in xrange(n):
2426			val = self.data_model.child_items[row].data[column]
2427			len = metrics.width(str(val) + "MM")
2428			max = len if len > max else max
2429		val = self.data_model.columnHeader(column)
2430		len = metrics.width(str(val) + "MM")
2431		max = len if len > max else max
2432		self.view.setColumnWidth(column, max)
2433
2434	def ResizeColumnsToContents(self):
2435		n = min(self.data_model.child_count, 100)
2436		if n < 1:
2437			# No data yet, so connect a signal to notify when there is
2438			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2439			return
2440		columns = self.data_model.columnCount()
2441		for i in xrange(columns):
2442			self.ResizeColumnToContents(i, n)
2443
2444	def UpdateColumnWidths(self, *x):
2445		# This only needs to be done once, so disconnect the signal now
2446		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2447		self.ResizeColumnsToContents()
2448
2449# Convert value to CSV
2450
2451def ToCSValue(val):
2452	if '"' in val:
2453		val = val.replace('"', '""')
2454	if "," in val or '"' in val:
2455		val = '"' + val + '"'
2456	return val
2457
2458# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2459
2460glb_max_cols = 1000
2461
2462def RowColumnKey(a):
2463	return a.row() * glb_max_cols + a.column()
2464
2465# Copy selected table cells to clipboard
2466
2467def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2468	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2469	idx_cnt = len(indexes)
2470	if not idx_cnt:
2471		return
2472	if idx_cnt == 1:
2473		with_hdr=False
2474	min_row = indexes[0].row()
2475	max_row = indexes[0].row()
2476	min_col = indexes[0].column()
2477	max_col = indexes[0].column()
2478	for i in indexes:
2479		min_row = min(min_row, i.row())
2480		max_row = max(max_row, i.row())
2481		min_col = min(min_col, i.column())
2482		max_col = max(max_col, i.column())
2483	if max_col > glb_max_cols:
2484		raise RuntimeError("glb_max_cols is too low")
2485	max_width = [0] * (1 + max_col - min_col)
2486	for i in indexes:
2487		c = i.column() - min_col
2488		max_width[c] = max(max_width[c], len(str(i.data())))
2489	text = ""
2490	pad = ""
2491	sep = ""
2492	if with_hdr:
2493		model = indexes[0].model()
2494		for col in range(min_col, max_col + 1):
2495			val = model.headerData(col, Qt.Horizontal)
2496			if as_csv:
2497				text += sep + ToCSValue(val)
2498				sep = ","
2499			else:
2500				c = col - min_col
2501				max_width[c] = max(max_width[c], len(val))
2502				width = max_width[c]
2503				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2504				if align & Qt.AlignRight:
2505					val = val.rjust(width)
2506				text += pad + sep + val
2507				pad = " " * (width - len(val))
2508				sep = "  "
2509		text += "\n"
2510		pad = ""
2511		sep = ""
2512	last_row = min_row
2513	for i in indexes:
2514		if i.row() > last_row:
2515			last_row = i.row()
2516			text += "\n"
2517			pad = ""
2518			sep = ""
2519		if as_csv:
2520			text += sep + ToCSValue(str(i.data()))
2521			sep = ","
2522		else:
2523			width = max_width[i.column() - min_col]
2524			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2525				val = str(i.data()).rjust(width)
2526			else:
2527				val = str(i.data())
2528			text += pad + sep + val
2529			pad = " " * (width - len(val))
2530			sep = "  "
2531	QApplication.clipboard().setText(text)
2532
2533def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2534	indexes = view.selectedIndexes()
2535	if not len(indexes):
2536		return
2537
2538	selection = view.selectionModel()
2539
2540	first = None
2541	for i in indexes:
2542		above = view.indexAbove(i)
2543		if not selection.isSelected(above):
2544			first = i
2545			break
2546
2547	if first is None:
2548		raise RuntimeError("CopyTreeCellsToClipboard internal error")
2549
2550	model = first.model()
2551	row_cnt = 0
2552	col_cnt = model.columnCount(first)
2553	max_width = [0] * col_cnt
2554
2555	indent_sz = 2
2556	indent_str = " " * indent_sz
2557
2558	expanded_mark_sz = 2
2559	if sys.version_info[0] == 3:
2560		expanded_mark = "\u25BC "
2561		not_expanded_mark = "\u25B6 "
2562	else:
2563		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2564		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2565	leaf_mark = "  "
2566
2567	if not as_csv:
2568		pos = first
2569		while True:
2570			row_cnt += 1
2571			row = pos.row()
2572			for c in range(col_cnt):
2573				i = pos.sibling(row, c)
2574				if c:
2575					n = len(str(i.data()))
2576				else:
2577					n = len(str(i.data()).strip())
2578					n += (i.internalPointer().level - 1) * indent_sz
2579					n += expanded_mark_sz
2580				max_width[c] = max(max_width[c], n)
2581			pos = view.indexBelow(pos)
2582			if not selection.isSelected(pos):
2583				break
2584
2585	text = ""
2586	pad = ""
2587	sep = ""
2588	if with_hdr:
2589		for c in range(col_cnt):
2590			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2591			if as_csv:
2592				text += sep + ToCSValue(val)
2593				sep = ","
2594			else:
2595				max_width[c] = max(max_width[c], len(val))
2596				width = max_width[c]
2597				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2598				if align & Qt.AlignRight:
2599					val = val.rjust(width)
2600				text += pad + sep + val
2601				pad = " " * (width - len(val))
2602				sep = "   "
2603		text += "\n"
2604		pad = ""
2605		sep = ""
2606
2607	pos = first
2608	while True:
2609		row = pos.row()
2610		for c in range(col_cnt):
2611			i = pos.sibling(row, c)
2612			val = str(i.data())
2613			if not c:
2614				if model.hasChildren(i):
2615					if view.isExpanded(i):
2616						mark = expanded_mark
2617					else:
2618						mark = not_expanded_mark
2619				else:
2620					mark = leaf_mark
2621				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2622			if as_csv:
2623				text += sep + ToCSValue(val)
2624				sep = ","
2625			else:
2626				width = max_width[c]
2627				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2628					val = val.rjust(width)
2629				text += pad + sep + val
2630				pad = " " * (width - len(val))
2631				sep = "   "
2632		pos = view.indexBelow(pos)
2633		if not selection.isSelected(pos):
2634			break
2635		text = text.rstrip() + "\n"
2636		pad = ""
2637		sep = ""
2638
2639	QApplication.clipboard().setText(text)
2640
2641def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2642	view.CopyCellsToClipboard(view, as_csv, with_hdr)
2643
2644def CopyCellsToClipboardHdr(view):
2645	CopyCellsToClipboard(view, False, True)
2646
2647def CopyCellsToClipboardCSV(view):
2648	CopyCellsToClipboard(view, True, True)
2649
2650# Context menu
2651
2652class ContextMenu(object):
2653
2654	def __init__(self, view):
2655		self.view = view
2656		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
2657		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
2658
2659	def ShowContextMenu(self, pos):
2660		menu = QMenu(self.view)
2661		self.AddActions(menu)
2662		menu.exec_(self.view.mapToGlobal(pos))
2663
2664	def AddCopy(self, menu):
2665		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
2666		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
2667
2668	def AddActions(self, menu):
2669		self.AddCopy(menu)
2670
2671class TreeContextMenu(ContextMenu):
2672
2673	def __init__(self, view):
2674		super(TreeContextMenu, self).__init__(view)
2675
2676	def AddActions(self, menu):
2677		i = self.view.currentIndex()
2678		text = str(i.data()).strip()
2679		if len(text):
2680			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
2681		self.AddCopy(menu)
2682
2683# Table window
2684
2685class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2686
2687	def __init__(self, glb, table_name, parent=None):
2688		super(TableWindow, self).__init__(parent)
2689
2690		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2691
2692		self.model = QSortFilterProxyModel()
2693		self.model.setSourceModel(self.data_model)
2694
2695		self.view = QTableView()
2696		self.view.setModel(self.model)
2697		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2698		self.view.verticalHeader().setVisible(False)
2699		self.view.sortByColumn(-1, Qt.AscendingOrder)
2700		self.view.setSortingEnabled(True)
2701		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2702		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2703
2704		self.ResizeColumnsToContents()
2705
2706		self.context_menu = ContextMenu(self.view)
2707
2708		self.find_bar = FindBar(self, self, True)
2709
2710		self.finder = ChildDataItemFinder(self.data_model)
2711
2712		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2713
2714		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2715
2716		self.setWidget(self.vbox.Widget())
2717
2718		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2719
2720	def Find(self, value, direction, pattern, context):
2721		self.view.setFocus()
2722		self.find_bar.Busy()
2723		self.finder.Find(value, direction, pattern, context, self.FindDone)
2724
2725	def FindDone(self, row):
2726		self.find_bar.Idle()
2727		if row >= 0:
2728			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2729		else:
2730			self.find_bar.NotFound()
2731
2732# Table list
2733
2734def GetTableList(glb):
2735	tables = []
2736	query = QSqlQuery(glb.db)
2737	if glb.dbref.is_sqlite3:
2738		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2739	else:
2740		QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2741	while query.next():
2742		tables.append(query.value(0))
2743	if glb.dbref.is_sqlite3:
2744		tables.append("sqlite_master")
2745	else:
2746		tables.append("information_schema.tables")
2747		tables.append("information_schema.views")
2748		tables.append("information_schema.columns")
2749	return tables
2750
2751# Top Calls data model
2752
2753class TopCallsModel(SQLTableModel):
2754
2755	def __init__(self, glb, report_vars, parent=None):
2756		text = ""
2757		if not glb.dbref.is_sqlite3:
2758			text = "::text"
2759		limit = ""
2760		if len(report_vars.limit):
2761			limit = " LIMIT " + report_vars.limit
2762		sql = ("SELECT comm, pid, tid, name,"
2763			" CASE"
2764			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2765			" ELSE short_name"
2766			" END AS dso,"
2767			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2768			" CASE"
2769			" WHEN (calls.flags = 1) THEN 'no call'" + text +
2770			" WHEN (calls.flags = 2) THEN 'no return'" + text +
2771			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2772			" ELSE ''" + text +
2773			" END AS flags"
2774			" FROM calls"
2775			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2776			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2777			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
2778			" INNER JOIN comms ON calls.comm_id = comms.id"
2779			" INNER JOIN threads ON calls.thread_id = threads.id" +
2780			report_vars.where_clause +
2781			" ORDER BY elapsed_time DESC" +
2782			limit
2783			)
2784		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2785		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2786		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2787
2788	def columnAlignment(self, column):
2789		return self.alignment[column]
2790
2791# Top Calls report creation dialog
2792
2793class TopCallsDialog(ReportDialogBase):
2794
2795	def __init__(self, glb, parent=None):
2796		title = "Top Calls by Elapsed Time"
2797		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2798			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2799			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2800			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2801			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2802			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2803			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2804			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2805		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2806
2807# Top Calls window
2808
2809class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2810
2811	def __init__(self, glb, report_vars, parent=None):
2812		super(TopCallsWindow, self).__init__(parent)
2813
2814		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2815		self.model = self.data_model
2816
2817		self.view = QTableView()
2818		self.view.setModel(self.model)
2819		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2820		self.view.verticalHeader().setVisible(False)
2821		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2822		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2823
2824		self.context_menu = ContextMenu(self.view)
2825
2826		self.ResizeColumnsToContents()
2827
2828		self.find_bar = FindBar(self, self, True)
2829
2830		self.finder = ChildDataItemFinder(self.model)
2831
2832		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2833
2834		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2835
2836		self.setWidget(self.vbox.Widget())
2837
2838		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2839
2840	def Find(self, value, direction, pattern, context):
2841		self.view.setFocus()
2842		self.find_bar.Busy()
2843		self.finder.Find(value, direction, pattern, context, self.FindDone)
2844
2845	def FindDone(self, row):
2846		self.find_bar.Idle()
2847		if row >= 0:
2848			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2849		else:
2850			self.find_bar.NotFound()
2851
2852# Action Definition
2853
2854def CreateAction(label, tip, callback, parent=None, shortcut=None):
2855	action = QAction(label, parent)
2856	if shortcut != None:
2857		action.setShortcuts(shortcut)
2858	action.setStatusTip(tip)
2859	action.triggered.connect(callback)
2860	return action
2861
2862# Typical application actions
2863
2864def CreateExitAction(app, parent=None):
2865	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2866
2867# Typical MDI actions
2868
2869def CreateCloseActiveWindowAction(mdi_area):
2870	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2871
2872def CreateCloseAllWindowsAction(mdi_area):
2873	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2874
2875def CreateTileWindowsAction(mdi_area):
2876	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2877
2878def CreateCascadeWindowsAction(mdi_area):
2879	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2880
2881def CreateNextWindowAction(mdi_area):
2882	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2883
2884def CreatePreviousWindowAction(mdi_area):
2885	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2886
2887# Typical MDI window menu
2888
2889class WindowMenu():
2890
2891	def __init__(self, mdi_area, menu):
2892		self.mdi_area = mdi_area
2893		self.window_menu = menu.addMenu("&Windows")
2894		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2895		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2896		self.tile_windows = CreateTileWindowsAction(mdi_area)
2897		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2898		self.next_window = CreateNextWindowAction(mdi_area)
2899		self.previous_window = CreatePreviousWindowAction(mdi_area)
2900		self.window_menu.aboutToShow.connect(self.Update)
2901
2902	def Update(self):
2903		self.window_menu.clear()
2904		sub_window_count = len(self.mdi_area.subWindowList())
2905		have_sub_windows = sub_window_count != 0
2906		self.close_active_window.setEnabled(have_sub_windows)
2907		self.close_all_windows.setEnabled(have_sub_windows)
2908		self.tile_windows.setEnabled(have_sub_windows)
2909		self.cascade_windows.setEnabled(have_sub_windows)
2910		self.next_window.setEnabled(have_sub_windows)
2911		self.previous_window.setEnabled(have_sub_windows)
2912		self.window_menu.addAction(self.close_active_window)
2913		self.window_menu.addAction(self.close_all_windows)
2914		self.window_menu.addSeparator()
2915		self.window_menu.addAction(self.tile_windows)
2916		self.window_menu.addAction(self.cascade_windows)
2917		self.window_menu.addSeparator()
2918		self.window_menu.addAction(self.next_window)
2919		self.window_menu.addAction(self.previous_window)
2920		if sub_window_count == 0:
2921			return
2922		self.window_menu.addSeparator()
2923		nr = 1
2924		for sub_window in self.mdi_area.subWindowList():
2925			label = str(nr) + " " + sub_window.name
2926			if nr < 10:
2927				label = "&" + label
2928			action = self.window_menu.addAction(label)
2929			action.setCheckable(True)
2930			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2931			action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
2932			self.window_menu.addAction(action)
2933			nr += 1
2934
2935	def setActiveSubWindow(self, nr):
2936		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2937
2938# Help text
2939
2940glb_help_text = """
2941<h1>Contents</h1>
2942<style>
2943p.c1 {
2944    text-indent: 40px;
2945}
2946p.c2 {
2947    text-indent: 80px;
2948}
2949}
2950</style>
2951<p class=c1><a href=#reports>1. Reports</a></p>
2952<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2953<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2954<p class=c2><a href=#allbranches>1.3 All branches</a></p>
2955<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2956<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2957<p class=c1><a href=#tables>2. Tables</a></p>
 
 
2958<h1 id=reports>1. Reports</h1>
2959<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2960The result is a GUI window with a tree representing a context-sensitive
2961call-graph. Expanding a couple of levels of the tree and adjusting column
2962widths to suit will display something like:
2963<pre>
2964                                         Call Graph: pt_example
2965Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
2966v- ls
2967    v- 2638:2638
2968        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
2969          |- unknown               unknown       1        13198     0.1              1              0.0
2970          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
2971          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
2972          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
2973             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
2974             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
2975             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
2976             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
2977             v- main               ls            1      8182043    99.6         180254             99.9
2978</pre>
2979<h3>Points to note:</h3>
2980<ul>
2981<li>The top level is a command name (comm)</li>
2982<li>The next level is a thread (pid:tid)</li>
2983<li>Subsequent levels are functions</li>
2984<li>'Count' is the number of calls</li>
2985<li>'Time' is the elapsed time until the function returns</li>
2986<li>Percentages are relative to the level above</li>
2987<li>'Branch Count' is the total number of branches for that function and all functions that it calls
2988</ul>
2989<h3>Find</h3>
2990Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2991The pattern matching symbols are ? for any character and * for zero or more characters.
2992<h2 id=calltree>1.2 Call Tree</h2>
2993The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2994Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2995<h2 id=allbranches>1.3 All branches</h2>
2996The All branches report displays all branches in chronological order.
2997Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2998<h3>Disassembly</h3>
2999Open a branch to display disassembly. This only works if:
3000<ol>
3001<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
3002<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
3003The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
3004One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
3005or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
3006</ol>
3007<h4 id=xed>Intel XED Setup</h4>
3008To use Intel XED, libxed.so must be present.  To build and install libxed.so:
3009<pre>
3010git clone https://github.com/intelxed/mbuild.git mbuild
3011git clone https://github.com/intelxed/xed
3012cd xed
3013./mfile.py --share
3014sudo ./mfile.py --prefix=/usr/local install
3015sudo ldconfig
3016</pre>
3017<h3>Instructions per Cycle (IPC)</h3>
3018If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
3019<p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
3020Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
3021In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
3022since the previous displayed 'IPC'.
3023<h3>Find</h3>
3024Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3025Refer to Python documentation for the regular expression syntax.
3026All columns are searched, but only currently fetched rows are searched.
3027<h2 id=selectedbranches>1.4 Selected branches</h2>
3028This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
3029by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3030<h3>1.4.1 Time ranges</h3>
3031The time ranges hint text shows the total time range. Relative time ranges can also be entered in
3032ms, us or ns. Also, negative values are relative to the end of trace.  Examples:
3033<pre>
3034	81073085947329-81073085958238	From 81073085947329 to 81073085958238
3035	100us-200us		From 100us to 200us
3036	10ms-			From 10ms to the end
3037	-100ns			The first 100ns
3038	-10ms-			The last 10ms
3039</pre>
3040N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
3041<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
3042The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
3043The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
3044If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
3045<h1 id=tables>2. Tables</h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3046The Tables menu shows all tables and views in the database. Most tables have an associated view
3047which displays the information in a more friendly way. Not all data for large tables is fetched
3048immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
3049but that can be slow for large tables.
3050<p>There are also tables of database meta-information.
3051For SQLite3 databases, the sqlite_master table is included.
3052For PostgreSQL databases, information_schema.tables/views/columns are included.
3053<h3>Find</h3>
3054Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
3055Refer to Python documentation for the regular expression syntax.
3056All columns are searched, but only currently fetched rows are searched.
3057<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
3058will go to the next/previous result in id order, instead of display order.
3059"""
3060
3061# Help window
3062
3063class HelpWindow(QMdiSubWindow):
3064
3065	def __init__(self, glb, parent=None):
3066		super(HelpWindow, self).__init__(parent)
3067
3068		self.text = QTextBrowser()
3069		self.text.setHtml(glb_help_text)
3070		self.text.setReadOnly(True)
3071		self.text.setOpenExternalLinks(True)
3072
3073		self.setWidget(self.text)
3074
3075		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
3076
3077# Main window that only displays the help text
3078
3079class HelpOnlyWindow(QMainWindow):
3080
3081	def __init__(self, parent=None):
3082		super(HelpOnlyWindow, self).__init__(parent)
3083
3084		self.setMinimumSize(200, 100)
3085		self.resize(800, 600)
3086		self.setWindowTitle("Exported SQL Viewer Help")
3087		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
3088
3089		self.text = QTextBrowser()
3090		self.text.setHtml(glb_help_text)
3091		self.text.setReadOnly(True)
3092		self.text.setOpenExternalLinks(True)
3093
3094		self.setCentralWidget(self.text)
3095
3096# PostqreSQL server version
3097
3098def PostqreSQLServerVersion(db):
3099	query = QSqlQuery(db)
3100	QueryExec(query, "SELECT VERSION()")
3101	if query.next():
3102		v_str = query.value(0)
3103		v_list = v_str.strip().split(" ")
3104		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
3105			return v_list[1]
3106		return v_str
3107	return "Unknown"
3108
3109# SQLite version
3110
3111def SQLiteVersion(db):
3112	query = QSqlQuery(db)
3113	QueryExec(query, "SELECT sqlite_version()")
3114	if query.next():
3115		return query.value(0)
3116	return "Unknown"
3117
3118# About dialog
3119
3120class AboutDialog(QDialog):
3121
3122	def __init__(self, glb, parent=None):
3123		super(AboutDialog, self).__init__(parent)
3124
3125		self.setWindowTitle("About Exported SQL Viewer")
3126		self.setMinimumWidth(300)
3127
3128		pyside_version = "1" if pyside_version_1 else "2"
3129
3130		text = "<pre>"
3131		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
3132		text += "PySide version:     " + pyside_version + "\n"
3133		text += "Qt version:         " + qVersion() + "\n"
3134		if glb.dbref.is_sqlite3:
3135			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
3136		else:
3137			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
3138		text += "</pre>"
3139
3140		self.text = QTextBrowser()
3141		self.text.setHtml(text)
3142		self.text.setReadOnly(True)
3143		self.text.setOpenExternalLinks(True)
3144
3145		self.vbox = QVBoxLayout()
3146		self.vbox.addWidget(self.text)
3147
3148		self.setLayout(self.vbox)
3149
3150# Font resize
3151
3152def ResizeFont(widget, diff):
3153	font = widget.font()
3154	sz = font.pointSize()
3155	font.setPointSize(sz + diff)
3156	widget.setFont(font)
3157
3158def ShrinkFont(widget):
3159	ResizeFont(widget, -1)
3160
3161def EnlargeFont(widget):
3162	ResizeFont(widget, 1)
3163
3164# Unique name for sub-windows
3165
3166def NumberedWindowName(name, nr):
3167	if nr > 1:
3168		name += " <" + str(nr) + ">"
3169	return name
3170
3171def UniqueSubWindowName(mdi_area, name):
3172	nr = 1
3173	while True:
3174		unique_name = NumberedWindowName(name, nr)
3175		ok = True
3176		for sub_window in mdi_area.subWindowList():
3177			if sub_window.name == unique_name:
3178				ok = False
3179				break
3180		if ok:
3181			return unique_name
3182		nr += 1
3183
3184# Add a sub-window
3185
3186def AddSubWindow(mdi_area, sub_window, name):
3187	unique_name = UniqueSubWindowName(mdi_area, name)
3188	sub_window.setMinimumSize(200, 100)
3189	sub_window.resize(800, 600)
3190	sub_window.setWindowTitle(unique_name)
3191	sub_window.setAttribute(Qt.WA_DeleteOnClose)
3192	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
3193	sub_window.name = unique_name
3194	mdi_area.addSubWindow(sub_window)
3195	sub_window.show()
3196
3197# Main window
3198
3199class MainWindow(QMainWindow):
3200
3201	def __init__(self, glb, parent=None):
3202		super(MainWindow, self).__init__(parent)
3203
3204		self.glb = glb
3205
3206		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3207		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3208		self.setMinimumSize(200, 100)
3209
3210		self.mdi_area = QMdiArea()
3211		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3212		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3213
3214		self.setCentralWidget(self.mdi_area)
3215
3216		menu = self.menuBar()
3217
3218		file_menu = menu.addMenu("&File")
3219		file_menu.addAction(CreateExitAction(glb.app, self))
3220
3221		edit_menu = menu.addMenu("&Edit")
3222		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
3223		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3224		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
3225		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
3226		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
3227		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3228
3229		reports_menu = menu.addMenu("&Reports")
3230		if IsSelectable(glb.db, "calls"):
3231			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
3232
3233		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3234			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3235
3236		self.EventMenu(GetEventList(glb.db), reports_menu)
3237
3238		if IsSelectable(glb.db, "calls"):
3239			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3240
 
 
 
 
3241		self.TableMenu(GetTableList(glb), menu)
3242
3243		self.window_menu = WindowMenu(self.mdi_area, menu)
3244
3245		help_menu = menu.addMenu("&Help")
3246		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3247		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
3248
3249	def Try(self, fn):
3250		win = self.mdi_area.activeSubWindow()
3251		if win:
3252			try:
3253				fn(win.view)
3254			except:
3255				pass
3256
3257	def CopyToClipboard(self):
3258		self.Try(CopyCellsToClipboardHdr)
3259
3260	def CopyToClipboardCSV(self):
3261		self.Try(CopyCellsToClipboardCSV)
3262
3263	def Find(self):
3264		win = self.mdi_area.activeSubWindow()
3265		if win:
3266			try:
3267				win.find_bar.Activate()
3268			except:
3269				pass
3270
3271	def FetchMoreRecords(self):
3272		win = self.mdi_area.activeSubWindow()
3273		if win:
3274			try:
3275				win.fetch_bar.Activate()
3276			except:
3277				pass
3278
3279	def ShrinkFont(self):
3280		self.Try(ShrinkFont)
3281
3282	def EnlargeFont(self):
3283		self.Try(EnlargeFont)
3284
3285	def EventMenu(self, events, reports_menu):
3286		branches_events = 0
3287		for event in events:
3288			event = event.split(":")[0]
3289			if event == "branches":
3290				branches_events += 1
3291		dbid = 0
3292		for event in events:
3293			dbid += 1
3294			event = event.split(":")[0]
3295			if event == "branches":
3296				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3297				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
3298				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3299				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
3300
 
 
 
3301	def TableMenu(self, tables, menu):
3302		table_menu = menu.addMenu("&Tables")
3303		for table in tables:
3304			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
3305
3306	def NewCallGraph(self):
3307		CallGraphWindow(self.glb, self)
3308
3309	def NewCallTree(self):
3310		CallTreeWindow(self.glb, self)
3311
3312	def NewTopCalls(self):
3313		dialog = TopCallsDialog(self.glb, self)
3314		ret = dialog.exec_()
3315		if ret:
3316			TopCallsWindow(self.glb, dialog.report_vars, self)
3317
3318	def NewBranchView(self, event_id):
3319		BranchWindow(self.glb, event_id, ReportVars(), self)
3320
3321	def NewSelectedBranchView(self, event_id):
3322		dialog = SelectedBranchDialog(self.glb, self)
3323		ret = dialog.exec_()
3324		if ret:
3325			BranchWindow(self.glb, event_id, dialog.report_vars, self)
3326
3327	def NewTableView(self, table_name):
3328		TableWindow(self.glb, table_name, self)
3329
3330	def Help(self):
3331		HelpWindow(self.glb, self)
3332
3333	def About(self):
3334		dialog = AboutDialog(self.glb, self)
3335		dialog.exec_()
3336
3337# XED Disassembler
3338
3339class xed_state_t(Structure):
3340
3341	_fields_ = [
3342		("mode", c_int),
3343		("width", c_int)
3344	]
3345
3346class XEDInstruction():
3347
3348	def __init__(self, libxed):
3349		# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3350		xedd_t = c_byte * 512
3351		self.xedd = xedd_t()
3352		self.xedp = addressof(self.xedd)
3353		libxed.xed_decoded_inst_zero(self.xedp)
3354		self.state = xed_state_t()
3355		self.statep = addressof(self.state)
3356		# Buffer for disassembled instruction text
3357		self.buffer = create_string_buffer(256)
3358		self.bufferp = addressof(self.buffer)
3359
3360class LibXED():
3361
3362	def __init__(self):
3363		try:
3364			self.libxed = CDLL("libxed.so")
3365		except:
3366			self.libxed = None
3367		if not self.libxed:
3368			self.libxed = CDLL("/usr/local/lib/libxed.so")
3369
3370		self.xed_tables_init = self.libxed.xed_tables_init
3371		self.xed_tables_init.restype = None
3372		self.xed_tables_init.argtypes = []
3373
3374		self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3375		self.xed_decoded_inst_zero.restype = None
3376		self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3377
3378		self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3379		self.xed_operand_values_set_mode.restype = None
3380		self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3381
3382		self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3383		self.xed_decoded_inst_zero_keep_mode.restype = None
3384		self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3385
3386		self.xed_decode = self.libxed.xed_decode
3387		self.xed_decode.restype = c_int
3388		self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3389
3390		self.xed_format_context = self.libxed.xed_format_context
3391		self.xed_format_context.restype = c_uint
3392		self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3393
3394		self.xed_tables_init()
3395
3396	def Instruction(self):
3397		return XEDInstruction(self)
3398
3399	def SetMode(self, inst, mode):
3400		if mode:
3401			inst.state.mode = 4 # 32-bit
3402			inst.state.width = 4 # 4 bytes
3403		else:
3404			inst.state.mode = 1 # 64-bit
3405			inst.state.width = 8 # 8 bytes
3406		self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3407
3408	def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3409		self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3410		err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3411		if err:
3412			return 0, ""
3413		# Use AT&T mode (2), alternative is Intel (3)
3414		ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3415		if not ok:
3416			return 0, ""
3417		if sys.version_info[0] == 2:
3418			result = inst.buffer.value
3419		else:
3420			result = inst.buffer.value.decode()
3421		# Return instruction length and the disassembled instruction text
3422		# For now, assume the length is in byte 166
3423		return inst.xedd[166], result
3424
3425def TryOpen(file_name):
3426	try:
3427		return open(file_name, "rb")
3428	except:
3429		return None
3430
3431def Is64Bit(f):
3432	result = sizeof(c_void_p)
3433	# ELF support only
3434	pos = f.tell()
3435	f.seek(0)
3436	header = f.read(7)
3437	f.seek(pos)
3438	magic = header[0:4]
3439	if sys.version_info[0] == 2:
3440		eclass = ord(header[4])
3441		encoding = ord(header[5])
3442		version = ord(header[6])
3443	else:
3444		eclass = header[4]
3445		encoding = header[5]
3446		version = header[6]
3447	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3448		result = True if eclass == 2 else False
3449	return result
3450
3451# Global data
3452
3453class Glb():
3454
3455	def __init__(self, dbref, db, dbname):
3456		self.dbref = dbref
3457		self.db = db
3458		self.dbname = dbname
3459		self.home_dir = os.path.expanduser("~")
3460		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3461		if self.buildid_dir:
3462			self.buildid_dir += "/.build-id/"
3463		else:
3464			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3465		self.app = None
3466		self.mainwindow = None
3467		self.instances_to_shutdown_on_exit = weakref.WeakSet()
3468		try:
3469			self.disassembler = LibXED()
3470			self.have_disassembler = True
3471		except:
3472			self.have_disassembler = False
 
 
 
3473
3474	def FileFromBuildId(self, build_id):
3475		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3476		return TryOpen(file_name)
3477
3478	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3479		# Assume current machine i.e. no support for virtualization
3480		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3481			file_name = os.getenv("PERF_KCORE")
3482			f = TryOpen(file_name) if file_name else None
3483			if f:
3484				return f
3485			# For now, no special handling if long_name is /proc/kcore
3486			f = TryOpen(long_name)
3487			if f:
3488				return f
3489		f = self.FileFromBuildId(build_id)
3490		if f:
3491			return f
3492		return None
3493
3494	def AddInstanceToShutdownOnExit(self, instance):
3495		self.instances_to_shutdown_on_exit.add(instance)
3496
3497	# Shutdown any background processes or threads
3498	def ShutdownInstances(self):
3499		for x in self.instances_to_shutdown_on_exit:
3500			try:
3501				x.Shutdown()
3502			except:
3503				pass
3504
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3505# Database reference
3506
3507class DBRef():
3508
3509	def __init__(self, is_sqlite3, dbname):
3510		self.is_sqlite3 = is_sqlite3
3511		self.dbname = dbname
 
 
 
 
 
 
3512
3513	def Open(self, connection_name):
3514		dbname = self.dbname
3515		if self.is_sqlite3:
3516			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3517		else:
3518			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3519			opts = dbname.split()
3520			for opt in opts:
3521				if "=" in opt:
3522					opt = opt.split("=")
3523					if opt[0] == "hostname":
3524						db.setHostName(opt[1])
3525					elif opt[0] == "port":
3526						db.setPort(int(opt[1]))
3527					elif opt[0] == "username":
3528						db.setUserName(opt[1])
3529					elif opt[0] == "password":
3530						db.setPassword(opt[1])
3531					elif opt[0] == "dbname":
3532						dbname = opt[1]
3533				else:
3534					dbname = opt
3535
3536		db.setDatabaseName(dbname)
3537		if not db.open():
3538			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3539		return db, dbname
3540
3541# Main
3542
3543def Main():
3544	usage_str =	"exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
3545			"   or: exported-sql-viewer.py --help-only"
3546	ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
3547	ap.add_argument("--pyside-version-1", action='store_true')
3548	ap.add_argument("dbname", nargs="?")
3549	ap.add_argument("--help-only", action='store_true')
3550	args = ap.parse_args()
3551
3552	if args.help_only:
3553		app = QApplication(sys.argv)
3554		mainwindow = HelpOnlyWindow()
3555		mainwindow.show()
3556		err = app.exec_()
3557		sys.exit(err)
3558
3559	dbname = args.dbname
3560	if dbname is None:
3561		ap.print_usage()
3562		print("Too few arguments")
3563		sys.exit(1)
3564
3565	is_sqlite3 = False
3566	try:
3567		f = open(dbname, "rb")
3568		if f.read(15) == b'SQLite format 3':
3569			is_sqlite3 = True
3570		f.close()
3571	except:
3572		pass
3573
3574	dbref = DBRef(is_sqlite3, dbname)
3575	db, dbname = dbref.Open("main")
3576	glb = Glb(dbref, db, dbname)
3577	app = QApplication(sys.argv)
3578	glb.app = app
3579	mainwindow = MainWindow(glb)
3580	glb.mainwindow = mainwindow
3581	mainwindow.show()
3582	err = app.exec_()
3583	glb.ShutdownInstances()
3584	db.close()
3585	sys.exit(err)
3586
3587if __name__ == "__main__":
3588	Main()
v6.13.7
   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()