]> Git Repo - linux.git/blob - tools/perf/scripts/python/exported-sql-viewer.py
728200e3a691219c8cea014364343729afaea4c2
[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                 self.accept()
1812
1813         def ShowMessage(self, msg):
1814                 self.status.setText("<font color=#FF0000>" + msg)
1815
1816         def ClearMessage(self):
1817                 self.status.setText("")
1818
1819 # Selected branch report creation dialog
1820
1821 class SelectedBranchDialog(ReportDialogBase):
1822
1823         def __init__(self, glb, parent=None):
1824                 title = "Selected Branches"
1825                 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
1826                          lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
1827                          lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
1828                          lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
1829                          lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
1830                          lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
1831                          lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
1832                          lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
1833                          lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
1834                 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
1835
1836 # Event list
1837
1838 def GetEventList(db):
1839         events = []
1840         query = QSqlQuery(db)
1841         QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
1842         while query.next():
1843                 events.append(query.value(0))
1844         return events
1845
1846 # Is a table selectable
1847
1848 def IsSelectable(db, table):
1849         query = QSqlQuery(db)
1850         try:
1851                 QueryExec(query, "SELECT * FROM " + table + " LIMIT 1")
1852         except:
1853                 return False
1854         return True
1855
1856 # SQL data preparation
1857
1858 def SQLTableDataPrep(query, count):
1859         data = []
1860         for i in xrange(count):
1861                 data.append(query.value(i))
1862         return data
1863
1864 # SQL table data model item
1865
1866 class SQLTableItem():
1867
1868         def __init__(self, row, data):
1869                 self.row = row
1870                 self.data = data
1871
1872         def getData(self, column):
1873                 return self.data[column]
1874
1875 # SQL table data model
1876
1877 class SQLTableModel(TableModel):
1878
1879         progress = Signal(object)
1880
1881         def __init__(self, glb, sql, column_headers, parent=None):
1882                 super(SQLTableModel, self).__init__(parent)
1883                 self.glb = glb
1884                 self.more = True
1885                 self.populated = 0
1886                 self.column_headers = column_headers
1887                 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): SQLTableDataPrep(x, y), self.AddSample)
1888                 self.fetcher.done.connect(self.Update)
1889                 self.fetcher.Fetch(glb_chunk_sz)
1890
1891         def DisplayData(self, item, index):
1892                 self.FetchIfNeeded(item.row)
1893                 return item.getData(index.column())
1894
1895         def AddSample(self, data):
1896                 child = SQLTableItem(self.populated, data)
1897                 self.child_items.append(child)
1898                 self.populated += 1
1899
1900         def Update(self, fetched):
1901                 if not fetched:
1902                         self.more = False
1903                         self.progress.emit(0)
1904                 child_count = self.child_count
1905                 count = self.populated - child_count
1906                 if count > 0:
1907                         parent = QModelIndex()
1908                         self.beginInsertRows(parent, child_count, child_count + count - 1)
1909                         self.insertRows(child_count, count, parent)
1910                         self.child_count += count
1911                         self.endInsertRows()
1912                         self.progress.emit(self.child_count)
1913
1914         def FetchMoreRecords(self, count):
1915                 current = self.child_count
1916                 if self.more:
1917                         self.fetcher.Fetch(count)
1918                 else:
1919                         self.progress.emit(0)
1920                 return current
1921
1922         def HasMoreRecords(self):
1923                 return self.more
1924
1925         def columnCount(self, parent=None):
1926                 return len(self.column_headers)
1927
1928         def columnHeader(self, column):
1929                 return self.column_headers[column]
1930
1931 # SQL automatic table data model
1932
1933 class SQLAutoTableModel(SQLTableModel):
1934
1935         def __init__(self, glb, table_name, parent=None):
1936                 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
1937                 if table_name == "comm_threads_view":
1938                         # For now, comm_threads_view has no id column
1939                         sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
1940                 column_headers = []
1941                 query = QSqlQuery(glb.db)
1942                 if glb.dbref.is_sqlite3:
1943                         QueryExec(query, "PRAGMA table_info(" + table_name + ")")
1944                         while query.next():
1945                                 column_headers.append(query.value(1))
1946                         if table_name == "sqlite_master":
1947                                 sql = "SELECT * FROM " + table_name
1948                 else:
1949                         if table_name[:19] == "information_schema.":
1950                                 sql = "SELECT * FROM " + table_name
1951                                 select_table_name = table_name[19:]
1952                                 schema = "information_schema"
1953                         else:
1954                                 select_table_name = table_name
1955                                 schema = "public"
1956                         QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
1957                         while query.next():
1958                                 column_headers.append(query.value(0))
1959                 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
1960
1961 # Base class for custom ResizeColumnsToContents
1962
1963 class ResizeColumnsToContentsBase(QObject):
1964
1965         def __init__(self, parent=None):
1966                 super(ResizeColumnsToContentsBase, self).__init__(parent)
1967
1968         def ResizeColumnToContents(self, column, n):
1969                 # Using the view's resizeColumnToContents() here is extrememly slow
1970                 # so implement a crude alternative
1971                 font = self.view.font()
1972                 metrics = QFontMetrics(font)
1973                 max = 0
1974                 for row in xrange(n):
1975                         val = self.data_model.child_items[row].data[column]
1976                         len = metrics.width(str(val) + "MM")
1977                         max = len if len > max else max
1978                 val = self.data_model.columnHeader(column)
1979                 len = metrics.width(str(val) + "MM")
1980                 max = len if len > max else max
1981                 self.view.setColumnWidth(column, max)
1982
1983         def ResizeColumnsToContents(self):
1984                 n = min(self.data_model.child_count, 100)
1985                 if n < 1:
1986                         # No data yet, so connect a signal to notify when there is
1987                         self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
1988                         return
1989                 columns = self.data_model.columnCount()
1990                 for i in xrange(columns):
1991                         self.ResizeColumnToContents(i, n)
1992
1993         def UpdateColumnWidths(self, *x):
1994                 # This only needs to be done once, so disconnect the signal now
1995                 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
1996                 self.ResizeColumnsToContents()
1997
1998 # Table window
1999
2000 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2001
2002         def __init__(self, glb, table_name, parent=None):
2003                 super(TableWindow, self).__init__(parent)
2004
2005                 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2006
2007                 self.model = QSortFilterProxyModel()
2008                 self.model.setSourceModel(self.data_model)
2009
2010                 self.view = QTableView()
2011                 self.view.setModel(self.model)
2012                 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2013                 self.view.verticalHeader().setVisible(False)
2014                 self.view.sortByColumn(-1, Qt.AscendingOrder)
2015                 self.view.setSortingEnabled(True)
2016
2017                 self.ResizeColumnsToContents()
2018
2019                 self.find_bar = FindBar(self, self, True)
2020
2021                 self.finder = ChildDataItemFinder(self.data_model)
2022
2023                 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2024
2025                 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2026
2027                 self.setWidget(self.vbox.Widget())
2028
2029                 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2030
2031         def Find(self, value, direction, pattern, context):
2032                 self.view.setFocus()
2033                 self.find_bar.Busy()
2034                 self.finder.Find(value, direction, pattern, context, self.FindDone)
2035
2036         def FindDone(self, row):
2037                 self.find_bar.Idle()
2038                 if row >= 0:
2039                         self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2040                 else:
2041                         self.find_bar.NotFound()
2042
2043 # Table list
2044
2045 def GetTableList(glb):
2046         tables = []
2047         query = QSqlQuery(glb.db)
2048         if glb.dbref.is_sqlite3:
2049                 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2050         else:
2051                 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2052         while query.next():
2053                 tables.append(query.value(0))
2054         if glb.dbref.is_sqlite3:
2055                 tables.append("sqlite_master")
2056         else:
2057                 tables.append("information_schema.tables")
2058                 tables.append("information_schema.views")
2059                 tables.append("information_schema.columns")
2060         return tables
2061
2062 # Action Definition
2063
2064 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2065         action = QAction(label, parent)
2066         if shortcut != None:
2067                 action.setShortcuts(shortcut)
2068         action.setStatusTip(tip)
2069         action.triggered.connect(callback)
2070         return action
2071
2072 # Typical application actions
2073
2074 def CreateExitAction(app, parent=None):
2075         return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2076
2077 # Typical MDI actions
2078
2079 def CreateCloseActiveWindowAction(mdi_area):
2080         return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2081
2082 def CreateCloseAllWindowsAction(mdi_area):
2083         return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2084
2085 def CreateTileWindowsAction(mdi_area):
2086         return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2087
2088 def CreateCascadeWindowsAction(mdi_area):
2089         return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2090
2091 def CreateNextWindowAction(mdi_area):
2092         return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2093
2094 def CreatePreviousWindowAction(mdi_area):
2095         return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2096
2097 # Typical MDI window menu
2098
2099 class WindowMenu():
2100
2101         def __init__(self, mdi_area, menu):
2102                 self.mdi_area = mdi_area
2103                 self.window_menu = menu.addMenu("&Windows")
2104                 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2105                 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2106                 self.tile_windows = CreateTileWindowsAction(mdi_area)
2107                 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2108                 self.next_window = CreateNextWindowAction(mdi_area)
2109                 self.previous_window = CreatePreviousWindowAction(mdi_area)
2110                 self.window_menu.aboutToShow.connect(self.Update)
2111
2112         def Update(self):
2113                 self.window_menu.clear()
2114                 sub_window_count = len(self.mdi_area.subWindowList())
2115                 have_sub_windows = sub_window_count != 0
2116                 self.close_active_window.setEnabled(have_sub_windows)
2117                 self.close_all_windows.setEnabled(have_sub_windows)
2118                 self.tile_windows.setEnabled(have_sub_windows)
2119                 self.cascade_windows.setEnabled(have_sub_windows)
2120                 self.next_window.setEnabled(have_sub_windows)
2121                 self.previous_window.setEnabled(have_sub_windows)
2122                 self.window_menu.addAction(self.close_active_window)
2123                 self.window_menu.addAction(self.close_all_windows)
2124                 self.window_menu.addSeparator()
2125                 self.window_menu.addAction(self.tile_windows)
2126                 self.window_menu.addAction(self.cascade_windows)
2127                 self.window_menu.addSeparator()
2128                 self.window_menu.addAction(self.next_window)
2129                 self.window_menu.addAction(self.previous_window)
2130                 if sub_window_count == 0:
2131                         return
2132                 self.window_menu.addSeparator()
2133                 nr = 1
2134                 for sub_window in self.mdi_area.subWindowList():
2135                         label = str(nr) + " " + sub_window.name
2136                         if nr < 10:
2137                                 label = "&" + label
2138                         action = self.window_menu.addAction(label)
2139                         action.setCheckable(True)
2140                         action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2141                         action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2142                         self.window_menu.addAction(action)
2143                         nr += 1
2144
2145         def setActiveSubWindow(self, nr):
2146                 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2147
2148 # Help text
2149
2150 glb_help_text = """
2151 <h1>Contents</h1>
2152 <style>
2153 p.c1 {
2154     text-indent: 40px;
2155 }
2156 p.c2 {
2157     text-indent: 80px;
2158 }
2159 }
2160 </style>
2161 <p class=c1><a href=#reports>1. Reports</a></p>
2162 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2163 <p class=c2><a href=#allbranches>1.2 All branches</a></p>
2164 <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p>
2165 <p class=c1><a href=#tables>2. Tables</a></p>
2166 <h1 id=reports>1. Reports</h1>
2167 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2168 The result is a GUI window with a tree representing a context-sensitive
2169 call-graph. Expanding a couple of levels of the tree and adjusting column
2170 widths to suit will display something like:
2171 <pre>
2172                                          Call Graph: pt_example
2173 Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
2174 v- ls
2175     v- 2638:2638
2176         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
2177           |- unknown               unknown       1        13198     0.1              1              0.0
2178           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
2179           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
2180           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
2181              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
2182              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
2183              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
2184              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
2185              v- main               ls            1      8182043    99.6         180254             99.9
2186 </pre>
2187 <h3>Points to note:</h3>
2188 <ul>
2189 <li>The top level is a command name (comm)</li>
2190 <li>The next level is a thread (pid:tid)</li>
2191 <li>Subsequent levels are functions</li>
2192 <li>'Count' is the number of calls</li>
2193 <li>'Time' is the elapsed time until the function returns</li>
2194 <li>Percentages are relative to the level above</li>
2195 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2196 </ul>
2197 <h3>Find</h3>
2198 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2199 The pattern matching symbols are ? for any character and * for zero or more characters.
2200 <h2 id=allbranches>1.2 All branches</h2>
2201 The All branches report displays all branches in chronological order.
2202 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2203 <h3>Disassembly</h3>
2204 Open a branch to display disassembly. This only works if:
2205 <ol>
2206 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2207 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2208 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2209 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2210 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2211 </ol>
2212 <h4 id=xed>Intel XED Setup</h4>
2213 To use Intel XED, libxed.so must be present.  To build and install libxed.so:
2214 <pre>
2215 git clone https://github.com/intelxed/mbuild.git mbuild
2216 git clone https://github.com/intelxed/xed
2217 cd xed
2218 ./mfile.py --share
2219 sudo ./mfile.py --prefix=/usr/local install
2220 sudo ldconfig
2221 </pre>
2222 <h3>Find</h3>
2223 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2224 Refer to Python documentation for the regular expression syntax.
2225 All columns are searched, but only currently fetched rows are searched.
2226 <h2 id=selectedbranches>1.3 Selected branches</h2>
2227 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2228 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2229 <h3>1.3.1 Time ranges</h3>
2230 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2231 ms, us or ns. Also, negative values are relative to the end of trace.  Examples:
2232 <pre>
2233         81073085947329-81073085958238   From 81073085947329 to 81073085958238
2234         100us-200us             From 100us to 200us
2235         10ms-                   From 10ms to the end
2236         -100ns                  The first 100ns
2237         -10ms-                  The last 10ms
2238 </pre>
2239 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2240 <h1 id=tables>2. Tables</h1>
2241 The Tables menu shows all tables and views in the database. Most tables have an associated view
2242 which displays the information in a more friendly way. Not all data for large tables is fetched
2243 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2244 but that can be slow for large tables.
2245 <p>There are also tables of database meta-information.
2246 For SQLite3 databases, the sqlite_master table is included.
2247 For PostgreSQL databases, information_schema.tables/views/columns are included.
2248 <h3>Find</h3>
2249 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2250 Refer to Python documentation for the regular expression syntax.
2251 All columns are searched, but only currently fetched rows are searched.
2252 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2253 will go to the next/previous result in id order, instead of display order.
2254 """
2255
2256 # Help window
2257
2258 class HelpWindow(QMdiSubWindow):
2259
2260         def __init__(self, glb, parent=None):
2261                 super(HelpWindow, self).__init__(parent)
2262
2263                 self.text = QTextBrowser()
2264                 self.text.setHtml(glb_help_text)
2265                 self.text.setReadOnly(True)
2266                 self.text.setOpenExternalLinks(True)
2267
2268                 self.setWidget(self.text)
2269
2270                 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2271
2272 # Main window that only displays the help text
2273
2274 class HelpOnlyWindow(QMainWindow):
2275
2276         def __init__(self, parent=None):
2277                 super(HelpOnlyWindow, self).__init__(parent)
2278
2279                 self.setMinimumSize(200, 100)
2280                 self.resize(800, 600)
2281                 self.setWindowTitle("Exported SQL Viewer Help")
2282                 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2283
2284                 self.text = QTextBrowser()
2285                 self.text.setHtml(glb_help_text)
2286                 self.text.setReadOnly(True)
2287                 self.text.setOpenExternalLinks(True)
2288
2289                 self.setCentralWidget(self.text)
2290
2291 # Font resize
2292
2293 def ResizeFont(widget, diff):
2294         font = widget.font()
2295         sz = font.pointSize()
2296         font.setPointSize(sz + diff)
2297         widget.setFont(font)
2298
2299 def ShrinkFont(widget):
2300         ResizeFont(widget, -1)
2301
2302 def EnlargeFont(widget):
2303         ResizeFont(widget, 1)
2304
2305 # Unique name for sub-windows
2306
2307 def NumberedWindowName(name, nr):
2308         if nr > 1:
2309                 name += " <" + str(nr) + ">"
2310         return name
2311
2312 def UniqueSubWindowName(mdi_area, name):
2313         nr = 1
2314         while True:
2315                 unique_name = NumberedWindowName(name, nr)
2316                 ok = True
2317                 for sub_window in mdi_area.subWindowList():
2318                         if sub_window.name == unique_name:
2319                                 ok = False
2320                                 break
2321                 if ok:
2322                         return unique_name
2323                 nr += 1
2324
2325 # Add a sub-window
2326
2327 def AddSubWindow(mdi_area, sub_window, name):
2328         unique_name = UniqueSubWindowName(mdi_area, name)
2329         sub_window.setMinimumSize(200, 100)
2330         sub_window.resize(800, 600)
2331         sub_window.setWindowTitle(unique_name)
2332         sub_window.setAttribute(Qt.WA_DeleteOnClose)
2333         sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
2334         sub_window.name = unique_name
2335         mdi_area.addSubWindow(sub_window)
2336         sub_window.show()
2337
2338 # Main window
2339
2340 class MainWindow(QMainWindow):
2341
2342         def __init__(self, glb, parent=None):
2343                 super(MainWindow, self).__init__(parent)
2344
2345                 self.glb = glb
2346
2347                 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
2348                 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
2349                 self.setMinimumSize(200, 100)
2350
2351                 self.mdi_area = QMdiArea()
2352                 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2353                 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2354
2355                 self.setCentralWidget(self.mdi_area)
2356
2357                 menu = self.menuBar()
2358
2359                 file_menu = menu.addMenu("&File")
2360                 file_menu.addAction(CreateExitAction(glb.app, self))
2361
2362                 edit_menu = menu.addMenu("&Edit")
2363                 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
2364                 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
2365                 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
2366                 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
2367
2368                 reports_menu = menu.addMenu("&Reports")
2369                 if IsSelectable(glb.db, "calls"):
2370                         reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
2371
2372                 self.EventMenu(GetEventList(glb.db), reports_menu)
2373
2374                 self.TableMenu(GetTableList(glb), menu)
2375
2376                 self.window_menu = WindowMenu(self.mdi_area, menu)
2377
2378                 help_menu = menu.addMenu("&Help")
2379                 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
2380
2381         def Find(self):
2382                 win = self.mdi_area.activeSubWindow()
2383                 if win:
2384                         try:
2385                                 win.find_bar.Activate()
2386                         except:
2387                                 pass
2388
2389         def FetchMoreRecords(self):
2390                 win = self.mdi_area.activeSubWindow()
2391                 if win:
2392                         try:
2393                                 win.fetch_bar.Activate()
2394                         except:
2395                                 pass
2396
2397         def ShrinkFont(self):
2398                 win = self.mdi_area.activeSubWindow()
2399                 ShrinkFont(win.view)
2400
2401         def EnlargeFont(self):
2402                 win = self.mdi_area.activeSubWindow()
2403                 EnlargeFont(win.view)
2404
2405         def EventMenu(self, events, reports_menu):
2406                 branches_events = 0
2407                 for event in events:
2408                         event = event.split(":")[0]
2409                         if event == "branches":
2410                                 branches_events += 1
2411                 dbid = 0
2412                 for event in events:
2413                         dbid += 1
2414                         event = event.split(":")[0]
2415                         if event == "branches":
2416                                 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
2417                                 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
2418                                 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
2419                                 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
2420
2421         def TableMenu(self, tables, menu):
2422                 table_menu = menu.addMenu("&Tables")
2423                 for table in tables:
2424                         table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
2425
2426         def NewCallGraph(self):
2427                 CallGraphWindow(self.glb, self)
2428
2429         def NewBranchView(self, event_id):
2430                 BranchWindow(self.glb, event_id, ReportVars(), self)
2431
2432         def NewSelectedBranchView(self, event_id):
2433                 dialog = SelectedBranchDialog(self.glb, self)
2434                 ret = dialog.exec_()
2435                 if ret:
2436                         BranchWindow(self.glb, event_id, dialog.report_vars, self)
2437
2438         def NewTableView(self, table_name):
2439                 TableWindow(self.glb, table_name, self)
2440
2441         def Help(self):
2442                 HelpWindow(self.glb, self)
2443
2444 # XED Disassembler
2445
2446 class xed_state_t(Structure):
2447
2448         _fields_ = [
2449                 ("mode", c_int),
2450                 ("width", c_int)
2451         ]
2452
2453 class XEDInstruction():
2454
2455         def __init__(self, libxed):
2456                 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
2457                 xedd_t = c_byte * 512
2458                 self.xedd = xedd_t()
2459                 self.xedp = addressof(self.xedd)
2460                 libxed.xed_decoded_inst_zero(self.xedp)
2461                 self.state = xed_state_t()
2462                 self.statep = addressof(self.state)
2463                 # Buffer for disassembled instruction text
2464                 self.buffer = create_string_buffer(256)
2465                 self.bufferp = addressof(self.buffer)
2466
2467 class LibXED():
2468
2469         def __init__(self):
2470                 try:
2471                         self.libxed = CDLL("libxed.so")
2472                 except:
2473                         self.libxed = None
2474                 if not self.libxed:
2475                         self.libxed = CDLL("/usr/local/lib/libxed.so")
2476
2477                 self.xed_tables_init = self.libxed.xed_tables_init
2478                 self.xed_tables_init.restype = None
2479                 self.xed_tables_init.argtypes = []
2480
2481                 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
2482                 self.xed_decoded_inst_zero.restype = None
2483                 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
2484
2485                 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
2486                 self.xed_operand_values_set_mode.restype = None
2487                 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
2488
2489                 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
2490                 self.xed_decoded_inst_zero_keep_mode.restype = None
2491                 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
2492
2493                 self.xed_decode = self.libxed.xed_decode
2494                 self.xed_decode.restype = c_int
2495                 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
2496
2497                 self.xed_format_context = self.libxed.xed_format_context
2498                 self.xed_format_context.restype = c_uint
2499                 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
2500
2501                 self.xed_tables_init()
2502
2503         def Instruction(self):
2504                 return XEDInstruction(self)
2505
2506         def SetMode(self, inst, mode):
2507                 if mode:
2508                         inst.state.mode = 4 # 32-bit
2509                         inst.state.width = 4 # 4 bytes
2510                 else:
2511                         inst.state.mode = 1 # 64-bit
2512                         inst.state.width = 8 # 8 bytes
2513                 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
2514
2515         def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
2516                 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
2517                 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
2518                 if err:
2519                         return 0, ""
2520                 # Use AT&T mode (2), alternative is Intel (3)
2521                 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
2522                 if not ok:
2523                         return 0, ""
2524                 # Return instruction length and the disassembled instruction text
2525                 # For now, assume the length is in byte 166
2526                 return inst.xedd[166], inst.buffer.value
2527
2528 def TryOpen(file_name):
2529         try:
2530                 return open(file_name, "rb")
2531         except:
2532                 return None
2533
2534 def Is64Bit(f):
2535         result = sizeof(c_void_p)
2536         # ELF support only
2537         pos = f.tell()
2538         f.seek(0)
2539         header = f.read(7)
2540         f.seek(pos)
2541         magic = header[0:4]
2542         eclass = ord(header[4])
2543         encoding = ord(header[5])
2544         version = ord(header[6])
2545         if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
2546                 result = True if eclass == 2 else False
2547         return result
2548
2549 # Global data
2550
2551 class Glb():
2552
2553         def __init__(self, dbref, db, dbname):
2554                 self.dbref = dbref
2555                 self.db = db
2556                 self.dbname = dbname
2557                 self.home_dir = os.path.expanduser("~")
2558                 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
2559                 if self.buildid_dir:
2560                         self.buildid_dir += "/.build-id/"
2561                 else:
2562                         self.buildid_dir = self.home_dir + "/.debug/.build-id/"
2563                 self.app = None
2564                 self.mainwindow = None
2565                 self.instances_to_shutdown_on_exit = weakref.WeakSet()
2566                 try:
2567                         self.disassembler = LibXED()
2568                         self.have_disassembler = True
2569                 except:
2570                         self.have_disassembler = False
2571
2572         def FileFromBuildId(self, build_id):
2573                 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
2574                 return TryOpen(file_name)
2575
2576         def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
2577                 # Assume current machine i.e. no support for virtualization
2578                 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
2579                         file_name = os.getenv("PERF_KCORE")
2580                         f = TryOpen(file_name) if file_name else None
2581                         if f:
2582                                 return f
2583                         # For now, no special handling if long_name is /proc/kcore
2584                         f = TryOpen(long_name)
2585                         if f:
2586                                 return f
2587                 f = self.FileFromBuildId(build_id)
2588                 if f:
2589                         return f
2590                 return None
2591
2592         def AddInstanceToShutdownOnExit(self, instance):
2593                 self.instances_to_shutdown_on_exit.add(instance)
2594
2595         # Shutdown any background processes or threads
2596         def ShutdownInstances(self):
2597                 for x in self.instances_to_shutdown_on_exit:
2598                         try:
2599                                 x.Shutdown()
2600                         except:
2601                                 pass
2602
2603 # Database reference
2604
2605 class DBRef():
2606
2607         def __init__(self, is_sqlite3, dbname):
2608                 self.is_sqlite3 = is_sqlite3
2609                 self.dbname = dbname
2610
2611         def Open(self, connection_name):
2612                 dbname = self.dbname
2613                 if self.is_sqlite3:
2614                         db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
2615                 else:
2616                         db = QSqlDatabase.addDatabase("QPSQL", connection_name)
2617                         opts = dbname.split()
2618                         for opt in opts:
2619                                 if "=" in opt:
2620                                         opt = opt.split("=")
2621                                         if opt[0] == "hostname":
2622                                                 db.setHostName(opt[1])
2623                                         elif opt[0] == "port":
2624                                                 db.setPort(int(opt[1]))
2625                                         elif opt[0] == "username":
2626                                                 db.setUserName(opt[1])
2627                                         elif opt[0] == "password":
2628                                                 db.setPassword(opt[1])
2629                                         elif opt[0] == "dbname":
2630                                                 dbname = opt[1]
2631                                 else:
2632                                         dbname = opt
2633
2634                 db.setDatabaseName(dbname)
2635                 if not db.open():
2636                         raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
2637                 return db, dbname
2638
2639 # Main
2640
2641 def Main():
2642         if (len(sys.argv) < 2):
2643                 print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}"
2644                 raise Exception("Too few arguments")
2645
2646         dbname = sys.argv[1]
2647         if dbname == "--help-only":
2648                 app = QApplication(sys.argv)
2649                 mainwindow = HelpOnlyWindow()
2650                 mainwindow.show()
2651                 err = app.exec_()
2652                 sys.exit(err)
2653
2654         is_sqlite3 = False
2655         try:
2656                 f = open(dbname)
2657                 if f.read(15) == "SQLite format 3":
2658                         is_sqlite3 = True
2659                 f.close()
2660         except:
2661                 pass
2662
2663         dbref = DBRef(is_sqlite3, dbname)
2664         db, dbname = dbref.Open("main")
2665         glb = Glb(dbref, db, dbname)
2666         app = QApplication(sys.argv)
2667         glb.app = app
2668         mainwindow = MainWindow(glb)
2669         glb.mainwindow = mainwindow
2670         mainwindow.show()
2671         err = app.exec_()
2672         glb.ShutdownInstances()
2673         db.close()
2674         sys.exit(err)
2675
2676 if __name__ == "__main__":
2677         Main()
This page took 0.179227 seconds and 2 git commands to generate.