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