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