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