Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[muen/linux.git] / tools / perf / scripts / python / exported-sql-viewer.py
1 #!/usr/bin/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 # Branch window
1402
1403 class BranchWindow(QMdiSubWindow):
1404
1405         def __init__(self, glb, event_id, name, where_clause, parent=None):
1406                 super(BranchWindow, self).__init__(parent)
1407
1408                 model_name = "Branch Events " + str(event_id)
1409                 if len(where_clause):
1410                         model_name = where_clause + " " + model_name
1411
1412                 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, where_clause))
1413
1414                 self.view = QTreeView()
1415                 self.view.setUniformRowHeights(True)
1416                 self.view.setModel(self.model)
1417
1418                 self.ResizeColumnsToContents()
1419
1420                 self.find_bar = FindBar(self, self, True)
1421
1422                 self.finder = ChildDataItemFinder(self.model.root)
1423
1424                 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1425
1426                 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1427
1428                 self.setWidget(self.vbox.Widget())
1429
1430                 AddSubWindow(glb.mainwindow.mdi_area, self, name + " Branch Events")
1431
1432         def ResizeColumnToContents(self, column, n):
1433                 # Using the view's resizeColumnToContents() here is extrememly slow
1434                 # so implement a crude alternative
1435                 mm = "MM" if column else "MMMM"
1436                 font = self.view.font()
1437                 metrics = QFontMetrics(font)
1438                 max = 0
1439                 for row in xrange(n):
1440                         val = self.model.root.child_items[row].data[column]
1441                         len = metrics.width(str(val) + mm)
1442                         max = len if len > max else max
1443                 val = self.model.columnHeader(column)
1444                 len = metrics.width(str(val) + mm)
1445                 max = len if len > max else max
1446                 self.view.setColumnWidth(column, max)
1447
1448         def ResizeColumnsToContents(self):
1449                 n = min(self.model.root.child_count, 100)
1450                 if n < 1:
1451                         # No data yet, so connect a signal to notify when there is
1452                         self.model.rowsInserted.connect(self.UpdateColumnWidths)
1453                         return
1454                 columns = self.model.columnCount()
1455                 for i in xrange(columns):
1456                         self.ResizeColumnToContents(i, n)
1457
1458         def UpdateColumnWidths(self, *x):
1459                 # This only needs to be done once, so disconnect the signal now
1460                 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1461                 self.ResizeColumnsToContents()
1462
1463         def Find(self, value, direction, pattern, context):
1464                 self.view.setFocus()
1465                 self.find_bar.Busy()
1466                 self.finder.Find(value, direction, pattern, context, self.FindDone)
1467
1468         def FindDone(self, row):
1469                 self.find_bar.Idle()
1470                 if row >= 0:
1471                         self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1472                 else:
1473                         self.find_bar.NotFound()
1474
1475 # Dialog data item converted and validated using a SQL table
1476
1477 class SQLTableDialogDataItem():
1478
1479         def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1480                 self.glb = glb
1481                 self.label = label
1482                 self.placeholder_text = placeholder_text
1483                 self.table_name = table_name
1484                 self.match_column = match_column
1485                 self.column_name1 = column_name1
1486                 self.column_name2 = column_name2
1487                 self.parent = parent
1488
1489                 self.value = ""
1490
1491                 self.widget = QLineEdit()
1492                 self.widget.editingFinished.connect(self.Validate)
1493                 self.widget.textChanged.connect(self.Invalidate)
1494                 self.red = False
1495                 self.error = ""
1496                 self.validated = True
1497
1498                 self.last_id = 0
1499                 self.first_time = 0
1500                 self.last_time = 2 ** 64
1501                 if self.table_name == "<timeranges>":
1502                         query = QSqlQuery(self.glb.db)
1503                         QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1504                         if query.next():
1505                                 self.last_id = int(query.value(0))
1506                                 self.last_time = int(query.value(1))
1507                         QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1508                         if query.next():
1509                                 self.first_time = int(query.value(0))
1510                         if placeholder_text:
1511                                 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1512
1513                 if placeholder_text:
1514                         self.widget.setPlaceholderText(placeholder_text)
1515
1516         def ValueToIds(self, value):
1517                 ids = []
1518                 query = QSqlQuery(self.glb.db)
1519                 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1520                 ret = query.exec_(stmt)
1521                 if ret:
1522                         while query.next():
1523                                 ids.append(str(query.value(0)))
1524                 return ids
1525
1526         def IdBetween(self, query, lower_id, higher_id, order):
1527                 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1528                 if query.next():
1529                         return True, int(query.value(0))
1530                 else:
1531                         return False, 0
1532
1533         def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1534                 query = QSqlQuery(self.glb.db)
1535                 while True:
1536                         next_id = int((lower_id + higher_id) / 2)
1537                         QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1538                         if not query.next():
1539                                 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1540                                 if not ok:
1541                                         ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1542                                         if not ok:
1543                                                 return str(higher_id)
1544                                 next_id = dbid
1545                                 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1546                         next_time = int(query.value(0))
1547                         if get_floor:
1548                                 if target_time > next_time:
1549                                         lower_id = next_id
1550                                 else:
1551                                         higher_id = next_id
1552                                 if higher_id <= lower_id + 1:
1553                                         return str(higher_id)
1554                         else:
1555                                 if target_time >= next_time:
1556                                         lower_id = next_id
1557                                 else:
1558                                         higher_id = next_id
1559                                 if higher_id <= lower_id + 1:
1560                                         return str(lower_id)
1561
1562         def ConvertRelativeTime(self, val):
1563                 print "val ", val
1564                 mult = 1
1565                 suffix = val[-2:]
1566                 if suffix == "ms":
1567                         mult = 1000000
1568                 elif suffix == "us":
1569                         mult = 1000
1570                 elif suffix == "ns":
1571                         mult = 1
1572                 else:
1573                         return val
1574                 val = val[:-2].strip()
1575                 if not self.IsNumber(val):
1576                         return val
1577                 val = int(val) * mult
1578                 if val >= 0:
1579                         val += self.first_time
1580                 else:
1581                         val += self.last_time
1582                 return str(val)
1583
1584         def ConvertTimeRange(self, vrange):
1585                 print "vrange ", vrange
1586                 if vrange[0] == "":
1587                         vrange[0] = str(self.first_time)
1588                 if vrange[1] == "":
1589                         vrange[1] = str(self.last_time)
1590                 vrange[0] = self.ConvertRelativeTime(vrange[0])
1591                 vrange[1] = self.ConvertRelativeTime(vrange[1])
1592                 print "vrange2 ", vrange
1593                 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1594                         return False
1595                 print "ok1"
1596                 beg_range = max(int(vrange[0]), self.first_time)
1597                 end_range = min(int(vrange[1]), self.last_time)
1598                 if beg_range > self.last_time or end_range < self.first_time:
1599                         return False
1600                 print "ok2"
1601                 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
1602                 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
1603                 print "vrange3 ", vrange
1604                 return True
1605
1606         def AddTimeRange(self, value, ranges):
1607                 print "value ", value
1608                 n = value.count("-")
1609                 if n == 1:
1610                         pass
1611                 elif n == 2:
1612                         if value.split("-")[1].strip() == "":
1613                                 n = 1
1614                 elif n == 3:
1615                         n = 2
1616                 else:
1617                         return False
1618                 pos = findnth(value, "-", n)
1619                 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
1620                 if self.ConvertTimeRange(vrange):
1621                         ranges.append(vrange)
1622                         return True
1623                 return False
1624
1625         def InvalidValue(self, value):
1626                 self.value = ""
1627                 palette = QPalette()
1628                 palette.setColor(QPalette.Text,Qt.red)
1629                 self.widget.setPalette(palette)
1630                 self.red = True
1631                 self.error = self.label + " invalid value '" + value + "'"
1632                 self.parent.ShowMessage(self.error)
1633
1634         def IsNumber(self, value):
1635                 try:
1636                         x = int(value)
1637                 except:
1638                         x = 0
1639                 return str(x) == value
1640
1641         def Invalidate(self):
1642                 self.validated = False
1643
1644         def Validate(self):
1645                 input_string = self.widget.text()
1646                 self.validated = True
1647                 if self.red:
1648                         palette = QPalette()
1649                         self.widget.setPalette(palette)
1650                         self.red = False
1651                 if not len(input_string.strip()):
1652                         self.error = ""
1653                         self.value = ""
1654                         return
1655                 if self.table_name == "<timeranges>":
1656                         ranges = []
1657                         for value in [x.strip() for x in input_string.split(",")]:
1658                                 if not self.AddTimeRange(value, ranges):
1659                                         return self.InvalidValue(value)
1660                         ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
1661                         self.value = " OR ".join(ranges)
1662                 elif self.table_name == "<ranges>":
1663                         singles = []
1664                         ranges = []
1665                         for value in [x.strip() for x in input_string.split(",")]:
1666                                 if "-" in value:
1667                                         vrange = value.split("-")
1668                                         if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1669                                                 return self.InvalidValue(value)
1670                                         ranges.append(vrange)
1671                                 else:
1672                                         if not self.IsNumber(value):
1673                                                 return self.InvalidValue(value)
1674                                         singles.append(value)
1675                         ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges]
1676                         if len(singles):
1677                                 ranges.append(self.column_name1 + " IN (" + ",".join(singles) + ")")
1678                         self.value = " OR ".join(ranges)
1679                 elif self.table_name:
1680                         all_ids = []
1681                         for value in [x.strip() for x in input_string.split(",")]:
1682                                 ids = self.ValueToIds(value)
1683                                 if len(ids):
1684                                         all_ids.extend(ids)
1685                                 else:
1686                                         return self.InvalidValue(value)
1687                         self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1688                         if self.column_name2:
1689                                 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1690                 else:
1691                         self.value = input_string.strip()
1692                 self.error = ""
1693                 self.parent.ClearMessage()
1694
1695         def IsValid(self):
1696                 if not self.validated:
1697                         self.Validate()
1698                 if len(self.error):
1699                         self.parent.ShowMessage(self.error)
1700                         return False
1701                 return True
1702
1703 # Selected branch report creation dialog
1704
1705 class SelectedBranchDialog(QDialog):
1706
1707         def __init__(self, glb, parent=None):
1708                 super(SelectedBranchDialog, self).__init__(parent)
1709
1710                 self.glb = glb
1711
1712                 self.name = ""
1713                 self.where_clause = ""
1714
1715                 self.setWindowTitle("Selected Branches")
1716                 self.setMinimumWidth(600)
1717
1718                 items = (
1719                         ("Report name:", "Enter a name to appear in the window title bar", "", "", "", ""),
1720                         ("Time ranges:", "Enter time ranges", "<timeranges>", "", "samples.id", ""),
1721                         ("CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "<ranges>", "", "cpu", ""),
1722                         ("Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", ""),
1723                         ("PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", ""),
1724                         ("TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", ""),
1725                         ("DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id"),
1726                         ("Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id"),
1727                         ("Raw SQL clause: ", "Enter a raw SQL WHERE clause", "", "", "", ""),
1728                         )
1729                 self.data_items = [SQLTableDialogDataItem(glb, *x, parent=self) for x in items]
1730
1731                 self.grid = QGridLayout()
1732
1733                 for row in xrange(len(self.data_items)):
1734                         self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
1735                         self.grid.addWidget(self.data_items[row].widget, row, 1)
1736
1737                 self.status = QLabel()
1738
1739                 self.ok_button = QPushButton("Ok", self)
1740                 self.ok_button.setDefault(True)
1741                 self.ok_button.released.connect(self.Ok)
1742                 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1743
1744                 self.cancel_button = QPushButton("Cancel", self)
1745                 self.cancel_button.released.connect(self.reject)
1746                 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1747
1748                 self.hbox = QHBoxLayout()
1749                 #self.hbox.addStretch()
1750                 self.hbox.addWidget(self.status)
1751                 self.hbox.addWidget(self.ok_button)
1752                 self.hbox.addWidget(self.cancel_button)
1753
1754                 self.vbox = QVBoxLayout()
1755                 self.vbox.addLayout(self.grid)
1756                 self.vbox.addLayout(self.hbox)
1757
1758                 self.setLayout(self.vbox);
1759
1760         def Ok(self):
1761                 self.name = self.data_items[0].value
1762                 if not self.name:
1763                         self.ShowMessage("Report name is required")
1764                         return
1765                 for d in self.data_items:
1766                         if not d.IsValid():
1767                                 return
1768                 for d in self.data_items[1:]:
1769                         if len(d.value):
1770                                 if len(self.where_clause):
1771                                         self.where_clause += " AND "
1772                                 self.where_clause += d.value
1773                 if len(self.where_clause):
1774                         self.where_clause = " AND ( " + self.where_clause + " ) "
1775                 else:
1776                         self.ShowMessage("No selection")
1777                         return
1778                 self.accept()
1779
1780         def ShowMessage(self, msg):
1781                 self.status.setText("<font color=#FF0000>" + msg)
1782
1783         def ClearMessage(self):
1784                 self.status.setText("")
1785
1786 # Event list
1787
1788 def GetEventList(db):
1789         events = []
1790         query = QSqlQuery(db)
1791         QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
1792         while query.next():
1793                 events.append(query.value(0))
1794         return events
1795
1796 # SQL data preparation
1797
1798 def SQLTableDataPrep(query, count):
1799         data = []
1800         for i in xrange(count):
1801                 data.append(query.value(i))
1802         return data
1803
1804 # SQL table data model item
1805
1806 class SQLTableItem():
1807
1808         def __init__(self, row, data):
1809                 self.row = row
1810                 self.data = data
1811
1812         def getData(self, column):
1813                 return self.data[column]
1814
1815 # SQL table data model
1816
1817 class SQLTableModel(TableModel):
1818
1819         progress = Signal(object)
1820
1821         def __init__(self, glb, sql, column_count, parent=None):
1822                 super(SQLTableModel, self).__init__(parent)
1823                 self.glb = glb
1824                 self.more = True
1825                 self.populated = 0
1826                 self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample)
1827                 self.fetcher.done.connect(self.Update)
1828                 self.fetcher.Fetch(glb_chunk_sz)
1829
1830         def DisplayData(self, item, index):
1831                 self.FetchIfNeeded(item.row)
1832                 return item.getData(index.column())
1833
1834         def AddSample(self, data):
1835                 child = SQLTableItem(self.populated, data)
1836                 self.child_items.append(child)
1837                 self.populated += 1
1838
1839         def Update(self, fetched):
1840                 if not fetched:
1841                         self.more = False
1842                         self.progress.emit(0)
1843                 child_count = self.child_count
1844                 count = self.populated - child_count
1845                 if count > 0:
1846                         parent = QModelIndex()
1847                         self.beginInsertRows(parent, child_count, child_count + count - 1)
1848                         self.insertRows(child_count, count, parent)
1849                         self.child_count += count
1850                         self.endInsertRows()
1851                         self.progress.emit(self.child_count)
1852
1853         def FetchMoreRecords(self, count):
1854                 current = self.child_count
1855                 if self.more:
1856                         self.fetcher.Fetch(count)
1857                 else:
1858                         self.progress.emit(0)
1859                 return current
1860
1861         def HasMoreRecords(self):
1862                 return self.more
1863
1864 # SQL automatic table data model
1865
1866 class SQLAutoTableModel(SQLTableModel):
1867
1868         def __init__(self, glb, table_name, parent=None):
1869                 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
1870                 if table_name == "comm_threads_view":
1871                         # For now, comm_threads_view has no id column
1872                         sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
1873                 self.column_headers = []
1874                 query = QSqlQuery(glb.db)
1875                 if glb.dbref.is_sqlite3:
1876                         QueryExec(query, "PRAGMA table_info(" + table_name + ")")
1877                         while query.next():
1878                                 self.column_headers.append(query.value(1))
1879                         if table_name == "sqlite_master":
1880                                 sql = "SELECT * FROM " + table_name
1881                 else:
1882                         if table_name[:19] == "information_schema.":
1883                                 sql = "SELECT * FROM " + table_name
1884                                 select_table_name = table_name[19:]
1885                                 schema = "information_schema"
1886                         else:
1887                                 select_table_name = table_name
1888                                 schema = "public"
1889                         QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
1890                         while query.next():
1891                                 self.column_headers.append(query.value(0))
1892                 super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent)
1893
1894         def columnCount(self, parent=None):
1895                 return len(self.column_headers)
1896
1897         def columnHeader(self, column):
1898                 return self.column_headers[column]
1899
1900 # Base class for custom ResizeColumnsToContents
1901
1902 class ResizeColumnsToContentsBase(QObject):
1903
1904         def __init__(self, parent=None):
1905                 super(ResizeColumnsToContentsBase, self).__init__(parent)
1906
1907         def ResizeColumnToContents(self, column, n):
1908                 # Using the view's resizeColumnToContents() here is extrememly slow
1909                 # so implement a crude alternative
1910                 font = self.view.font()
1911                 metrics = QFontMetrics(font)
1912                 max = 0
1913                 for row in xrange(n):
1914                         val = self.data_model.child_items[row].data[column]
1915                         len = metrics.width(str(val) + "MM")
1916                         max = len if len > max else max
1917                 val = self.data_model.columnHeader(column)
1918                 len = metrics.width(str(val) + "MM")
1919                 max = len if len > max else max
1920                 self.view.setColumnWidth(column, max)
1921
1922         def ResizeColumnsToContents(self):
1923                 n = min(self.data_model.child_count, 100)
1924                 if n < 1:
1925                         # No data yet, so connect a signal to notify when there is
1926                         self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
1927                         return
1928                 columns = self.data_model.columnCount()
1929                 for i in xrange(columns):
1930                         self.ResizeColumnToContents(i, n)
1931
1932         def UpdateColumnWidths(self, *x):
1933                 # This only needs to be done once, so disconnect the signal now
1934                 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
1935                 self.ResizeColumnsToContents()
1936
1937 # Table window
1938
1939 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
1940
1941         def __init__(self, glb, table_name, parent=None):
1942                 super(TableWindow, self).__init__(parent)
1943
1944                 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
1945
1946                 self.model = QSortFilterProxyModel()
1947                 self.model.setSourceModel(self.data_model)
1948
1949                 self.view = QTableView()
1950                 self.view.setModel(self.model)
1951                 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
1952                 self.view.verticalHeader().setVisible(False)
1953                 self.view.sortByColumn(-1, Qt.AscendingOrder)
1954                 self.view.setSortingEnabled(True)
1955
1956                 self.ResizeColumnsToContents()
1957
1958                 self.find_bar = FindBar(self, self, True)
1959
1960                 self.finder = ChildDataItemFinder(self.data_model)
1961
1962                 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
1963
1964                 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1965
1966                 self.setWidget(self.vbox.Widget())
1967
1968                 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
1969
1970         def Find(self, value, direction, pattern, context):
1971                 self.view.setFocus()
1972                 self.find_bar.Busy()
1973                 self.finder.Find(value, direction, pattern, context, self.FindDone)
1974
1975         def FindDone(self, row):
1976                 self.find_bar.Idle()
1977                 if row >= 0:
1978                         self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
1979                 else:
1980                         self.find_bar.NotFound()
1981
1982 # Table list
1983
1984 def GetTableList(glb):
1985         tables = []
1986         query = QSqlQuery(glb.db)
1987         if glb.dbref.is_sqlite3:
1988                 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
1989         else:
1990                 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
1991         while query.next():
1992                 tables.append(query.value(0))
1993         if glb.dbref.is_sqlite3:
1994                 tables.append("sqlite_master")
1995         else:
1996                 tables.append("information_schema.tables")
1997                 tables.append("information_schema.views")
1998                 tables.append("information_schema.columns")
1999         return tables
2000
2001 # Action Definition
2002
2003 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2004         action = QAction(label, parent)
2005         if shortcut != None:
2006                 action.setShortcuts(shortcut)
2007         action.setStatusTip(tip)
2008         action.triggered.connect(callback)
2009         return action
2010
2011 # Typical application actions
2012
2013 def CreateExitAction(app, parent=None):
2014         return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2015
2016 # Typical MDI actions
2017
2018 def CreateCloseActiveWindowAction(mdi_area):
2019         return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2020
2021 def CreateCloseAllWindowsAction(mdi_area):
2022         return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2023
2024 def CreateTileWindowsAction(mdi_area):
2025         return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2026
2027 def CreateCascadeWindowsAction(mdi_area):
2028         return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2029
2030 def CreateNextWindowAction(mdi_area):
2031         return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2032
2033 def CreatePreviousWindowAction(mdi_area):
2034         return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2035
2036 # Typical MDI window menu
2037
2038 class WindowMenu():
2039
2040         def __init__(self, mdi_area, menu):
2041                 self.mdi_area = mdi_area
2042                 self.window_menu = menu.addMenu("&Windows")
2043                 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2044                 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2045                 self.tile_windows = CreateTileWindowsAction(mdi_area)
2046                 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2047                 self.next_window = CreateNextWindowAction(mdi_area)
2048                 self.previous_window = CreatePreviousWindowAction(mdi_area)
2049                 self.window_menu.aboutToShow.connect(self.Update)
2050
2051         def Update(self):
2052                 self.window_menu.clear()
2053                 sub_window_count = len(self.mdi_area.subWindowList())
2054                 have_sub_windows = sub_window_count != 0
2055                 self.close_active_window.setEnabled(have_sub_windows)
2056                 self.close_all_windows.setEnabled(have_sub_windows)
2057                 self.tile_windows.setEnabled(have_sub_windows)
2058                 self.cascade_windows.setEnabled(have_sub_windows)
2059                 self.next_window.setEnabled(have_sub_windows)
2060                 self.previous_window.setEnabled(have_sub_windows)
2061                 self.window_menu.addAction(self.close_active_window)
2062                 self.window_menu.addAction(self.close_all_windows)
2063                 self.window_menu.addSeparator()
2064                 self.window_menu.addAction(self.tile_windows)
2065                 self.window_menu.addAction(self.cascade_windows)
2066                 self.window_menu.addSeparator()
2067                 self.window_menu.addAction(self.next_window)
2068                 self.window_menu.addAction(self.previous_window)
2069                 if sub_window_count == 0:
2070                         return
2071                 self.window_menu.addSeparator()
2072                 nr = 1
2073                 for sub_window in self.mdi_area.subWindowList():
2074                         label = str(nr) + " " + sub_window.name
2075                         if nr < 10:
2076                                 label = "&" + label
2077                         action = self.window_menu.addAction(label)
2078                         action.setCheckable(True)
2079                         action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2080                         action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2081                         self.window_menu.addAction(action)
2082                         nr += 1
2083
2084         def setActiveSubWindow(self, nr):
2085                 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2086
2087 # Help text
2088
2089 glb_help_text = """
2090 <h1>Contents</h1>
2091 <style>
2092 p.c1 {
2093     text-indent: 40px;
2094 }
2095 p.c2 {
2096     text-indent: 80px;
2097 }
2098 }
2099 </style>
2100 <p class=c1><a href=#reports>1. Reports</a></p>
2101 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2102 <p class=c2><a href=#allbranches>1.2 All branches</a></p>
2103 <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p>
2104 <p class=c1><a href=#tables>2. Tables</a></p>
2105 <h1 id=reports>1. Reports</h1>
2106 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2107 The result is a GUI window with a tree representing a context-sensitive
2108 call-graph. Expanding a couple of levels of the tree and adjusting column
2109 widths to suit will display something like:
2110 <pre>
2111                                          Call Graph: pt_example
2112 Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
2113 v- ls
2114     v- 2638:2638
2115         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
2116           |- unknown               unknown       1        13198     0.1              1              0.0
2117           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
2118           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
2119           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
2120              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
2121              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
2122              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
2123              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
2124              v- main               ls            1      8182043    99.6         180254             99.9
2125 </pre>
2126 <h3>Points to note:</h3>
2127 <ul>
2128 <li>The top level is a command name (comm)</li>
2129 <li>The next level is a thread (pid:tid)</li>
2130 <li>Subsequent levels are functions</li>
2131 <li>'Count' is the number of calls</li>
2132 <li>'Time' is the elapsed time until the function returns</li>
2133 <li>Percentages are relative to the level above</li>
2134 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2135 </ul>
2136 <h3>Find</h3>
2137 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2138 The pattern matching symbols are ? for any character and * for zero or more characters.
2139 <h2 id=allbranches>1.2 All branches</h2>
2140 The All branches report displays all branches in chronological order.
2141 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2142 <h3>Disassembly</h3>
2143 Open a branch to display disassembly. This only works if:
2144 <ol>
2145 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2146 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2147 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2148 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2149 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2150 </ol>
2151 <h4 id=xed>Intel XED Setup</h4>
2152 To use Intel XED, libxed.so must be present.  To build and install libxed.so:
2153 <pre>
2154 git clone https://github.com/intelxed/mbuild.git mbuild
2155 git clone https://github.com/intelxed/xed
2156 cd xed
2157 ./mfile.py --share
2158 sudo ./mfile.py --prefix=/usr/local install
2159 sudo ldconfig
2160 </pre>
2161 <h3>Find</h3>
2162 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2163 Refer to Python documentation for the regular expression syntax.
2164 All columns are searched, but only currently fetched rows are searched.
2165 <h2 id=selectedbranches>1.3 Selected branches</h2>
2166 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2167 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2168 <h3>1.3.1 Time ranges</h3>
2169 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2170 ms, us or ns. Also, negative values are relative to the end of trace.  Examples:
2171 <pre>
2172         81073085947329-81073085958238   From 81073085947329 to 81073085958238
2173         100us-200us             From 100us to 200us
2174         10ms-                   From 10ms to the end
2175         -100ns                  The first 100ns
2176         -10ms-                  The last 10ms
2177 </pre>
2178 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2179 <h1 id=tables>2. Tables</h1>
2180 The Tables menu shows all tables and views in the database. Most tables have an associated view
2181 which displays the information in a more friendly way. Not all data for large tables is fetched
2182 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2183 but that can be slow for large tables.
2184 <p>There are also tables of database meta-information.
2185 For SQLite3 databases, the sqlite_master table is included.
2186 For PostgreSQL databases, information_schema.tables/views/columns are included.
2187 <h3>Find</h3>
2188 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2189 Refer to Python documentation for the regular expression syntax.
2190 All columns are searched, but only currently fetched rows are searched.
2191 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2192 will go to the next/previous result in id order, instead of display order.
2193 """
2194
2195 # Help window
2196
2197 class HelpWindow(QMdiSubWindow):
2198
2199         def __init__(self, glb, parent=None):
2200                 super(HelpWindow, self).__init__(parent)
2201
2202                 self.text = QTextBrowser()
2203                 self.text.setHtml(glb_help_text)
2204                 self.text.setReadOnly(True)
2205                 self.text.setOpenExternalLinks(True)
2206
2207                 self.setWidget(self.text)
2208
2209                 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2210
2211 # Main window that only displays the help text
2212
2213 class HelpOnlyWindow(QMainWindow):
2214
2215         def __init__(self, parent=None):
2216                 super(HelpOnlyWindow, self).__init__(parent)
2217
2218                 self.setMinimumSize(200, 100)
2219                 self.resize(800, 600)
2220                 self.setWindowTitle("Exported SQL Viewer Help")
2221                 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2222
2223                 self.text = QTextBrowser()
2224                 self.text.setHtml(glb_help_text)
2225                 self.text.setReadOnly(True)
2226                 self.text.setOpenExternalLinks(True)
2227
2228                 self.setCentralWidget(self.text)
2229
2230 # Font resize
2231
2232 def ResizeFont(widget, diff):
2233         font = widget.font()
2234         sz = font.pointSize()
2235         font.setPointSize(sz + diff)
2236         widget.setFont(font)
2237
2238 def ShrinkFont(widget):
2239         ResizeFont(widget, -1)
2240
2241 def EnlargeFont(widget):
2242         ResizeFont(widget, 1)
2243
2244 # Unique name for sub-windows
2245
2246 def NumberedWindowName(name, nr):
2247         if nr > 1:
2248                 name += " <" + str(nr) + ">"
2249         return name
2250
2251 def UniqueSubWindowName(mdi_area, name):
2252         nr = 1
2253         while True:
2254                 unique_name = NumberedWindowName(name, nr)
2255                 ok = True
2256                 for sub_window in mdi_area.subWindowList():
2257                         if sub_window.name == unique_name:
2258                                 ok = False
2259                                 break
2260                 if ok:
2261                         return unique_name
2262                 nr += 1
2263
2264 # Add a sub-window
2265
2266 def AddSubWindow(mdi_area, sub_window, name):
2267         unique_name = UniqueSubWindowName(mdi_area, name)
2268         sub_window.setMinimumSize(200, 100)
2269         sub_window.resize(800, 600)
2270         sub_window.setWindowTitle(unique_name)
2271         sub_window.setAttribute(Qt.WA_DeleteOnClose)
2272         sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
2273         sub_window.name = unique_name
2274         mdi_area.addSubWindow(sub_window)
2275         sub_window.show()
2276
2277 # Main window
2278
2279 class MainWindow(QMainWindow):
2280
2281         def __init__(self, glb, parent=None):
2282                 super(MainWindow, self).__init__(parent)
2283
2284                 self.glb = glb
2285
2286                 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
2287                 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
2288                 self.setMinimumSize(200, 100)
2289
2290                 self.mdi_area = QMdiArea()
2291                 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2292                 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2293
2294                 self.setCentralWidget(self.mdi_area)
2295
2296                 menu = self.menuBar()
2297
2298                 file_menu = menu.addMenu("&File")
2299                 file_menu.addAction(CreateExitAction(glb.app, self))
2300
2301                 edit_menu = menu.addMenu("&Edit")
2302                 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
2303                 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
2304                 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
2305                 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
2306
2307                 reports_menu = menu.addMenu("&Reports")
2308                 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
2309
2310                 self.EventMenu(GetEventList(glb.db), reports_menu)
2311
2312                 self.TableMenu(GetTableList(glb), menu)
2313
2314                 self.window_menu = WindowMenu(self.mdi_area, menu)
2315
2316                 help_menu = menu.addMenu("&Help")
2317                 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
2318
2319         def Find(self):
2320                 win = self.mdi_area.activeSubWindow()
2321                 if win:
2322                         try:
2323                                 win.find_bar.Activate()
2324                         except:
2325                                 pass
2326
2327         def FetchMoreRecords(self):
2328                 win = self.mdi_area.activeSubWindow()
2329                 if win:
2330                         try:
2331                                 win.fetch_bar.Activate()
2332                         except:
2333                                 pass
2334
2335         def ShrinkFont(self):
2336                 win = self.mdi_area.activeSubWindow()
2337                 ShrinkFont(win.view)
2338
2339         def EnlargeFont(self):
2340                 win = self.mdi_area.activeSubWindow()
2341                 EnlargeFont(win.view)
2342
2343         def EventMenu(self, events, reports_menu):
2344                 branches_events = 0
2345                 for event in events:
2346                         event = event.split(":")[0]
2347                         if event == "branches":
2348                                 branches_events += 1
2349                 dbid = 0
2350                 for event in events:
2351                         dbid += 1
2352                         event = event.split(":")[0]
2353                         if event == "branches":
2354                                 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
2355                                 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
2356                                 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
2357                                 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
2358
2359         def TableMenu(self, tables, menu):
2360                 table_menu = menu.addMenu("&Tables")
2361                 for table in tables:
2362                         table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
2363
2364         def NewCallGraph(self):
2365                 CallGraphWindow(self.glb, self)
2366
2367         def NewBranchView(self, event_id):
2368                 BranchWindow(self.glb, event_id, "", "", self)
2369
2370         def NewSelectedBranchView(self, event_id):
2371                 dialog = SelectedBranchDialog(self.glb, self)
2372                 ret = dialog.exec_()
2373                 if ret:
2374                         BranchWindow(self.glb, event_id, dialog.name, dialog.where_clause, self)
2375
2376         def NewTableView(self, table_name):
2377                 TableWindow(self.glb, table_name, self)
2378
2379         def Help(self):
2380                 HelpWindow(self.glb, self)
2381
2382 # XED Disassembler
2383
2384 class xed_state_t(Structure):
2385
2386         _fields_ = [
2387                 ("mode", c_int),
2388                 ("width", c_int)
2389         ]
2390
2391 class XEDInstruction():
2392
2393         def __init__(self, libxed):
2394                 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
2395                 xedd_t = c_byte * 512
2396                 self.xedd = xedd_t()
2397                 self.xedp = addressof(self.xedd)
2398                 libxed.xed_decoded_inst_zero(self.xedp)
2399                 self.state = xed_state_t()
2400                 self.statep = addressof(self.state)
2401                 # Buffer for disassembled instruction text
2402                 self.buffer = create_string_buffer(256)
2403                 self.bufferp = addressof(self.buffer)
2404
2405 class LibXED():
2406
2407         def __init__(self):
2408                 try:
2409                         self.libxed = CDLL("libxed.so")
2410                 except:
2411                         self.libxed = None
2412                 if not self.libxed:
2413                         self.libxed = CDLL("/usr/local/lib/libxed.so")
2414
2415                 self.xed_tables_init = self.libxed.xed_tables_init
2416                 self.xed_tables_init.restype = None
2417                 self.xed_tables_init.argtypes = []
2418
2419                 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
2420                 self.xed_decoded_inst_zero.restype = None
2421                 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
2422
2423                 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
2424                 self.xed_operand_values_set_mode.restype = None
2425                 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
2426
2427                 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
2428                 self.xed_decoded_inst_zero_keep_mode.restype = None
2429                 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
2430
2431                 self.xed_decode = self.libxed.xed_decode
2432                 self.xed_decode.restype = c_int
2433                 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
2434
2435                 self.xed_format_context = self.libxed.xed_format_context
2436                 self.xed_format_context.restype = c_uint
2437                 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
2438
2439                 self.xed_tables_init()
2440
2441         def Instruction(self):
2442                 return XEDInstruction(self)
2443
2444         def SetMode(self, inst, mode):
2445                 if mode:
2446                         inst.state.mode = 4 # 32-bit
2447                         inst.state.width = 4 # 4 bytes
2448                 else:
2449                         inst.state.mode = 1 # 64-bit
2450                         inst.state.width = 8 # 8 bytes
2451                 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
2452
2453         def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
2454                 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
2455                 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
2456                 if err:
2457                         return 0, ""
2458                 # Use AT&T mode (2), alternative is Intel (3)
2459                 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
2460                 if not ok:
2461                         return 0, ""
2462                 # Return instruction length and the disassembled instruction text
2463                 # For now, assume the length is in byte 166
2464                 return inst.xedd[166], inst.buffer.value
2465
2466 def TryOpen(file_name):
2467         try:
2468                 return open(file_name, "rb")
2469         except:
2470                 return None
2471
2472 def Is64Bit(f):
2473         result = sizeof(c_void_p)
2474         # ELF support only
2475         pos = f.tell()
2476         f.seek(0)
2477         header = f.read(7)
2478         f.seek(pos)
2479         magic = header[0:4]
2480         eclass = ord(header[4])
2481         encoding = ord(header[5])
2482         version = ord(header[6])
2483         if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
2484                 result = True if eclass == 2 else False
2485         return result
2486
2487 # Global data
2488
2489 class Glb():
2490
2491         def __init__(self, dbref, db, dbname):
2492                 self.dbref = dbref
2493                 self.db = db
2494                 self.dbname = dbname
2495                 self.home_dir = os.path.expanduser("~")
2496                 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
2497                 if self.buildid_dir:
2498                         self.buildid_dir += "/.build-id/"
2499                 else:
2500                         self.buildid_dir = self.home_dir + "/.debug/.build-id/"
2501                 self.app = None
2502                 self.mainwindow = None
2503                 self.instances_to_shutdown_on_exit = weakref.WeakSet()
2504                 try:
2505                         self.disassembler = LibXED()
2506                         self.have_disassembler = True
2507                 except:
2508                         self.have_disassembler = False
2509
2510         def FileFromBuildId(self, build_id):
2511                 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
2512                 return TryOpen(file_name)
2513
2514         def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
2515                 # Assume current machine i.e. no support for virtualization
2516                 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
2517                         file_name = os.getenv("PERF_KCORE")
2518                         f = TryOpen(file_name) if file_name else None
2519                         if f:
2520                                 return f
2521                         # For now, no special handling if long_name is /proc/kcore
2522                         f = TryOpen(long_name)
2523                         if f:
2524                                 return f
2525                 f = self.FileFromBuildId(build_id)
2526                 if f:
2527                         return f
2528                 return None
2529
2530         def AddInstanceToShutdownOnExit(self, instance):
2531                 self.instances_to_shutdown_on_exit.add(instance)
2532
2533         # Shutdown any background processes or threads
2534         def ShutdownInstances(self):
2535                 for x in self.instances_to_shutdown_on_exit:
2536                         try:
2537                                 x.Shutdown()
2538                         except:
2539                                 pass
2540
2541 # Database reference
2542
2543 class DBRef():
2544
2545         def __init__(self, is_sqlite3, dbname):
2546                 self.is_sqlite3 = is_sqlite3
2547                 self.dbname = dbname
2548
2549         def Open(self, connection_name):
2550                 dbname = self.dbname
2551                 if self.is_sqlite3:
2552                         db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
2553                 else:
2554                         db = QSqlDatabase.addDatabase("QPSQL", connection_name)
2555                         opts = dbname.split()
2556                         for opt in opts:
2557                                 if "=" in opt:
2558                                         opt = opt.split("=")
2559                                         if opt[0] == "hostname":
2560                                                 db.setHostName(opt[1])
2561                                         elif opt[0] == "port":
2562                                                 db.setPort(int(opt[1]))
2563                                         elif opt[0] == "username":
2564                                                 db.setUserName(opt[1])
2565                                         elif opt[0] == "password":
2566                                                 db.setPassword(opt[1])
2567                                         elif opt[0] == "dbname":
2568                                                 dbname = opt[1]
2569                                 else:
2570                                         dbname = opt
2571
2572                 db.setDatabaseName(dbname)
2573                 if not db.open():
2574                         raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
2575                 return db, dbname
2576
2577 # Main
2578
2579 def Main():
2580         if (len(sys.argv) < 2):
2581                 print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}"
2582                 raise Exception("Too few arguments")
2583
2584         dbname = sys.argv[1]
2585         if dbname == "--help-only":
2586                 app = QApplication(sys.argv)
2587                 mainwindow = HelpOnlyWindow()
2588                 mainwindow.show()
2589                 err = app.exec_()
2590                 sys.exit(err)
2591
2592         is_sqlite3 = False
2593         try:
2594                 f = open(dbname)
2595                 if f.read(15) == "SQLite format 3":
2596                         is_sqlite3 = True
2597                 f.close()
2598         except:
2599                 pass
2600
2601         dbref = DBRef(is_sqlite3, dbname)
2602         db, dbname = dbref.Open("main")
2603         glb = Glb(dbref, db, dbname)
2604         app = QApplication(sys.argv)
2605         glb.app = app
2606         mainwindow = MainWindow(glb)
2607         glb.mainwindow = mainwindow
2608         mainwindow.show()
2609         err = app.exec_()
2610         glb.ShutdownInstances()
2611         db.close()
2612         sys.exit(err)
2613
2614 if __name__ == "__main__":
2615         Main()