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