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