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