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