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