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