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