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