xref: /linux/tools/perf/scripts/python/exported-sql-viewer.py (revision 64dc40f7523369912d7adb22c8cb655f71610505)
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 # Only change warnings if the python -W option was not used
95 if not sys.warnoptions:
96 	import warnings
97 	# PySide2 causes deprecation warnings, ignore them.
98 	warnings.filterwarnings("ignore", category=DeprecationWarning)
99 import argparse
100 import weakref
101 import threading
102 import string
103 try:
104 	# Python2
105 	import cPickle as pickle
106 	# size of pickled integer big enough for record size
107 	glb_nsz = 8
108 except ImportError:
109 	import pickle
110 	glb_nsz = 16
111 import re
112 import os
113 import random
114 import copy
115 import math
116 from libxed import LibXED
117 
118 pyside_version_1 = True
119 if not "--pyside-version-1" in sys.argv:
120 	try:
121 		from PySide2.QtCore import *
122 		from PySide2.QtGui import *
123 		from PySide2.QtSql import *
124 		from PySide2.QtWidgets import *
125 		pyside_version_1 = False
126 	except:
127 		pass
128 
129 if pyside_version_1:
130 	from PySide.QtCore import *
131 	from PySide.QtGui import *
132 	from PySide.QtSql import *
133 
134 from decimal import Decimal, ROUND_HALF_UP
135 from ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \
136 		   c_void_p, c_bool, c_byte, c_char, c_int, c_uint, c_longlong, c_ulonglong
137 from multiprocessing import Process, Array, Value, Event
138 
139 # xrange is range in Python3
140 try:
141 	xrange
142 except NameError:
143 	xrange = range
144 
145 def printerr(*args, **keyword_args):
146 	print(*args, file=sys.stderr, **keyword_args)
147 
148 # Data formatting helpers
149 
150 def tohex(ip):
151 	if ip < 0:
152 		ip += 1 << 64
153 	return "%x" % ip
154 
155 def offstr(offset):
156 	if offset:
157 		return "+0x%x" % offset
158 	return ""
159 
160 def dsoname(name):
161 	if name == "[kernel.kallsyms]":
162 		return "[kernel]"
163 	return name
164 
165 def findnth(s, sub, n, offs=0):
166 	pos = s.find(sub)
167 	if pos < 0:
168 		return pos
169 	if n <= 1:
170 		return offs + pos
171 	return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
172 
173 # Percent to one decimal place
174 
175 def PercentToOneDP(n, d):
176 	if not d:
177 		return "0.0"
178 	x = (n * Decimal(100)) / d
179 	return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
180 
181 # Helper for queries that must not fail
182 
183 def QueryExec(query, stmt):
184 	ret = query.exec_(stmt)
185 	if not ret:
186 		raise Exception("Query failed: " + query.lastError().text())
187 
188 # Background thread
189 
190 class Thread(QThread):
191 
192 	done = Signal(object)
193 
194 	def __init__(self, task, param=None, parent=None):
195 		super(Thread, self).__init__(parent)
196 		self.task = task
197 		self.param = param
198 
199 	def run(self):
200 		while True:
201 			if self.param is None:
202 				done, result = self.task()
203 			else:
204 				done, result = self.task(self.param)
205 			self.done.emit(result)
206 			if done:
207 				break
208 
209 # Tree data model
210 
211 class TreeModel(QAbstractItemModel):
212 
213 	def __init__(self, glb, params, parent=None):
214 		super(TreeModel, self).__init__(parent)
215 		self.glb = glb
216 		self.params = params
217 		self.root = self.GetRoot()
218 		self.last_row_read = 0
219 
220 	def Item(self, parent):
221 		if parent.isValid():
222 			return parent.internalPointer()
223 		else:
224 			return self.root
225 
226 	def rowCount(self, parent):
227 		result = self.Item(parent).childCount()
228 		if result < 0:
229 			result = 0
230 			self.dataChanged.emit(parent, parent)
231 		return result
232 
233 	def hasChildren(self, parent):
234 		return self.Item(parent).hasChildren()
235 
236 	def headerData(self, section, orientation, role):
237 		if role == Qt.TextAlignmentRole:
238 			return self.columnAlignment(section)
239 		if role != Qt.DisplayRole:
240 			return None
241 		if orientation != Qt.Horizontal:
242 			return None
243 		return self.columnHeader(section)
244 
245 	def parent(self, child):
246 		child_item = child.internalPointer()
247 		if child_item is self.root:
248 			return QModelIndex()
249 		parent_item = child_item.getParentItem()
250 		return self.createIndex(parent_item.getRow(), 0, parent_item)
251 
252 	def index(self, row, column, parent):
253 		child_item = self.Item(parent).getChildItem(row)
254 		return self.createIndex(row, column, child_item)
255 
256 	def DisplayData(self, item, index):
257 		return item.getData(index.column())
258 
259 	def FetchIfNeeded(self, row):
260 		if row > self.last_row_read:
261 			self.last_row_read = row
262 			if row + 10 >= self.root.child_count:
263 				self.fetcher.Fetch(glb_chunk_sz)
264 
265 	def columnAlignment(self, column):
266 		return Qt.AlignLeft
267 
268 	def columnFont(self, column):
269 		return None
270 
271 	def data(self, index, role):
272 		if role == Qt.TextAlignmentRole:
273 			return self.columnAlignment(index.column())
274 		if role == Qt.FontRole:
275 			return self.columnFont(index.column())
276 		if role != Qt.DisplayRole:
277 			return None
278 		item = index.internalPointer()
279 		return self.DisplayData(item, index)
280 
281 # Table data model
282 
283 class TableModel(QAbstractTableModel):
284 
285 	def __init__(self, parent=None):
286 		super(TableModel, self).__init__(parent)
287 		self.child_count = 0
288 		self.child_items = []
289 		self.last_row_read = 0
290 
291 	def Item(self, parent):
292 		if parent.isValid():
293 			return parent.internalPointer()
294 		else:
295 			return self
296 
297 	def rowCount(self, parent):
298 		return self.child_count
299 
300 	def headerData(self, section, orientation, role):
301 		if role == Qt.TextAlignmentRole:
302 			return self.columnAlignment(section)
303 		if role != Qt.DisplayRole:
304 			return None
305 		if orientation != Qt.Horizontal:
306 			return None
307 		return self.columnHeader(section)
308 
309 	def index(self, row, column, parent):
310 		return self.createIndex(row, column, self.child_items[row])
311 
312 	def DisplayData(self, item, index):
313 		return item.getData(index.column())
314 
315 	def FetchIfNeeded(self, row):
316 		if row > self.last_row_read:
317 			self.last_row_read = row
318 			if row + 10 >= self.child_count:
319 				self.fetcher.Fetch(glb_chunk_sz)
320 
321 	def columnAlignment(self, column):
322 		return Qt.AlignLeft
323 
324 	def columnFont(self, column):
325 		return None
326 
327 	def data(self, index, role):
328 		if role == Qt.TextAlignmentRole:
329 			return self.columnAlignment(index.column())
330 		if role == Qt.FontRole:
331 			return self.columnFont(index.column())
332 		if role != Qt.DisplayRole:
333 			return None
334 		item = index.internalPointer()
335 		return self.DisplayData(item, index)
336 
337 # Model cache
338 
339 model_cache = weakref.WeakValueDictionary()
340 model_cache_lock = threading.Lock()
341 
342 def LookupCreateModel(model_name, create_fn):
343 	model_cache_lock.acquire()
344 	try:
345 		model = model_cache[model_name]
346 	except:
347 		model = None
348 	if model is None:
349 		model = create_fn()
350 		model_cache[model_name] = model
351 	model_cache_lock.release()
352 	return model
353 
354 def LookupModel(model_name):
355 	model_cache_lock.acquire()
356 	try:
357 		model = model_cache[model_name]
358 	except:
359 		model = None
360 	model_cache_lock.release()
361 	return model
362 
363 # Find bar
364 
365 class FindBar():
366 
367 	def __init__(self, parent, finder, is_reg_expr=False):
368 		self.finder = finder
369 		self.context = []
370 		self.last_value = None
371 		self.last_pattern = None
372 
373 		label = QLabel("Find:")
374 		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
375 
376 		self.textbox = QComboBox()
377 		self.textbox.setEditable(True)
378 		self.textbox.currentIndexChanged.connect(self.ValueChanged)
379 
380 		self.progress = QProgressBar()
381 		self.progress.setRange(0, 0)
382 		self.progress.hide()
383 
384 		if is_reg_expr:
385 			self.pattern = QCheckBox("Regular Expression")
386 		else:
387 			self.pattern = QCheckBox("Pattern")
388 		self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
389 
390 		self.next_button = QToolButton()
391 		self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
392 		self.next_button.released.connect(lambda: self.NextPrev(1))
393 
394 		self.prev_button = QToolButton()
395 		self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
396 		self.prev_button.released.connect(lambda: self.NextPrev(-1))
397 
398 		self.close_button = QToolButton()
399 		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
400 		self.close_button.released.connect(self.Deactivate)
401 
402 		self.hbox = QHBoxLayout()
403 		self.hbox.setContentsMargins(0, 0, 0, 0)
404 
405 		self.hbox.addWidget(label)
406 		self.hbox.addWidget(self.textbox)
407 		self.hbox.addWidget(self.progress)
408 		self.hbox.addWidget(self.pattern)
409 		self.hbox.addWidget(self.next_button)
410 		self.hbox.addWidget(self.prev_button)
411 		self.hbox.addWidget(self.close_button)
412 
413 		self.bar = QWidget()
414 		self.bar.setLayout(self.hbox)
415 		self.bar.hide()
416 
417 	def Widget(self):
418 		return self.bar
419 
420 	def Activate(self):
421 		self.bar.show()
422 		self.textbox.lineEdit().selectAll()
423 		self.textbox.setFocus()
424 
425 	def Deactivate(self):
426 		self.bar.hide()
427 
428 	def Busy(self):
429 		self.textbox.setEnabled(False)
430 		self.pattern.hide()
431 		self.next_button.hide()
432 		self.prev_button.hide()
433 		self.progress.show()
434 
435 	def Idle(self):
436 		self.textbox.setEnabled(True)
437 		self.progress.hide()
438 		self.pattern.show()
439 		self.next_button.show()
440 		self.prev_button.show()
441 
442 	def Find(self, direction):
443 		value = self.textbox.currentText()
444 		pattern = self.pattern.isChecked()
445 		self.last_value = value
446 		self.last_pattern = pattern
447 		self.finder.Find(value, direction, pattern, self.context)
448 
449 	def ValueChanged(self):
450 		value = self.textbox.currentText()
451 		pattern = self.pattern.isChecked()
452 		index = self.textbox.currentIndex()
453 		data = self.textbox.itemData(index)
454 		# Store the pattern in the combo box to keep it with the text value
455 		if data == None:
456 			self.textbox.setItemData(index, pattern)
457 		else:
458 			self.pattern.setChecked(data)
459 		self.Find(0)
460 
461 	def NextPrev(self, direction):
462 		value = self.textbox.currentText()
463 		pattern = self.pattern.isChecked()
464 		if value != self.last_value:
465 			index = self.textbox.findText(value)
466 			# Allow for a button press before the value has been added to the combo box
467 			if index < 0:
468 				index = self.textbox.count()
469 				self.textbox.addItem(value, pattern)
470 				self.textbox.setCurrentIndex(index)
471 				return
472 			else:
473 				self.textbox.setItemData(index, pattern)
474 		elif pattern != self.last_pattern:
475 			# Keep the pattern recorded in the combo box up to date
476 			index = self.textbox.currentIndex()
477 			self.textbox.setItemData(index, pattern)
478 		self.Find(direction)
479 
480 	def NotFound(self):
481 		QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
482 
483 # Context-sensitive call graph data model item base
484 
485 class CallGraphLevelItemBase(object):
486 
487 	def __init__(self, glb, params, row, parent_item):
488 		self.glb = glb
489 		self.params = params
490 		self.row = row
491 		self.parent_item = parent_item
492 		self.query_done = False
493 		self.child_count = 0
494 		self.child_items = []
495 		if parent_item:
496 			self.level = parent_item.level + 1
497 		else:
498 			self.level = 0
499 
500 	def getChildItem(self, row):
501 		return self.child_items[row]
502 
503 	def getParentItem(self):
504 		return self.parent_item
505 
506 	def getRow(self):
507 		return self.row
508 
509 	def childCount(self):
510 		if not self.query_done:
511 			self.Select()
512 			if not self.child_count:
513 				return -1
514 		return self.child_count
515 
516 	def hasChildren(self):
517 		if not self.query_done:
518 			return True
519 		return self.child_count > 0
520 
521 	def getData(self, column):
522 		return self.data[column]
523 
524 # Context-sensitive call graph data model level 2+ item base
525 
526 class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
527 
528 	def __init__(self, glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item):
529 		super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
530 		self.comm_id = comm_id
531 		self.thread_id = thread_id
532 		self.call_path_id = call_path_id
533 		self.insn_cnt = insn_cnt
534 		self.cyc_cnt = cyc_cnt
535 		self.branch_count = branch_count
536 		self.time = time
537 
538 	def Select(self):
539 		self.query_done = True
540 		query = QSqlQuery(self.glb.db)
541 		if self.params.have_ipc:
542 			ipc_str = ", SUM(insn_count), SUM(cyc_count)"
543 		else:
544 			ipc_str = ""
545 		QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time)" + ipc_str + ", SUM(branch_count)"
546 					" FROM calls"
547 					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
548 					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
549 					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
550 					" WHERE parent_call_path_id = " + str(self.call_path_id) +
551 					" AND comm_id = " + str(self.comm_id) +
552 					" AND thread_id = " + str(self.thread_id) +
553 					" GROUP BY call_path_id, name, short_name"
554 					" ORDER BY call_path_id")
555 		while query.next():
556 			if self.params.have_ipc:
557 				insn_cnt = int(query.value(5))
558 				cyc_cnt = int(query.value(6))
559 				branch_count = int(query.value(7))
560 			else:
561 				insn_cnt = 0
562 				cyc_cnt = 0
563 				branch_count = int(query.value(5))
564 			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)
565 			self.child_items.append(child_item)
566 			self.child_count += 1
567 
568 # Context-sensitive call graph data model level three item
569 
570 class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
571 
572 	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):
573 		super(CallGraphLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, call_path_id, time, insn_cnt, cyc_cnt, branch_count, parent_item)
574 		dso = dsoname(dso)
575 		if self.params.have_ipc:
576 			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
577 			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
578 			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
579 			ipc = CalcIPC(cyc_cnt, insn_cnt)
580 			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 ]
581 		else:
582 			self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
583 		self.dbid = call_path_id
584 
585 # Context-sensitive call graph data model level two item
586 
587 class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
588 
589 	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
590 		super(CallGraphLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 1, 0, 0, 0, 0, parent_item)
591 		if self.params.have_ipc:
592 			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
593 		else:
594 			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
595 		self.dbid = thread_id
596 
597 	def Select(self):
598 		super(CallGraphLevelTwoItem, self).Select()
599 		for child_item in self.child_items:
600 			self.time += child_item.time
601 			self.insn_cnt += child_item.insn_cnt
602 			self.cyc_cnt += child_item.cyc_cnt
603 			self.branch_count += child_item.branch_count
604 		for child_item in self.child_items:
605 			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
606 			if self.params.have_ipc:
607 				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
608 				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
609 				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
610 			else:
611 				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
612 
613 # Context-sensitive call graph data model level one item
614 
615 class CallGraphLevelOneItem(CallGraphLevelItemBase):
616 
617 	def __init__(self, glb, params, row, comm_id, comm, parent_item):
618 		super(CallGraphLevelOneItem, self).__init__(glb, params, row, parent_item)
619 		if self.params.have_ipc:
620 			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
621 		else:
622 			self.data = [comm, "", "", "", "", "", ""]
623 		self.dbid = comm_id
624 
625 	def Select(self):
626 		self.query_done = True
627 		query = QSqlQuery(self.glb.db)
628 		QueryExec(query, "SELECT thread_id, pid, tid"
629 					" FROM comm_threads"
630 					" INNER JOIN threads ON thread_id = threads.id"
631 					" WHERE comm_id = " + str(self.dbid))
632 		while query.next():
633 			child_item = CallGraphLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
634 			self.child_items.append(child_item)
635 			self.child_count += 1
636 
637 # Context-sensitive call graph data model root item
638 
639 class CallGraphRootItem(CallGraphLevelItemBase):
640 
641 	def __init__(self, glb, params):
642 		super(CallGraphRootItem, self).__init__(glb, params, 0, None)
643 		self.dbid = 0
644 		self.query_done = True
645 		if_has_calls = ""
646 		if IsSelectable(glb.db, "comms", columns = "has_calls"):
647 			if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
648 		query = QSqlQuery(glb.db)
649 		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
650 		while query.next():
651 			if not query.value(0):
652 				continue
653 			child_item = CallGraphLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
654 			self.child_items.append(child_item)
655 			self.child_count += 1
656 
657 # Call graph model parameters
658 
659 class CallGraphModelParams():
660 
661 	def __init__(self, glb, parent=None):
662 		self.have_ipc = IsSelectable(glb.db, "calls", columns = "insn_count, cyc_count")
663 
664 # Context-sensitive call graph data model base
665 
666 class CallGraphModelBase(TreeModel):
667 
668 	def __init__(self, glb, parent=None):
669 		super(CallGraphModelBase, self).__init__(glb, CallGraphModelParams(glb), parent)
670 
671 	def FindSelect(self, value, pattern, query):
672 		if pattern:
673 			# postgresql and sqlite pattern patching differences:
674 			#   postgresql LIKE is case sensitive but sqlite LIKE is not
675 			#   postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
676 			#   postgresql supports ILIKE which is case insensitive
677 			#   sqlite supports GLOB (text only) which uses * and ? and is case sensitive
678 			if not self.glb.dbref.is_sqlite3:
679 				# Escape % and _
680 				s = value.replace("%", "\\%")
681 				s = s.replace("_", "\\_")
682 				# Translate * and ? into SQL LIKE pattern characters % and _
683 				trans = string.maketrans("*?", "%_")
684 				match = " LIKE '" + str(s).translate(trans) + "'"
685 			else:
686 				match = " GLOB '" + str(value) + "'"
687 		else:
688 			match = " = '" + str(value) + "'"
689 		self.DoFindSelect(query, match)
690 
691 	def Found(self, query, found):
692 		if found:
693 			return self.FindPath(query)
694 		return []
695 
696 	def FindValue(self, value, pattern, query, last_value, last_pattern):
697 		if last_value == value and pattern == last_pattern:
698 			found = query.first()
699 		else:
700 			self.FindSelect(value, pattern, query)
701 			found = query.next()
702 		return self.Found(query, found)
703 
704 	def FindNext(self, query):
705 		found = query.next()
706 		if not found:
707 			found = query.first()
708 		return self.Found(query, found)
709 
710 	def FindPrev(self, query):
711 		found = query.previous()
712 		if not found:
713 			found = query.last()
714 		return self.Found(query, found)
715 
716 	def FindThread(self, c):
717 		if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
718 			ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
719 		elif c.direction > 0:
720 			ids = self.FindNext(c.query)
721 		else:
722 			ids = self.FindPrev(c.query)
723 		return (True, ids)
724 
725 	def Find(self, value, direction, pattern, context, callback):
726 		class Context():
727 			def __init__(self, *x):
728 				self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
729 			def Update(self, *x):
730 				self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
731 		if len(context):
732 			context[0].Update(value, direction, pattern)
733 		else:
734 			context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
735 		# Use a thread so the UI is not blocked during the SELECT
736 		thread = Thread(self.FindThread, context[0])
737 		thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
738 		thread.start()
739 
740 	def FindDone(self, thread, callback, ids):
741 		callback(ids)
742 
743 # Context-sensitive call graph data model
744 
745 class CallGraphModel(CallGraphModelBase):
746 
747 	def __init__(self, glb, parent=None):
748 		super(CallGraphModel, self).__init__(glb, parent)
749 
750 	def GetRoot(self):
751 		return CallGraphRootItem(self.glb, self.params)
752 
753 	def columnCount(self, parent=None):
754 		if self.params.have_ipc:
755 			return 12
756 		else:
757 			return 7
758 
759 	def columnHeader(self, column):
760 		if self.params.have_ipc:
761 			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
762 		else:
763 			headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
764 		return headers[column]
765 
766 	def columnAlignment(self, column):
767 		if self.params.have_ipc:
768 			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 ]
769 		else:
770 			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
771 		return alignment[column]
772 
773 	def DoFindSelect(self, query, match):
774 		QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
775 						" FROM calls"
776 						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
777 						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
778 						" WHERE calls.id <> 0"
779 						" AND symbols.name" + match +
780 						" GROUP BY comm_id, thread_id, call_path_id"
781 						" ORDER BY comm_id, thread_id, call_path_id")
782 
783 	def FindPath(self, query):
784 		# Turn the query result into a list of ids that the tree view can walk
785 		# to open the tree at the right place.
786 		ids = []
787 		parent_id = query.value(0)
788 		while parent_id:
789 			ids.insert(0, parent_id)
790 			q2 = QSqlQuery(self.glb.db)
791 			QueryExec(q2, "SELECT parent_id"
792 					" FROM call_paths"
793 					" WHERE id = " + str(parent_id))
794 			if not q2.next():
795 				break
796 			parent_id = q2.value(0)
797 		# The call path root is not used
798 		if ids[0] == 1:
799 			del ids[0]
800 		ids.insert(0, query.value(2))
801 		ids.insert(0, query.value(1))
802 		return ids
803 
804 # Call tree data model level 2+ item base
805 
806 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
807 
808 	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
809 		super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, params, row, parent_item)
810 		self.comm_id = comm_id
811 		self.thread_id = thread_id
812 		self.calls_id = calls_id
813 		self.call_time = call_time
814 		self.time = time
815 		self.insn_cnt = insn_cnt
816 		self.cyc_cnt = cyc_cnt
817 		self.branch_count = branch_count
818 
819 	def Select(self):
820 		self.query_done = True
821 		if self.calls_id == 0:
822 			comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
823 		else:
824 			comm_thread = ""
825 		if self.params.have_ipc:
826 			ipc_str = ", insn_count, cyc_count"
827 		else:
828 			ipc_str = ""
829 		query = QSqlQuery(self.glb.db)
830 		QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time" + ipc_str + ", branch_count"
831 					" FROM calls"
832 					" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
833 					" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
834 					" INNER JOIN dsos ON symbols.dso_id = dsos.id"
835 					" WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
836 					" ORDER BY call_time, calls.id")
837 		while query.next():
838 			if self.params.have_ipc:
839 				insn_cnt = int(query.value(5))
840 				cyc_cnt = int(query.value(6))
841 				branch_count = int(query.value(7))
842 			else:
843 				insn_cnt = 0
844 				cyc_cnt = 0
845 				branch_count = int(query.value(5))
846 			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)
847 			self.child_items.append(child_item)
848 			self.child_count += 1
849 
850 # Call tree data model level three item
851 
852 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
853 
854 	def __init__(self, glb, params, row, comm_id, thread_id, calls_id, name, dso, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item):
855 		super(CallTreeLevelThreeItem, self).__init__(glb, params, row, comm_id, thread_id, calls_id, call_time, time, insn_cnt, cyc_cnt, branch_count, parent_item)
856 		dso = dsoname(dso)
857 		if self.params.have_ipc:
858 			insn_pcnt = PercentToOneDP(insn_cnt, parent_item.insn_cnt)
859 			cyc_pcnt = PercentToOneDP(cyc_cnt, parent_item.cyc_cnt)
860 			br_pcnt = PercentToOneDP(branch_count, parent_item.branch_count)
861 			ipc = CalcIPC(cyc_cnt, insn_cnt)
862 			self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(insn_cnt), insn_pcnt, str(cyc_cnt), cyc_pcnt, ipc, str(branch_count), br_pcnt ]
863 		else:
864 			self.data = [ name, dso, str(call_time), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
865 		self.dbid = calls_id
866 
867 # Call tree data model level two item
868 
869 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
870 
871 	def __init__(self, glb, params, row, comm_id, thread_id, pid, tid, parent_item):
872 		super(CallTreeLevelTwoItem, self).__init__(glb, params, row, comm_id, thread_id, 0, 0, 0, 0, 0, 0, parent_item)
873 		if self.params.have_ipc:
874 			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", "", "", "", "", "", ""]
875 		else:
876 			self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
877 		self.dbid = thread_id
878 
879 	def Select(self):
880 		super(CallTreeLevelTwoItem, self).Select()
881 		for child_item in self.child_items:
882 			self.time += child_item.time
883 			self.insn_cnt += child_item.insn_cnt
884 			self.cyc_cnt += child_item.cyc_cnt
885 			self.branch_count += child_item.branch_count
886 		for child_item in self.child_items:
887 			child_item.data[4] = PercentToOneDP(child_item.time, self.time)
888 			if self.params.have_ipc:
889 				child_item.data[6] = PercentToOneDP(child_item.insn_cnt, self.insn_cnt)
890 				child_item.data[8] = PercentToOneDP(child_item.cyc_cnt, self.cyc_cnt)
891 				child_item.data[11] = PercentToOneDP(child_item.branch_count, self.branch_count)
892 			else:
893 				child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
894 
895 # Call tree data model level one item
896 
897 class CallTreeLevelOneItem(CallGraphLevelItemBase):
898 
899 	def __init__(self, glb, params, row, comm_id, comm, parent_item):
900 		super(CallTreeLevelOneItem, self).__init__(glb, params, row, parent_item)
901 		if self.params.have_ipc:
902 			self.data = [comm, "", "", "", "", "", "", "", "", "", "", ""]
903 		else:
904 			self.data = [comm, "", "", "", "", "", ""]
905 		self.dbid = comm_id
906 
907 	def Select(self):
908 		self.query_done = True
909 		query = QSqlQuery(self.glb.db)
910 		QueryExec(query, "SELECT thread_id, pid, tid"
911 					" FROM comm_threads"
912 					" INNER JOIN threads ON thread_id = threads.id"
913 					" WHERE comm_id = " + str(self.dbid))
914 		while query.next():
915 			child_item = CallTreeLevelTwoItem(self.glb, self.params, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
916 			self.child_items.append(child_item)
917 			self.child_count += 1
918 
919 # Call tree data model root item
920 
921 class CallTreeRootItem(CallGraphLevelItemBase):
922 
923 	def __init__(self, glb, params):
924 		super(CallTreeRootItem, self).__init__(glb, params, 0, None)
925 		self.dbid = 0
926 		self.query_done = True
927 		if_has_calls = ""
928 		if IsSelectable(glb.db, "comms", columns = "has_calls"):
929 			if_has_calls = " WHERE has_calls = " + glb.dbref.TRUE
930 		query = QSqlQuery(glb.db)
931 		QueryExec(query, "SELECT id, comm FROM comms" + if_has_calls)
932 		while query.next():
933 			if not query.value(0):
934 				continue
935 			child_item = CallTreeLevelOneItem(glb, params, self.child_count, query.value(0), query.value(1), self)
936 			self.child_items.append(child_item)
937 			self.child_count += 1
938 
939 # Call Tree data model
940 
941 class CallTreeModel(CallGraphModelBase):
942 
943 	def __init__(self, glb, parent=None):
944 		super(CallTreeModel, self).__init__(glb, parent)
945 
946 	def GetRoot(self):
947 		return CallTreeRootItem(self.glb, self.params)
948 
949 	def columnCount(self, parent=None):
950 		if self.params.have_ipc:
951 			return 12
952 		else:
953 			return 7
954 
955 	def columnHeader(self, column):
956 		if self.params.have_ipc:
957 			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Insn Cnt", "Insn Cnt (%)", "Cyc Cnt", "Cyc Cnt (%)", "IPC", "Branch Count ", "Branch Count (%) "]
958 		else:
959 			headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
960 		return headers[column]
961 
962 	def columnAlignment(self, column):
963 		if self.params.have_ipc:
964 			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 ]
965 		else:
966 			alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
967 		return alignment[column]
968 
969 	def DoFindSelect(self, query, match):
970 		QueryExec(query, "SELECT calls.id, comm_id, thread_id"
971 						" FROM calls"
972 						" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
973 						" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
974 						" WHERE calls.id <> 0"
975 						" AND symbols.name" + match +
976 						" ORDER BY comm_id, thread_id, call_time, calls.id")
977 
978 	def FindPath(self, query):
979 		# Turn the query result into a list of ids that the tree view can walk
980 		# to open the tree at the right place.
981 		ids = []
982 		parent_id = query.value(0)
983 		while parent_id:
984 			ids.insert(0, parent_id)
985 			q2 = QSqlQuery(self.glb.db)
986 			QueryExec(q2, "SELECT parent_id"
987 					" FROM calls"
988 					" WHERE id = " + str(parent_id))
989 			if not q2.next():
990 				break
991 			parent_id = q2.value(0)
992 		ids.insert(0, query.value(2))
993 		ids.insert(0, query.value(1))
994 		return ids
995 
996 # Vertical layout
997 
998 class HBoxLayout(QHBoxLayout):
999 
1000 	def __init__(self, *children):
1001 		super(HBoxLayout, self).__init__()
1002 
1003 		self.layout().setContentsMargins(0, 0, 0, 0)
1004 		for child in children:
1005 			if child.isWidgetType():
1006 				self.layout().addWidget(child)
1007 			else:
1008 				self.layout().addLayout(child)
1009 
1010 # Horizontal layout
1011 
1012 class VBoxLayout(QVBoxLayout):
1013 
1014 	def __init__(self, *children):
1015 		super(VBoxLayout, self).__init__()
1016 
1017 		self.layout().setContentsMargins(0, 0, 0, 0)
1018 		for child in children:
1019 			if child.isWidgetType():
1020 				self.layout().addWidget(child)
1021 			else:
1022 				self.layout().addLayout(child)
1023 
1024 # Vertical layout widget
1025 
1026 class VBox():
1027 
1028 	def __init__(self, *children):
1029 		self.vbox = QWidget()
1030 		self.vbox.setLayout(VBoxLayout(*children))
1031 
1032 	def Widget(self):
1033 		return self.vbox
1034 
1035 # Tree window base
1036 
1037 class TreeWindowBase(QMdiSubWindow):
1038 
1039 	def __init__(self, parent=None):
1040 		super(TreeWindowBase, self).__init__(parent)
1041 
1042 		self.model = None
1043 		self.find_bar = None
1044 
1045 		self.view = QTreeView()
1046 		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1047 		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
1048 
1049 		self.context_menu = TreeContextMenu(self.view)
1050 
1051 	def DisplayFound(self, ids):
1052 		if not len(ids):
1053 			return False
1054 		parent = QModelIndex()
1055 		for dbid in ids:
1056 			found = False
1057 			n = self.model.rowCount(parent)
1058 			for row in xrange(n):
1059 				child = self.model.index(row, 0, parent)
1060 				if child.internalPointer().dbid == dbid:
1061 					found = True
1062 					self.view.setExpanded(parent, True)
1063 					self.view.setCurrentIndex(child)
1064 					parent = child
1065 					break
1066 			if not found:
1067 				break
1068 		return found
1069 
1070 	def Find(self, value, direction, pattern, context):
1071 		self.view.setFocus()
1072 		self.find_bar.Busy()
1073 		self.model.Find(value, direction, pattern, context, self.FindDone)
1074 
1075 	def FindDone(self, ids):
1076 		found = True
1077 		if not self.DisplayFound(ids):
1078 			found = False
1079 		self.find_bar.Idle()
1080 		if not found:
1081 			self.find_bar.NotFound()
1082 
1083 
1084 # Context-sensitive call graph window
1085 
1086 class CallGraphWindow(TreeWindowBase):
1087 
1088 	def __init__(self, glb, parent=None):
1089 		super(CallGraphWindow, self).__init__(parent)
1090 
1091 		self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
1092 
1093 		self.view.setModel(self.model)
1094 
1095 		for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
1096 			self.view.setColumnWidth(c, w)
1097 
1098 		self.find_bar = FindBar(self, self)
1099 
1100 		self.vbox = VBox(self.view, self.find_bar.Widget())
1101 
1102 		self.setWidget(self.vbox.Widget())
1103 
1104 		AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
1105 
1106 # Call tree window
1107 
1108 class CallTreeWindow(TreeWindowBase):
1109 
1110 	def __init__(self, glb, parent=None, thread_at_time=None):
1111 		super(CallTreeWindow, self).__init__(parent)
1112 
1113 		self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
1114 
1115 		self.view.setModel(self.model)
1116 
1117 		for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
1118 			self.view.setColumnWidth(c, w)
1119 
1120 		self.find_bar = FindBar(self, self)
1121 
1122 		self.vbox = VBox(self.view, self.find_bar.Widget())
1123 
1124 		self.setWidget(self.vbox.Widget())
1125 
1126 		AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
1127 
1128 		if thread_at_time:
1129 			self.DisplayThreadAtTime(*thread_at_time)
1130 
1131 	def DisplayThreadAtTime(self, comm_id, thread_id, time):
1132 		parent = QModelIndex()
1133 		for dbid in (comm_id, thread_id):
1134 			found = False
1135 			n = self.model.rowCount(parent)
1136 			for row in xrange(n):
1137 				child = self.model.index(row, 0, parent)
1138 				if child.internalPointer().dbid == dbid:
1139 					found = True
1140 					self.view.setExpanded(parent, True)
1141 					self.view.setCurrentIndex(child)
1142 					parent = child
1143 					break
1144 			if not found:
1145 				return
1146 		found = False
1147 		while True:
1148 			n = self.model.rowCount(parent)
1149 			if not n:
1150 				return
1151 			last_child = None
1152 			for row in xrange(n):
1153 				self.view.setExpanded(parent, True)
1154 				child = self.model.index(row, 0, parent)
1155 				child_call_time = child.internalPointer().call_time
1156 				if child_call_time < time:
1157 					last_child = child
1158 				elif child_call_time == time:
1159 					self.view.setCurrentIndex(child)
1160 					return
1161 				elif child_call_time > time:
1162 					break
1163 			if not last_child:
1164 				if not found:
1165 					child = self.model.index(0, 0, parent)
1166 					self.view.setExpanded(parent, True)
1167 					self.view.setCurrentIndex(child)
1168 				return
1169 			found = True
1170 			self.view.setExpanded(parent, True)
1171 			self.view.setCurrentIndex(last_child)
1172 			parent = last_child
1173 
1174 # ExecComm() gets the comm_id of the command string that was set when the process exec'd i.e. the program name
1175 
1176 def ExecComm(db, thread_id, time):
1177 	query = QSqlQuery(db)
1178 	QueryExec(query, "SELECT comm_threads.comm_id, comms.c_time, comms.exec_flag"
1179 				" FROM comm_threads"
1180 				" INNER JOIN comms ON comms.id = comm_threads.comm_id"
1181 				" WHERE comm_threads.thread_id = " + str(thread_id) +
1182 				" ORDER BY comms.c_time, comms.id")
1183 	first = None
1184 	last = None
1185 	while query.next():
1186 		if first is None:
1187 			first = query.value(0)
1188 		if query.value(2) and Decimal(query.value(1)) <= Decimal(time):
1189 			last = query.value(0)
1190 	if not(last is None):
1191 		return last
1192 	return first
1193 
1194 # Container for (x, y) data
1195 
1196 class XY():
1197 	def __init__(self, x=0, y=0):
1198 		self.x = x
1199 		self.y = y
1200 
1201 	def __str__(self):
1202 		return "XY({}, {})".format(str(self.x), str(self.y))
1203 
1204 # Container for sub-range data
1205 
1206 class Subrange():
1207 	def __init__(self, lo=0, hi=0):
1208 		self.lo = lo
1209 		self.hi = hi
1210 
1211 	def __str__(self):
1212 		return "Subrange({}, {})".format(str(self.lo), str(self.hi))
1213 
1214 # Graph data region base class
1215 
1216 class GraphDataRegion(object):
1217 
1218 	def __init__(self, key, title = "", ordinal = ""):
1219 		self.key = key
1220 		self.title = title
1221 		self.ordinal = ordinal
1222 
1223 # Function to sort GraphDataRegion
1224 
1225 def GraphDataRegionOrdinal(data_region):
1226 	return data_region.ordinal
1227 
1228 # Attributes for a graph region
1229 
1230 class GraphRegionAttribute():
1231 
1232 	def __init__(self, colour):
1233 		self.colour = colour
1234 
1235 # Switch graph data region represents a task
1236 
1237 class SwitchGraphDataRegion(GraphDataRegion):
1238 
1239 	def __init__(self, key, exec_comm_id, pid, tid, comm, thread_id, comm_id):
1240 		super(SwitchGraphDataRegion, self).__init__(key)
1241 
1242 		self.title = str(pid) + " / " + str(tid) + " " + comm
1243 		# Order graph legend within exec comm by pid / tid / time
1244 		self.ordinal = str(pid).rjust(16) + str(exec_comm_id).rjust(8) + str(tid).rjust(16)
1245 		self.exec_comm_id = exec_comm_id
1246 		self.pid = pid
1247 		self.tid = tid
1248 		self.comm = comm
1249 		self.thread_id = thread_id
1250 		self.comm_id = comm_id
1251 
1252 # Graph data point
1253 
1254 class GraphDataPoint():
1255 
1256 	def __init__(self, data, index, x, y, altx=None, alty=None, hregion=None, vregion=None):
1257 		self.data = data
1258 		self.index = index
1259 		self.x = x
1260 		self.y = y
1261 		self.altx = altx
1262 		self.alty = alty
1263 		self.hregion = hregion
1264 		self.vregion = vregion
1265 
1266 # Graph data (single graph) base class
1267 
1268 class GraphData(object):
1269 
1270 	def __init__(self, collection, xbase=Decimal(0), ybase=Decimal(0)):
1271 		self.collection = collection
1272 		self.points = []
1273 		self.xbase = xbase
1274 		self.ybase = ybase
1275 		self.title = ""
1276 
1277 	def AddPoint(self, x, y, altx=None, alty=None, hregion=None, vregion=None):
1278 		index = len(self.points)
1279 
1280 		x = float(Decimal(x) - self.xbase)
1281 		y = float(Decimal(y) - self.ybase)
1282 
1283 		self.points.append(GraphDataPoint(self, index, x, y, altx, alty, hregion, vregion))
1284 
1285 	def XToData(self, x):
1286 		return Decimal(x) + self.xbase
1287 
1288 	def YToData(self, y):
1289 		return Decimal(y) + self.ybase
1290 
1291 # Switch graph data (for one CPU)
1292 
1293 class SwitchGraphData(GraphData):
1294 
1295 	def __init__(self, db, collection, cpu, xbase):
1296 		super(SwitchGraphData, self).__init__(collection, xbase)
1297 
1298 		self.cpu = cpu
1299 		self.title = "CPU " + str(cpu)
1300 		self.SelectSwitches(db)
1301 
1302 	def SelectComms(self, db, thread_id, last_comm_id, start_time, end_time):
1303 		query = QSqlQuery(db)
1304 		QueryExec(query, "SELECT id, c_time"
1305 					" FROM comms"
1306 					" WHERE c_thread_id = " + str(thread_id) +
1307 					"   AND exec_flag = " + self.collection.glb.dbref.TRUE +
1308 					"   AND c_time >= " + str(start_time) +
1309 					"   AND c_time <= " + str(end_time) +
1310 					" ORDER BY c_time, id")
1311 		while query.next():
1312 			comm_id = query.value(0)
1313 			if comm_id == last_comm_id:
1314 				continue
1315 			time = query.value(1)
1316 			hregion = self.HRegion(db, thread_id, comm_id, time)
1317 			self.AddPoint(time, 1000, None, None, hregion)
1318 
1319 	def SelectSwitches(self, db):
1320 		last_time = None
1321 		last_comm_id = None
1322 		last_thread_id = None
1323 		query = QSqlQuery(db)
1324 		QueryExec(query, "SELECT time, thread_out_id, thread_in_id, comm_out_id, comm_in_id, flags"
1325 					" FROM context_switches"
1326 					" WHERE machine_id = " + str(self.collection.machine_id) +
1327 					"   AND cpu = " + str(self.cpu) +
1328 					" ORDER BY time, id")
1329 		while query.next():
1330 			flags = int(query.value(5))
1331 			if flags & 1:
1332 				# Schedule-out: detect and add exec's
1333 				if last_thread_id == query.value(1) and last_comm_id is not None and last_comm_id != query.value(3):
1334 					self.SelectComms(db, last_thread_id, last_comm_id, last_time, query.value(0))
1335 				continue
1336 			# Schedule-in: add data point
1337 			if len(self.points) == 0:
1338 				start_time = self.collection.glb.StartTime(self.collection.machine_id)
1339 				hregion = self.HRegion(db, query.value(1), query.value(3), start_time)
1340 				self.AddPoint(start_time, 1000, None, None, hregion)
1341 			time = query.value(0)
1342 			comm_id = query.value(4)
1343 			thread_id = query.value(2)
1344 			hregion = self.HRegion(db, thread_id, comm_id, time)
1345 			self.AddPoint(time, 1000, None, None, hregion)
1346 			last_time = time
1347 			last_comm_id = comm_id
1348 			last_thread_id = thread_id
1349 
1350 	def NewHRegion(self, db, key, thread_id, comm_id, time):
1351 		exec_comm_id = ExecComm(db, thread_id, time)
1352 		query = QSqlQuery(db)
1353 		QueryExec(query, "SELECT pid, tid FROM threads WHERE id = " + str(thread_id))
1354 		if query.next():
1355 			pid = query.value(0)
1356 			tid = query.value(1)
1357 		else:
1358 			pid = -1
1359 			tid = -1
1360 		query = QSqlQuery(db)
1361 		QueryExec(query, "SELECT comm FROM comms WHERE id = " + str(comm_id))
1362 		if query.next():
1363 			comm = query.value(0)
1364 		else:
1365 			comm = ""
1366 		return SwitchGraphDataRegion(key, exec_comm_id, pid, tid, comm, thread_id, comm_id)
1367 
1368 	def HRegion(self, db, thread_id, comm_id, time):
1369 		key = str(thread_id) + ":" + str(comm_id)
1370 		hregion = self.collection.LookupHRegion(key)
1371 		if hregion is None:
1372 			hregion = self.NewHRegion(db, key, thread_id, comm_id, time)
1373 			self.collection.AddHRegion(key, hregion)
1374 		return hregion
1375 
1376 # Graph data collection (multiple related graphs) base class
1377 
1378 class GraphDataCollection(object):
1379 
1380 	def __init__(self, glb):
1381 		self.glb = glb
1382 		self.data = []
1383 		self.hregions = {}
1384 		self.xrangelo = None
1385 		self.xrangehi = None
1386 		self.yrangelo = None
1387 		self.yrangehi = None
1388 		self.dp = XY(0, 0)
1389 
1390 	def AddGraphData(self, data):
1391 		self.data.append(data)
1392 
1393 	def LookupHRegion(self, key):
1394 		if key in self.hregions:
1395 			return self.hregions[key]
1396 		return None
1397 
1398 	def AddHRegion(self, key, hregion):
1399 		self.hregions[key] = hregion
1400 
1401 # Switch graph data collection (SwitchGraphData for each CPU)
1402 
1403 class SwitchGraphDataCollection(GraphDataCollection):
1404 
1405 	def __init__(self, glb, db, machine_id):
1406 		super(SwitchGraphDataCollection, self).__init__(glb)
1407 
1408 		self.machine_id = machine_id
1409 		self.cpus = self.SelectCPUs(db)
1410 
1411 		self.xrangelo = glb.StartTime(machine_id)
1412 		self.xrangehi = glb.FinishTime(machine_id)
1413 
1414 		self.yrangelo = Decimal(0)
1415 		self.yrangehi = Decimal(1000)
1416 
1417 		for cpu in self.cpus:
1418 			self.AddGraphData(SwitchGraphData(db, self, cpu, self.xrangelo))
1419 
1420 	def SelectCPUs(self, db):
1421 		cpus = []
1422 		query = QSqlQuery(db)
1423 		QueryExec(query, "SELECT DISTINCT cpu"
1424 					" FROM context_switches"
1425 					" WHERE machine_id = " + str(self.machine_id))
1426 		while query.next():
1427 			cpus.append(int(query.value(0)))
1428 		return sorted(cpus)
1429 
1430 # Switch graph data graphics item displays the graphed data
1431 
1432 class SwitchGraphDataGraphicsItem(QGraphicsItem):
1433 
1434 	def __init__(self, data, graph_width, graph_height, attrs, event_handler, parent=None):
1435 		super(SwitchGraphDataGraphicsItem, self).__init__(parent)
1436 
1437 		self.data = data
1438 		self.graph_width = graph_width
1439 		self.graph_height = graph_height
1440 		self.attrs = attrs
1441 		self.event_handler = event_handler
1442 		self.setAcceptHoverEvents(True)
1443 
1444 	def boundingRect(self):
1445 		return QRectF(0, 0, self.graph_width, self.graph_height)
1446 
1447 	def PaintPoint(self, painter, last, x):
1448 		if not(last is None or last.hregion.pid == 0 or x < self.attrs.subrange.x.lo):
1449 			if last.x < self.attrs.subrange.x.lo:
1450 				x0 = self.attrs.subrange.x.lo
1451 			else:
1452 				x0 = last.x
1453 			if x > self.attrs.subrange.x.hi:
1454 				x1 = self.attrs.subrange.x.hi
1455 			else:
1456 				x1 = x - 1
1457 			x0 = self.attrs.XToPixel(x0)
1458 			x1 = self.attrs.XToPixel(x1)
1459 
1460 			y0 = self.attrs.YToPixel(last.y)
1461 
1462 			colour = self.attrs.region_attributes[last.hregion.key].colour
1463 
1464 			width = x1 - x0 + 1
1465 			if width < 2:
1466 				painter.setPen(colour)
1467 				painter.drawLine(x0, self.graph_height - y0, x0, self.graph_height)
1468 			else:
1469 				painter.fillRect(x0, self.graph_height - y0, width, self.graph_height - 1, colour)
1470 
1471 	def paint(self, painter, option, widget):
1472 		last = None
1473 		for point in self.data.points:
1474 			self.PaintPoint(painter, last, point.x)
1475 			if point.x > self.attrs.subrange.x.hi:
1476 				break;
1477 			last = point
1478 		self.PaintPoint(painter, last, self.attrs.subrange.x.hi + 1)
1479 
1480 	def BinarySearchPoint(self, target):
1481 		lower_pos = 0
1482 		higher_pos = len(self.data.points)
1483 		while True:
1484 			pos = int((lower_pos + higher_pos) / 2)
1485 			val = self.data.points[pos].x
1486 			if target >= val:
1487 				lower_pos = pos
1488 			else:
1489 				higher_pos = pos
1490 			if higher_pos <= lower_pos + 1:
1491 				return lower_pos
1492 
1493 	def XPixelToData(self, x):
1494 		x = self.attrs.PixelToX(x)
1495 		if x < self.data.points[0].x:
1496 			x = 0
1497 			pos = 0
1498 			low = True
1499 		else:
1500 			pos = self.BinarySearchPoint(x)
1501 			low = False
1502 		return (low, pos, self.data.XToData(x))
1503 
1504 	def EventToData(self, event):
1505 		no_data = (None,) * 4
1506 		if len(self.data.points) < 1:
1507 			return no_data
1508 		x = event.pos().x()
1509 		if x < 0:
1510 			return no_data
1511 		low0, pos0, time_from = self.XPixelToData(x)
1512 		low1, pos1, time_to = self.XPixelToData(x + 1)
1513 		hregions = set()
1514 		hregion_times = []
1515 		if not low1:
1516 			for i in xrange(pos0, pos1 + 1):
1517 				hregion = self.data.points[i].hregion
1518 				hregions.add(hregion)
1519 				if i == pos0:
1520 					time = time_from
1521 				else:
1522 					time = self.data.XToData(self.data.points[i].x)
1523 				hregion_times.append((hregion, time))
1524 		return (time_from, time_to, hregions, hregion_times)
1525 
1526 	def hoverMoveEvent(self, event):
1527 		time_from, time_to, hregions, hregion_times = self.EventToData(event)
1528 		if time_from is not None:
1529 			self.event_handler.PointEvent(self.data.cpu, time_from, time_to, hregions)
1530 
1531 	def hoverLeaveEvent(self, event):
1532 		self.event_handler.NoPointEvent()
1533 
1534 	def mousePressEvent(self, event):
1535 		if event.button() != Qt.RightButton:
1536 			super(SwitchGraphDataGraphicsItem, self).mousePressEvent(event)
1537 			return
1538 		time_from, time_to, hregions, hregion_times = self.EventToData(event)
1539 		if hregion_times:
1540 			self.event_handler.RightClickEvent(self.data.cpu, hregion_times, event.screenPos())
1541 
1542 # X-axis graphics item
1543 
1544 class XAxisGraphicsItem(QGraphicsItem):
1545 
1546 	def __init__(self, width, parent=None):
1547 		super(XAxisGraphicsItem, self).__init__(parent)
1548 
1549 		self.width = width
1550 		self.max_mark_sz = 4
1551 		self.height = self.max_mark_sz + 1
1552 
1553 	def boundingRect(self):
1554 		return QRectF(0, 0, self.width, self.height)
1555 
1556 	def Step(self):
1557 		attrs = self.parentItem().attrs
1558 		subrange = attrs.subrange.x
1559 		t = subrange.hi - subrange.lo
1560 		s = (3.0 * t) / self.width
1561 		n = 1.0
1562 		while s > n:
1563 			n = n * 10.0
1564 		return n
1565 
1566 	def PaintMarks(self, painter, at_y, lo, hi, step, i):
1567 		attrs = self.parentItem().attrs
1568 		x = lo
1569 		while x <= hi:
1570 			xp = attrs.XToPixel(x)
1571 			if i % 10:
1572 				if i % 5:
1573 					sz = 1
1574 				else:
1575 					sz = 2
1576 			else:
1577 				sz = self.max_mark_sz
1578 				i = 0
1579 			painter.drawLine(xp, at_y, xp, at_y + sz)
1580 			x += step
1581 			i += 1
1582 
1583 	def paint(self, painter, option, widget):
1584 		# Using QPainter::drawLine(int x1, int y1, int x2, int y2) so x2 = width -1
1585 		painter.drawLine(0, 0, self.width - 1, 0)
1586 		n = self.Step()
1587 		attrs = self.parentItem().attrs
1588 		subrange = attrs.subrange.x
1589 		if subrange.lo:
1590 			x_offset = n - (subrange.lo % n)
1591 		else:
1592 			x_offset = 0.0
1593 		x = subrange.lo + x_offset
1594 		i = (x / n) % 10
1595 		self.PaintMarks(painter, 0, x, subrange.hi, n, i)
1596 
1597 	def ScaleDimensions(self):
1598 		n = self.Step()
1599 		attrs = self.parentItem().attrs
1600 		lo = attrs.subrange.x.lo
1601 		hi = (n * 10.0) + lo
1602 		width = attrs.XToPixel(hi)
1603 		if width > 500:
1604 			width = 0
1605 		return (n, lo, hi, width)
1606 
1607 	def PaintScale(self, painter, at_x, at_y):
1608 		n, lo, hi, width = self.ScaleDimensions()
1609 		if not width:
1610 			return
1611 		painter.drawLine(at_x, at_y, at_x + width, at_y)
1612 		self.PaintMarks(painter, at_y, lo, hi, n, 0)
1613 
1614 	def ScaleWidth(self):
1615 		n, lo, hi, width = self.ScaleDimensions()
1616 		return width
1617 
1618 	def ScaleHeight(self):
1619 		return self.height
1620 
1621 	def ScaleUnit(self):
1622 		return self.Step() * 10
1623 
1624 # Scale graphics item base class
1625 
1626 class ScaleGraphicsItem(QGraphicsItem):
1627 
1628 	def __init__(self, axis, parent=None):
1629 		super(ScaleGraphicsItem, self).__init__(parent)
1630 		self.axis = axis
1631 
1632 	def boundingRect(self):
1633 		scale_width = self.axis.ScaleWidth()
1634 		if not scale_width:
1635 			return QRectF()
1636 		return QRectF(0, 0, self.axis.ScaleWidth() + 100, self.axis.ScaleHeight())
1637 
1638 	def paint(self, painter, option, widget):
1639 		scale_width = self.axis.ScaleWidth()
1640 		if not scale_width:
1641 			return
1642 		self.axis.PaintScale(painter, 0, 5)
1643 		x = scale_width + 4
1644 		painter.drawText(QPointF(x, 10), self.Text())
1645 
1646 	def Unit(self):
1647 		return self.axis.ScaleUnit()
1648 
1649 	def Text(self):
1650 		return ""
1651 
1652 # Switch graph scale graphics item
1653 
1654 class SwitchScaleGraphicsItem(ScaleGraphicsItem):
1655 
1656 	def __init__(self, axis, parent=None):
1657 		super(SwitchScaleGraphicsItem, self).__init__(axis, parent)
1658 
1659 	def Text(self):
1660 		unit = self.Unit()
1661 		if unit >= 1000000000:
1662 			unit = int(unit / 1000000000)
1663 			us = "s"
1664 		elif unit >= 1000000:
1665 			unit = int(unit / 1000000)
1666 			us = "ms"
1667 		elif unit >= 1000:
1668 			unit = int(unit / 1000)
1669 			us = "us"
1670 		else:
1671 			unit = int(unit)
1672 			us = "ns"
1673 		return " = " + str(unit) + " " + us
1674 
1675 # Switch graph graphics item contains graph title, scale, x/y-axis, and the graphed data
1676 
1677 class SwitchGraphGraphicsItem(QGraphicsItem):
1678 
1679 	def __init__(self, collection, data, attrs, event_handler, first, parent=None):
1680 		super(SwitchGraphGraphicsItem, self).__init__(parent)
1681 		self.collection = collection
1682 		self.data = data
1683 		self.attrs = attrs
1684 		self.event_handler = event_handler
1685 
1686 		margin = 20
1687 		title_width = 50
1688 
1689 		self.title_graphics = QGraphicsSimpleTextItem(data.title, self)
1690 
1691 		self.title_graphics.setPos(margin, margin)
1692 		graph_width = attrs.XToPixel(attrs.subrange.x.hi) + 1
1693 		graph_height = attrs.YToPixel(attrs.subrange.y.hi) + 1
1694 
1695 		self.graph_origin_x = margin + title_width + margin
1696 		self.graph_origin_y = graph_height + margin
1697 
1698 		x_axis_size = 1
1699 		y_axis_size = 1
1700 		self.yline = QGraphicsLineItem(0, 0, 0, graph_height, self)
1701 
1702 		self.x_axis = XAxisGraphicsItem(graph_width, self)
1703 		self.x_axis.setPos(self.graph_origin_x, self.graph_origin_y + 1)
1704 
1705 		if first:
1706 			self.scale_item = SwitchScaleGraphicsItem(self.x_axis, self)
1707 			self.scale_item.setPos(self.graph_origin_x, self.graph_origin_y + 10)
1708 
1709 		self.yline.setPos(self.graph_origin_x - y_axis_size, self.graph_origin_y - graph_height)
1710 
1711 		self.axis_point = QGraphicsLineItem(0, 0, 0, 0, self)
1712 		self.axis_point.setPos(self.graph_origin_x - 1, self.graph_origin_y +1)
1713 
1714 		self.width = self.graph_origin_x + graph_width + margin
1715 		self.height = self.graph_origin_y + margin
1716 
1717 		self.graph = SwitchGraphDataGraphicsItem(data, graph_width, graph_height, attrs, event_handler, self)
1718 		self.graph.setPos(self.graph_origin_x, self.graph_origin_y - graph_height)
1719 
1720 		if parent and 'EnableRubberBand' in dir(parent):
1721 			parent.EnableRubberBand(self.graph_origin_x, self.graph_origin_x + graph_width - 1, self)
1722 
1723 	def boundingRect(self):
1724 		return QRectF(0, 0, self.width, self.height)
1725 
1726 	def paint(self, painter, option, widget):
1727 		pass
1728 
1729 	def RBXToPixel(self, x):
1730 		return self.attrs.PixelToX(x - self.graph_origin_x)
1731 
1732 	def RBXRangeToPixel(self, x0, x1):
1733 		return (self.RBXToPixel(x0), self.RBXToPixel(x1 + 1))
1734 
1735 	def RBPixelToTime(self, x):
1736 		if x < self.data.points[0].x:
1737 			return self.data.XToData(0)
1738 		return self.data.XToData(x)
1739 
1740 	def RBEventTimes(self, x0, x1):
1741 		x0, x1 = self.RBXRangeToPixel(x0, x1)
1742 		time_from = self.RBPixelToTime(x0)
1743 		time_to = self.RBPixelToTime(x1)
1744 		return (time_from, time_to)
1745 
1746 	def RBEvent(self, x0, x1):
1747 		time_from, time_to = self.RBEventTimes(x0, x1)
1748 		self.event_handler.RangeEvent(time_from, time_to)
1749 
1750 	def RBMoveEvent(self, x0, x1):
1751 		if x1 < x0:
1752 			x0, x1 = x1, x0
1753 		self.RBEvent(x0, x1)
1754 
1755 	def RBReleaseEvent(self, x0, x1, selection_state):
1756 		if x1 < x0:
1757 			x0, x1 = x1, x0
1758 		x0, x1 = self.RBXRangeToPixel(x0, x1)
1759 		self.event_handler.SelectEvent(x0, x1, selection_state)
1760 
1761 # Graphics item to draw a vertical bracket (used to highlight "forward" sub-range)
1762 
1763 class VerticalBracketGraphicsItem(QGraphicsItem):
1764 
1765 	def __init__(self, parent=None):
1766 		super(VerticalBracketGraphicsItem, self).__init__(parent)
1767 
1768 		self.width = 0
1769 		self.height = 0
1770 		self.hide()
1771 
1772 	def SetSize(self, width, height):
1773 		self.width = width + 1
1774 		self.height = height + 1
1775 
1776 	def boundingRect(self):
1777 		return QRectF(0, 0, self.width, self.height)
1778 
1779 	def paint(self, painter, option, widget):
1780 		colour = QColor(255, 255, 0, 32)
1781 		painter.fillRect(0, 0, self.width, self.height, colour)
1782 		x1 = self.width - 1
1783 		y1 = self.height - 1
1784 		painter.drawLine(0, 0, x1, 0)
1785 		painter.drawLine(0, 0, 0, 3)
1786 		painter.drawLine(x1, 0, x1, 3)
1787 		painter.drawLine(0, y1, x1, y1)
1788 		painter.drawLine(0, y1, 0, y1 - 3)
1789 		painter.drawLine(x1, y1, x1, y1 - 3)
1790 
1791 # Graphics item to contain graphs arranged vertically
1792 
1793 class VertcalGraphSetGraphicsItem(QGraphicsItem):
1794 
1795 	def __init__(self, collection, attrs, event_handler, child_class, parent=None):
1796 		super(VertcalGraphSetGraphicsItem, self).__init__(parent)
1797 
1798 		self.collection = collection
1799 
1800 		self.top = 10
1801 
1802 		self.width = 0
1803 		self.height = self.top
1804 
1805 		self.rubber_band = None
1806 		self.rb_enabled = False
1807 
1808 		first = True
1809 		for data in collection.data:
1810 			child = child_class(collection, data, attrs, event_handler, first, self)
1811 			child.setPos(0, self.height + 1)
1812 			rect = child.boundingRect()
1813 			if rect.right() > self.width:
1814 				self.width = rect.right()
1815 			self.height = self.height + rect.bottom() + 1
1816 			first = False
1817 
1818 		self.bracket = VerticalBracketGraphicsItem(self)
1819 
1820 	def EnableRubberBand(self, xlo, xhi, rb_event_handler):
1821 		if self.rb_enabled:
1822 			return
1823 		self.rb_enabled = True
1824 		self.rb_in_view = False
1825 		self.setAcceptedMouseButtons(Qt.LeftButton)
1826 		self.rb_xlo = xlo
1827 		self.rb_xhi = xhi
1828 		self.rb_event_handler = rb_event_handler
1829 		self.mousePressEvent = self.MousePressEvent
1830 		self.mouseMoveEvent = self.MouseMoveEvent
1831 		self.mouseReleaseEvent = self.MouseReleaseEvent
1832 
1833 	def boundingRect(self):
1834 		return QRectF(0, 0, self.width, self.height)
1835 
1836 	def paint(self, painter, option, widget):
1837 		pass
1838 
1839 	def RubberBandParent(self):
1840 		scene = self.scene()
1841 		view = scene.views()[0]
1842 		viewport = view.viewport()
1843 		return viewport
1844 
1845 	def RubberBandSetGeometry(self, rect):
1846 		scene_rectf = self.mapRectToScene(QRectF(rect))
1847 		scene = self.scene()
1848 		view = scene.views()[0]
1849 		poly = view.mapFromScene(scene_rectf)
1850 		self.rubber_band.setGeometry(poly.boundingRect())
1851 
1852 	def SetSelection(self, selection_state):
1853 		if self.rubber_band:
1854 			if selection_state:
1855 				self.RubberBandSetGeometry(selection_state)
1856 				self.rubber_band.show()
1857 			else:
1858 				self.rubber_band.hide()
1859 
1860 	def SetBracket(self, rect):
1861 		if rect:
1862 			x, y, width, height = rect.x(), rect.y(), rect.width(), rect.height()
1863 			self.bracket.setPos(x, y)
1864 			self.bracket.SetSize(width, height)
1865 			self.bracket.show()
1866 		else:
1867 			self.bracket.hide()
1868 
1869 	def RubberBandX(self, event):
1870 		x = event.pos().toPoint().x()
1871 		if x < self.rb_xlo:
1872 			x = self.rb_xlo
1873 		elif x > self.rb_xhi:
1874 			x = self.rb_xhi
1875 		else:
1876 			self.rb_in_view = True
1877 		return x
1878 
1879 	def RubberBandRect(self, x):
1880 		if self.rb_origin.x() <= x:
1881 			width = x - self.rb_origin.x()
1882 			rect = QRect(self.rb_origin, QSize(width, self.height))
1883 		else:
1884 			width = self.rb_origin.x() - x
1885 			top_left = QPoint(self.rb_origin.x() - width, self.rb_origin.y())
1886 			rect = QRect(top_left, QSize(width, self.height))
1887 		return rect
1888 
1889 	def MousePressEvent(self, event):
1890 		self.rb_in_view = False
1891 		x = self.RubberBandX(event)
1892 		self.rb_origin = QPoint(x, self.top)
1893 		if self.rubber_band is None:
1894 			self.rubber_band = QRubberBand(QRubberBand.Rectangle, self.RubberBandParent())
1895 		self.RubberBandSetGeometry(QRect(self.rb_origin, QSize(0, self.height)))
1896 		if self.rb_in_view:
1897 			self.rubber_band.show()
1898 			self.rb_event_handler.RBMoveEvent(x, x)
1899 		else:
1900 			self.rubber_band.hide()
1901 
1902 	def MouseMoveEvent(self, event):
1903 		x = self.RubberBandX(event)
1904 		rect = self.RubberBandRect(x)
1905 		self.RubberBandSetGeometry(rect)
1906 		if self.rb_in_view:
1907 			self.rubber_band.show()
1908 			self.rb_event_handler.RBMoveEvent(self.rb_origin.x(), x)
1909 
1910 	def MouseReleaseEvent(self, event):
1911 		x = self.RubberBandX(event)
1912 		if self.rb_in_view:
1913 			selection_state = self.RubberBandRect(x)
1914 		else:
1915 			selection_state = None
1916 		self.rb_event_handler.RBReleaseEvent(self.rb_origin.x(), x, selection_state)
1917 
1918 # Switch graph legend data model
1919 
1920 class SwitchGraphLegendModel(QAbstractTableModel):
1921 
1922 	def __init__(self, collection, region_attributes, parent=None):
1923 		super(SwitchGraphLegendModel, self).__init__(parent)
1924 
1925 		self.region_attributes = region_attributes
1926 
1927 		self.child_items = sorted(collection.hregions.values(), key=GraphDataRegionOrdinal)
1928 		self.child_count = len(self.child_items)
1929 
1930 		self.highlight_set = set()
1931 
1932 		self.column_headers = ("pid", "tid", "comm")
1933 
1934 	def rowCount(self, parent):
1935 		return self.child_count
1936 
1937 	def headerData(self, section, orientation, role):
1938 		if role != Qt.DisplayRole:
1939 			return None
1940 		if orientation != Qt.Horizontal:
1941 			return None
1942 		return self.columnHeader(section)
1943 
1944 	def index(self, row, column, parent):
1945 		return self.createIndex(row, column, self.child_items[row])
1946 
1947 	def columnCount(self, parent=None):
1948 		return len(self.column_headers)
1949 
1950 	def columnHeader(self, column):
1951 		return self.column_headers[column]
1952 
1953 	def data(self, index, role):
1954 		if role == Qt.BackgroundRole:
1955 			child = self.child_items[index.row()]
1956 			if child in self.highlight_set:
1957 				return self.region_attributes[child.key].colour
1958 			return None
1959 		if role == Qt.ForegroundRole:
1960 			child = self.child_items[index.row()]
1961 			if child in self.highlight_set:
1962 				return QColor(255, 255, 255)
1963 			return self.region_attributes[child.key].colour
1964 		if role != Qt.DisplayRole:
1965 			return None
1966 		hregion = self.child_items[index.row()]
1967 		col = index.column()
1968 		if col == 0:
1969 			return hregion.pid
1970 		if col == 1:
1971 			return hregion.tid
1972 		if col == 2:
1973 			return hregion.comm
1974 		return None
1975 
1976 	def SetHighlight(self, row, set_highlight):
1977 		child = self.child_items[row]
1978 		top_left = self.createIndex(row, 0, child)
1979 		bottom_right = self.createIndex(row, len(self.column_headers) - 1, child)
1980 		self.dataChanged.emit(top_left, bottom_right)
1981 
1982 	def Highlight(self, highlight_set):
1983 		for row in xrange(self.child_count):
1984 			child = self.child_items[row]
1985 			if child in self.highlight_set:
1986 				if child not in highlight_set:
1987 					self.SetHighlight(row, False)
1988 			elif child in highlight_set:
1989 				self.SetHighlight(row, True)
1990 		self.highlight_set = highlight_set
1991 
1992 # Switch graph legend is a table
1993 
1994 class SwitchGraphLegend(QWidget):
1995 
1996 	def __init__(self, collection, region_attributes, parent=None):
1997 		super(SwitchGraphLegend, self).__init__(parent)
1998 
1999 		self.data_model = SwitchGraphLegendModel(collection, region_attributes)
2000 
2001 		self.model = QSortFilterProxyModel()
2002 		self.model.setSourceModel(self.data_model)
2003 
2004 		self.view = QTableView()
2005 		self.view.setModel(self.model)
2006 		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2007 		self.view.verticalHeader().setVisible(False)
2008 		self.view.sortByColumn(-1, Qt.AscendingOrder)
2009 		self.view.setSortingEnabled(True)
2010 		self.view.resizeColumnsToContents()
2011 		self.view.resizeRowsToContents()
2012 
2013 		self.vbox = VBoxLayout(self.view)
2014 		self.setLayout(self.vbox)
2015 
2016 		sz1 = self.view.columnWidth(0) + self.view.columnWidth(1) + self.view.columnWidth(2) + 2
2017 		sz1 = sz1 + self.view.verticalScrollBar().sizeHint().width()
2018 		self.saved_size = sz1
2019 
2020 	def resizeEvent(self, event):
2021 		self.saved_size = self.size().width()
2022 		super(SwitchGraphLegend, self).resizeEvent(event)
2023 
2024 	def Highlight(self, highlight_set):
2025 		self.data_model.Highlight(highlight_set)
2026 		self.update()
2027 
2028 	def changeEvent(self, event):
2029 		if event.type() == QEvent.FontChange:
2030 			self.view.resizeRowsToContents()
2031 			self.view.resizeColumnsToContents()
2032 			# Need to resize rows again after column resize
2033 			self.view.resizeRowsToContents()
2034 		super(SwitchGraphLegend, self).changeEvent(event)
2035 
2036 # Random colour generation
2037 
2038 def RGBColourTooLight(r, g, b):
2039 	if g > 230:
2040 		return True
2041 	if g <= 160:
2042 		return False
2043 	if r <= 180 and g <= 180:
2044 		return False
2045 	if r < 60:
2046 		return False
2047 	return True
2048 
2049 def GenerateColours(x):
2050 	cs = [0]
2051 	for i in xrange(1, x):
2052 		cs.append(int((255.0 / i) + 0.5))
2053 	colours = []
2054 	for r in cs:
2055 		for g in cs:
2056 			for b in cs:
2057 				# Exclude black and colours that look too light against a white background
2058 				if (r, g, b) == (0, 0, 0) or RGBColourTooLight(r, g, b):
2059 					continue
2060 				colours.append(QColor(r, g, b))
2061 	return colours
2062 
2063 def GenerateNColours(n):
2064 	for x in xrange(2, n + 2):
2065 		colours = GenerateColours(x)
2066 		if len(colours) >= n:
2067 			return colours
2068 	return []
2069 
2070 def GenerateNRandomColours(n, seed):
2071 	colours = GenerateNColours(n)
2072 	random.seed(seed)
2073 	random.shuffle(colours)
2074 	return colours
2075 
2076 # Graph attributes, in particular the scale and subrange that change when zooming
2077 
2078 class GraphAttributes():
2079 
2080 	def __init__(self, scale, subrange, region_attributes, dp):
2081 		self.scale = scale
2082 		self.subrange = subrange
2083 		self.region_attributes = region_attributes
2084 		# Rounding avoids errors due to finite floating point precision
2085 		self.dp = dp	# data decimal places
2086 		self.Update()
2087 
2088 	def XToPixel(self, x):
2089 		return int(round((x - self.subrange.x.lo) * self.scale.x, self.pdp.x))
2090 
2091 	def YToPixel(self, y):
2092 		return int(round((y - self.subrange.y.lo) * self.scale.y, self.pdp.y))
2093 
2094 	def PixelToXRounded(self, px):
2095 		return round((round(px, 0) / self.scale.x), self.dp.x) + self.subrange.x.lo
2096 
2097 	def PixelToYRounded(self, py):
2098 		return round((round(py, 0) / self.scale.y), self.dp.y) + self.subrange.y.lo
2099 
2100 	def PixelToX(self, px):
2101 		x = self.PixelToXRounded(px)
2102 		if self.pdp.x == 0:
2103 			rt = self.XToPixel(x)
2104 			if rt > px:
2105 				return x - 1
2106 		return x
2107 
2108 	def PixelToY(self, py):
2109 		y = self.PixelToYRounded(py)
2110 		if self.pdp.y == 0:
2111 			rt = self.YToPixel(y)
2112 			if rt > py:
2113 				return y - 1
2114 		return y
2115 
2116 	def ToPDP(self, dp, scale):
2117 		# Calculate pixel decimal places:
2118 		#    (10 ** dp) is the minimum delta in the data
2119 		#    scale it to get the minimum delta in pixels
2120 		#    log10 gives the number of decimals places negatively
2121 		#    subtrace 1 to divide by 10
2122 		#    round to the lower negative number
2123 		#    change the sign to get the number of decimals positively
2124 		x = math.log10((10 ** dp) * scale)
2125 		if x < 0:
2126 			x -= 1
2127 			x = -int(math.floor(x) - 0.1)
2128 		else:
2129 			x = 0
2130 		return x
2131 
2132 	def Update(self):
2133 		x = self.ToPDP(self.dp.x, self.scale.x)
2134 		y = self.ToPDP(self.dp.y, self.scale.y)
2135 		self.pdp = XY(x, y) # pixel decimal places
2136 
2137 # Switch graph splitter which divides the CPU graphs from the legend
2138 
2139 class SwitchGraphSplitter(QSplitter):
2140 
2141 	def __init__(self, parent=None):
2142 		super(SwitchGraphSplitter, self).__init__(parent)
2143 
2144 		self.first_time = False
2145 
2146 	def resizeEvent(self, ev):
2147 		if self.first_time:
2148 			self.first_time = False
2149 			sz1 = self.widget(1).view.columnWidth(0) + self.widget(1).view.columnWidth(1) + self.widget(1).view.columnWidth(2) + 2
2150 			sz1 = sz1 + self.widget(1).view.verticalScrollBar().sizeHint().width()
2151 			sz0 = self.size().width() - self.handleWidth() - sz1
2152 			self.setSizes([sz0, sz1])
2153 		elif not(self.widget(1).saved_size is None):
2154 			sz1 = self.widget(1).saved_size
2155 			sz0 = self.size().width() - self.handleWidth() - sz1
2156 			self.setSizes([sz0, sz1])
2157 		super(SwitchGraphSplitter, self).resizeEvent(ev)
2158 
2159 # Graph widget base class
2160 
2161 class GraphWidget(QWidget):
2162 
2163 	graph_title_changed = Signal(object)
2164 
2165 	def __init__(self, parent=None):
2166 		super(GraphWidget, self).__init__(parent)
2167 
2168 	def GraphTitleChanged(self, title):
2169 		self.graph_title_changed.emit(title)
2170 
2171 	def Title(self):
2172 		return ""
2173 
2174 # Display time in s, ms, us or ns
2175 
2176 def ToTimeStr(val):
2177 	val = Decimal(val)
2178 	if val >= 1000000000:
2179 		return "{} s".format((val / 1000000000).quantize(Decimal("0.000000001")))
2180 	if val >= 1000000:
2181 		return "{} ms".format((val / 1000000).quantize(Decimal("0.000001")))
2182 	if val >= 1000:
2183 		return "{} us".format((val / 1000).quantize(Decimal("0.001")))
2184 	return "{} ns".format(val.quantize(Decimal("1")))
2185 
2186 # Switch (i.e. context switch i.e. Time Chart by CPU) graph widget which contains the CPU graphs and the legend and control buttons
2187 
2188 class SwitchGraphWidget(GraphWidget):
2189 
2190 	def __init__(self, glb, collection, parent=None):
2191 		super(SwitchGraphWidget, self).__init__(parent)
2192 
2193 		self.glb = glb
2194 		self.collection = collection
2195 
2196 		self.back_state = []
2197 		self.forward_state = []
2198 		self.selection_state = (None, None)
2199 		self.fwd_rect = None
2200 		self.start_time = self.glb.StartTime(collection.machine_id)
2201 
2202 		i = 0
2203 		hregions = collection.hregions.values()
2204 		colours = GenerateNRandomColours(len(hregions), 1013)
2205 		region_attributes = {}
2206 		for hregion in hregions:
2207 			if hregion.pid == 0 and hregion.tid == 0:
2208 				region_attributes[hregion.key] = GraphRegionAttribute(QColor(0, 0, 0))
2209 			else:
2210 				region_attributes[hregion.key] = GraphRegionAttribute(colours[i])
2211 				i = i + 1
2212 
2213 		# Default to entire range
2214 		xsubrange = Subrange(0.0, float(collection.xrangehi - collection.xrangelo) + 1.0)
2215 		ysubrange = Subrange(0.0, float(collection.yrangehi - collection.yrangelo) + 1.0)
2216 		subrange = XY(xsubrange, ysubrange)
2217 
2218 		scale = self.GetScaleForRange(subrange)
2219 
2220 		self.attrs = GraphAttributes(scale, subrange, region_attributes, collection.dp)
2221 
2222 		self.item = VertcalGraphSetGraphicsItem(collection, self.attrs, self, SwitchGraphGraphicsItem)
2223 
2224 		self.scene = QGraphicsScene()
2225 		self.scene.addItem(self.item)
2226 
2227 		self.view = QGraphicsView(self.scene)
2228 		self.view.centerOn(0, 0)
2229 		self.view.setAlignment(Qt.AlignLeft | Qt.AlignTop)
2230 
2231 		self.legend = SwitchGraphLegend(collection, region_attributes)
2232 
2233 		self.splitter = SwitchGraphSplitter()
2234 		self.splitter.addWidget(self.view)
2235 		self.splitter.addWidget(self.legend)
2236 
2237 		self.point_label = QLabel("")
2238 		self.point_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
2239 
2240 		self.back_button = QToolButton()
2241 		self.back_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowLeft))
2242 		self.back_button.setDisabled(True)
2243 		self.back_button.released.connect(lambda: self.Back())
2244 
2245 		self.forward_button = QToolButton()
2246 		self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_ArrowRight))
2247 		self.forward_button.setDisabled(True)
2248 		self.forward_button.released.connect(lambda: self.Forward())
2249 
2250 		self.zoom_button = QToolButton()
2251 		self.zoom_button.setText("Zoom")
2252 		self.zoom_button.setDisabled(True)
2253 		self.zoom_button.released.connect(lambda: self.Zoom())
2254 
2255 		self.hbox = HBoxLayout(self.back_button, self.forward_button, self.zoom_button, self.point_label)
2256 
2257 		self.vbox = VBoxLayout(self.splitter, self.hbox)
2258 
2259 		self.setLayout(self.vbox)
2260 
2261 	def GetScaleForRangeX(self, xsubrange):
2262 		# Default graph 1000 pixels wide
2263 		dflt = 1000.0
2264 		r = xsubrange.hi - xsubrange.lo
2265 		return dflt / r
2266 
2267 	def GetScaleForRangeY(self, ysubrange):
2268 		# Default graph 50 pixels high
2269 		dflt = 50.0
2270 		r = ysubrange.hi - ysubrange.lo
2271 		return dflt / r
2272 
2273 	def GetScaleForRange(self, subrange):
2274 		# Default graph 1000 pixels wide, 50 pixels high
2275 		xscale = self.GetScaleForRangeX(subrange.x)
2276 		yscale = self.GetScaleForRangeY(subrange.y)
2277 		return XY(xscale, yscale)
2278 
2279 	def PointEvent(self, cpu, time_from, time_to, hregions):
2280 		text = "CPU: " + str(cpu)
2281 		time_from = time_from.quantize(Decimal(1))
2282 		rel_time_from = time_from - self.glb.StartTime(self.collection.machine_id)
2283 		text = text + " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ")"
2284 		self.point_label.setText(text)
2285 		self.legend.Highlight(hregions)
2286 
2287 	def RightClickEvent(self, cpu, hregion_times, pos):
2288 		if not IsSelectable(self.glb.db, "calls", "WHERE parent_id >= 0"):
2289 			return
2290 		menu = QMenu(self.view)
2291 		for hregion, time in hregion_times:
2292 			thread_at_time = (hregion.exec_comm_id, hregion.thread_id, time)
2293 			menu_text = "Show Call Tree for {} {}:{} at {}".format(hregion.comm, hregion.pid, hregion.tid, time)
2294 			menu.addAction(CreateAction(menu_text, "Show Call Tree", lambda a=None, args=thread_at_time: self.RightClickSelect(args), self.view))
2295 		menu.exec_(pos)
2296 
2297 	def RightClickSelect(self, args):
2298 		CallTreeWindow(self.glb, self.glb.mainwindow, thread_at_time=args)
2299 
2300 	def NoPointEvent(self):
2301 		self.point_label.setText("")
2302 		self.legend.Highlight({})
2303 
2304 	def RangeEvent(self, time_from, time_to):
2305 		time_from = time_from.quantize(Decimal(1))
2306 		time_to = time_to.quantize(Decimal(1))
2307 		if time_to <= time_from:
2308 			self.point_label.setText("")
2309 			return
2310 		rel_time_from = time_from - self.start_time
2311 		rel_time_to = time_to - self.start_time
2312 		text = " Time: " + str(time_from) + " (+" + ToTimeStr(rel_time_from) + ") to: " + str(time_to) + " (+" + ToTimeStr(rel_time_to) + ")"
2313 		text = text + " duration: " + ToTimeStr(time_to - time_from)
2314 		self.point_label.setText(text)
2315 
2316 	def BackState(self):
2317 		return (self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect)
2318 
2319 	def PushBackState(self):
2320 		state = copy.deepcopy(self.BackState())
2321 		self.back_state.append(state)
2322 		self.back_button.setEnabled(True)
2323 
2324 	def PopBackState(self):
2325 		self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.back_state.pop()
2326 		self.attrs.Update()
2327 		if not self.back_state:
2328 			self.back_button.setDisabled(True)
2329 
2330 	def PushForwardState(self):
2331 		state = copy.deepcopy(self.BackState())
2332 		self.forward_state.append(state)
2333 		self.forward_button.setEnabled(True)
2334 
2335 	def PopForwardState(self):
2336 		self.attrs.subrange, self.attrs.scale, self.selection_state, self.fwd_rect = self.forward_state.pop()
2337 		self.attrs.Update()
2338 		if not self.forward_state:
2339 			self.forward_button.setDisabled(True)
2340 
2341 	def Title(self):
2342 		time_from = self.collection.xrangelo + Decimal(self.attrs.subrange.x.lo)
2343 		time_to = self.collection.xrangelo + Decimal(self.attrs.subrange.x.hi)
2344 		rel_time_from = time_from - self.start_time
2345 		rel_time_to = time_to - self.start_time
2346 		title = "+" + ToTimeStr(rel_time_from) + " to +" + ToTimeStr(rel_time_to)
2347 		title = title + " (" + ToTimeStr(time_to - time_from) + ")"
2348 		return title
2349 
2350 	def Update(self):
2351 		selected_subrange, selection_state = self.selection_state
2352 		self.item.SetSelection(selection_state)
2353 		self.item.SetBracket(self.fwd_rect)
2354 		self.zoom_button.setDisabled(selected_subrange is None)
2355 		self.GraphTitleChanged(self.Title())
2356 		self.item.update(self.item.boundingRect())
2357 
2358 	def Back(self):
2359 		if not self.back_state:
2360 			return
2361 		self.PushForwardState()
2362 		self.PopBackState()
2363 		self.Update()
2364 
2365 	def Forward(self):
2366 		if not self.forward_state:
2367 			return
2368 		self.PushBackState()
2369 		self.PopForwardState()
2370 		self.Update()
2371 
2372 	def SelectEvent(self, x0, x1, selection_state):
2373 		if selection_state is None:
2374 			selected_subrange = None
2375 		else:
2376 			if x1 - x0 < 1.0:
2377 				x1 += 1.0
2378 			selected_subrange = Subrange(x0, x1)
2379 		self.selection_state = (selected_subrange, selection_state)
2380 		self.zoom_button.setDisabled(selected_subrange is None)
2381 
2382 	def Zoom(self):
2383 		selected_subrange, selection_state = self.selection_state
2384 		if selected_subrange is None:
2385 			return
2386 		self.fwd_rect = selection_state
2387 		self.item.SetSelection(None)
2388 		self.PushBackState()
2389 		self.attrs.subrange.x = selected_subrange
2390 		self.forward_state = []
2391 		self.forward_button.setDisabled(True)
2392 		self.selection_state = (None, None)
2393 		self.fwd_rect = None
2394 		self.attrs.scale.x = self.GetScaleForRangeX(self.attrs.subrange.x)
2395 		self.attrs.Update()
2396 		self.Update()
2397 
2398 # Slow initialization - perform non-GUI initialization in a separate thread and put up a modal message box while waiting
2399 
2400 class SlowInitClass():
2401 
2402 	def __init__(self, glb, title, init_fn):
2403 		self.init_fn = init_fn
2404 		self.done = False
2405 		self.result = None
2406 
2407 		self.msg_box = QMessageBox(glb.mainwindow)
2408 		self.msg_box.setText("Initializing " + title + ". Please wait.")
2409 		self.msg_box.setWindowTitle("Initializing " + title)
2410 		self.msg_box.setWindowIcon(glb.mainwindow.style().standardIcon(QStyle.SP_MessageBoxInformation))
2411 
2412 		self.init_thread = Thread(self.ThreadFn, glb)
2413 		self.init_thread.done.connect(lambda: self.Done(), Qt.QueuedConnection)
2414 
2415 		self.init_thread.start()
2416 
2417 	def Done(self):
2418 		self.msg_box.done(0)
2419 
2420 	def ThreadFn(self, glb):
2421 		conn_name = "SlowInitClass" + str(os.getpid())
2422 		db, dbname = glb.dbref.Open(conn_name)
2423 		self.result = self.init_fn(db)
2424 		self.done = True
2425 		return (True, 0)
2426 
2427 	def Result(self):
2428 		while not self.done:
2429 			self.msg_box.exec_()
2430 		self.init_thread.wait()
2431 		return self.result
2432 
2433 def SlowInit(glb, title, init_fn):
2434 	init = SlowInitClass(glb, title, init_fn)
2435 	return init.Result()
2436 
2437 # Time chart by CPU window
2438 
2439 class TimeChartByCPUWindow(QMdiSubWindow):
2440 
2441 	def __init__(self, glb, parent=None):
2442 		super(TimeChartByCPUWindow, self).__init__(parent)
2443 
2444 		self.glb = glb
2445 		self.machine_id = glb.HostMachineId()
2446 		self.collection_name = "SwitchGraphDataCollection " + str(self.machine_id)
2447 
2448 		collection = LookupModel(self.collection_name)
2449 		if collection is None:
2450 			collection = SlowInit(glb, "Time Chart", self.Init)
2451 
2452 		self.widget = SwitchGraphWidget(glb, collection, self)
2453 		self.view = self.widget
2454 
2455 		self.base_title = "Time Chart by CPU"
2456 		self.setWindowTitle(self.base_title + self.widget.Title())
2457 		self.widget.graph_title_changed.connect(self.GraphTitleChanged)
2458 
2459 		self.setWidget(self.widget)
2460 
2461 		AddSubWindow(glb.mainwindow.mdi_area, self, self.windowTitle())
2462 
2463 	def Init(self, db):
2464 		return LookupCreateModel(self.collection_name, lambda : SwitchGraphDataCollection(self.glb, db, self.machine_id))
2465 
2466 	def GraphTitleChanged(self, title):
2467 		self.setWindowTitle(self.base_title + " : " + title)
2468 
2469 # Child data item  finder
2470 
2471 class ChildDataItemFinder():
2472 
2473 	def __init__(self, root):
2474 		self.root = root
2475 		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
2476 		self.rows = []
2477 		self.pos = 0
2478 
2479 	def FindSelect(self):
2480 		self.rows = []
2481 		if self.pattern:
2482 			pattern = re.compile(self.value)
2483 			for child in self.root.child_items:
2484 				for column_data in child.data:
2485 					if re.search(pattern, str(column_data)) is not None:
2486 						self.rows.append(child.row)
2487 						break
2488 		else:
2489 			for child in self.root.child_items:
2490 				for column_data in child.data:
2491 					if self.value in str(column_data):
2492 						self.rows.append(child.row)
2493 						break
2494 
2495 	def FindValue(self):
2496 		self.pos = 0
2497 		if self.last_value != self.value or self.pattern != self.last_pattern:
2498 			self.FindSelect()
2499 		if not len(self.rows):
2500 			return -1
2501 		return self.rows[self.pos]
2502 
2503 	def FindThread(self):
2504 		if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
2505 			row = self.FindValue()
2506 		elif len(self.rows):
2507 			if self.direction > 0:
2508 				self.pos += 1
2509 				if self.pos >= len(self.rows):
2510 					self.pos = 0
2511 			else:
2512 				self.pos -= 1
2513 				if self.pos < 0:
2514 					self.pos = len(self.rows) - 1
2515 			row = self.rows[self.pos]
2516 		else:
2517 			row = -1
2518 		return (True, row)
2519 
2520 	def Find(self, value, direction, pattern, context, callback):
2521 		self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
2522 		# Use a thread so the UI is not blocked
2523 		thread = Thread(self.FindThread)
2524 		thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
2525 		thread.start()
2526 
2527 	def FindDone(self, thread, callback, row):
2528 		callback(row)
2529 
2530 # Number of database records to fetch in one go
2531 
2532 glb_chunk_sz = 10000
2533 
2534 # Background process for SQL data fetcher
2535 
2536 class SQLFetcherProcess():
2537 
2538 	def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
2539 		# Need a unique connection name
2540 		conn_name = "SQLFetcher" + str(os.getpid())
2541 		self.db, dbname = dbref.Open(conn_name)
2542 		self.sql = sql
2543 		self.buffer = buffer
2544 		self.head = head
2545 		self.tail = tail
2546 		self.fetch_count = fetch_count
2547 		self.fetching_done = fetching_done
2548 		self.process_target = process_target
2549 		self.wait_event = wait_event
2550 		self.fetched_event = fetched_event
2551 		self.prep = prep
2552 		self.query = QSqlQuery(self.db)
2553 		self.query_limit = 0 if "$$last_id$$" in sql else 2
2554 		self.last_id = -1
2555 		self.fetched = 0
2556 		self.more = True
2557 		self.local_head = self.head.value
2558 		self.local_tail = self.tail.value
2559 
2560 	def Select(self):
2561 		if self.query_limit:
2562 			if self.query_limit == 1:
2563 				return
2564 			self.query_limit -= 1
2565 		stmt = self.sql.replace("$$last_id$$", str(self.last_id))
2566 		QueryExec(self.query, stmt)
2567 
2568 	def Next(self):
2569 		if not self.query.next():
2570 			self.Select()
2571 			if not self.query.next():
2572 				return None
2573 		self.last_id = self.query.value(0)
2574 		return self.prep(self.query)
2575 
2576 	def WaitForTarget(self):
2577 		while True:
2578 			self.wait_event.clear()
2579 			target = self.process_target.value
2580 			if target > self.fetched or target < 0:
2581 				break
2582 			self.wait_event.wait()
2583 		return target
2584 
2585 	def HasSpace(self, sz):
2586 		if self.local_tail <= self.local_head:
2587 			space = len(self.buffer) - self.local_head
2588 			if space > sz:
2589 				return True
2590 			if space >= glb_nsz:
2591 				# Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
2592 				nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
2593 				self.buffer[self.local_head : self.local_head + len(nd)] = nd
2594 			self.local_head = 0
2595 		if self.local_tail - self.local_head > sz:
2596 			return True
2597 		return False
2598 
2599 	def WaitForSpace(self, sz):
2600 		if self.HasSpace(sz):
2601 			return
2602 		while True:
2603 			self.wait_event.clear()
2604 			self.local_tail = self.tail.value
2605 			if self.HasSpace(sz):
2606 				return
2607 			self.wait_event.wait()
2608 
2609 	def AddToBuffer(self, obj):
2610 		d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
2611 		n = len(d)
2612 		nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
2613 		sz = n + glb_nsz
2614 		self.WaitForSpace(sz)
2615 		pos = self.local_head
2616 		self.buffer[pos : pos + len(nd)] = nd
2617 		self.buffer[pos + glb_nsz : pos + sz] = d
2618 		self.local_head += sz
2619 
2620 	def FetchBatch(self, batch_size):
2621 		fetched = 0
2622 		while batch_size > fetched:
2623 			obj = self.Next()
2624 			if obj is None:
2625 				self.more = False
2626 				break
2627 			self.AddToBuffer(obj)
2628 			fetched += 1
2629 		if fetched:
2630 			self.fetched += fetched
2631 			with self.fetch_count.get_lock():
2632 				self.fetch_count.value += fetched
2633 			self.head.value = self.local_head
2634 			self.fetched_event.set()
2635 
2636 	def Run(self):
2637 		while self.more:
2638 			target = self.WaitForTarget()
2639 			if target < 0:
2640 				break
2641 			batch_size = min(glb_chunk_sz, target - self.fetched)
2642 			self.FetchBatch(batch_size)
2643 		self.fetching_done.value = True
2644 		self.fetched_event.set()
2645 
2646 def SQLFetcherFn(*x):
2647 	process = SQLFetcherProcess(*x)
2648 	process.Run()
2649 
2650 # SQL data fetcher
2651 
2652 class SQLFetcher(QObject):
2653 
2654 	done = Signal(object)
2655 
2656 	def __init__(self, glb, sql, prep, process_data, parent=None):
2657 		super(SQLFetcher, self).__init__(parent)
2658 		self.process_data = process_data
2659 		self.more = True
2660 		self.target = 0
2661 		self.last_target = 0
2662 		self.fetched = 0
2663 		self.buffer_size = 16 * 1024 * 1024
2664 		self.buffer = Array(c_char, self.buffer_size, lock=False)
2665 		self.head = Value(c_longlong)
2666 		self.tail = Value(c_longlong)
2667 		self.local_tail = 0
2668 		self.fetch_count = Value(c_longlong)
2669 		self.fetching_done = Value(c_bool)
2670 		self.last_count = 0
2671 		self.process_target = Value(c_longlong)
2672 		self.wait_event = Event()
2673 		self.fetched_event = Event()
2674 		glb.AddInstanceToShutdownOnExit(self)
2675 		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))
2676 		self.process.start()
2677 		self.thread = Thread(self.Thread)
2678 		self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
2679 		self.thread.start()
2680 
2681 	def Shutdown(self):
2682 		# Tell the thread and process to exit
2683 		self.process_target.value = -1
2684 		self.wait_event.set()
2685 		self.more = False
2686 		self.fetching_done.value = True
2687 		self.fetched_event.set()
2688 
2689 	def Thread(self):
2690 		if not self.more:
2691 			return True, 0
2692 		while True:
2693 			self.fetched_event.clear()
2694 			fetch_count = self.fetch_count.value
2695 			if fetch_count != self.last_count:
2696 				break
2697 			if self.fetching_done.value:
2698 				self.more = False
2699 				return True, 0
2700 			self.fetched_event.wait()
2701 		count = fetch_count - self.last_count
2702 		self.last_count = fetch_count
2703 		self.fetched += count
2704 		return False, count
2705 
2706 	def Fetch(self, nr):
2707 		if not self.more:
2708 			# -1 inidcates there are no more
2709 			return -1
2710 		result = self.fetched
2711 		extra = result + nr - self.target
2712 		if extra > 0:
2713 			self.target += extra
2714 			# process_target < 0 indicates shutting down
2715 			if self.process_target.value >= 0:
2716 				self.process_target.value = self.target
2717 			self.wait_event.set()
2718 		return result
2719 
2720 	def RemoveFromBuffer(self):
2721 		pos = self.local_tail
2722 		if len(self.buffer) - pos < glb_nsz:
2723 			pos = 0
2724 		n = pickle.loads(self.buffer[pos : pos + glb_nsz])
2725 		if n == 0:
2726 			pos = 0
2727 			n = pickle.loads(self.buffer[0 : glb_nsz])
2728 		pos += glb_nsz
2729 		obj = pickle.loads(self.buffer[pos : pos + n])
2730 		self.local_tail = pos + n
2731 		return obj
2732 
2733 	def ProcessData(self, count):
2734 		for i in xrange(count):
2735 			obj = self.RemoveFromBuffer()
2736 			self.process_data(obj)
2737 		self.tail.value = self.local_tail
2738 		self.wait_event.set()
2739 		self.done.emit(count)
2740 
2741 # Fetch more records bar
2742 
2743 class FetchMoreRecordsBar():
2744 
2745 	def __init__(self, model, parent):
2746 		self.model = model
2747 
2748 		self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
2749 		self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2750 
2751 		self.fetch_count = QSpinBox()
2752 		self.fetch_count.setRange(1, 1000000)
2753 		self.fetch_count.setValue(10)
2754 		self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2755 
2756 		self.fetch = QPushButton("Go!")
2757 		self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2758 		self.fetch.released.connect(self.FetchMoreRecords)
2759 
2760 		self.progress = QProgressBar()
2761 		self.progress.setRange(0, 100)
2762 		self.progress.hide()
2763 
2764 		self.done_label = QLabel("All records fetched")
2765 		self.done_label.hide()
2766 
2767 		self.spacer = QLabel("")
2768 
2769 		self.close_button = QToolButton()
2770 		self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
2771 		self.close_button.released.connect(self.Deactivate)
2772 
2773 		self.hbox = QHBoxLayout()
2774 		self.hbox.setContentsMargins(0, 0, 0, 0)
2775 
2776 		self.hbox.addWidget(self.label)
2777 		self.hbox.addWidget(self.fetch_count)
2778 		self.hbox.addWidget(self.fetch)
2779 		self.hbox.addWidget(self.spacer)
2780 		self.hbox.addWidget(self.progress)
2781 		self.hbox.addWidget(self.done_label)
2782 		self.hbox.addWidget(self.close_button)
2783 
2784 		self.bar = QWidget()
2785 		self.bar.setLayout(self.hbox)
2786 		self.bar.show()
2787 
2788 		self.in_progress = False
2789 		self.model.progress.connect(self.Progress)
2790 
2791 		self.done = False
2792 
2793 		if not model.HasMoreRecords():
2794 			self.Done()
2795 
2796 	def Widget(self):
2797 		return self.bar
2798 
2799 	def Activate(self):
2800 		self.bar.show()
2801 		self.fetch.setFocus()
2802 
2803 	def Deactivate(self):
2804 		self.bar.hide()
2805 
2806 	def Enable(self, enable):
2807 		self.fetch.setEnabled(enable)
2808 		self.fetch_count.setEnabled(enable)
2809 
2810 	def Busy(self):
2811 		self.Enable(False)
2812 		self.fetch.hide()
2813 		self.spacer.hide()
2814 		self.progress.show()
2815 
2816 	def Idle(self):
2817 		self.in_progress = False
2818 		self.Enable(True)
2819 		self.progress.hide()
2820 		self.fetch.show()
2821 		self.spacer.show()
2822 
2823 	def Target(self):
2824 		return self.fetch_count.value() * glb_chunk_sz
2825 
2826 	def Done(self):
2827 		self.done = True
2828 		self.Idle()
2829 		self.label.hide()
2830 		self.fetch_count.hide()
2831 		self.fetch.hide()
2832 		self.spacer.hide()
2833 		self.done_label.show()
2834 
2835 	def Progress(self, count):
2836 		if self.in_progress:
2837 			if count:
2838 				percent = ((count - self.start) * 100) / self.Target()
2839 				if percent >= 100:
2840 					self.Idle()
2841 				else:
2842 					self.progress.setValue(percent)
2843 		if not count:
2844 			# Count value of zero means no more records
2845 			self.Done()
2846 
2847 	def FetchMoreRecords(self):
2848 		if self.done:
2849 			return
2850 		self.progress.setValue(0)
2851 		self.Busy()
2852 		self.in_progress = True
2853 		self.start = self.model.FetchMoreRecords(self.Target())
2854 
2855 # Brance data model level two item
2856 
2857 class BranchLevelTwoItem():
2858 
2859 	def __init__(self, row, col, text, parent_item):
2860 		self.row = row
2861 		self.parent_item = parent_item
2862 		self.data = [""] * (col + 1)
2863 		self.data[col] = text
2864 		self.level = 2
2865 
2866 	def getParentItem(self):
2867 		return self.parent_item
2868 
2869 	def getRow(self):
2870 		return self.row
2871 
2872 	def childCount(self):
2873 		return 0
2874 
2875 	def hasChildren(self):
2876 		return False
2877 
2878 	def getData(self, column):
2879 		return self.data[column]
2880 
2881 # Brance data model level one item
2882 
2883 class BranchLevelOneItem():
2884 
2885 	def __init__(self, glb, row, data, parent_item):
2886 		self.glb = glb
2887 		self.row = row
2888 		self.parent_item = parent_item
2889 		self.child_count = 0
2890 		self.child_items = []
2891 		self.data = data[1:]
2892 		self.dbid = data[0]
2893 		self.level = 1
2894 		self.query_done = False
2895 		self.br_col = len(self.data) - 1
2896 
2897 	def getChildItem(self, row):
2898 		return self.child_items[row]
2899 
2900 	def getParentItem(self):
2901 		return self.parent_item
2902 
2903 	def getRow(self):
2904 		return self.row
2905 
2906 	def Select(self):
2907 		self.query_done = True
2908 
2909 		if not self.glb.have_disassembler:
2910 			return
2911 
2912 		query = QSqlQuery(self.glb.db)
2913 
2914 		QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
2915 				  " FROM samples"
2916 				  " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
2917 				  " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
2918 				  " WHERE samples.id = " + str(self.dbid))
2919 		if not query.next():
2920 			return
2921 		cpu = query.value(0)
2922 		dso = query.value(1)
2923 		sym = query.value(2)
2924 		if dso == 0 or sym == 0:
2925 			return
2926 		off = query.value(3)
2927 		short_name = query.value(4)
2928 		long_name = query.value(5)
2929 		build_id = query.value(6)
2930 		sym_start = query.value(7)
2931 		ip = query.value(8)
2932 
2933 		QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
2934 				  " FROM samples"
2935 				  " INNER JOIN symbols ON samples.symbol_id = symbols.id"
2936 				  " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
2937 				  " ORDER BY samples.id"
2938 				  " LIMIT 1")
2939 		if not query.next():
2940 			return
2941 		if query.value(0) != dso:
2942 			# Cannot disassemble from one dso to another
2943 			return
2944 		bsym = query.value(1)
2945 		boff = query.value(2)
2946 		bsym_start = query.value(3)
2947 		if bsym == 0:
2948 			return
2949 		tot = bsym_start + boff + 1 - sym_start - off
2950 		if tot <= 0 or tot > 16384:
2951 			return
2952 
2953 		inst = self.glb.disassembler.Instruction()
2954 		f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
2955 		if not f:
2956 			return
2957 		mode = 0 if Is64Bit(f) else 1
2958 		self.glb.disassembler.SetMode(inst, mode)
2959 
2960 		buf_sz = tot + 16
2961 		buf = create_string_buffer(tot + 16)
2962 		f.seek(sym_start + off)
2963 		buf.value = f.read(buf_sz)
2964 		buf_ptr = addressof(buf)
2965 		i = 0
2966 		while tot > 0:
2967 			cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
2968 			if cnt:
2969 				byte_str = tohex(ip).rjust(16)
2970 				for k in xrange(cnt):
2971 					byte_str += " %02x" % ord(buf[i])
2972 					i += 1
2973 				while k < 15:
2974 					byte_str += "   "
2975 					k += 1
2976 				self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
2977 				self.child_count += 1
2978 			else:
2979 				return
2980 			buf_ptr += cnt
2981 			tot -= cnt
2982 			buf_sz -= cnt
2983 			ip += cnt
2984 
2985 	def childCount(self):
2986 		if not self.query_done:
2987 			self.Select()
2988 			if not self.child_count:
2989 				return -1
2990 		return self.child_count
2991 
2992 	def hasChildren(self):
2993 		if not self.query_done:
2994 			return True
2995 		return self.child_count > 0
2996 
2997 	def getData(self, column):
2998 		return self.data[column]
2999 
3000 # Brance data model root item
3001 
3002 class BranchRootItem():
3003 
3004 	def __init__(self):
3005 		self.child_count = 0
3006 		self.child_items = []
3007 		self.level = 0
3008 
3009 	def getChildItem(self, row):
3010 		return self.child_items[row]
3011 
3012 	def getParentItem(self):
3013 		return None
3014 
3015 	def getRow(self):
3016 		return 0
3017 
3018 	def childCount(self):
3019 		return self.child_count
3020 
3021 	def hasChildren(self):
3022 		return self.child_count > 0
3023 
3024 	def getData(self, column):
3025 		return ""
3026 
3027 # Calculate instructions per cycle
3028 
3029 def CalcIPC(cyc_cnt, insn_cnt):
3030 	if cyc_cnt and insn_cnt:
3031 		ipc = Decimal(float(insn_cnt) / cyc_cnt)
3032 		ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
3033 	else:
3034 		ipc = "0"
3035 	return ipc
3036 
3037 # Branch data preparation
3038 
3039 def BranchDataPrepBr(query, data):
3040 	data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
3041 			" (" + dsoname(query.value(11)) + ")" + " -> " +
3042 			tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
3043 			" (" + dsoname(query.value(15)) + ")")
3044 
3045 def BranchDataPrepIPC(query, data):
3046 	insn_cnt = query.value(16)
3047 	cyc_cnt = query.value(17)
3048 	ipc = CalcIPC(cyc_cnt, insn_cnt)
3049 	data.append(insn_cnt)
3050 	data.append(cyc_cnt)
3051 	data.append(ipc)
3052 
3053 def BranchDataPrep(query):
3054 	data = []
3055 	for i in xrange(0, 8):
3056 		data.append(query.value(i))
3057 	BranchDataPrepBr(query, data)
3058 	return data
3059 
3060 def BranchDataPrepWA(query):
3061 	data = []
3062 	data.append(query.value(0))
3063 	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3064 	data.append("{:>19}".format(query.value(1)))
3065 	for i in xrange(2, 8):
3066 		data.append(query.value(i))
3067 	BranchDataPrepBr(query, data)
3068 	return data
3069 
3070 def BranchDataWithIPCPrep(query):
3071 	data = []
3072 	for i in xrange(0, 8):
3073 		data.append(query.value(i))
3074 	BranchDataPrepIPC(query, data)
3075 	BranchDataPrepBr(query, data)
3076 	return data
3077 
3078 def BranchDataWithIPCPrepWA(query):
3079 	data = []
3080 	data.append(query.value(0))
3081 	# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3082 	data.append("{:>19}".format(query.value(1)))
3083 	for i in xrange(2, 8):
3084 		data.append(query.value(i))
3085 	BranchDataPrepIPC(query, data)
3086 	BranchDataPrepBr(query, data)
3087 	return data
3088 
3089 # Branch data model
3090 
3091 class BranchModel(TreeModel):
3092 
3093 	progress = Signal(object)
3094 
3095 	def __init__(self, glb, event_id, where_clause, parent=None):
3096 		super(BranchModel, self).__init__(glb, None, parent)
3097 		self.event_id = event_id
3098 		self.more = True
3099 		self.populated = 0
3100 		self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
3101 		if self.have_ipc:
3102 			select_ipc = ", insn_count, cyc_count"
3103 			prep_fn = BranchDataWithIPCPrep
3104 			prep_wa_fn = BranchDataWithIPCPrepWA
3105 		else:
3106 			select_ipc = ""
3107 			prep_fn = BranchDataPrep
3108 			prep_wa_fn = BranchDataPrepWA
3109 		sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
3110 			" CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
3111 			" ip, symbols.name, sym_offset, dsos.short_name,"
3112 			" to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
3113 			+ select_ipc +
3114 			" FROM samples"
3115 			" INNER JOIN comms ON comm_id = comms.id"
3116 			" INNER JOIN threads ON thread_id = threads.id"
3117 			" INNER JOIN branch_types ON branch_type = branch_types.id"
3118 			" INNER JOIN symbols ON symbol_id = symbols.id"
3119 			" INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
3120 			" INNER JOIN dsos ON samples.dso_id = dsos.id"
3121 			" INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
3122 			" WHERE samples.id > $$last_id$$" + where_clause +
3123 			" AND evsel_id = " + str(self.event_id) +
3124 			" ORDER BY samples.id"
3125 			" LIMIT " + str(glb_chunk_sz))
3126 		if pyside_version_1 and sys.version_info[0] == 3:
3127 			prep = prep_fn
3128 		else:
3129 			prep = prep_wa_fn
3130 		self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
3131 		self.fetcher.done.connect(self.Update)
3132 		self.fetcher.Fetch(glb_chunk_sz)
3133 
3134 	def GetRoot(self):
3135 		return BranchRootItem()
3136 
3137 	def columnCount(self, parent=None):
3138 		if self.have_ipc:
3139 			return 11
3140 		else:
3141 			return 8
3142 
3143 	def columnHeader(self, column):
3144 		if self.have_ipc:
3145 			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
3146 		else:
3147 			return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
3148 
3149 	def columnFont(self, column):
3150 		if self.have_ipc:
3151 			br_col = 10
3152 		else:
3153 			br_col = 7
3154 		if column != br_col:
3155 			return None
3156 		return QFont("Monospace")
3157 
3158 	def DisplayData(self, item, index):
3159 		if item.level == 1:
3160 			self.FetchIfNeeded(item.row)
3161 		return item.getData(index.column())
3162 
3163 	def AddSample(self, data):
3164 		child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
3165 		self.root.child_items.append(child)
3166 		self.populated += 1
3167 
3168 	def Update(self, fetched):
3169 		if not fetched:
3170 			self.more = False
3171 			self.progress.emit(0)
3172 		child_count = self.root.child_count
3173 		count = self.populated - child_count
3174 		if count > 0:
3175 			parent = QModelIndex()
3176 			self.beginInsertRows(parent, child_count, child_count + count - 1)
3177 			self.insertRows(child_count, count, parent)
3178 			self.root.child_count += count
3179 			self.endInsertRows()
3180 			self.progress.emit(self.root.child_count)
3181 
3182 	def FetchMoreRecords(self, count):
3183 		current = self.root.child_count
3184 		if self.more:
3185 			self.fetcher.Fetch(count)
3186 		else:
3187 			self.progress.emit(0)
3188 		return current
3189 
3190 	def HasMoreRecords(self):
3191 		return self.more
3192 
3193 # Report Variables
3194 
3195 class ReportVars():
3196 
3197 	def __init__(self, name = "", where_clause = "", limit = ""):
3198 		self.name = name
3199 		self.where_clause = where_clause
3200 		self.limit = limit
3201 
3202 	def UniqueId(self):
3203 		return str(self.where_clause + ";" + self.limit)
3204 
3205 # Branch window
3206 
3207 class BranchWindow(QMdiSubWindow):
3208 
3209 	def __init__(self, glb, event_id, report_vars, parent=None):
3210 		super(BranchWindow, self).__init__(parent)
3211 
3212 		model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
3213 
3214 		self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
3215 
3216 		self.view = QTreeView()
3217 		self.view.setUniformRowHeights(True)
3218 		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
3219 		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
3220 		self.view.setModel(self.model)
3221 
3222 		self.ResizeColumnsToContents()
3223 
3224 		self.context_menu = TreeContextMenu(self.view)
3225 
3226 		self.find_bar = FindBar(self, self, True)
3227 
3228 		self.finder = ChildDataItemFinder(self.model.root)
3229 
3230 		self.fetch_bar = FetchMoreRecordsBar(self.model, self)
3231 
3232 		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
3233 
3234 		self.setWidget(self.vbox.Widget())
3235 
3236 		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
3237 
3238 	def ResizeColumnToContents(self, column, n):
3239 		# Using the view's resizeColumnToContents() here is extrememly slow
3240 		# so implement a crude alternative
3241 		mm = "MM" if column else "MMMM"
3242 		font = self.view.font()
3243 		metrics = QFontMetrics(font)
3244 		max = 0
3245 		for row in xrange(n):
3246 			val = self.model.root.child_items[row].data[column]
3247 			len = metrics.width(str(val) + mm)
3248 			max = len if len > max else max
3249 		val = self.model.columnHeader(column)
3250 		len = metrics.width(str(val) + mm)
3251 		max = len if len > max else max
3252 		self.view.setColumnWidth(column, max)
3253 
3254 	def ResizeColumnsToContents(self):
3255 		n = min(self.model.root.child_count, 100)
3256 		if n < 1:
3257 			# No data yet, so connect a signal to notify when there is
3258 			self.model.rowsInserted.connect(self.UpdateColumnWidths)
3259 			return
3260 		columns = self.model.columnCount()
3261 		for i in xrange(columns):
3262 			self.ResizeColumnToContents(i, n)
3263 
3264 	def UpdateColumnWidths(self, *x):
3265 		# This only needs to be done once, so disconnect the signal now
3266 		self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
3267 		self.ResizeColumnsToContents()
3268 
3269 	def Find(self, value, direction, pattern, context):
3270 		self.view.setFocus()
3271 		self.find_bar.Busy()
3272 		self.finder.Find(value, direction, pattern, context, self.FindDone)
3273 
3274 	def FindDone(self, row):
3275 		self.find_bar.Idle()
3276 		if row >= 0:
3277 			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
3278 		else:
3279 			self.find_bar.NotFound()
3280 
3281 # Line edit data item
3282 
3283 class LineEditDataItem(object):
3284 
3285 	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
3286 		self.glb = glb
3287 		self.label = label
3288 		self.placeholder_text = placeholder_text
3289 		self.parent = parent
3290 		self.id = id
3291 
3292 		self.value = default
3293 
3294 		self.widget = QLineEdit(default)
3295 		self.widget.editingFinished.connect(self.Validate)
3296 		self.widget.textChanged.connect(self.Invalidate)
3297 		self.red = False
3298 		self.error = ""
3299 		self.validated = True
3300 
3301 		if placeholder_text:
3302 			self.widget.setPlaceholderText(placeholder_text)
3303 
3304 	def TurnTextRed(self):
3305 		if not self.red:
3306 			palette = QPalette()
3307 			palette.setColor(QPalette.Text,Qt.red)
3308 			self.widget.setPalette(palette)
3309 			self.red = True
3310 
3311 	def TurnTextNormal(self):
3312 		if self.red:
3313 			palette = QPalette()
3314 			self.widget.setPalette(palette)
3315 			self.red = False
3316 
3317 	def InvalidValue(self, value):
3318 		self.value = ""
3319 		self.TurnTextRed()
3320 		self.error = self.label + " invalid value '" + value + "'"
3321 		self.parent.ShowMessage(self.error)
3322 
3323 	def Invalidate(self):
3324 		self.validated = False
3325 
3326 	def DoValidate(self, input_string):
3327 		self.value = input_string.strip()
3328 
3329 	def Validate(self):
3330 		self.validated = True
3331 		self.error = ""
3332 		self.TurnTextNormal()
3333 		self.parent.ClearMessage()
3334 		input_string = self.widget.text()
3335 		if not len(input_string.strip()):
3336 			self.value = ""
3337 			return
3338 		self.DoValidate(input_string)
3339 
3340 	def IsValid(self):
3341 		if not self.validated:
3342 			self.Validate()
3343 		if len(self.error):
3344 			self.parent.ShowMessage(self.error)
3345 			return False
3346 		return True
3347 
3348 	def IsNumber(self, value):
3349 		try:
3350 			x = int(value)
3351 		except:
3352 			x = 0
3353 		return str(x) == value
3354 
3355 # Non-negative integer ranges dialog data item
3356 
3357 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
3358 
3359 	def __init__(self, glb, label, placeholder_text, column_name, parent):
3360 		super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
3361 
3362 		self.column_name = column_name
3363 
3364 	def DoValidate(self, input_string):
3365 		singles = []
3366 		ranges = []
3367 		for value in [x.strip() for x in input_string.split(",")]:
3368 			if "-" in value:
3369 				vrange = value.split("-")
3370 				if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
3371 					return self.InvalidValue(value)
3372 				ranges.append(vrange)
3373 			else:
3374 				if not self.IsNumber(value):
3375 					return self.InvalidValue(value)
3376 				singles.append(value)
3377 		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
3378 		if len(singles):
3379 			ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
3380 		self.value = " OR ".join(ranges)
3381 
3382 # Positive integer dialog data item
3383 
3384 class PositiveIntegerDataItem(LineEditDataItem):
3385 
3386 	def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
3387 		super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
3388 
3389 	def DoValidate(self, input_string):
3390 		if not self.IsNumber(input_string.strip()):
3391 			return self.InvalidValue(input_string)
3392 		value = int(input_string.strip())
3393 		if value <= 0:
3394 			return self.InvalidValue(input_string)
3395 		self.value = str(value)
3396 
3397 # Dialog data item converted and validated using a SQL table
3398 
3399 class SQLTableDataItem(LineEditDataItem):
3400 
3401 	def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
3402 		super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
3403 
3404 		self.table_name = table_name
3405 		self.match_column = match_column
3406 		self.column_name1 = column_name1
3407 		self.column_name2 = column_name2
3408 
3409 	def ValueToIds(self, value):
3410 		ids = []
3411 		query = QSqlQuery(self.glb.db)
3412 		stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
3413 		ret = query.exec_(stmt)
3414 		if ret:
3415 			while query.next():
3416 				ids.append(str(query.value(0)))
3417 		return ids
3418 
3419 	def DoValidate(self, input_string):
3420 		all_ids = []
3421 		for value in [x.strip() for x in input_string.split(",")]:
3422 			ids = self.ValueToIds(value)
3423 			if len(ids):
3424 				all_ids.extend(ids)
3425 			else:
3426 				return self.InvalidValue(value)
3427 		self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
3428 		if self.column_name2:
3429 			self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
3430 
3431 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
3432 
3433 class SampleTimeRangesDataItem(LineEditDataItem):
3434 
3435 	def __init__(self, glb, label, placeholder_text, column_name, parent):
3436 		self.column_name = column_name
3437 
3438 		self.last_id = 0
3439 		self.first_time = 0
3440 		self.last_time = 2 ** 64
3441 
3442 		query = QSqlQuery(glb.db)
3443 		QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
3444 		if query.next():
3445 			self.last_id = int(query.value(0))
3446 		self.first_time = int(glb.HostStartTime())
3447 		self.last_time = int(glb.HostFinishTime())
3448 		if placeholder_text:
3449 			placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
3450 
3451 		super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
3452 
3453 	def IdBetween(self, query, lower_id, higher_id, order):
3454 		QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
3455 		if query.next():
3456 			return True, int(query.value(0))
3457 		else:
3458 			return False, 0
3459 
3460 	def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
3461 		query = QSqlQuery(self.glb.db)
3462 		while True:
3463 			next_id = int((lower_id + higher_id) / 2)
3464 			QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
3465 			if not query.next():
3466 				ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
3467 				if not ok:
3468 					ok, dbid = self.IdBetween(query, next_id, higher_id, "")
3469 					if not ok:
3470 						return str(higher_id)
3471 				next_id = dbid
3472 				QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
3473 			next_time = int(query.value(0))
3474 			if get_floor:
3475 				if target_time > next_time:
3476 					lower_id = next_id
3477 				else:
3478 					higher_id = next_id
3479 				if higher_id <= lower_id + 1:
3480 					return str(higher_id)
3481 			else:
3482 				if target_time >= next_time:
3483 					lower_id = next_id
3484 				else:
3485 					higher_id = next_id
3486 				if higher_id <= lower_id + 1:
3487 					return str(lower_id)
3488 
3489 	def ConvertRelativeTime(self, val):
3490 		mult = 1
3491 		suffix = val[-2:]
3492 		if suffix == "ms":
3493 			mult = 1000000
3494 		elif suffix == "us":
3495 			mult = 1000
3496 		elif suffix == "ns":
3497 			mult = 1
3498 		else:
3499 			return val
3500 		val = val[:-2].strip()
3501 		if not self.IsNumber(val):
3502 			return val
3503 		val = int(val) * mult
3504 		if val >= 0:
3505 			val += self.first_time
3506 		else:
3507 			val += self.last_time
3508 		return str(val)
3509 
3510 	def ConvertTimeRange(self, vrange):
3511 		if vrange[0] == "":
3512 			vrange[0] = str(self.first_time)
3513 		if vrange[1] == "":
3514 			vrange[1] = str(self.last_time)
3515 		vrange[0] = self.ConvertRelativeTime(vrange[0])
3516 		vrange[1] = self.ConvertRelativeTime(vrange[1])
3517 		if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
3518 			return False
3519 		beg_range = max(int(vrange[0]), self.first_time)
3520 		end_range = min(int(vrange[1]), self.last_time)
3521 		if beg_range > self.last_time or end_range < self.first_time:
3522 			return False
3523 		vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
3524 		vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
3525 		return True
3526 
3527 	def AddTimeRange(self, value, ranges):
3528 		n = value.count("-")
3529 		if n == 1:
3530 			pass
3531 		elif n == 2:
3532 			if value.split("-")[1].strip() == "":
3533 				n = 1
3534 		elif n == 3:
3535 			n = 2
3536 		else:
3537 			return False
3538 		pos = findnth(value, "-", n)
3539 		vrange = [value[:pos].strip() ,value[pos+1:].strip()]
3540 		if self.ConvertTimeRange(vrange):
3541 			ranges.append(vrange)
3542 			return True
3543 		return False
3544 
3545 	def DoValidate(self, input_string):
3546 		ranges = []
3547 		for value in [x.strip() for x in input_string.split(",")]:
3548 			if not self.AddTimeRange(value, ranges):
3549 				return self.InvalidValue(value)
3550 		ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
3551 		self.value = " OR ".join(ranges)
3552 
3553 # Report Dialog Base
3554 
3555 class ReportDialogBase(QDialog):
3556 
3557 	def __init__(self, glb, title, items, partial, parent=None):
3558 		super(ReportDialogBase, self).__init__(parent)
3559 
3560 		self.glb = glb
3561 
3562 		self.report_vars = ReportVars()
3563 
3564 		self.setWindowTitle(title)
3565 		self.setMinimumWidth(600)
3566 
3567 		self.data_items = [x(glb, self) for x in items]
3568 
3569 		self.partial = partial
3570 
3571 		self.grid = QGridLayout()
3572 
3573 		for row in xrange(len(self.data_items)):
3574 			self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
3575 			self.grid.addWidget(self.data_items[row].widget, row, 1)
3576 
3577 		self.status = QLabel()
3578 
3579 		self.ok_button = QPushButton("Ok", self)
3580 		self.ok_button.setDefault(True)
3581 		self.ok_button.released.connect(self.Ok)
3582 		self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
3583 
3584 		self.cancel_button = QPushButton("Cancel", self)
3585 		self.cancel_button.released.connect(self.reject)
3586 		self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
3587 
3588 		self.hbox = QHBoxLayout()
3589 		#self.hbox.addStretch()
3590 		self.hbox.addWidget(self.status)
3591 		self.hbox.addWidget(self.ok_button)
3592 		self.hbox.addWidget(self.cancel_button)
3593 
3594 		self.vbox = QVBoxLayout()
3595 		self.vbox.addLayout(self.grid)
3596 		self.vbox.addLayout(self.hbox)
3597 
3598 		self.setLayout(self.vbox)
3599 
3600 	def Ok(self):
3601 		vars = self.report_vars
3602 		for d in self.data_items:
3603 			if d.id == "REPORTNAME":
3604 				vars.name = d.value
3605 		if not vars.name:
3606 			self.ShowMessage("Report name is required")
3607 			return
3608 		for d in self.data_items:
3609 			if not d.IsValid():
3610 				return
3611 		for d in self.data_items[1:]:
3612 			if d.id == "LIMIT":
3613 				vars.limit = d.value
3614 			elif len(d.value):
3615 				if len(vars.where_clause):
3616 					vars.where_clause += " AND "
3617 				vars.where_clause += d.value
3618 		if len(vars.where_clause):
3619 			if self.partial:
3620 				vars.where_clause = " AND ( " + vars.where_clause + " ) "
3621 			else:
3622 				vars.where_clause = " WHERE " + vars.where_clause + " "
3623 		self.accept()
3624 
3625 	def ShowMessage(self, msg):
3626 		self.status.setText("<font color=#FF0000>" + msg)
3627 
3628 	def ClearMessage(self):
3629 		self.status.setText("")
3630 
3631 # Selected branch report creation dialog
3632 
3633 class SelectedBranchDialog(ReportDialogBase):
3634 
3635 	def __init__(self, glb, parent=None):
3636 		title = "Selected Branches"
3637 		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
3638 			 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
3639 			 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
3640 			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
3641 			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
3642 			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
3643 			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
3644 			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
3645 			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
3646 		super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
3647 
3648 # Event list
3649 
3650 def GetEventList(db):
3651 	events = []
3652 	query = QSqlQuery(db)
3653 	QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
3654 	while query.next():
3655 		events.append(query.value(0))
3656 	return events
3657 
3658 # Is a table selectable
3659 
3660 def IsSelectable(db, table, sql = "", columns = "*"):
3661 	query = QSqlQuery(db)
3662 	try:
3663 		QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
3664 	except:
3665 		return False
3666 	return True
3667 
3668 # SQL table data model item
3669 
3670 class SQLTableItem():
3671 
3672 	def __init__(self, row, data):
3673 		self.row = row
3674 		self.data = data
3675 
3676 	def getData(self, column):
3677 		return self.data[column]
3678 
3679 # SQL table data model
3680 
3681 class SQLTableModel(TableModel):
3682 
3683 	progress = Signal(object)
3684 
3685 	def __init__(self, glb, sql, column_headers, parent=None):
3686 		super(SQLTableModel, self).__init__(parent)
3687 		self.glb = glb
3688 		self.more = True
3689 		self.populated = 0
3690 		self.column_headers = column_headers
3691 		self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
3692 		self.fetcher.done.connect(self.Update)
3693 		self.fetcher.Fetch(glb_chunk_sz)
3694 
3695 	def DisplayData(self, item, index):
3696 		self.FetchIfNeeded(item.row)
3697 		return item.getData(index.column())
3698 
3699 	def AddSample(self, data):
3700 		child = SQLTableItem(self.populated, data)
3701 		self.child_items.append(child)
3702 		self.populated += 1
3703 
3704 	def Update(self, fetched):
3705 		if not fetched:
3706 			self.more = False
3707 			self.progress.emit(0)
3708 		child_count = self.child_count
3709 		count = self.populated - child_count
3710 		if count > 0:
3711 			parent = QModelIndex()
3712 			self.beginInsertRows(parent, child_count, child_count + count - 1)
3713 			self.insertRows(child_count, count, parent)
3714 			self.child_count += count
3715 			self.endInsertRows()
3716 			self.progress.emit(self.child_count)
3717 
3718 	def FetchMoreRecords(self, count):
3719 		current = self.child_count
3720 		if self.more:
3721 			self.fetcher.Fetch(count)
3722 		else:
3723 			self.progress.emit(0)
3724 		return current
3725 
3726 	def HasMoreRecords(self):
3727 		return self.more
3728 
3729 	def columnCount(self, parent=None):
3730 		return len(self.column_headers)
3731 
3732 	def columnHeader(self, column):
3733 		return self.column_headers[column]
3734 
3735 	def SQLTableDataPrep(self, query, count):
3736 		data = []
3737 		for i in xrange(count):
3738 			data.append(query.value(i))
3739 		return data
3740 
3741 # SQL automatic table data model
3742 
3743 class SQLAutoTableModel(SQLTableModel):
3744 
3745 	def __init__(self, glb, table_name, parent=None):
3746 		sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
3747 		if table_name == "comm_threads_view":
3748 			# For now, comm_threads_view has no id column
3749 			sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
3750 		column_headers = []
3751 		query = QSqlQuery(glb.db)
3752 		if glb.dbref.is_sqlite3:
3753 			QueryExec(query, "PRAGMA table_info(" + table_name + ")")
3754 			while query.next():
3755 				column_headers.append(query.value(1))
3756 			if table_name == "sqlite_master":
3757 				sql = "SELECT * FROM " + table_name
3758 		else:
3759 			if table_name[:19] == "information_schema.":
3760 				sql = "SELECT * FROM " + table_name
3761 				select_table_name = table_name[19:]
3762 				schema = "information_schema"
3763 			else:
3764 				select_table_name = table_name
3765 				schema = "public"
3766 			QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
3767 			while query.next():
3768 				column_headers.append(query.value(0))
3769 		if pyside_version_1 and sys.version_info[0] == 3:
3770 			if table_name == "samples_view":
3771 				self.SQLTableDataPrep = self.samples_view_DataPrep
3772 			if table_name == "samples":
3773 				self.SQLTableDataPrep = self.samples_DataPrep
3774 		super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
3775 
3776 	def samples_view_DataPrep(self, query, count):
3777 		data = []
3778 		data.append(query.value(0))
3779 		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3780 		data.append("{:>19}".format(query.value(1)))
3781 		for i in xrange(2, count):
3782 			data.append(query.value(i))
3783 		return data
3784 
3785 	def samples_DataPrep(self, query, count):
3786 		data = []
3787 		for i in xrange(9):
3788 			data.append(query.value(i))
3789 		# Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
3790 		data.append("{:>19}".format(query.value(9)))
3791 		for i in xrange(10, count):
3792 			data.append(query.value(i))
3793 		return data
3794 
3795 # Base class for custom ResizeColumnsToContents
3796 
3797 class ResizeColumnsToContentsBase(QObject):
3798 
3799 	def __init__(self, parent=None):
3800 		super(ResizeColumnsToContentsBase, self).__init__(parent)
3801 
3802 	def ResizeColumnToContents(self, column, n):
3803 		# Using the view's resizeColumnToContents() here is extrememly slow
3804 		# so implement a crude alternative
3805 		font = self.view.font()
3806 		metrics = QFontMetrics(font)
3807 		max = 0
3808 		for row in xrange(n):
3809 			val = self.data_model.child_items[row].data[column]
3810 			len = metrics.width(str(val) + "MM")
3811 			max = len if len > max else max
3812 		val = self.data_model.columnHeader(column)
3813 		len = metrics.width(str(val) + "MM")
3814 		max = len if len > max else max
3815 		self.view.setColumnWidth(column, max)
3816 
3817 	def ResizeColumnsToContents(self):
3818 		n = min(self.data_model.child_count, 100)
3819 		if n < 1:
3820 			# No data yet, so connect a signal to notify when there is
3821 			self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
3822 			return
3823 		columns = self.data_model.columnCount()
3824 		for i in xrange(columns):
3825 			self.ResizeColumnToContents(i, n)
3826 
3827 	def UpdateColumnWidths(self, *x):
3828 		# This only needs to be done once, so disconnect the signal now
3829 		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
3830 		self.ResizeColumnsToContents()
3831 
3832 # Convert value to CSV
3833 
3834 def ToCSValue(val):
3835 	if '"' in val:
3836 		val = val.replace('"', '""')
3837 	if "," in val or '"' in val:
3838 		val = '"' + val + '"'
3839 	return val
3840 
3841 # Key to sort table model indexes by row / column, assuming fewer than 1000 columns
3842 
3843 glb_max_cols = 1000
3844 
3845 def RowColumnKey(a):
3846 	return a.row() * glb_max_cols + a.column()
3847 
3848 # Copy selected table cells to clipboard
3849 
3850 def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
3851 	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
3852 	idx_cnt = len(indexes)
3853 	if not idx_cnt:
3854 		return
3855 	if idx_cnt == 1:
3856 		with_hdr=False
3857 	min_row = indexes[0].row()
3858 	max_row = indexes[0].row()
3859 	min_col = indexes[0].column()
3860 	max_col = indexes[0].column()
3861 	for i in indexes:
3862 		min_row = min(min_row, i.row())
3863 		max_row = max(max_row, i.row())
3864 		min_col = min(min_col, i.column())
3865 		max_col = max(max_col, i.column())
3866 	if max_col > glb_max_cols:
3867 		raise RuntimeError("glb_max_cols is too low")
3868 	max_width = [0] * (1 + max_col - min_col)
3869 	for i in indexes:
3870 		c = i.column() - min_col
3871 		max_width[c] = max(max_width[c], len(str(i.data())))
3872 	text = ""
3873 	pad = ""
3874 	sep = ""
3875 	if with_hdr:
3876 		model = indexes[0].model()
3877 		for col in range(min_col, max_col + 1):
3878 			val = model.headerData(col, Qt.Horizontal, Qt.DisplayRole)
3879 			if as_csv:
3880 				text += sep + ToCSValue(val)
3881 				sep = ","
3882 			else:
3883 				c = col - min_col
3884 				max_width[c] = max(max_width[c], len(val))
3885 				width = max_width[c]
3886 				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
3887 				if align & Qt.AlignRight:
3888 					val = val.rjust(width)
3889 				text += pad + sep + val
3890 				pad = " " * (width - len(val))
3891 				sep = "  "
3892 		text += "\n"
3893 		pad = ""
3894 		sep = ""
3895 	last_row = min_row
3896 	for i in indexes:
3897 		if i.row() > last_row:
3898 			last_row = i.row()
3899 			text += "\n"
3900 			pad = ""
3901 			sep = ""
3902 		if as_csv:
3903 			text += sep + ToCSValue(str(i.data()))
3904 			sep = ","
3905 		else:
3906 			width = max_width[i.column() - min_col]
3907 			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
3908 				val = str(i.data()).rjust(width)
3909 			else:
3910 				val = str(i.data())
3911 			text += pad + sep + val
3912 			pad = " " * (width - len(val))
3913 			sep = "  "
3914 	QApplication.clipboard().setText(text)
3915 
3916 def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
3917 	indexes = view.selectedIndexes()
3918 	if not len(indexes):
3919 		return
3920 
3921 	selection = view.selectionModel()
3922 
3923 	first = None
3924 	for i in indexes:
3925 		above = view.indexAbove(i)
3926 		if not selection.isSelected(above):
3927 			first = i
3928 			break
3929 
3930 	if first is None:
3931 		raise RuntimeError("CopyTreeCellsToClipboard internal error")
3932 
3933 	model = first.model()
3934 	row_cnt = 0
3935 	col_cnt = model.columnCount(first)
3936 	max_width = [0] * col_cnt
3937 
3938 	indent_sz = 2
3939 	indent_str = " " * indent_sz
3940 
3941 	expanded_mark_sz = 2
3942 	if sys.version_info[0] == 3:
3943 		expanded_mark = "\u25BC "
3944 		not_expanded_mark = "\u25B6 "
3945 	else:
3946 		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
3947 		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
3948 	leaf_mark = "  "
3949 
3950 	if not as_csv:
3951 		pos = first
3952 		while True:
3953 			row_cnt += 1
3954 			row = pos.row()
3955 			for c in range(col_cnt):
3956 				i = pos.sibling(row, c)
3957 				if c:
3958 					n = len(str(i.data()))
3959 				else:
3960 					n = len(str(i.data()).strip())
3961 					n += (i.internalPointer().level - 1) * indent_sz
3962 					n += expanded_mark_sz
3963 				max_width[c] = max(max_width[c], n)
3964 			pos = view.indexBelow(pos)
3965 			if not selection.isSelected(pos):
3966 				break
3967 
3968 	text = ""
3969 	pad = ""
3970 	sep = ""
3971 	if with_hdr:
3972 		for c in range(col_cnt):
3973 			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
3974 			if as_csv:
3975 				text += sep + ToCSValue(val)
3976 				sep = ","
3977 			else:
3978 				max_width[c] = max(max_width[c], len(val))
3979 				width = max_width[c]
3980 				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
3981 				if align & Qt.AlignRight:
3982 					val = val.rjust(width)
3983 				text += pad + sep + val
3984 				pad = " " * (width - len(val))
3985 				sep = "   "
3986 		text += "\n"
3987 		pad = ""
3988 		sep = ""
3989 
3990 	pos = first
3991 	while True:
3992 		row = pos.row()
3993 		for c in range(col_cnt):
3994 			i = pos.sibling(row, c)
3995 			val = str(i.data())
3996 			if not c:
3997 				if model.hasChildren(i):
3998 					if view.isExpanded(i):
3999 						mark = expanded_mark
4000 					else:
4001 						mark = not_expanded_mark
4002 				else:
4003 					mark = leaf_mark
4004 				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
4005 			if as_csv:
4006 				text += sep + ToCSValue(val)
4007 				sep = ","
4008 			else:
4009 				width = max_width[c]
4010 				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
4011 					val = val.rjust(width)
4012 				text += pad + sep + val
4013 				pad = " " * (width - len(val))
4014 				sep = "   "
4015 		pos = view.indexBelow(pos)
4016 		if not selection.isSelected(pos):
4017 			break
4018 		text = text.rstrip() + "\n"
4019 		pad = ""
4020 		sep = ""
4021 
4022 	QApplication.clipboard().setText(text)
4023 
4024 def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
4025 	view.CopyCellsToClipboard(view, as_csv, with_hdr)
4026 
4027 def CopyCellsToClipboardHdr(view):
4028 	CopyCellsToClipboard(view, False, True)
4029 
4030 def CopyCellsToClipboardCSV(view):
4031 	CopyCellsToClipboard(view, True, True)
4032 
4033 # Context menu
4034 
4035 class ContextMenu(object):
4036 
4037 	def __init__(self, view):
4038 		self.view = view
4039 		self.view.setContextMenuPolicy(Qt.CustomContextMenu)
4040 		self.view.customContextMenuRequested.connect(self.ShowContextMenu)
4041 
4042 	def ShowContextMenu(self, pos):
4043 		menu = QMenu(self.view)
4044 		self.AddActions(menu)
4045 		menu.exec_(self.view.mapToGlobal(pos))
4046 
4047 	def AddCopy(self, menu):
4048 		menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
4049 		menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
4050 
4051 	def AddActions(self, menu):
4052 		self.AddCopy(menu)
4053 
4054 class TreeContextMenu(ContextMenu):
4055 
4056 	def __init__(self, view):
4057 		super(TreeContextMenu, self).__init__(view)
4058 
4059 	def AddActions(self, menu):
4060 		i = self.view.currentIndex()
4061 		text = str(i.data()).strip()
4062 		if len(text):
4063 			menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
4064 		self.AddCopy(menu)
4065 
4066 # Table window
4067 
4068 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
4069 
4070 	def __init__(self, glb, table_name, parent=None):
4071 		super(TableWindow, self).__init__(parent)
4072 
4073 		self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
4074 
4075 		self.model = QSortFilterProxyModel()
4076 		self.model.setSourceModel(self.data_model)
4077 
4078 		self.view = QTableView()
4079 		self.view.setModel(self.model)
4080 		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
4081 		self.view.verticalHeader().setVisible(False)
4082 		self.view.sortByColumn(-1, Qt.AscendingOrder)
4083 		self.view.setSortingEnabled(True)
4084 		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
4085 		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
4086 
4087 		self.ResizeColumnsToContents()
4088 
4089 		self.context_menu = ContextMenu(self.view)
4090 
4091 		self.find_bar = FindBar(self, self, True)
4092 
4093 		self.finder = ChildDataItemFinder(self.data_model)
4094 
4095 		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
4096 
4097 		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
4098 
4099 		self.setWidget(self.vbox.Widget())
4100 
4101 		AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
4102 
4103 	def Find(self, value, direction, pattern, context):
4104 		self.view.setFocus()
4105 		self.find_bar.Busy()
4106 		self.finder.Find(value, direction, pattern, context, self.FindDone)
4107 
4108 	def FindDone(self, row):
4109 		self.find_bar.Idle()
4110 		if row >= 0:
4111 			self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
4112 		else:
4113 			self.find_bar.NotFound()
4114 
4115 # Table list
4116 
4117 def GetTableList(glb):
4118 	tables = []
4119 	query = QSqlQuery(glb.db)
4120 	if glb.dbref.is_sqlite3:
4121 		QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
4122 	else:
4123 		QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
4124 	while query.next():
4125 		tables.append(query.value(0))
4126 	if glb.dbref.is_sqlite3:
4127 		tables.append("sqlite_master")
4128 	else:
4129 		tables.append("information_schema.tables")
4130 		tables.append("information_schema.views")
4131 		tables.append("information_schema.columns")
4132 	return tables
4133 
4134 # Top Calls data model
4135 
4136 class TopCallsModel(SQLTableModel):
4137 
4138 	def __init__(self, glb, report_vars, parent=None):
4139 		text = ""
4140 		if not glb.dbref.is_sqlite3:
4141 			text = "::text"
4142 		limit = ""
4143 		if len(report_vars.limit):
4144 			limit = " LIMIT " + report_vars.limit
4145 		sql = ("SELECT comm, pid, tid, name,"
4146 			" CASE"
4147 			" WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
4148 			" ELSE short_name"
4149 			" END AS dso,"
4150 			" call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
4151 			" CASE"
4152 			" WHEN (calls.flags = 1) THEN 'no call'" + text +
4153 			" WHEN (calls.flags = 2) THEN 'no return'" + text +
4154 			" WHEN (calls.flags = 3) THEN 'no call/return'" + text +
4155 			" ELSE ''" + text +
4156 			" END AS flags"
4157 			" FROM calls"
4158 			" INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
4159 			" INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
4160 			" INNER JOIN dsos ON symbols.dso_id = dsos.id"
4161 			" INNER JOIN comms ON calls.comm_id = comms.id"
4162 			" INNER JOIN threads ON calls.thread_id = threads.id" +
4163 			report_vars.where_clause +
4164 			" ORDER BY elapsed_time DESC" +
4165 			limit
4166 			)
4167 		column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
4168 		self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
4169 		super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
4170 
4171 	def columnAlignment(self, column):
4172 		return self.alignment[column]
4173 
4174 # Top Calls report creation dialog
4175 
4176 class TopCallsDialog(ReportDialogBase):
4177 
4178 	def __init__(self, glb, parent=None):
4179 		title = "Top Calls by Elapsed Time"
4180 		items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
4181 			 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
4182 			 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
4183 			 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
4184 			 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
4185 			 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
4186 			 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
4187 			 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
4188 		super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
4189 
4190 # Top Calls window
4191 
4192 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
4193 
4194 	def __init__(self, glb, report_vars, parent=None):
4195 		super(TopCallsWindow, self).__init__(parent)
4196 
4197 		self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
4198 		self.model = self.data_model
4199 
4200 		self.view = QTableView()
4201 		self.view.setModel(self.model)
4202 		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
4203 		self.view.verticalHeader().setVisible(False)
4204 		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
4205 		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
4206 
4207 		self.context_menu = ContextMenu(self.view)
4208 
4209 		self.ResizeColumnsToContents()
4210 
4211 		self.find_bar = FindBar(self, self, True)
4212 
4213 		self.finder = ChildDataItemFinder(self.model)
4214 
4215 		self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
4216 
4217 		self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
4218 
4219 		self.setWidget(self.vbox.Widget())
4220 
4221 		AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
4222 
4223 	def Find(self, value, direction, pattern, context):
4224 		self.view.setFocus()
4225 		self.find_bar.Busy()
4226 		self.finder.Find(value, direction, pattern, context, self.FindDone)
4227 
4228 	def FindDone(self, row):
4229 		self.find_bar.Idle()
4230 		if row >= 0:
4231 			self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
4232 		else:
4233 			self.find_bar.NotFound()
4234 
4235 # Action Definition
4236 
4237 def CreateAction(label, tip, callback, parent=None, shortcut=None):
4238 	action = QAction(label, parent)
4239 	if shortcut != None:
4240 		action.setShortcuts(shortcut)
4241 	action.setStatusTip(tip)
4242 	action.triggered.connect(callback)
4243 	return action
4244 
4245 # Typical application actions
4246 
4247 def CreateExitAction(app, parent=None):
4248 	return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
4249 
4250 # Typical MDI actions
4251 
4252 def CreateCloseActiveWindowAction(mdi_area):
4253 	return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
4254 
4255 def CreateCloseAllWindowsAction(mdi_area):
4256 	return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
4257 
4258 def CreateTileWindowsAction(mdi_area):
4259 	return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
4260 
4261 def CreateCascadeWindowsAction(mdi_area):
4262 	return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
4263 
4264 def CreateNextWindowAction(mdi_area):
4265 	return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
4266 
4267 def CreatePreviousWindowAction(mdi_area):
4268 	return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
4269 
4270 # Typical MDI window menu
4271 
4272 class WindowMenu():
4273 
4274 	def __init__(self, mdi_area, menu):
4275 		self.mdi_area = mdi_area
4276 		self.window_menu = menu.addMenu("&Windows")
4277 		self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
4278 		self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
4279 		self.tile_windows = CreateTileWindowsAction(mdi_area)
4280 		self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
4281 		self.next_window = CreateNextWindowAction(mdi_area)
4282 		self.previous_window = CreatePreviousWindowAction(mdi_area)
4283 		self.window_menu.aboutToShow.connect(self.Update)
4284 
4285 	def Update(self):
4286 		self.window_menu.clear()
4287 		sub_window_count = len(self.mdi_area.subWindowList())
4288 		have_sub_windows = sub_window_count != 0
4289 		self.close_active_window.setEnabled(have_sub_windows)
4290 		self.close_all_windows.setEnabled(have_sub_windows)
4291 		self.tile_windows.setEnabled(have_sub_windows)
4292 		self.cascade_windows.setEnabled(have_sub_windows)
4293 		self.next_window.setEnabled(have_sub_windows)
4294 		self.previous_window.setEnabled(have_sub_windows)
4295 		self.window_menu.addAction(self.close_active_window)
4296 		self.window_menu.addAction(self.close_all_windows)
4297 		self.window_menu.addSeparator()
4298 		self.window_menu.addAction(self.tile_windows)
4299 		self.window_menu.addAction(self.cascade_windows)
4300 		self.window_menu.addSeparator()
4301 		self.window_menu.addAction(self.next_window)
4302 		self.window_menu.addAction(self.previous_window)
4303 		if sub_window_count == 0:
4304 			return
4305 		self.window_menu.addSeparator()
4306 		nr = 1
4307 		for sub_window in self.mdi_area.subWindowList():
4308 			label = str(nr) + " " + sub_window.name
4309 			if nr < 10:
4310 				label = "&" + label
4311 			action = self.window_menu.addAction(label)
4312 			action.setCheckable(True)
4313 			action.setChecked(sub_window == self.mdi_area.activeSubWindow())
4314 			action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
4315 			self.window_menu.addAction(action)
4316 			nr += 1
4317 
4318 	def setActiveSubWindow(self, nr):
4319 		self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
4320 
4321 # Help text
4322 
4323 glb_help_text = """
4324 <h1>Contents</h1>
4325 <style>
4326 p.c1 {
4327     text-indent: 40px;
4328 }
4329 p.c2 {
4330     text-indent: 80px;
4331 }
4332 }
4333 </style>
4334 <p class=c1><a href=#reports>1. Reports</a></p>
4335 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
4336 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
4337 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
4338 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
4339 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
4340 <p class=c1><a href=#charts>2. Charts</a></p>
4341 <p class=c2><a href=#timechartbycpu>2.1 Time chart by CPU</a></p>
4342 <p class=c1><a href=#tables>3. Tables</a></p>
4343 <h1 id=reports>1. Reports</h1>
4344 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
4345 The result is a GUI window with a tree representing a context-sensitive
4346 call-graph. Expanding a couple of levels of the tree and adjusting column
4347 widths to suit will display something like:
4348 <pre>
4349                                          Call Graph: pt_example
4350 Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
4351 v- ls
4352     v- 2638:2638
4353         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
4354           |- unknown               unknown       1        13198     0.1              1              0.0
4355           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
4356           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
4357           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
4358              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
4359              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
4360              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
4361              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
4362              v- main               ls            1      8182043    99.6         180254             99.9
4363 </pre>
4364 <h3>Points to note:</h3>
4365 <ul>
4366 <li>The top level is a command name (comm)</li>
4367 <li>The next level is a thread (pid:tid)</li>
4368 <li>Subsequent levels are functions</li>
4369 <li>'Count' is the number of calls</li>
4370 <li>'Time' is the elapsed time until the function returns</li>
4371 <li>Percentages are relative to the level above</li>
4372 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
4373 </ul>
4374 <h3>Find</h3>
4375 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
4376 The pattern matching symbols are ? for any character and * for zero or more characters.
4377 <h2 id=calltree>1.2 Call Tree</h2>
4378 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
4379 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
4380 <h2 id=allbranches>1.3 All branches</h2>
4381 The All branches report displays all branches in chronological order.
4382 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
4383 <h3>Disassembly</h3>
4384 Open a branch to display disassembly. This only works if:
4385 <ol>
4386 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
4387 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
4388 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
4389 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
4390 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
4391 </ol>
4392 <h4 id=xed>Intel XED Setup</h4>
4393 To use Intel XED, libxed.so must be present.  To build and install libxed.so:
4394 <pre>
4395 git clone https://github.com/intelxed/mbuild.git mbuild
4396 git clone https://github.com/intelxed/xed
4397 cd xed
4398 ./mfile.py --share
4399 sudo ./mfile.py --prefix=/usr/local install
4400 sudo ldconfig
4401 </pre>
4402 <h3>Instructions per Cycle (IPC)</h3>
4403 If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
4404 <p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
4405 Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
4406 In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
4407 since the previous displayed 'IPC'.
4408 <h3>Find</h3>
4409 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
4410 Refer to Python documentation for the regular expression syntax.
4411 All columns are searched, but only currently fetched rows are searched.
4412 <h2 id=selectedbranches>1.4 Selected branches</h2>
4413 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
4414 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
4415 <h3>1.4.1 Time ranges</h3>
4416 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
4417 ms, us or ns. Also, negative values are relative to the end of trace.  Examples:
4418 <pre>
4419 	81073085947329-81073085958238	From 81073085947329 to 81073085958238
4420 	100us-200us		From 100us to 200us
4421 	10ms-			From 10ms to the end
4422 	-100ns			The first 100ns
4423 	-10ms-			The last 10ms
4424 </pre>
4425 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
4426 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
4427 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.
4428 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
4429 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
4430 <h1 id=charts>2. Charts</h1>
4431 <h2 id=timechartbycpu>2.1 Time chart by CPU</h2>
4432 This chart displays context switch information when that data is available. Refer to context_switches_view on the Tables menu.
4433 <h3>Features</h3>
4434 <ol>
4435 <li>Mouse over to highight the task and show the time</li>
4436 <li>Drag the mouse to select a region and zoom by pushing the Zoom button</li>
4437 <li>Go back and forward by pressing the arrow buttons</li>
4438 <li>If call information is available, right-click to show a call tree opened to that task and time.
4439 Note, the call tree may take some time to appear, and there may not be call information for the task or time selected.
4440 </li>
4441 </ol>
4442 <h3>Important</h3>
4443 The graph can be misleading in the following respects:
4444 <ol>
4445 <li>The graph shows the first task on each CPU as running from the beginning of the time range.
4446 Because tracing might start on different CPUs at different times, that is not necessarily the case.
4447 Refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4448 <li>Similarly, the last task on each CPU can be showing running longer than it really was.
4449 Again, refer to context_switches_view on the Tables menu to understand what data the graph is based upon.</li>
4450 <li>When the mouse is over a task, the highlighted task might not be visible on the legend without scrolling if the legend does not fit fully in the window</li>
4451 </ol>
4452 <h1 id=tables>3. Tables</h1>
4453 The Tables menu shows all tables and views in the database. Most tables have an associated view
4454 which displays the information in a more friendly way. Not all data for large tables is fetched
4455 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
4456 but that can be slow for large tables.
4457 <p>There are also tables of database meta-information.
4458 For SQLite3 databases, the sqlite_master table is included.
4459 For PostgreSQL databases, information_schema.tables/views/columns are included.
4460 <h3>Find</h3>
4461 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
4462 Refer to Python documentation for the regular expression syntax.
4463 All columns are searched, but only currently fetched rows are searched.
4464 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
4465 will go to the next/previous result in id order, instead of display order.
4466 """
4467 
4468 # Help window
4469 
4470 class HelpWindow(QMdiSubWindow):
4471 
4472 	def __init__(self, glb, parent=None):
4473 		super(HelpWindow, self).__init__(parent)
4474 
4475 		self.text = QTextBrowser()
4476 		self.text.setHtml(glb_help_text)
4477 		self.text.setReadOnly(True)
4478 		self.text.setOpenExternalLinks(True)
4479 
4480 		self.setWidget(self.text)
4481 
4482 		AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
4483 
4484 # Main window that only displays the help text
4485 
4486 class HelpOnlyWindow(QMainWindow):
4487 
4488 	def __init__(self, parent=None):
4489 		super(HelpOnlyWindow, self).__init__(parent)
4490 
4491 		self.setMinimumSize(200, 100)
4492 		self.resize(800, 600)
4493 		self.setWindowTitle("Exported SQL Viewer Help")
4494 		self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
4495 
4496 		self.text = QTextBrowser()
4497 		self.text.setHtml(glb_help_text)
4498 		self.text.setReadOnly(True)
4499 		self.text.setOpenExternalLinks(True)
4500 
4501 		self.setCentralWidget(self.text)
4502 
4503 # PostqreSQL server version
4504 
4505 def PostqreSQLServerVersion(db):
4506 	query = QSqlQuery(db)
4507 	QueryExec(query, "SELECT VERSION()")
4508 	if query.next():
4509 		v_str = query.value(0)
4510 		v_list = v_str.strip().split(" ")
4511 		if v_list[0] == "PostgreSQL" and v_list[2] == "on":
4512 			return v_list[1]
4513 		return v_str
4514 	return "Unknown"
4515 
4516 # SQLite version
4517 
4518 def SQLiteVersion(db):
4519 	query = QSqlQuery(db)
4520 	QueryExec(query, "SELECT sqlite_version()")
4521 	if query.next():
4522 		return query.value(0)
4523 	return "Unknown"
4524 
4525 # About dialog
4526 
4527 class AboutDialog(QDialog):
4528 
4529 	def __init__(self, glb, parent=None):
4530 		super(AboutDialog, self).__init__(parent)
4531 
4532 		self.setWindowTitle("About Exported SQL Viewer")
4533 		self.setMinimumWidth(300)
4534 
4535 		pyside_version = "1" if pyside_version_1 else "2"
4536 
4537 		text = "<pre>"
4538 		text += "Python version:     " + sys.version.split(" ")[0] + "\n"
4539 		text += "PySide version:     " + pyside_version + "\n"
4540 		text += "Qt version:         " + qVersion() + "\n"
4541 		if glb.dbref.is_sqlite3:
4542 			text += "SQLite version:     " + SQLiteVersion(glb.db) + "\n"
4543 		else:
4544 			text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
4545 		text += "</pre>"
4546 
4547 		self.text = QTextBrowser()
4548 		self.text.setHtml(text)
4549 		self.text.setReadOnly(True)
4550 		self.text.setOpenExternalLinks(True)
4551 
4552 		self.vbox = QVBoxLayout()
4553 		self.vbox.addWidget(self.text)
4554 
4555 		self.setLayout(self.vbox)
4556 
4557 # Font resize
4558 
4559 def ResizeFont(widget, diff):
4560 	font = widget.font()
4561 	sz = font.pointSize()
4562 	font.setPointSize(sz + diff)
4563 	widget.setFont(font)
4564 
4565 def ShrinkFont(widget):
4566 	ResizeFont(widget, -1)
4567 
4568 def EnlargeFont(widget):
4569 	ResizeFont(widget, 1)
4570 
4571 # Unique name for sub-windows
4572 
4573 def NumberedWindowName(name, nr):
4574 	if nr > 1:
4575 		name += " <" + str(nr) + ">"
4576 	return name
4577 
4578 def UniqueSubWindowName(mdi_area, name):
4579 	nr = 1
4580 	while True:
4581 		unique_name = NumberedWindowName(name, nr)
4582 		ok = True
4583 		for sub_window in mdi_area.subWindowList():
4584 			if sub_window.name == unique_name:
4585 				ok = False
4586 				break
4587 		if ok:
4588 			return unique_name
4589 		nr += 1
4590 
4591 # Add a sub-window
4592 
4593 def AddSubWindow(mdi_area, sub_window, name):
4594 	unique_name = UniqueSubWindowName(mdi_area, name)
4595 	sub_window.setMinimumSize(200, 100)
4596 	sub_window.resize(800, 600)
4597 	sub_window.setWindowTitle(unique_name)
4598 	sub_window.setAttribute(Qt.WA_DeleteOnClose)
4599 	sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
4600 	sub_window.name = unique_name
4601 	mdi_area.addSubWindow(sub_window)
4602 	sub_window.show()
4603 
4604 # Main window
4605 
4606 class MainWindow(QMainWindow):
4607 
4608 	def __init__(self, glb, parent=None):
4609 		super(MainWindow, self).__init__(parent)
4610 
4611 		self.glb = glb
4612 
4613 		self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
4614 		self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
4615 		self.setMinimumSize(200, 100)
4616 
4617 		self.mdi_area = QMdiArea()
4618 		self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
4619 		self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
4620 
4621 		self.setCentralWidget(self.mdi_area)
4622 
4623 		menu = self.menuBar()
4624 
4625 		file_menu = menu.addMenu("&File")
4626 		file_menu.addAction(CreateExitAction(glb.app, self))
4627 
4628 		edit_menu = menu.addMenu("&Edit")
4629 		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
4630 		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
4631 		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
4632 		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
4633 		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
4634 		edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
4635 
4636 		reports_menu = menu.addMenu("&Reports")
4637 		if IsSelectable(glb.db, "calls"):
4638 			reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
4639 
4640 		if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
4641 			reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
4642 
4643 		self.EventMenu(GetEventList(glb.db), reports_menu)
4644 
4645 		if IsSelectable(glb.db, "calls"):
4646 			reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
4647 
4648 		if IsSelectable(glb.db, "context_switches"):
4649 			charts_menu = menu.addMenu("&Charts")
4650 			charts_menu.addAction(CreateAction("&Time chart by CPU", "Create a new window displaying time charts by CPU", self.TimeChartByCPU, self))
4651 
4652 		self.TableMenu(GetTableList(glb), menu)
4653 
4654 		self.window_menu = WindowMenu(self.mdi_area, menu)
4655 
4656 		help_menu = menu.addMenu("&Help")
4657 		help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
4658 		help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
4659 
4660 	def Try(self, fn):
4661 		win = self.mdi_area.activeSubWindow()
4662 		if win:
4663 			try:
4664 				fn(win.view)
4665 			except:
4666 				pass
4667 
4668 	def CopyToClipboard(self):
4669 		self.Try(CopyCellsToClipboardHdr)
4670 
4671 	def CopyToClipboardCSV(self):
4672 		self.Try(CopyCellsToClipboardCSV)
4673 
4674 	def Find(self):
4675 		win = self.mdi_area.activeSubWindow()
4676 		if win:
4677 			try:
4678 				win.find_bar.Activate()
4679 			except:
4680 				pass
4681 
4682 	def FetchMoreRecords(self):
4683 		win = self.mdi_area.activeSubWindow()
4684 		if win:
4685 			try:
4686 				win.fetch_bar.Activate()
4687 			except:
4688 				pass
4689 
4690 	def ShrinkFont(self):
4691 		self.Try(ShrinkFont)
4692 
4693 	def EnlargeFont(self):
4694 		self.Try(EnlargeFont)
4695 
4696 	def EventMenu(self, events, reports_menu):
4697 		branches_events = 0
4698 		for event in events:
4699 			event = event.split(":")[0]
4700 			if event == "branches":
4701 				branches_events += 1
4702 		dbid = 0
4703 		for event in events:
4704 			dbid += 1
4705 			event = event.split(":")[0]
4706 			if event == "branches":
4707 				label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
4708 				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
4709 				label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
4710 				reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
4711 
4712 	def TimeChartByCPU(self):
4713 		TimeChartByCPUWindow(self.glb, self)
4714 
4715 	def TableMenu(self, tables, menu):
4716 		table_menu = menu.addMenu("&Tables")
4717 		for table in tables:
4718 			table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
4719 
4720 	def NewCallGraph(self):
4721 		CallGraphWindow(self.glb, self)
4722 
4723 	def NewCallTree(self):
4724 		CallTreeWindow(self.glb, self)
4725 
4726 	def NewTopCalls(self):
4727 		dialog = TopCallsDialog(self.glb, self)
4728 		ret = dialog.exec_()
4729 		if ret:
4730 			TopCallsWindow(self.glb, dialog.report_vars, self)
4731 
4732 	def NewBranchView(self, event_id):
4733 		BranchWindow(self.glb, event_id, ReportVars(), self)
4734 
4735 	def NewSelectedBranchView(self, event_id):
4736 		dialog = SelectedBranchDialog(self.glb, self)
4737 		ret = dialog.exec_()
4738 		if ret:
4739 			BranchWindow(self.glb, event_id, dialog.report_vars, self)
4740 
4741 	def NewTableView(self, table_name):
4742 		TableWindow(self.glb, table_name, self)
4743 
4744 	def Help(self):
4745 		HelpWindow(self.glb, self)
4746 
4747 	def About(self):
4748 		dialog = AboutDialog(self.glb, self)
4749 		dialog.exec_()
4750 
4751 def TryOpen(file_name):
4752 	try:
4753 		return open(file_name, "rb")
4754 	except:
4755 		return None
4756 
4757 def Is64Bit(f):
4758 	result = sizeof(c_void_p)
4759 	# ELF support only
4760 	pos = f.tell()
4761 	f.seek(0)
4762 	header = f.read(7)
4763 	f.seek(pos)
4764 	magic = header[0:4]
4765 	if sys.version_info[0] == 2:
4766 		eclass = ord(header[4])
4767 		encoding = ord(header[5])
4768 		version = ord(header[6])
4769 	else:
4770 		eclass = header[4]
4771 		encoding = header[5]
4772 		version = header[6]
4773 	if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
4774 		result = True if eclass == 2 else False
4775 	return result
4776 
4777 # Global data
4778 
4779 class Glb():
4780 
4781 	def __init__(self, dbref, db, dbname):
4782 		self.dbref = dbref
4783 		self.db = db
4784 		self.dbname = dbname
4785 		self.home_dir = os.path.expanduser("~")
4786 		self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
4787 		if self.buildid_dir:
4788 			self.buildid_dir += "/.build-id/"
4789 		else:
4790 			self.buildid_dir = self.home_dir + "/.debug/.build-id/"
4791 		self.app = None
4792 		self.mainwindow = None
4793 		self.instances_to_shutdown_on_exit = weakref.WeakSet()
4794 		try:
4795 			self.disassembler = LibXED()
4796 			self.have_disassembler = True
4797 		except:
4798 			self.have_disassembler = False
4799 		self.host_machine_id = 0
4800 		self.host_start_time = 0
4801 		self.host_finish_time = 0
4802 
4803 	def FileFromBuildId(self, build_id):
4804 		file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
4805 		return TryOpen(file_name)
4806 
4807 	def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
4808 		# Assume current machine i.e. no support for virtualization
4809 		if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
4810 			file_name = os.getenv("PERF_KCORE")
4811 			f = TryOpen(file_name) if file_name else None
4812 			if f:
4813 				return f
4814 			# For now, no special handling if long_name is /proc/kcore
4815 			f = TryOpen(long_name)
4816 			if f:
4817 				return f
4818 		f = self.FileFromBuildId(build_id)
4819 		if f:
4820 			return f
4821 		return None
4822 
4823 	def AddInstanceToShutdownOnExit(self, instance):
4824 		self.instances_to_shutdown_on_exit.add(instance)
4825 
4826 	# Shutdown any background processes or threads
4827 	def ShutdownInstances(self):
4828 		for x in self.instances_to_shutdown_on_exit:
4829 			try:
4830 				x.Shutdown()
4831 			except:
4832 				pass
4833 
4834 	def GetHostMachineId(self):
4835 		query = QSqlQuery(self.db)
4836 		QueryExec(query, "SELECT id FROM machines WHERE pid = -1")
4837 		if query.next():
4838 			self.host_machine_id = query.value(0)
4839 		else:
4840 			self.host_machine_id = 0
4841 		return self.host_machine_id
4842 
4843 	def HostMachineId(self):
4844 		if self.host_machine_id:
4845 			return self.host_machine_id
4846 		return self.GetHostMachineId()
4847 
4848 	def SelectValue(self, sql):
4849 		query = QSqlQuery(self.db)
4850 		try:
4851 			QueryExec(query, sql)
4852 		except:
4853 			return None
4854 		if query.next():
4855 			return Decimal(query.value(0))
4856 		return None
4857 
4858 	def SwitchesMinTime(self, machine_id):
4859 		return self.SelectValue("SELECT time"
4860 					" FROM context_switches"
4861 					" WHERE time != 0 AND machine_id = " + str(machine_id) +
4862 					" ORDER BY id LIMIT 1")
4863 
4864 	def SwitchesMaxTime(self, machine_id):
4865 		return self.SelectValue("SELECT time"
4866 					" FROM context_switches"
4867 					" WHERE time != 0 AND machine_id = " + str(machine_id) +
4868 					" ORDER BY id DESC LIMIT 1")
4869 
4870 	def SamplesMinTime(self, machine_id):
4871 		return self.SelectValue("SELECT time"
4872 					" FROM samples"
4873 					" WHERE time != 0 AND machine_id = " + str(machine_id) +
4874 					" ORDER BY id LIMIT 1")
4875 
4876 	def SamplesMaxTime(self, machine_id):
4877 		return self.SelectValue("SELECT time"
4878 					" FROM samples"
4879 					" WHERE time != 0 AND machine_id = " + str(machine_id) +
4880 					" ORDER BY id DESC LIMIT 1")
4881 
4882 	def CallsMinTime(self, machine_id):
4883 		return self.SelectValue("SELECT calls.call_time"
4884 					" FROM calls"
4885 					" INNER JOIN threads ON threads.thread_id = calls.thread_id"
4886 					" WHERE calls.call_time != 0 AND threads.machine_id = " + str(machine_id) +
4887 					" ORDER BY calls.id LIMIT 1")
4888 
4889 	def CallsMaxTime(self, machine_id):
4890 		return self.SelectValue("SELECT calls.return_time"
4891 					" FROM calls"
4892 					" INNER JOIN threads ON threads.thread_id = calls.thread_id"
4893 					" WHERE calls.return_time != 0 AND threads.machine_id = " + str(machine_id) +
4894 					" ORDER BY calls.return_time DESC LIMIT 1")
4895 
4896 	def GetStartTime(self, machine_id):
4897 		t0 = self.SwitchesMinTime(machine_id)
4898 		t1 = self.SamplesMinTime(machine_id)
4899 		t2 = self.CallsMinTime(machine_id)
4900 		if t0 is None or (not(t1 is None) and t1 < t0):
4901 			t0 = t1
4902 		if t0 is None or (not(t2 is None) and t2 < t0):
4903 			t0 = t2
4904 		return t0
4905 
4906 	def GetFinishTime(self, machine_id):
4907 		t0 = self.SwitchesMaxTime(machine_id)
4908 		t1 = self.SamplesMaxTime(machine_id)
4909 		t2 = self.CallsMaxTime(machine_id)
4910 		if t0 is None or (not(t1 is None) and t1 > t0):
4911 			t0 = t1
4912 		if t0 is None or (not(t2 is None) and t2 > t0):
4913 			t0 = t2
4914 		return t0
4915 
4916 	def HostStartTime(self):
4917 		if self.host_start_time:
4918 			return self.host_start_time
4919 		self.host_start_time = self.GetStartTime(self.HostMachineId())
4920 		return self.host_start_time
4921 
4922 	def HostFinishTime(self):
4923 		if self.host_finish_time:
4924 			return self.host_finish_time
4925 		self.host_finish_time = self.GetFinishTime(self.HostMachineId())
4926 		return self.host_finish_time
4927 
4928 	def StartTime(self, machine_id):
4929 		if machine_id == self.HostMachineId():
4930 			return self.HostStartTime()
4931 		return self.GetStartTime(machine_id)
4932 
4933 	def FinishTime(self, machine_id):
4934 		if machine_id == self.HostMachineId():
4935 			return self.HostFinishTime()
4936 		return self.GetFinishTime(machine_id)
4937 
4938 # Database reference
4939 
4940 class DBRef():
4941 
4942 	def __init__(self, is_sqlite3, dbname):
4943 		self.is_sqlite3 = is_sqlite3
4944 		self.dbname = dbname
4945 		self.TRUE = "TRUE"
4946 		self.FALSE = "FALSE"
4947 		# SQLite prior to version 3.23 does not support TRUE and FALSE
4948 		if self.is_sqlite3:
4949 			self.TRUE = "1"
4950 			self.FALSE = "0"
4951 
4952 	def Open(self, connection_name):
4953 		dbname = self.dbname
4954 		if self.is_sqlite3:
4955 			db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
4956 		else:
4957 			db = QSqlDatabase.addDatabase("QPSQL", connection_name)
4958 			opts = dbname.split()
4959 			for opt in opts:
4960 				if "=" in opt:
4961 					opt = opt.split("=")
4962 					if opt[0] == "hostname":
4963 						db.setHostName(opt[1])
4964 					elif opt[0] == "port":
4965 						db.setPort(int(opt[1]))
4966 					elif opt[0] == "username":
4967 						db.setUserName(opt[1])
4968 					elif opt[0] == "password":
4969 						db.setPassword(opt[1])
4970 					elif opt[0] == "dbname":
4971 						dbname = opt[1]
4972 				else:
4973 					dbname = opt
4974 
4975 		db.setDatabaseName(dbname)
4976 		if not db.open():
4977 			raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
4978 		return db, dbname
4979 
4980 # Main
4981 
4982 def Main():
4983 	usage_str =	"exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
4984 			"   or: exported-sql-viewer.py --help-only"
4985 	ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
4986 	ap.add_argument("--pyside-version-1", action='store_true')
4987 	ap.add_argument("dbname", nargs="?")
4988 	ap.add_argument("--help-only", action='store_true')
4989 	args = ap.parse_args()
4990 
4991 	if args.help_only:
4992 		app = QApplication(sys.argv)
4993 		mainwindow = HelpOnlyWindow()
4994 		mainwindow.show()
4995 		err = app.exec_()
4996 		sys.exit(err)
4997 
4998 	dbname = args.dbname
4999 	if dbname is None:
5000 		ap.print_usage()
5001 		print("Too few arguments")
5002 		sys.exit(1)
5003 
5004 	is_sqlite3 = False
5005 	try:
5006 		f = open(dbname, "rb")
5007 		if f.read(15) == b'SQLite format 3':
5008 			is_sqlite3 = True
5009 		f.close()
5010 	except:
5011 		pass
5012 
5013 	dbref = DBRef(is_sqlite3, dbname)
5014 	db, dbname = dbref.Open("main")
5015 	glb = Glb(dbref, db, dbname)
5016 	app = QApplication(sys.argv)
5017 	glb.app = app
5018 	mainwindow = MainWindow(glb)
5019 	glb.mainwindow = mainwindow
5020 	mainwindow.show()
5021 	err = app.exec_()
5022 	glb.ShutdownInstances()
5023 	db.close()
5024 	sys.exit(err)
5025 
5026 if __name__ == "__main__":
5027 	Main()
5028