xref: /linux/tools/perf/scripts/python/exported-sql-viewer.py (revision ebf68996de0ab250c5d520eb2291ab65643e9a1e)
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		self.context_menu = TreeContextMenu(self.view)
891
892	def DisplayFound(self, ids):
893		if not len(ids):
894			return False
895		parent = QModelIndex()
896		for dbid in ids:
897			found = False
898			n = self.model.rowCount(parent)
899			for row in xrange(n):
900				child = self.model.index(row, 0, parent)
901				if child.internalPointer().dbid == dbid:
902					found = True
903					self.view.setCurrentIndex(child)
904					parent = child
905					break
906			if not found:
907				break
908		return found
909
910	def Find(self, value, direction, pattern, context):
911		self.view.setFocus()
912		self.find_bar.Busy()
913		self.model.Find(value, direction, pattern, context, self.FindDone)
914
915	def FindDone(self, ids):
916		found = True
917		if not self.DisplayFound(ids):
918			found = False
919		self.find_bar.Idle()
920		if not found:
921			self.find_bar.NotFound()
922
923
924# Context-sensitive call graph window
925
926class CallGraphWindow(TreeWindowBase):
927
928	def __init__(self, glb, parent=None):
929		super(CallGraphWindow, self).__init__(parent)
930
931		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
932
933		self.view.setModel(self.model)
934
935		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
936			self.view.setColumnWidth(c, w)
937
938		self.find_bar = FindBar(self, self)
939
940		self.vbox = VBox(self.view, self.find_bar.Widget())
941
942		self.setWidget(self.vbox.Widget())
943
944		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
945
946# Call tree window
947
948class CallTreeWindow(TreeWindowBase):
949
950	def __init__(self, glb, parent=None):
951		super(CallTreeWindow, self).__init__(parent)
952
953		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
954
955		self.view.setModel(self.model)
956
957		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
958			self.view.setColumnWidth(c, w)
959
960		self.find_bar = FindBar(self, self)
961
962		self.vbox = VBox(self.view, self.find_bar.Widget())
963
964		self.setWidget(self.vbox.Widget())
965
966		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
967
968# Child data item  finder
969
970class ChildDataItemFinder():
971
972	def __init__(self, root):
973		self.root = root
974		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
975		self.rows = []
976		self.pos = 0
977
978	def FindSelect(self):
979		self.rows = []
980		if self.pattern:
981			pattern = re.compile(self.value)
982			for child in self.root.child_items:
983				for column_data in child.data:
984					if re.search(pattern, str(column_data)) is not None:
985						self.rows.append(child.row)
986						break
987		else:
988			for child in self.root.child_items:
989				for column_data in child.data:
990					if self.value in str(column_data):
991						self.rows.append(child.row)
992						break
993
994	def FindValue(self):
995		self.pos = 0
996		if self.last_value != self.value or self.pattern != self.last_pattern:
997			self.FindSelect()
998		if not len(self.rows):
999			return -1
1000		return self.rows[self.pos]
1001
1002	def FindThread(self):
1003		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1004			row = self.FindValue()
1005		elif len(self.rows):
1006			if self.direction > 0:
1007				self.pos += 1
1008				if self.pos >= len(self.rows):
1009					self.pos = 0
1010			else:
1011				self.pos -= 1
1012				if self.pos < 0:
1013					self.pos = len(self.rows) - 1
1014			row = self.rows[self.pos]
1015		else:
1016			row = -1
1017		return (True, row)
1018
1019	def Find(self, value, direction, pattern, context, callback):
1020		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1021		# Use a thread so the UI is not blocked
1022		thread = Thread(self.FindThread)
1023		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1024		thread.start()
1025
1026	def FindDone(self, thread, callback, row):
1027		callback(row)
1028
1029# Number of database records to fetch in one go
1030
1031glb_chunk_sz = 10000
1032
1033# Background process for SQL data fetcher
1034
1035class SQLFetcherProcess():
1036
1037	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1038		# Need a unique connection name
1039		conn_name = "SQLFetcher" + str(os.getpid())
1040		self.db, dbname = dbref.Open(conn_name)
1041		self.sql = sql
1042		self.buffer = buffer
1043		self.head = head
1044		self.tail = tail
1045		self.fetch_count = fetch_count
1046		self.fetching_done = fetching_done
1047		self.process_target = process_target
1048		self.wait_event = wait_event
1049		self.fetched_event = fetched_event
1050		self.prep = prep
1051		self.query = QSqlQuery(self.db)
1052		self.query_limit = 0 if "$$last_id$$" in sql else 2
1053		self.last_id = -1
1054		self.fetched = 0
1055		self.more = True
1056		self.local_head = self.head.value
1057		self.local_tail = self.tail.value
1058
1059	def Select(self):
1060		if self.query_limit:
1061			if self.query_limit == 1:
1062				return
1063			self.query_limit -= 1
1064		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1065		QueryExec(self.query, stmt)
1066
1067	def Next(self):
1068		if not self.query.next():
1069			self.Select()
1070			if not self.query.next():
1071				return None
1072		self.last_id = self.query.value(0)
1073		return self.prep(self.query)
1074
1075	def WaitForTarget(self):
1076		while True:
1077			self.wait_event.clear()
1078			target = self.process_target.value
1079			if target > self.fetched or target < 0:
1080				break
1081			self.wait_event.wait()
1082		return target
1083
1084	def HasSpace(self, sz):
1085		if self.local_tail <= self.local_head:
1086			space = len(self.buffer) - self.local_head
1087			if space > sz:
1088				return True
1089			if space >= glb_nsz:
1090				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1091				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
1092				self.buffer[self.local_head : self.local_head + len(nd)] = nd
1093			self.local_head = 0
1094		if self.local_tail - self.local_head > sz:
1095			return True
1096		return False
1097
1098	def WaitForSpace(self, sz):
1099		if self.HasSpace(sz):
1100			return
1101		while True:
1102			self.wait_event.clear()
1103			self.local_tail = self.tail.value
1104			if self.HasSpace(sz):
1105				return
1106			self.wait_event.wait()
1107
1108	def AddToBuffer(self, obj):
1109		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
1110		n = len(d)
1111		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
1112		sz = n + glb_nsz
1113		self.WaitForSpace(sz)
1114		pos = self.local_head
1115		self.buffer[pos : pos + len(nd)] = nd
1116		self.buffer[pos + glb_nsz : pos + sz] = d
1117		self.local_head += sz
1118
1119	def FetchBatch(self, batch_size):
1120		fetched = 0
1121		while batch_size > fetched:
1122			obj = self.Next()
1123			if obj is None:
1124				self.more = False
1125				break
1126			self.AddToBuffer(obj)
1127			fetched += 1
1128		if fetched:
1129			self.fetched += fetched
1130			with self.fetch_count.get_lock():
1131				self.fetch_count.value += fetched
1132			self.head.value = self.local_head
1133			self.fetched_event.set()
1134
1135	def Run(self):
1136		while self.more:
1137			target = self.WaitForTarget()
1138			if target < 0:
1139				break
1140			batch_size = min(glb_chunk_sz, target - self.fetched)
1141			self.FetchBatch(batch_size)
1142		self.fetching_done.value = True
1143		self.fetched_event.set()
1144
1145def SQLFetcherFn(*x):
1146	process = SQLFetcherProcess(*x)
1147	process.Run()
1148
1149# SQL data fetcher
1150
1151class SQLFetcher(QObject):
1152
1153	done = Signal(object)
1154
1155	def __init__(self, glb, sql, prep, process_data, parent=None):
1156		super(SQLFetcher, self).__init__(parent)
1157		self.process_data = process_data
1158		self.more = True
1159		self.target = 0
1160		self.last_target = 0
1161		self.fetched = 0
1162		self.buffer_size = 16 * 1024 * 1024
1163		self.buffer = Array(c_char, self.buffer_size, lock=False)
1164		self.head = Value(c_longlong)
1165		self.tail = Value(c_longlong)
1166		self.local_tail = 0
1167		self.fetch_count = Value(c_longlong)
1168		self.fetching_done = Value(c_bool)
1169		self.last_count = 0
1170		self.process_target = Value(c_longlong)
1171		self.wait_event = Event()
1172		self.fetched_event = Event()
1173		glb.AddInstanceToShutdownOnExit(self)
1174		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))
1175		self.process.start()
1176		self.thread = Thread(self.Thread)
1177		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1178		self.thread.start()
1179
1180	def Shutdown(self):
1181		# Tell the thread and process to exit
1182		self.process_target.value = -1
1183		self.wait_event.set()
1184		self.more = False
1185		self.fetching_done.value = True
1186		self.fetched_event.set()
1187
1188	def Thread(self):
1189		if not self.more:
1190			return True, 0
1191		while True:
1192			self.fetched_event.clear()
1193			fetch_count = self.fetch_count.value
1194			if fetch_count != self.last_count:
1195				break
1196			if self.fetching_done.value:
1197				self.more = False
1198				return True, 0
1199			self.fetched_event.wait()
1200		count = fetch_count - self.last_count
1201		self.last_count = fetch_count
1202		self.fetched += count
1203		return False, count
1204
1205	def Fetch(self, nr):
1206		if not self.more:
1207			# -1 inidcates there are no more
1208			return -1
1209		result = self.fetched
1210		extra = result + nr - self.target
1211		if extra > 0:
1212			self.target += extra
1213			# process_target < 0 indicates shutting down
1214			if self.process_target.value >= 0:
1215				self.process_target.value = self.target
1216			self.wait_event.set()
1217		return result
1218
1219	def RemoveFromBuffer(self):
1220		pos = self.local_tail
1221		if len(self.buffer) - pos < glb_nsz:
1222			pos = 0
1223		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
1224		if n == 0:
1225			pos = 0
1226			n = pickle.loads(self.buffer[0 : glb_nsz])
1227		pos += glb_nsz
1228		obj = pickle.loads(self.buffer[pos : pos + n])
1229		self.local_tail = pos + n
1230		return obj
1231
1232	def ProcessData(self, count):
1233		for i in xrange(count):
1234			obj = self.RemoveFromBuffer()
1235			self.process_data(obj)
1236		self.tail.value = self.local_tail
1237		self.wait_event.set()
1238		self.done.emit(count)
1239
1240# Fetch more records bar
1241
1242class FetchMoreRecordsBar():
1243
1244	def __init__(self, model, parent):
1245		self.model = model
1246
1247		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1248		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1249
1250		self.fetch_count = QSpinBox()
1251		self.fetch_count.setRange(1, 1000000)
1252		self.fetch_count.setValue(10)
1253		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1254
1255		self.fetch = QPushButton("Go!")
1256		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1257		self.fetch.released.connect(self.FetchMoreRecords)
1258
1259		self.progress = QProgressBar()
1260		self.progress.setRange(0, 100)
1261		self.progress.hide()
1262
1263		self.done_label = QLabel("All records fetched")
1264		self.done_label.hide()
1265
1266		self.spacer = QLabel("")
1267
1268		self.close_button = QToolButton()
1269		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1270		self.close_button.released.connect(self.Deactivate)
1271
1272		self.hbox = QHBoxLayout()
1273		self.hbox.setContentsMargins(0, 0, 0, 0)
1274
1275		self.hbox.addWidget(self.label)
1276		self.hbox.addWidget(self.fetch_count)
1277		self.hbox.addWidget(self.fetch)
1278		self.hbox.addWidget(self.spacer)
1279		self.hbox.addWidget(self.progress)
1280		self.hbox.addWidget(self.done_label)
1281		self.hbox.addWidget(self.close_button)
1282
1283		self.bar = QWidget()
1284		self.bar.setLayout(self.hbox);
1285		self.bar.show()
1286
1287		self.in_progress = False
1288		self.model.progress.connect(self.Progress)
1289
1290		self.done = False
1291
1292		if not model.HasMoreRecords():
1293			self.Done()
1294
1295	def Widget(self):
1296		return self.bar
1297
1298	def Activate(self):
1299		self.bar.show()
1300		self.fetch.setFocus()
1301
1302	def Deactivate(self):
1303		self.bar.hide()
1304
1305	def Enable(self, enable):
1306		self.fetch.setEnabled(enable)
1307		self.fetch_count.setEnabled(enable)
1308
1309	def Busy(self):
1310		self.Enable(False)
1311		self.fetch.hide()
1312		self.spacer.hide()
1313		self.progress.show()
1314
1315	def Idle(self):
1316		self.in_progress = False
1317		self.Enable(True)
1318		self.progress.hide()
1319		self.fetch.show()
1320		self.spacer.show()
1321
1322	def Target(self):
1323		return self.fetch_count.value() * glb_chunk_sz
1324
1325	def Done(self):
1326		self.done = True
1327		self.Idle()
1328		self.label.hide()
1329		self.fetch_count.hide()
1330		self.fetch.hide()
1331		self.spacer.hide()
1332		self.done_label.show()
1333
1334	def Progress(self, count):
1335		if self.in_progress:
1336			if count:
1337				percent = ((count - self.start) * 100) / self.Target()
1338				if percent >= 100:
1339					self.Idle()
1340				else:
1341					self.progress.setValue(percent)
1342		if not count:
1343			# Count value of zero means no more records
1344			self.Done()
1345
1346	def FetchMoreRecords(self):
1347		if self.done:
1348			return
1349		self.progress.setValue(0)
1350		self.Busy()
1351		self.in_progress = True
1352		self.start = self.model.FetchMoreRecords(self.Target())
1353
1354# Brance data model level two item
1355
1356class BranchLevelTwoItem():
1357
1358	def __init__(self, row, text, parent_item):
1359		self.row = row
1360		self.parent_item = parent_item
1361		self.data = [""] * 8
1362		self.data[7] = text
1363		self.level = 2
1364
1365	def getParentItem(self):
1366		return self.parent_item
1367
1368	def getRow(self):
1369		return self.row
1370
1371	def childCount(self):
1372		return 0
1373
1374	def hasChildren(self):
1375		return False
1376
1377	def getData(self, column):
1378		return self.data[column]
1379
1380# Brance data model level one item
1381
1382class BranchLevelOneItem():
1383
1384	def __init__(self, glb, row, data, parent_item):
1385		self.glb = glb
1386		self.row = row
1387		self.parent_item = parent_item
1388		self.child_count = 0
1389		self.child_items = []
1390		self.data = data[1:]
1391		self.dbid = data[0]
1392		self.level = 1
1393		self.query_done = False
1394
1395	def getChildItem(self, row):
1396		return self.child_items[row]
1397
1398	def getParentItem(self):
1399		return self.parent_item
1400
1401	def getRow(self):
1402		return self.row
1403
1404	def Select(self):
1405		self.query_done = True
1406
1407		if not self.glb.have_disassembler:
1408			return
1409
1410		query = QSqlQuery(self.glb.db)
1411
1412		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1413				  " FROM samples"
1414				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1415				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1416				  " WHERE samples.id = " + str(self.dbid))
1417		if not query.next():
1418			return
1419		cpu = query.value(0)
1420		dso = query.value(1)
1421		sym = query.value(2)
1422		if dso == 0 or sym == 0:
1423			return
1424		off = query.value(3)
1425		short_name = query.value(4)
1426		long_name = query.value(5)
1427		build_id = query.value(6)
1428		sym_start = query.value(7)
1429		ip = query.value(8)
1430
1431		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1432				  " FROM samples"
1433				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1434				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1435				  " ORDER BY samples.id"
1436				  " LIMIT 1")
1437		if not query.next():
1438			return
1439		if query.value(0) != dso:
1440			# Cannot disassemble from one dso to another
1441			return
1442		bsym = query.value(1)
1443		boff = query.value(2)
1444		bsym_start = query.value(3)
1445		if bsym == 0:
1446			return
1447		tot = bsym_start + boff + 1 - sym_start - off
1448		if tot <= 0 or tot > 16384:
1449			return
1450
1451		inst = self.glb.disassembler.Instruction()
1452		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1453		if not f:
1454			return
1455		mode = 0 if Is64Bit(f) else 1
1456		self.glb.disassembler.SetMode(inst, mode)
1457
1458		buf_sz = tot + 16
1459		buf = create_string_buffer(tot + 16)
1460		f.seek(sym_start + off)
1461		buf.value = f.read(buf_sz)
1462		buf_ptr = addressof(buf)
1463		i = 0
1464		while tot > 0:
1465			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1466			if cnt:
1467				byte_str = tohex(ip).rjust(16)
1468				for k in xrange(cnt):
1469					byte_str += " %02x" % ord(buf[i])
1470					i += 1
1471				while k < 15:
1472					byte_str += "   "
1473					k += 1
1474				self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
1475				self.child_count += 1
1476			else:
1477				return
1478			buf_ptr += cnt
1479			tot -= cnt
1480			buf_sz -= cnt
1481			ip += cnt
1482
1483	def childCount(self):
1484		if not self.query_done:
1485			self.Select()
1486			if not self.child_count:
1487				return -1
1488		return self.child_count
1489
1490	def hasChildren(self):
1491		if not self.query_done:
1492			return True
1493		return self.child_count > 0
1494
1495	def getData(self, column):
1496		return self.data[column]
1497
1498# Brance data model root item
1499
1500class BranchRootItem():
1501
1502	def __init__(self):
1503		self.child_count = 0
1504		self.child_items = []
1505		self.level = 0
1506
1507	def getChildItem(self, row):
1508		return self.child_items[row]
1509
1510	def getParentItem(self):
1511		return None
1512
1513	def getRow(self):
1514		return 0
1515
1516	def childCount(self):
1517		return self.child_count
1518
1519	def hasChildren(self):
1520		return self.child_count > 0
1521
1522	def getData(self, column):
1523		return ""
1524
1525# Branch data preparation
1526
1527def BranchDataPrep(query):
1528	data = []
1529	for i in xrange(0, 8):
1530		data.append(query.value(i))
1531	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1532			" (" + dsoname(query.value(11)) + ")" + " -> " +
1533			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1534			" (" + dsoname(query.value(15)) + ")")
1535	return data
1536
1537def BranchDataPrepWA(query):
1538	data = []
1539	data.append(query.value(0))
1540	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1541	data.append("{:>19}".format(query.value(1)))
1542	for i in xrange(2, 8):
1543		data.append(query.value(i))
1544	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1545			" (" + dsoname(query.value(11)) + ")" + " -> " +
1546			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1547			" (" + dsoname(query.value(15)) + ")")
1548	return data
1549
1550# Branch data model
1551
1552class BranchModel(TreeModel):
1553
1554	progress = Signal(object)
1555
1556	def __init__(self, glb, event_id, where_clause, parent=None):
1557		super(BranchModel, self).__init__(glb, parent)
1558		self.event_id = event_id
1559		self.more = True
1560		self.populated = 0
1561		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1562			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1563			" ip, symbols.name, sym_offset, dsos.short_name,"
1564			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1565			" FROM samples"
1566			" INNER JOIN comms ON comm_id = comms.id"
1567			" INNER JOIN threads ON thread_id = threads.id"
1568			" INNER JOIN branch_types ON branch_type = branch_types.id"
1569			" INNER JOIN symbols ON symbol_id = symbols.id"
1570			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1571			" INNER JOIN dsos ON samples.dso_id = dsos.id"
1572			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1573			" WHERE samples.id > $$last_id$$" + where_clause +
1574			" AND evsel_id = " + str(self.event_id) +
1575			" ORDER BY samples.id"
1576			" LIMIT " + str(glb_chunk_sz))
1577		if pyside_version_1 and sys.version_info[0] == 3:
1578			prep = BranchDataPrepWA
1579		else:
1580			prep = BranchDataPrep
1581		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
1582		self.fetcher.done.connect(self.Update)
1583		self.fetcher.Fetch(glb_chunk_sz)
1584
1585	def GetRoot(self):
1586		return BranchRootItem()
1587
1588	def columnCount(self, parent=None):
1589		return 8
1590
1591	def columnHeader(self, column):
1592		return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1593
1594	def columnFont(self, column):
1595		if column != 7:
1596			return None
1597		return QFont("Monospace")
1598
1599	def DisplayData(self, item, index):
1600		if item.level == 1:
1601			self.FetchIfNeeded(item.row)
1602		return item.getData(index.column())
1603
1604	def AddSample(self, data):
1605		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1606		self.root.child_items.append(child)
1607		self.populated += 1
1608
1609	def Update(self, fetched):
1610		if not fetched:
1611			self.more = False
1612			self.progress.emit(0)
1613		child_count = self.root.child_count
1614		count = self.populated - child_count
1615		if count > 0:
1616			parent = QModelIndex()
1617			self.beginInsertRows(parent, child_count, child_count + count - 1)
1618			self.insertRows(child_count, count, parent)
1619			self.root.child_count += count
1620			self.endInsertRows()
1621			self.progress.emit(self.root.child_count)
1622
1623	def FetchMoreRecords(self, count):
1624		current = self.root.child_count
1625		if self.more:
1626			self.fetcher.Fetch(count)
1627		else:
1628			self.progress.emit(0)
1629		return current
1630
1631	def HasMoreRecords(self):
1632		return self.more
1633
1634# Report Variables
1635
1636class ReportVars():
1637
1638	def __init__(self, name = "", where_clause = "", limit = ""):
1639		self.name = name
1640		self.where_clause = where_clause
1641		self.limit = limit
1642
1643	def UniqueId(self):
1644		return str(self.where_clause + ";" + self.limit)
1645
1646# Branch window
1647
1648class BranchWindow(QMdiSubWindow):
1649
1650	def __init__(self, glb, event_id, report_vars, parent=None):
1651		super(BranchWindow, self).__init__(parent)
1652
1653		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
1654
1655		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1656
1657		self.view = QTreeView()
1658		self.view.setUniformRowHeights(True)
1659		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1660		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1661		self.view.setModel(self.model)
1662
1663		self.ResizeColumnsToContents()
1664
1665		self.context_menu = TreeContextMenu(self.view)
1666
1667		self.find_bar = FindBar(self, self, True)
1668
1669		self.finder = ChildDataItemFinder(self.model.root)
1670
1671		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1672
1673		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1674
1675		self.setWidget(self.vbox.Widget())
1676
1677		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1678
1679	def ResizeColumnToContents(self, column, n):
1680		# Using the view's resizeColumnToContents() here is extrememly slow
1681		# so implement a crude alternative
1682		mm = "MM" if column else "MMMM"
1683		font = self.view.font()
1684		metrics = QFontMetrics(font)
1685		max = 0
1686		for row in xrange(n):
1687			val = self.model.root.child_items[row].data[column]
1688			len = metrics.width(str(val) + mm)
1689			max = len if len > max else max
1690		val = self.model.columnHeader(column)
1691		len = metrics.width(str(val) + mm)
1692		max = len if len > max else max
1693		self.view.setColumnWidth(column, max)
1694
1695	def ResizeColumnsToContents(self):
1696		n = min(self.model.root.child_count, 100)
1697		if n < 1:
1698			# No data yet, so connect a signal to notify when there is
1699			self.model.rowsInserted.connect(self.UpdateColumnWidths)
1700			return
1701		columns = self.model.columnCount()
1702		for i in xrange(columns):
1703			self.ResizeColumnToContents(i, n)
1704
1705	def UpdateColumnWidths(self, *x):
1706		# This only needs to be done once, so disconnect the signal now
1707		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1708		self.ResizeColumnsToContents()
1709
1710	def Find(self, value, direction, pattern, context):
1711		self.view.setFocus()
1712		self.find_bar.Busy()
1713		self.finder.Find(value, direction, pattern, context, self.FindDone)
1714
1715	def FindDone(self, row):
1716		self.find_bar.Idle()
1717		if row >= 0:
1718			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1719		else:
1720			self.find_bar.NotFound()
1721
1722# Line edit data item
1723
1724class LineEditDataItem(object):
1725
1726	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1727		self.glb = glb
1728		self.label = label
1729		self.placeholder_text = placeholder_text
1730		self.parent = parent
1731		self.id = id
1732
1733		self.value = default
1734
1735		self.widget = QLineEdit(default)
1736		self.widget.editingFinished.connect(self.Validate)
1737		self.widget.textChanged.connect(self.Invalidate)
1738		self.red = False
1739		self.error = ""
1740		self.validated = True
1741
1742		if placeholder_text:
1743			self.widget.setPlaceholderText(placeholder_text)
1744
1745	def TurnTextRed(self):
1746		if not self.red:
1747			palette = QPalette()
1748			palette.setColor(QPalette.Text,Qt.red)
1749			self.widget.setPalette(palette)
1750			self.red = True
1751
1752	def TurnTextNormal(self):
1753		if self.red:
1754			palette = QPalette()
1755			self.widget.setPalette(palette)
1756			self.red = False
1757
1758	def InvalidValue(self, value):
1759		self.value = ""
1760		self.TurnTextRed()
1761		self.error = self.label + " invalid value '" + value + "'"
1762		self.parent.ShowMessage(self.error)
1763
1764	def Invalidate(self):
1765		self.validated = False
1766
1767	def DoValidate(self, input_string):
1768		self.value = input_string.strip()
1769
1770	def Validate(self):
1771		self.validated = True
1772		self.error = ""
1773		self.TurnTextNormal()
1774		self.parent.ClearMessage()
1775		input_string = self.widget.text()
1776		if not len(input_string.strip()):
1777			self.value = ""
1778			return
1779		self.DoValidate(input_string)
1780
1781	def IsValid(self):
1782		if not self.validated:
1783			self.Validate()
1784		if len(self.error):
1785			self.parent.ShowMessage(self.error)
1786			return False
1787		return True
1788
1789	def IsNumber(self, value):
1790		try:
1791			x = int(value)
1792		except:
1793			x = 0
1794		return str(x) == value
1795
1796# Non-negative integer ranges dialog data item
1797
1798class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1799
1800	def __init__(self, glb, label, placeholder_text, column_name, parent):
1801		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1802
1803		self.column_name = column_name
1804
1805	def DoValidate(self, input_string):
1806		singles = []
1807		ranges = []
1808		for value in [x.strip() for x in input_string.split(",")]:
1809			if "-" in value:
1810				vrange = value.split("-")
1811				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1812					return self.InvalidValue(value)
1813				ranges.append(vrange)
1814			else:
1815				if not self.IsNumber(value):
1816					return self.InvalidValue(value)
1817				singles.append(value)
1818		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1819		if len(singles):
1820			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1821		self.value = " OR ".join(ranges)
1822
1823# Positive integer dialog data item
1824
1825class PositiveIntegerDataItem(LineEditDataItem):
1826
1827	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1828		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1829
1830	def DoValidate(self, input_string):
1831		if not self.IsNumber(input_string.strip()):
1832			return self.InvalidValue(input_string)
1833		value = int(input_string.strip())
1834		if value <= 0:
1835			return self.InvalidValue(input_string)
1836		self.value = str(value)
1837
1838# Dialog data item converted and validated using a SQL table
1839
1840class SQLTableDataItem(LineEditDataItem):
1841
1842	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1843		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
1844
1845		self.table_name = table_name
1846		self.match_column = match_column
1847		self.column_name1 = column_name1
1848		self.column_name2 = column_name2
1849
1850	def ValueToIds(self, value):
1851		ids = []
1852		query = QSqlQuery(self.glb.db)
1853		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1854		ret = query.exec_(stmt)
1855		if ret:
1856			while query.next():
1857				ids.append(str(query.value(0)))
1858		return ids
1859
1860	def DoValidate(self, input_string):
1861		all_ids = []
1862		for value in [x.strip() for x in input_string.split(",")]:
1863			ids = self.ValueToIds(value)
1864			if len(ids):
1865				all_ids.extend(ids)
1866			else:
1867				return self.InvalidValue(value)
1868		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1869		if self.column_name2:
1870			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1871
1872# Sample time ranges dialog data item converted and validated using 'samples' SQL table
1873
1874class SampleTimeRangesDataItem(LineEditDataItem):
1875
1876	def __init__(self, glb, label, placeholder_text, column_name, parent):
1877		self.column_name = column_name
1878
1879		self.last_id = 0
1880		self.first_time = 0
1881		self.last_time = 2 ** 64
1882
1883		query = QSqlQuery(glb.db)
1884		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1885		if query.next():
1886			self.last_id = int(query.value(0))
1887			self.last_time = int(query.value(1))
1888		QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1889		if query.next():
1890			self.first_time = int(query.value(0))
1891		if placeholder_text:
1892			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1893
1894		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1895
1896	def IdBetween(self, query, lower_id, higher_id, order):
1897		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1898		if query.next():
1899			return True, int(query.value(0))
1900		else:
1901			return False, 0
1902
1903	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1904		query = QSqlQuery(self.glb.db)
1905		while True:
1906			next_id = int((lower_id + higher_id) / 2)
1907			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1908			if not query.next():
1909				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1910				if not ok:
1911					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1912					if not ok:
1913						return str(higher_id)
1914				next_id = dbid
1915				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1916			next_time = int(query.value(0))
1917			if get_floor:
1918				if target_time > next_time:
1919					lower_id = next_id
1920				else:
1921					higher_id = next_id
1922				if higher_id <= lower_id + 1:
1923					return str(higher_id)
1924			else:
1925				if target_time >= next_time:
1926					lower_id = next_id
1927				else:
1928					higher_id = next_id
1929				if higher_id <= lower_id + 1:
1930					return str(lower_id)
1931
1932	def ConvertRelativeTime(self, val):
1933		mult = 1
1934		suffix = val[-2:]
1935		if suffix == "ms":
1936			mult = 1000000
1937		elif suffix == "us":
1938			mult = 1000
1939		elif suffix == "ns":
1940			mult = 1
1941		else:
1942			return val
1943		val = val[:-2].strip()
1944		if not self.IsNumber(val):
1945			return val
1946		val = int(val) * mult
1947		if val >= 0:
1948			val += self.first_time
1949		else:
1950			val += self.last_time
1951		return str(val)
1952
1953	def ConvertTimeRange(self, vrange):
1954		if vrange[0] == "":
1955			vrange[0] = str(self.first_time)
1956		if vrange[1] == "":
1957			vrange[1] = str(self.last_time)
1958		vrange[0] = self.ConvertRelativeTime(vrange[0])
1959		vrange[1] = self.ConvertRelativeTime(vrange[1])
1960		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1961			return False
1962		beg_range = max(int(vrange[0]), self.first_time)
1963		end_range = min(int(vrange[1]), self.last_time)
1964		if beg_range > self.last_time or end_range < self.first_time:
1965			return False
1966		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
1967		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
1968		return True
1969
1970	def AddTimeRange(self, value, ranges):
1971		n = value.count("-")
1972		if n == 1:
1973			pass
1974		elif n == 2:
1975			if value.split("-")[1].strip() == "":
1976				n = 1
1977		elif n == 3:
1978			n = 2
1979		else:
1980			return False
1981		pos = findnth(value, "-", n)
1982		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
1983		if self.ConvertTimeRange(vrange):
1984			ranges.append(vrange)
1985			return True
1986		return False
1987
1988	def DoValidate(self, input_string):
1989		ranges = []
1990		for value in [x.strip() for x in input_string.split(",")]:
1991			if not self.AddTimeRange(value, ranges):
1992				return self.InvalidValue(value)
1993		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1994		self.value = " OR ".join(ranges)
1995
1996# Report Dialog Base
1997
1998class ReportDialogBase(QDialog):
1999
2000	def __init__(self, glb, title, items, partial, parent=None):
2001		super(ReportDialogBase, self).__init__(parent)
2002
2003		self.glb = glb
2004
2005		self.report_vars = ReportVars()
2006
2007		self.setWindowTitle(title)
2008		self.setMinimumWidth(600)
2009
2010		self.data_items = [x(glb, self) for x in items]
2011
2012		self.partial = partial
2013
2014		self.grid = QGridLayout()
2015
2016		for row in xrange(len(self.data_items)):
2017			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2018			self.grid.addWidget(self.data_items[row].widget, row, 1)
2019
2020		self.status = QLabel()
2021
2022		self.ok_button = QPushButton("Ok", self)
2023		self.ok_button.setDefault(True)
2024		self.ok_button.released.connect(self.Ok)
2025		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2026
2027		self.cancel_button = QPushButton("Cancel", self)
2028		self.cancel_button.released.connect(self.reject)
2029		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2030
2031		self.hbox = QHBoxLayout()
2032		#self.hbox.addStretch()
2033		self.hbox.addWidget(self.status)
2034		self.hbox.addWidget(self.ok_button)
2035		self.hbox.addWidget(self.cancel_button)
2036
2037		self.vbox = QVBoxLayout()
2038		self.vbox.addLayout(self.grid)
2039		self.vbox.addLayout(self.hbox)
2040
2041		self.setLayout(self.vbox);
2042
2043	def Ok(self):
2044		vars = self.report_vars
2045		for d in self.data_items:
2046			if d.id == "REPORTNAME":
2047				vars.name = d.value
2048		if not vars.name:
2049			self.ShowMessage("Report name is required")
2050			return
2051		for d in self.data_items:
2052			if not d.IsValid():
2053				return
2054		for d in self.data_items[1:]:
2055			if d.id == "LIMIT":
2056				vars.limit = d.value
2057			elif len(d.value):
2058				if len(vars.where_clause):
2059					vars.where_clause += " AND "
2060				vars.where_clause += d.value
2061		if len(vars.where_clause):
2062			if self.partial:
2063				vars.where_clause = " AND ( " + vars.where_clause + " ) "
2064			else:
2065				vars.where_clause = " WHERE " + vars.where_clause + " "
2066		self.accept()
2067
2068	def ShowMessage(self, msg):
2069		self.status.setText("<font color=#FF0000>" + msg)
2070
2071	def ClearMessage(self):
2072		self.status.setText("")
2073
2074# Selected branch report creation dialog
2075
2076class SelectedBranchDialog(ReportDialogBase):
2077
2078	def __init__(self, glb, parent=None):
2079		title = "Selected Branches"
2080		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2081			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2082			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2083			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2084			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2085			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2086			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2087			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2088			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2089		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2090
2091# Event list
2092
2093def GetEventList(db):
2094	events = []
2095	query = QSqlQuery(db)
2096	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2097	while query.next():
2098		events.append(query.value(0))
2099	return events
2100
2101# Is a table selectable
2102
2103def IsSelectable(db, table, sql = ""):
2104	query = QSqlQuery(db)
2105	try:
2106		QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1")
2107	except:
2108		return False
2109	return True
2110
2111# SQL table data model item
2112
2113class SQLTableItem():
2114
2115	def __init__(self, row, data):
2116		self.row = row
2117		self.data = data
2118
2119	def getData(self, column):
2120		return self.data[column]
2121
2122# SQL table data model
2123
2124class SQLTableModel(TableModel):
2125
2126	progress = Signal(object)
2127
2128	def __init__(self, glb, sql, column_headers, parent=None):
2129		super(SQLTableModel, self).__init__(parent)
2130		self.glb = glb
2131		self.more = True
2132		self.populated = 0
2133		self.column_headers = column_headers
2134		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
2135		self.fetcher.done.connect(self.Update)
2136		self.fetcher.Fetch(glb_chunk_sz)
2137
2138	def DisplayData(self, item, index):
2139		self.FetchIfNeeded(item.row)
2140		return item.getData(index.column())
2141
2142	def AddSample(self, data):
2143		child = SQLTableItem(self.populated, data)
2144		self.child_items.append(child)
2145		self.populated += 1
2146
2147	def Update(self, fetched):
2148		if not fetched:
2149			self.more = False
2150			self.progress.emit(0)
2151		child_count = self.child_count
2152		count = self.populated - child_count
2153		if count > 0:
2154			parent = QModelIndex()
2155			self.beginInsertRows(parent, child_count, child_count + count - 1)
2156			self.insertRows(child_count, count, parent)
2157			self.child_count += count
2158			self.endInsertRows()
2159			self.progress.emit(self.child_count)
2160
2161	def FetchMoreRecords(self, count):
2162		current = self.child_count
2163		if self.more:
2164			self.fetcher.Fetch(count)
2165		else:
2166			self.progress.emit(0)
2167		return current
2168
2169	def HasMoreRecords(self):
2170		return self.more
2171
2172	def columnCount(self, parent=None):
2173		return len(self.column_headers)
2174
2175	def columnHeader(self, column):
2176		return self.column_headers[column]
2177
2178	def SQLTableDataPrep(self, query, count):
2179		data = []
2180		for i in xrange(count):
2181			data.append(query.value(i))
2182		return data
2183
2184# SQL automatic table data model
2185
2186class SQLAutoTableModel(SQLTableModel):
2187
2188	def __init__(self, glb, table_name, parent=None):
2189		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2190		if table_name == "comm_threads_view":
2191			# For now, comm_threads_view has no id column
2192			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2193		column_headers = []
2194		query = QSqlQuery(glb.db)
2195		if glb.dbref.is_sqlite3:
2196			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2197			while query.next():
2198				column_headers.append(query.value(1))
2199			if table_name == "sqlite_master":
2200				sql = "SELECT * FROM " + table_name
2201		else:
2202			if table_name[:19] == "information_schema.":
2203				sql = "SELECT * FROM " + table_name
2204				select_table_name = table_name[19:]
2205				schema = "information_schema"
2206			else:
2207				select_table_name = table_name
2208				schema = "public"
2209			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2210			while query.next():
2211				column_headers.append(query.value(0))
2212		if pyside_version_1 and sys.version_info[0] == 3:
2213			if table_name == "samples_view":
2214				self.SQLTableDataPrep = self.samples_view_DataPrep
2215			if table_name == "samples":
2216				self.SQLTableDataPrep = self.samples_DataPrep
2217		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2218
2219	def samples_view_DataPrep(self, query, count):
2220		data = []
2221		data.append(query.value(0))
2222		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2223		data.append("{:>19}".format(query.value(1)))
2224		for i in xrange(2, count):
2225			data.append(query.value(i))
2226		return data
2227
2228	def samples_DataPrep(self, query, count):
2229		data = []
2230		for i in xrange(9):
2231			data.append(query.value(i))
2232		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2233		data.append("{:>19}".format(query.value(9)))
2234		for i in xrange(10, count):
2235			data.append(query.value(i))
2236		return data
2237
2238# Base class for custom ResizeColumnsToContents
2239
2240class ResizeColumnsToContentsBase(QObject):
2241
2242	def __init__(self, parent=None):
2243		super(ResizeColumnsToContentsBase, self).__init__(parent)
2244
2245	def ResizeColumnToContents(self, column, n):
2246		# Using the view's resizeColumnToContents() here is extrememly slow
2247		# so implement a crude alternative
2248		font = self.view.font()
2249		metrics = QFontMetrics(font)
2250		max = 0
2251		for row in xrange(n):
2252			val = self.data_model.child_items[row].data[column]
2253			len = metrics.width(str(val) + "MM")
2254			max = len if len > max else max
2255		val = self.data_model.columnHeader(column)
2256		len = metrics.width(str(val) + "MM")
2257		max = len if len > max else max
2258		self.view.setColumnWidth(column, max)
2259
2260	def ResizeColumnsToContents(self):
2261		n = min(self.data_model.child_count, 100)
2262		if n < 1:
2263			# No data yet, so connect a signal to notify when there is
2264			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2265			return
2266		columns = self.data_model.columnCount()
2267		for i in xrange(columns):
2268			self.ResizeColumnToContents(i, n)
2269
2270	def UpdateColumnWidths(self, *x):
2271		# This only needs to be done once, so disconnect the signal now
2272		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2273		self.ResizeColumnsToContents()
2274
2275# Convert value to CSV
2276
2277def ToCSValue(val):
2278	if '"' in val:
2279		val = val.replace('"', '""')
2280	if "," in val or '"' in val:
2281		val = '"' + val + '"'
2282	return val
2283
2284# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2285
2286glb_max_cols = 1000
2287
2288def RowColumnKey(a):
2289	return a.row() * glb_max_cols + a.column()
2290
2291# Copy selected table cells to clipboard
2292
2293def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2294	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2295	idx_cnt = len(indexes)
2296	if not idx_cnt:
2297		return
2298	if idx_cnt == 1:
2299		with_hdr=False
2300	min_row = indexes[0].row()
2301	max_row = indexes[0].row()
2302	min_col = indexes[0].column()
2303	max_col = indexes[0].column()
2304	for i in indexes:
2305		min_row = min(min_row, i.row())
2306		max_row = max(max_row, i.row())
2307		min_col = min(min_col, i.column())
2308		max_col = max(max_col, i.column())
2309	if max_col > glb_max_cols:
2310		raise RuntimeError("glb_max_cols is too low")
2311	max_width = [0] * (1 + max_col - min_col)
2312	for i in indexes:
2313		c = i.column() - min_col
2314		max_width[c] = max(max_width[c], len(str(i.data())))
2315	text = ""
2316	pad = ""
2317	sep = ""
2318	if with_hdr:
2319		model = indexes[0].model()
2320		for col in range(min_col, max_col + 1):
2321			val = model.headerData(col, Qt.Horizontal)
2322			if as_csv:
2323				text += sep + ToCSValue(val)
2324				sep = ","
2325			else:
2326				c = col - min_col
2327				max_width[c] = max(max_width[c], len(val))
2328				width = max_width[c]
2329				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2330				if align & Qt.AlignRight:
2331					val = val.rjust(width)
2332				text += pad + sep + val
2333				pad = " " * (width - len(val))
2334				sep = "  "
2335		text += "\n"
2336		pad = ""
2337		sep = ""
2338	last_row = min_row
2339	for i in indexes:
2340		if i.row() > last_row:
2341			last_row = i.row()
2342			text += "\n"
2343			pad = ""
2344			sep = ""
2345		if as_csv:
2346			text += sep + ToCSValue(str(i.data()))
2347			sep = ","
2348		else:
2349			width = max_width[i.column() - min_col]
2350			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2351				val = str(i.data()).rjust(width)
2352			else:
2353				val = str(i.data())
2354			text += pad + sep + val
2355			pad = " " * (width - len(val))
2356			sep = "  "
2357	QApplication.clipboard().setText(text)
2358
2359def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2360	indexes = view.selectedIndexes()
2361	if not len(indexes):
2362		return
2363
2364	selection = view.selectionModel()
2365
2366	first = None
2367	for i in indexes:
2368		above = view.indexAbove(i)
2369		if not selection.isSelected(above):
2370			first = i
2371			break
2372
2373	if first is None:
2374		raise RuntimeError("CopyTreeCellsToClipboard internal error")
2375
2376	model = first.model()
2377	row_cnt = 0
2378	col_cnt = model.columnCount(first)
2379	max_width = [0] * col_cnt
2380
2381	indent_sz = 2
2382	indent_str = " " * indent_sz
2383
2384	expanded_mark_sz = 2
2385	if sys.version_info[0] == 3:
2386		expanded_mark = "\u25BC "
2387		not_expanded_mark = "\u25B6 "
2388	else:
2389		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2390		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2391	leaf_mark = "  "
2392
2393	if not as_csv:
2394		pos = first
2395		while True:
2396			row_cnt += 1
2397			row = pos.row()
2398			for c in range(col_cnt):
2399				i = pos.sibling(row, c)
2400				if c:
2401					n = len(str(i.data()))
2402				else:
2403					n = len(str(i.data()).strip())
2404					n += (i.internalPointer().level - 1) * indent_sz
2405					n += expanded_mark_sz
2406				max_width[c] = max(max_width[c], n)
2407			pos = view.indexBelow(pos)
2408			if not selection.isSelected(pos):
2409				break
2410
2411	text = ""
2412	pad = ""
2413	sep = ""
2414	if with_hdr:
2415		for c in range(col_cnt):
2416			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2417			if as_csv:
2418				text += sep + ToCSValue(val)
2419				sep = ","
2420			else:
2421				max_width[c] = max(max_width[c], len(val))
2422				width = max_width[c]
2423				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2424				if align & Qt.AlignRight:
2425					val = val.rjust(width)
2426				text += pad + sep + val
2427				pad = " " * (width - len(val))
2428				sep = "   "
2429		text += "\n"
2430		pad = ""
2431		sep = ""
2432
2433	pos = first
2434	while True:
2435		row = pos.row()
2436		for c in range(col_cnt):
2437			i = pos.sibling(row, c)
2438			val = str(i.data())
2439			if not c:
2440				if model.hasChildren(i):
2441					if view.isExpanded(i):
2442						mark = expanded_mark
2443					else:
2444						mark = not_expanded_mark
2445				else:
2446					mark = leaf_mark
2447				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2448			if as_csv:
2449				text += sep + ToCSValue(val)
2450				sep = ","
2451			else:
2452				width = max_width[c]
2453				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2454					val = val.rjust(width)
2455				text += pad + sep + val
2456				pad = " " * (width - len(val))
2457				sep = "   "
2458		pos = view.indexBelow(pos)
2459		if not selection.isSelected(pos):
2460			break
2461		text = text.rstrip() + "\n"
2462		pad = ""
2463		sep = ""
2464
2465	QApplication.clipboard().setText(text)
2466
2467def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2468	view.CopyCellsToClipboard(view, as_csv, with_hdr)
2469
2470def CopyCellsToClipboardHdr(view):
2471	CopyCellsToClipboard(view, False, True)
2472
2473def CopyCellsToClipboardCSV(view):
2474	CopyCellsToClipboard(view, True, True)
2475
2476# Context menu
2477
2478class ContextMenu(object):
2479
2480	def __init__(self, view):
2481		self.view = view
2482		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
2483		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
2484
2485	def ShowContextMenu(self, pos):
2486		menu = QMenu(self.view)
2487		self.AddActions(menu)
2488		menu.exec_(self.view.mapToGlobal(pos))
2489
2490	def AddCopy(self, menu):
2491		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
2492		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
2493
2494	def AddActions(self, menu):
2495		self.AddCopy(menu)
2496
2497class TreeContextMenu(ContextMenu):
2498
2499	def __init__(self, view):
2500		super(TreeContextMenu, self).__init__(view)
2501
2502	def AddActions(self, menu):
2503		i = self.view.currentIndex()
2504		text = str(i.data()).strip()
2505		if len(text):
2506			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
2507		self.AddCopy(menu)
2508
2509# Table window
2510
2511class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2512
2513	def __init__(self, glb, table_name, parent=None):
2514		super(TableWindow, self).__init__(parent)
2515
2516		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2517
2518		self.model = QSortFilterProxyModel()
2519		self.model.setSourceModel(self.data_model)
2520
2521		self.view = QTableView()
2522		self.view.setModel(self.model)
2523		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2524		self.view.verticalHeader().setVisible(False)
2525		self.view.sortByColumn(-1, Qt.AscendingOrder)
2526		self.view.setSortingEnabled(True)
2527		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2528		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2529
2530		self.ResizeColumnsToContents()
2531
2532		self.context_menu = ContextMenu(self.view)
2533
2534		self.find_bar = FindBar(self, self, True)
2535
2536		self.finder = ChildDataItemFinder(self.data_model)
2537
2538		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2539
2540		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2541
2542		self.setWidget(self.vbox.Widget())
2543
2544		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2545
2546	def Find(self, value, direction, pattern, context):
2547		self.view.setFocus()
2548		self.find_bar.Busy()
2549		self.finder.Find(value, direction, pattern, context, self.FindDone)
2550
2551	def FindDone(self, row):
2552		self.find_bar.Idle()
2553		if row >= 0:
2554			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2555		else:
2556			self.find_bar.NotFound()
2557
2558# Table list
2559
2560def GetTableList(glb):
2561	tables = []
2562	query = QSqlQuery(glb.db)
2563	if glb.dbref.is_sqlite3:
2564		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2565	else:
2566		QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2567	while query.next():
2568		tables.append(query.value(0))
2569	if glb.dbref.is_sqlite3:
2570		tables.append("sqlite_master")
2571	else:
2572		tables.append("information_schema.tables")
2573		tables.append("information_schema.views")
2574		tables.append("information_schema.columns")
2575	return tables
2576
2577# Top Calls data model
2578
2579class TopCallsModel(SQLTableModel):
2580
2581	def __init__(self, glb, report_vars, parent=None):
2582		text = ""
2583		if not glb.dbref.is_sqlite3:
2584			text = "::text"
2585		limit = ""
2586		if len(report_vars.limit):
2587			limit = " LIMIT " + report_vars.limit
2588		sql = ("SELECT comm, pid, tid, name,"
2589			" CASE"
2590			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2591			" ELSE short_name"
2592			" END AS dso,"
2593			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2594			" CASE"
2595			" WHEN (calls.flags = 1) THEN 'no call'" + text +
2596			" WHEN (calls.flags = 2) THEN 'no return'" + text +
2597			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2598			" ELSE ''" + text +
2599			" END AS flags"
2600			" FROM calls"
2601			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2602			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2603			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
2604			" INNER JOIN comms ON calls.comm_id = comms.id"
2605			" INNER JOIN threads ON calls.thread_id = threads.id" +
2606			report_vars.where_clause +
2607			" ORDER BY elapsed_time DESC" +
2608			limit
2609			)
2610		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2611		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2612		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2613
2614	def columnAlignment(self, column):
2615		return self.alignment[column]
2616
2617# Top Calls report creation dialog
2618
2619class TopCallsDialog(ReportDialogBase):
2620
2621	def __init__(self, glb, parent=None):
2622		title = "Top Calls by Elapsed Time"
2623		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2624			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2625			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2626			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2627			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2628			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2629			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2630			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2631		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2632
2633# Top Calls window
2634
2635class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2636
2637	def __init__(self, glb, report_vars, parent=None):
2638		super(TopCallsWindow, self).__init__(parent)
2639
2640		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2641		self.model = self.data_model
2642
2643		self.view = QTableView()
2644		self.view.setModel(self.model)
2645		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2646		self.view.verticalHeader().setVisible(False)
2647		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2648		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
2649
2650		self.context_menu = ContextMenu(self.view)
2651
2652		self.ResizeColumnsToContents()
2653
2654		self.find_bar = FindBar(self, self, True)
2655
2656		self.finder = ChildDataItemFinder(self.model)
2657
2658		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2659
2660		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2661
2662		self.setWidget(self.vbox.Widget())
2663
2664		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2665
2666	def Find(self, value, direction, pattern, context):
2667		self.view.setFocus()
2668		self.find_bar.Busy()
2669		self.finder.Find(value, direction, pattern, context, self.FindDone)
2670
2671	def FindDone(self, row):
2672		self.find_bar.Idle()
2673		if row >= 0:
2674			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2675		else:
2676			self.find_bar.NotFound()
2677
2678# Action Definition
2679
2680def CreateAction(label, tip, callback, parent=None, shortcut=None):
2681	action = QAction(label, parent)
2682	if shortcut != None:
2683		action.setShortcuts(shortcut)
2684	action.setStatusTip(tip)
2685	action.triggered.connect(callback)
2686	return action
2687
2688# Typical application actions
2689
2690def CreateExitAction(app, parent=None):
2691	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2692
2693# Typical MDI actions
2694
2695def CreateCloseActiveWindowAction(mdi_area):
2696	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2697
2698def CreateCloseAllWindowsAction(mdi_area):
2699	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2700
2701def CreateTileWindowsAction(mdi_area):
2702	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2703
2704def CreateCascadeWindowsAction(mdi_area):
2705	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2706
2707def CreateNextWindowAction(mdi_area):
2708	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2709
2710def CreatePreviousWindowAction(mdi_area):
2711	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2712
2713# Typical MDI window menu
2714
2715class WindowMenu():
2716
2717	def __init__(self, mdi_area, menu):
2718		self.mdi_area = mdi_area
2719		self.window_menu = menu.addMenu("&Windows")
2720		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2721		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2722		self.tile_windows = CreateTileWindowsAction(mdi_area)
2723		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2724		self.next_window = CreateNextWindowAction(mdi_area)
2725		self.previous_window = CreatePreviousWindowAction(mdi_area)
2726		self.window_menu.aboutToShow.connect(self.Update)
2727
2728	def Update(self):
2729		self.window_menu.clear()
2730		sub_window_count = len(self.mdi_area.subWindowList())
2731		have_sub_windows = sub_window_count != 0
2732		self.close_active_window.setEnabled(have_sub_windows)
2733		self.close_all_windows.setEnabled(have_sub_windows)
2734		self.tile_windows.setEnabled(have_sub_windows)
2735		self.cascade_windows.setEnabled(have_sub_windows)
2736		self.next_window.setEnabled(have_sub_windows)
2737		self.previous_window.setEnabled(have_sub_windows)
2738		self.window_menu.addAction(self.close_active_window)
2739		self.window_menu.addAction(self.close_all_windows)
2740		self.window_menu.addSeparator()
2741		self.window_menu.addAction(self.tile_windows)
2742		self.window_menu.addAction(self.cascade_windows)
2743		self.window_menu.addSeparator()
2744		self.window_menu.addAction(self.next_window)
2745		self.window_menu.addAction(self.previous_window)
2746		if sub_window_count == 0:
2747			return
2748		self.window_menu.addSeparator()
2749		nr = 1
2750		for sub_window in self.mdi_area.subWindowList():
2751			label = str(nr) + " " + sub_window.name
2752			if nr < 10:
2753				label = "&" + label
2754			action = self.window_menu.addAction(label)
2755			action.setCheckable(True)
2756			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2757			action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2758			self.window_menu.addAction(action)
2759			nr += 1
2760
2761	def setActiveSubWindow(self, nr):
2762		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2763
2764# Help text
2765
2766glb_help_text = """
2767<h1>Contents</h1>
2768<style>
2769p.c1 {
2770    text-indent: 40px;
2771}
2772p.c2 {
2773    text-indent: 80px;
2774}
2775}
2776</style>
2777<p class=c1><a href=#reports>1. Reports</a></p>
2778<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2779<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2780<p class=c2><a href=#allbranches>1.3 All branches</a></p>
2781<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2782<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2783<p class=c1><a href=#tables>2. Tables</a></p>
2784<h1 id=reports>1. Reports</h1>
2785<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2786The result is a GUI window with a tree representing a context-sensitive
2787call-graph. Expanding a couple of levels of the tree and adjusting column
2788widths to suit will display something like:
2789<pre>
2790                                         Call Graph: pt_example
2791Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
2792v- ls
2793    v- 2638:2638
2794        v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
2795          |- unknown               unknown       1        13198     0.1              1              0.0
2796          >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
2797          >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
2798          v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
2799             >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
2800             >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
2801             >- __libc_csu_init    ls            1        10354     0.1             10              0.0
2802             |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
2803             v- main               ls            1      8182043    99.6         180254             99.9
2804</pre>
2805<h3>Points to note:</h3>
2806<ul>
2807<li>The top level is a command name (comm)</li>
2808<li>The next level is a thread (pid:tid)</li>
2809<li>Subsequent levels are functions</li>
2810<li>'Count' is the number of calls</li>
2811<li>'Time' is the elapsed time until the function returns</li>
2812<li>Percentages are relative to the level above</li>
2813<li>'Branch Count' is the total number of branches for that function and all functions that it calls
2814</ul>
2815<h3>Find</h3>
2816Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2817The pattern matching symbols are ? for any character and * for zero or more characters.
2818<h2 id=calltree>1.2 Call Tree</h2>
2819The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2820Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2821<h2 id=allbranches>1.3 All branches</h2>
2822The All branches report displays all branches in chronological order.
2823Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2824<h3>Disassembly</h3>
2825Open a branch to display disassembly. This only works if:
2826<ol>
2827<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2828<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2829The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2830One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2831or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2832</ol>
2833<h4 id=xed>Intel XED Setup</h4>
2834To use Intel XED, libxed.so must be present.  To build and install libxed.so:
2835<pre>
2836git clone https://github.com/intelxed/mbuild.git mbuild
2837git clone https://github.com/intelxed/xed
2838cd xed
2839./mfile.py --share
2840sudo ./mfile.py --prefix=/usr/local install
2841sudo ldconfig
2842</pre>
2843<h3>Find</h3>
2844Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2845Refer to Python documentation for the regular expression syntax.
2846All columns are searched, but only currently fetched rows are searched.
2847<h2 id=selectedbranches>1.4 Selected branches</h2>
2848This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2849by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2850<h3>1.4.1 Time ranges</h3>
2851The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2852ms, us or ns. Also, negative values are relative to the end of trace.  Examples:
2853<pre>
2854	81073085947329-81073085958238	From 81073085947329 to 81073085958238
2855	100us-200us		From 100us to 200us
2856	10ms-			From 10ms to the end
2857	-100ns			The first 100ns
2858	-10ms-			The last 10ms
2859</pre>
2860N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2861<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2862The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
2863The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2864If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
2865<h1 id=tables>2. Tables</h1>
2866The Tables menu shows all tables and views in the database. Most tables have an associated view
2867which displays the information in a more friendly way. Not all data for large tables is fetched
2868immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2869but that can be slow for large tables.
2870<p>There are also tables of database meta-information.
2871For SQLite3 databases, the sqlite_master table is included.
2872For PostgreSQL databases, information_schema.tables/views/columns are included.
2873<h3>Find</h3>
2874Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2875Refer to Python documentation for the regular expression syntax.
2876All columns are searched, but only currently fetched rows are searched.
2877<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2878will go to the next/previous result in id order, instead of display order.
2879"""
2880
2881# Help window
2882
2883class HelpWindow(QMdiSubWindow):
2884
2885	def __init__(self, glb, parent=None):
2886		super(HelpWindow, self).__init__(parent)
2887
2888		self.text = QTextBrowser()
2889		self.text.setHtml(glb_help_text)
2890		self.text.setReadOnly(True)
2891		self.text.setOpenExternalLinks(True)
2892
2893		self.setWidget(self.text)
2894
2895		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2896
2897# Main window that only displays the help text
2898
2899class HelpOnlyWindow(QMainWindow):
2900
2901	def __init__(self, parent=None):
2902		super(HelpOnlyWindow, self).__init__(parent)
2903
2904		self.setMinimumSize(200, 100)
2905		self.resize(800, 600)
2906		self.setWindowTitle("Exported SQL Viewer Help")
2907		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2908
2909		self.text = QTextBrowser()
2910		self.text.setHtml(glb_help_text)
2911		self.text.setReadOnly(True)
2912		self.text.setOpenExternalLinks(True)
2913
2914		self.setCentralWidget(self.text)
2915
2916# PostqreSQL server version
2917
2918def PostqreSQLServerVersion(db):
2919	query = QSqlQuery(db)
2920	QueryExec(query, "SELECT VERSION()")
2921	if query.next():
2922		v_str = query.value(0)
2923		v_list = v_str.strip().split(" ")
2924		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
2925			return v_list[1]
2926		return v_str
2927	return "Unknown"
2928
2929# SQLite version
2930
2931def SQLiteVersion(db):
2932	query = QSqlQuery(db)
2933	QueryExec(query, "SELECT sqlite_version()")
2934	if query.next():
2935		return query.value(0)
2936	return "Unknown"
2937
2938# About dialog
2939
2940class AboutDialog(QDialog):
2941
2942	def __init__(self, glb, parent=None):
2943		super(AboutDialog, self).__init__(parent)
2944
2945		self.setWindowTitle("About Exported SQL Viewer")
2946		self.setMinimumWidth(300)
2947
2948		pyside_version = "1" if pyside_version_1 else "2"
2949
2950		text = "<pre>"
2951		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
2952		text += "PySide version:     " + pyside_version + "\n"
2953		text += "Qt version:         " + qVersion() + "\n"
2954		if glb.dbref.is_sqlite3:
2955			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
2956		else:
2957			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
2958		text += "</pre>"
2959
2960		self.text = QTextBrowser()
2961		self.text.setHtml(text)
2962		self.text.setReadOnly(True)
2963		self.text.setOpenExternalLinks(True)
2964
2965		self.vbox = QVBoxLayout()
2966		self.vbox.addWidget(self.text)
2967
2968		self.setLayout(self.vbox);
2969
2970# Font resize
2971
2972def ResizeFont(widget, diff):
2973	font = widget.font()
2974	sz = font.pointSize()
2975	font.setPointSize(sz + diff)
2976	widget.setFont(font)
2977
2978def ShrinkFont(widget):
2979	ResizeFont(widget, -1)
2980
2981def EnlargeFont(widget):
2982	ResizeFont(widget, 1)
2983
2984# Unique name for sub-windows
2985
2986def NumberedWindowName(name, nr):
2987	if nr > 1:
2988		name += " <" + str(nr) + ">"
2989	return name
2990
2991def UniqueSubWindowName(mdi_area, name):
2992	nr = 1
2993	while True:
2994		unique_name = NumberedWindowName(name, nr)
2995		ok = True
2996		for sub_window in mdi_area.subWindowList():
2997			if sub_window.name == unique_name:
2998				ok = False
2999				break
3000		if ok:
3001			return unique_name
3002		nr += 1
3003
3004# Add a sub-window
3005
3006def AddSubWindow(mdi_area, sub_window, name):
3007	unique_name = UniqueSubWindowName(mdi_area, name)
3008	sub_window.setMinimumSize(200, 100)
3009	sub_window.resize(800, 600)
3010	sub_window.setWindowTitle(unique_name)
3011	sub_window.setAttribute(Qt.WA_DeleteOnClose)
3012	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
3013	sub_window.name = unique_name
3014	mdi_area.addSubWindow(sub_window)
3015	sub_window.show()
3016
3017# Main window
3018
3019class MainWindow(QMainWindow):
3020
3021	def __init__(self, glb, parent=None):
3022		super(MainWindow, self).__init__(parent)
3023
3024		self.glb = glb
3025
3026		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
3027		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
3028		self.setMinimumSize(200, 100)
3029
3030		self.mdi_area = QMdiArea()
3031		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3032		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3033
3034		self.setCentralWidget(self.mdi_area)
3035
3036		menu = self.menuBar()
3037
3038		file_menu = menu.addMenu("&File")
3039		file_menu.addAction(CreateExitAction(glb.app, self))
3040
3041		edit_menu = menu.addMenu("&Edit")
3042		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
3043		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
3044		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
3045		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
3046		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
3047		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
3048
3049		reports_menu = menu.addMenu("&Reports")
3050		if IsSelectable(glb.db, "calls"):
3051			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
3052
3053		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3054			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3055
3056		self.EventMenu(GetEventList(glb.db), reports_menu)
3057
3058		if IsSelectable(glb.db, "calls"):
3059			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3060
3061		self.TableMenu(GetTableList(glb), menu)
3062
3063		self.window_menu = WindowMenu(self.mdi_area, menu)
3064
3065		help_menu = menu.addMenu("&Help")
3066		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
3067		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
3068
3069	def Try(self, fn):
3070		win = self.mdi_area.activeSubWindow()
3071		if win:
3072			try:
3073				fn(win.view)
3074			except:
3075				pass
3076
3077	def CopyToClipboard(self):
3078		self.Try(CopyCellsToClipboardHdr)
3079
3080	def CopyToClipboardCSV(self):
3081		self.Try(CopyCellsToClipboardCSV)
3082
3083	def Find(self):
3084		win = self.mdi_area.activeSubWindow()
3085		if win:
3086			try:
3087				win.find_bar.Activate()
3088			except:
3089				pass
3090
3091	def FetchMoreRecords(self):
3092		win = self.mdi_area.activeSubWindow()
3093		if win:
3094			try:
3095				win.fetch_bar.Activate()
3096			except:
3097				pass
3098
3099	def ShrinkFont(self):
3100		self.Try(ShrinkFont)
3101
3102	def EnlargeFont(self):
3103		self.Try(EnlargeFont)
3104
3105	def EventMenu(self, events, reports_menu):
3106		branches_events = 0
3107		for event in events:
3108			event = event.split(":")[0]
3109			if event == "branches":
3110				branches_events += 1
3111		dbid = 0
3112		for event in events:
3113			dbid += 1
3114			event = event.split(":")[0]
3115			if event == "branches":
3116				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
3117				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
3118				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
3119				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
3120
3121	def TableMenu(self, tables, menu):
3122		table_menu = menu.addMenu("&Tables")
3123		for table in tables:
3124			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
3125
3126	def NewCallGraph(self):
3127		CallGraphWindow(self.glb, self)
3128
3129	def NewCallTree(self):
3130		CallTreeWindow(self.glb, self)
3131
3132	def NewTopCalls(self):
3133		dialog = TopCallsDialog(self.glb, self)
3134		ret = dialog.exec_()
3135		if ret:
3136			TopCallsWindow(self.glb, dialog.report_vars, self)
3137
3138	def NewBranchView(self, event_id):
3139		BranchWindow(self.glb, event_id, ReportVars(), self)
3140
3141	def NewSelectedBranchView(self, event_id):
3142		dialog = SelectedBranchDialog(self.glb, self)
3143		ret = dialog.exec_()
3144		if ret:
3145			BranchWindow(self.glb, event_id, dialog.report_vars, self)
3146
3147	def NewTableView(self, table_name):
3148		TableWindow(self.glb, table_name, self)
3149
3150	def Help(self):
3151		HelpWindow(self.glb, self)
3152
3153	def About(self):
3154		dialog = AboutDialog(self.glb, self)
3155		dialog.exec_()
3156
3157# XED Disassembler
3158
3159class xed_state_t(Structure):
3160
3161	_fields_ = [
3162		("mode", c_int),
3163		("width", c_int)
3164	]
3165
3166class XEDInstruction():
3167
3168	def __init__(self, libxed):
3169		# Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3170		xedd_t = c_byte * 512
3171		self.xedd = xedd_t()
3172		self.xedp = addressof(self.xedd)
3173		libxed.xed_decoded_inst_zero(self.xedp)
3174		self.state = xed_state_t()
3175		self.statep = addressof(self.state)
3176		# Buffer for disassembled instruction text
3177		self.buffer = create_string_buffer(256)
3178		self.bufferp = addressof(self.buffer)
3179
3180class LibXED():
3181
3182	def __init__(self):
3183		try:
3184			self.libxed = CDLL("libxed.so")
3185		except:
3186			self.libxed = None
3187		if not self.libxed:
3188			self.libxed = CDLL("/usr/local/lib/libxed.so")
3189
3190		self.xed_tables_init = self.libxed.xed_tables_init
3191		self.xed_tables_init.restype = None
3192		self.xed_tables_init.argtypes = []
3193
3194		self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3195		self.xed_decoded_inst_zero.restype = None
3196		self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3197
3198		self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3199		self.xed_operand_values_set_mode.restype = None
3200		self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3201
3202		self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3203		self.xed_decoded_inst_zero_keep_mode.restype = None
3204		self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3205
3206		self.xed_decode = self.libxed.xed_decode
3207		self.xed_decode.restype = c_int
3208		self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3209
3210		self.xed_format_context = self.libxed.xed_format_context
3211		self.xed_format_context.restype = c_uint
3212		self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3213
3214		self.xed_tables_init()
3215
3216	def Instruction(self):
3217		return XEDInstruction(self)
3218
3219	def SetMode(self, inst, mode):
3220		if mode:
3221			inst.state.mode = 4 # 32-bit
3222			inst.state.width = 4 # 4 bytes
3223		else:
3224			inst.state.mode = 1 # 64-bit
3225			inst.state.width = 8 # 8 bytes
3226		self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3227
3228	def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3229		self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3230		err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3231		if err:
3232			return 0, ""
3233		# Use AT&T mode (2), alternative is Intel (3)
3234		ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3235		if not ok:
3236			return 0, ""
3237		if sys.version_info[0] == 2:
3238			result = inst.buffer.value
3239		else:
3240			result = inst.buffer.value.decode()
3241		# Return instruction length and the disassembled instruction text
3242		# For now, assume the length is in byte 166
3243		return inst.xedd[166], result
3244
3245def TryOpen(file_name):
3246	try:
3247		return open(file_name, "rb")
3248	except:
3249		return None
3250
3251def Is64Bit(f):
3252	result = sizeof(c_void_p)
3253	# ELF support only
3254	pos = f.tell()
3255	f.seek(0)
3256	header = f.read(7)
3257	f.seek(pos)
3258	magic = header[0:4]
3259	if sys.version_info[0] == 2:
3260		eclass = ord(header[4])
3261		encoding = ord(header[5])
3262		version = ord(header[6])
3263	else:
3264		eclass = header[4]
3265		encoding = header[5]
3266		version = header[6]
3267	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3268		result = True if eclass == 2 else False
3269	return result
3270
3271# Global data
3272
3273class Glb():
3274
3275	def __init__(self, dbref, db, dbname):
3276		self.dbref = dbref
3277		self.db = db
3278		self.dbname = dbname
3279		self.home_dir = os.path.expanduser("~")
3280		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3281		if self.buildid_dir:
3282			self.buildid_dir += "/.build-id/"
3283		else:
3284			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
3285		self.app = None
3286		self.mainwindow = None
3287		self.instances_to_shutdown_on_exit = weakref.WeakSet()
3288		try:
3289			self.disassembler = LibXED()
3290			self.have_disassembler = True
3291		except:
3292			self.have_disassembler = False
3293
3294	def FileFromBuildId(self, build_id):
3295		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3296		return TryOpen(file_name)
3297
3298	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3299		# Assume current machine i.e. no support for virtualization
3300		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3301			file_name = os.getenv("PERF_KCORE")
3302			f = TryOpen(file_name) if file_name else None
3303			if f:
3304				return f
3305			# For now, no special handling if long_name is /proc/kcore
3306			f = TryOpen(long_name)
3307			if f:
3308				return f
3309		f = self.FileFromBuildId(build_id)
3310		if f:
3311			return f
3312		return None
3313
3314	def AddInstanceToShutdownOnExit(self, instance):
3315		self.instances_to_shutdown_on_exit.add(instance)
3316
3317	# Shutdown any background processes or threads
3318	def ShutdownInstances(self):
3319		for x in self.instances_to_shutdown_on_exit:
3320			try:
3321				x.Shutdown()
3322			except:
3323				pass
3324
3325# Database reference
3326
3327class DBRef():
3328
3329	def __init__(self, is_sqlite3, dbname):
3330		self.is_sqlite3 = is_sqlite3
3331		self.dbname = dbname
3332
3333	def Open(self, connection_name):
3334		dbname = self.dbname
3335		if self.is_sqlite3:
3336			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3337		else:
3338			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3339			opts = dbname.split()
3340			for opt in opts:
3341				if "=" in opt:
3342					opt = opt.split("=")
3343					if opt[0] == "hostname":
3344						db.setHostName(opt[1])
3345					elif opt[0] == "port":
3346						db.setPort(int(opt[1]))
3347					elif opt[0] == "username":
3348						db.setUserName(opt[1])
3349					elif opt[0] == "password":
3350						db.setPassword(opt[1])
3351					elif opt[0] == "dbname":
3352						dbname = opt[1]
3353				else:
3354					dbname = opt
3355
3356		db.setDatabaseName(dbname)
3357		if not db.open():
3358			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3359		return db, dbname
3360
3361# Main
3362
3363def Main():
3364	if (len(sys.argv) < 2):
3365		printerr("Usage is: exported-sql-viewer.py {<database name> | --help-only}");
3366		raise Exception("Too few arguments")
3367
3368	dbname = sys.argv[1]
3369	if dbname == "--help-only":
3370		app = QApplication(sys.argv)
3371		mainwindow = HelpOnlyWindow()
3372		mainwindow.show()
3373		err = app.exec_()
3374		sys.exit(err)
3375
3376	is_sqlite3 = False
3377	try:
3378		f = open(dbname, "rb")
3379		if f.read(15) == b'SQLite format 3':
3380			is_sqlite3 = True
3381		f.close()
3382	except:
3383		pass
3384
3385	dbref = DBRef(is_sqlite3, dbname)
3386	db, dbname = dbref.Open("main")
3387	glb = Glb(dbref, db, dbname)
3388	app = QApplication(sys.argv)
3389	glb.app = app
3390	mainwindow = MainWindow(glb)
3391	glb.mainwindow = mainwindow
3392	mainwindow.show()
3393	err = app.exec_()
3394	glb.ShutdownInstances()
3395	db.close()
3396	sys.exit(err)
3397
3398if __name__ == "__main__":
3399	Main()
3400