OSDN Git Service

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