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