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