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