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