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