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