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