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