]> Git Repo - linux.git/blob - tools/perf/scripts/python/exported-sql-viewer.py
Merge tag 'wireless-drivers-for-davem-2019-03-19' of git://git.kernel.org/pub/scm...
[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 # Call tree data model level 2+ item base
692
693 class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
694
695         def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
696                 super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
697                 self.comm_id = comm_id
698                 self.thread_id = thread_id
699                 self.calls_id = calls_id
700                 self.branch_count = branch_count
701                 self.time = time
702
703         def Select(self):
704                 self.query_done = True;
705                 if self.calls_id == 0:
706                         comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
707                 else:
708                         comm_thread = ""
709                 query = QSqlQuery(self.glb.db)
710                 QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
711                                         " FROM calls"
712                                         " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
713                                         " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
714                                         " INNER JOIN dsos ON symbols.dso_id = dsos.id"
715                                         " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
716                                         " ORDER BY call_time, calls.id")
717                 while query.next():
718                         child_item = CallTreeLevelThreeItem(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)
719                         self.child_items.append(child_item)
720                         self.child_count += 1
721
722 # Call tree data model level three item
723
724 class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
725
726         def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
727                 super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
728                 dso = dsoname(dso)
729                 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
730                 self.dbid = calls_id
731
732 # Call tree data model level two item
733
734 class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
735
736         def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
737                 super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item)
738                 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
739                 self.dbid = thread_id
740
741         def Select(self):
742                 super(CallTreeLevelTwoItem, self).Select()
743                 for child_item in self.child_items:
744                         self.time += child_item.time
745                         self.branch_count += child_item.branch_count
746                 for child_item in self.child_items:
747                         child_item.data[4] = PercentToOneDP(child_item.time, self.time)
748                         child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
749
750 # Call tree data model level one item
751
752 class CallTreeLevelOneItem(CallGraphLevelItemBase):
753
754         def __init__(self, glb, row, comm_id, comm, parent_item):
755                 super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item)
756                 self.data = [comm, "", "", "", "", "", ""]
757                 self.dbid = comm_id
758
759         def Select(self):
760                 self.query_done = True;
761                 query = QSqlQuery(self.glb.db)
762                 QueryExec(query, "SELECT thread_id, pid, tid"
763                                         " FROM comm_threads"
764                                         " INNER JOIN threads ON thread_id = threads.id"
765                                         " WHERE comm_id = " + str(self.dbid))
766                 while query.next():
767                         child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
768                         self.child_items.append(child_item)
769                         self.child_count += 1
770
771 # Call tree data model root item
772
773 class CallTreeRootItem(CallGraphLevelItemBase):
774
775         def __init__(self, glb):
776                 super(CallTreeRootItem, self).__init__(glb, 0, None)
777                 self.dbid = 0
778                 self.query_done = True;
779                 query = QSqlQuery(glb.db)
780                 QueryExec(query, "SELECT id, comm FROM comms")
781                 while query.next():
782                         if not query.value(0):
783                                 continue
784                         child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
785                         self.child_items.append(child_item)
786                         self.child_count += 1
787
788 # Call Tree data model
789
790 class CallTreeModel(CallGraphModelBase):
791
792         def __init__(self, glb, parent=None):
793                 super(CallTreeModel, self).__init__(glb, parent)
794
795         def GetRoot(self):
796                 return CallTreeRootItem(self.glb)
797
798         def columnCount(self, parent=None):
799                 return 7
800
801         def columnHeader(self, column):
802                 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
803                 return headers[column]
804
805         def columnAlignment(self, column):
806                 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
807                 return alignment[column]
808
809         def DoFindSelect(self, query, match):
810                 QueryExec(query, "SELECT calls.id, comm_id, thread_id"
811                                                 " FROM calls"
812                                                 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
813                                                 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
814                                                 " WHERE symbols.name" + match +
815                                                 " ORDER BY comm_id, thread_id, call_time, calls.id")
816
817         def FindPath(self, query):
818                 # Turn the query result into a list of ids that the tree view can walk
819                 # to open the tree at the right place.
820                 ids = []
821                 parent_id = query.value(0)
822                 while parent_id:
823                         ids.insert(0, parent_id)
824                         q2 = QSqlQuery(self.glb.db)
825                         QueryExec(q2, "SELECT parent_id"
826                                         " FROM calls"
827                                         " WHERE id = " + str(parent_id))
828                         if not q2.next():
829                                 break
830                         parent_id = q2.value(0)
831                 ids.insert(0, query.value(2))
832                 ids.insert(0, query.value(1))
833                 return ids
834
835 # Vertical widget layout
836
837 class VBox():
838
839         def __init__(self, w1, w2, w3=None):
840                 self.vbox = QWidget()
841                 self.vbox.setLayout(QVBoxLayout());
842
843                 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
844
845                 self.vbox.layout().addWidget(w1)
846                 self.vbox.layout().addWidget(w2)
847                 if w3:
848                         self.vbox.layout().addWidget(w3)
849
850         def Widget(self):
851                 return self.vbox
852
853 # Tree window base
854
855 class TreeWindowBase(QMdiSubWindow):
856
857         def __init__(self, parent=None):
858                 super(TreeWindowBase, self).__init__(parent)
859
860                 self.model = None
861                 self.view = None
862                 self.find_bar = None
863
864         def DisplayFound(self, ids):
865                 if not len(ids):
866                         return False
867                 parent = QModelIndex()
868                 for dbid in ids:
869                         found = False
870                         n = self.model.rowCount(parent)
871                         for row in xrange(n):
872                                 child = self.model.index(row, 0, parent)
873                                 if child.internalPointer().dbid == dbid:
874                                         found = True
875                                         self.view.setCurrentIndex(child)
876                                         parent = child
877                                         break
878                         if not found:
879                                 break
880                 return found
881
882         def Find(self, value, direction, pattern, context):
883                 self.view.setFocus()
884                 self.find_bar.Busy()
885                 self.model.Find(value, direction, pattern, context, self.FindDone)
886
887         def FindDone(self, ids):
888                 found = True
889                 if not self.DisplayFound(ids):
890                         found = False
891                 self.find_bar.Idle()
892                 if not found:
893                         self.find_bar.NotFound()
894
895
896 # Context-sensitive call graph window
897
898 class CallGraphWindow(TreeWindowBase):
899
900         def __init__(self, glb, parent=None):
901                 super(CallGraphWindow, self).__init__(parent)
902
903                 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
904
905                 self.view = QTreeView()
906                 self.view.setModel(self.model)
907
908                 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
909                         self.view.setColumnWidth(c, w)
910
911                 self.find_bar = FindBar(self, self)
912
913                 self.vbox = VBox(self.view, self.find_bar.Widget())
914
915                 self.setWidget(self.vbox.Widget())
916
917                 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
918
919 # Call tree window
920
921 class CallTreeWindow(TreeWindowBase):
922
923         def __init__(self, glb, parent=None):
924                 super(CallTreeWindow, self).__init__(parent)
925
926                 self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
927
928                 self.view = QTreeView()
929                 self.view.setModel(self.model)
930
931                 for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
932                         self.view.setColumnWidth(c, w)
933
934                 self.find_bar = FindBar(self, self)
935
936                 self.vbox = VBox(self.view, self.find_bar.Widget())
937
938                 self.setWidget(self.vbox.Widget())
939
940                 AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
941
942 # Child data item  finder
943
944 class ChildDataItemFinder():
945
946         def __init__(self, root):
947                 self.root = root
948                 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
949                 self.rows = []
950                 self.pos = 0
951
952         def FindSelect(self):
953                 self.rows = []
954                 if self.pattern:
955                         pattern = re.compile(self.value)
956                         for child in self.root.child_items:
957                                 for column_data in child.data:
958                                         if re.search(pattern, str(column_data)) is not None:
959                                                 self.rows.append(child.row)
960                                                 break
961                 else:
962                         for child in self.root.child_items:
963                                 for column_data in child.data:
964                                         if self.value in str(column_data):
965                                                 self.rows.append(child.row)
966                                                 break
967
968         def FindValue(self):
969                 self.pos = 0
970                 if self.last_value != self.value or self.pattern != self.last_pattern:
971                         self.FindSelect()
972                 if not len(self.rows):
973                         return -1
974                 return self.rows[self.pos]
975
976         def FindThread(self):
977                 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
978                         row = self.FindValue()
979                 elif len(self.rows):
980                         if self.direction > 0:
981                                 self.pos += 1
982                                 if self.pos >= len(self.rows):
983                                         self.pos = 0
984                         else:
985                                 self.pos -= 1
986                                 if self.pos < 0:
987                                         self.pos = len(self.rows) - 1
988                         row = self.rows[self.pos]
989                 else:
990                         row = -1
991                 return (True, row)
992
993         def Find(self, value, direction, pattern, context, callback):
994                 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
995                 # Use a thread so the UI is not blocked
996                 thread = Thread(self.FindThread)
997                 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
998                 thread.start()
999
1000         def FindDone(self, thread, callback, row):
1001                 callback(row)
1002
1003 # Number of database records to fetch in one go
1004
1005 glb_chunk_sz = 10000
1006
1007 # size of pickled integer big enough for record size
1008
1009 glb_nsz = 8
1010
1011 # Background process for SQL data fetcher
1012
1013 class SQLFetcherProcess():
1014
1015         def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1016                 # Need a unique connection name
1017                 conn_name = "SQLFetcher" + str(os.getpid())
1018                 self.db, dbname = dbref.Open(conn_name)
1019                 self.sql = sql
1020                 self.buffer = buffer
1021                 self.head = head
1022                 self.tail = tail
1023                 self.fetch_count = fetch_count
1024                 self.fetching_done = fetching_done
1025                 self.process_target = process_target
1026                 self.wait_event = wait_event
1027                 self.fetched_event = fetched_event
1028                 self.prep = prep
1029                 self.query = QSqlQuery(self.db)
1030                 self.query_limit = 0 if "$$last_id$$" in sql else 2
1031                 self.last_id = -1
1032                 self.fetched = 0
1033                 self.more = True
1034                 self.local_head = self.head.value
1035                 self.local_tail = self.tail.value
1036
1037         def Select(self):
1038                 if self.query_limit:
1039                         if self.query_limit == 1:
1040                                 return
1041                         self.query_limit -= 1
1042                 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1043                 QueryExec(self.query, stmt)
1044
1045         def Next(self):
1046                 if not self.query.next():
1047                         self.Select()
1048                         if not self.query.next():
1049                                 return None
1050                 self.last_id = self.query.value(0)
1051                 return self.prep(self.query)
1052
1053         def WaitForTarget(self):
1054                 while True:
1055                         self.wait_event.clear()
1056                         target = self.process_target.value
1057                         if target > self.fetched or target < 0:
1058                                 break
1059                         self.wait_event.wait()
1060                 return target
1061
1062         def HasSpace(self, sz):
1063                 if self.local_tail <= self.local_head:
1064                         space = len(self.buffer) - self.local_head
1065                         if space > sz:
1066                                 return True
1067                         if space >= glb_nsz:
1068                                 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
1069                                 nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL)
1070                                 self.buffer[self.local_head : self.local_head + len(nd)] = nd
1071                         self.local_head = 0
1072                 if self.local_tail - self.local_head > sz:
1073                         return True
1074                 return False
1075
1076         def WaitForSpace(self, sz):
1077                 if self.HasSpace(sz):
1078                         return
1079                 while True:
1080                         self.wait_event.clear()
1081                         self.local_tail = self.tail.value
1082                         if self.HasSpace(sz):
1083                                 return
1084                         self.wait_event.wait()
1085
1086         def AddToBuffer(self, obj):
1087                 d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL)
1088                 n = len(d)
1089                 nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL)
1090                 sz = n + glb_nsz
1091                 self.WaitForSpace(sz)
1092                 pos = self.local_head
1093                 self.buffer[pos : pos + len(nd)] = nd
1094                 self.buffer[pos + glb_nsz : pos + sz] = d
1095                 self.local_head += sz
1096
1097         def FetchBatch(self, batch_size):
1098                 fetched = 0
1099                 while batch_size > fetched:
1100                         obj = self.Next()
1101                         if obj is None:
1102                                 self.more = False
1103                                 break
1104                         self.AddToBuffer(obj)
1105                         fetched += 1
1106                 if fetched:
1107                         self.fetched += fetched
1108                         with self.fetch_count.get_lock():
1109                                 self.fetch_count.value += fetched
1110                         self.head.value = self.local_head
1111                         self.fetched_event.set()
1112
1113         def Run(self):
1114                 while self.more:
1115                         target = self.WaitForTarget()
1116                         if target < 0:
1117                                 break
1118                         batch_size = min(glb_chunk_sz, target - self.fetched)
1119                         self.FetchBatch(batch_size)
1120                 self.fetching_done.value = True
1121                 self.fetched_event.set()
1122
1123 def SQLFetcherFn(*x):
1124         process = SQLFetcherProcess(*x)
1125         process.Run()
1126
1127 # SQL data fetcher
1128
1129 class SQLFetcher(QObject):
1130
1131         done = Signal(object)
1132
1133         def __init__(self, glb, sql, prep, process_data, parent=None):
1134                 super(SQLFetcher, self).__init__(parent)
1135                 self.process_data = process_data
1136                 self.more = True
1137                 self.target = 0
1138                 self.last_target = 0
1139                 self.fetched = 0
1140                 self.buffer_size = 16 * 1024 * 1024
1141                 self.buffer = Array(c_char, self.buffer_size, lock=False)
1142                 self.head = Value(c_longlong)
1143                 self.tail = Value(c_longlong)
1144                 self.local_tail = 0
1145                 self.fetch_count = Value(c_longlong)
1146                 self.fetching_done = Value(c_bool)
1147                 self.last_count = 0
1148                 self.process_target = Value(c_longlong)
1149                 self.wait_event = Event()
1150                 self.fetched_event = Event()
1151                 glb.AddInstanceToShutdownOnExit(self)
1152                 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))
1153                 self.process.start()
1154                 self.thread = Thread(self.Thread)
1155                 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1156                 self.thread.start()
1157
1158         def Shutdown(self):
1159                 # Tell the thread and process to exit
1160                 self.process_target.value = -1
1161                 self.wait_event.set()
1162                 self.more = False
1163                 self.fetching_done.value = True
1164                 self.fetched_event.set()
1165
1166         def Thread(self):
1167                 if not self.more:
1168                         return True, 0
1169                 while True:
1170                         self.fetched_event.clear()
1171                         fetch_count = self.fetch_count.value
1172                         if fetch_count != self.last_count:
1173                                 break
1174                         if self.fetching_done.value:
1175                                 self.more = False
1176                                 return True, 0
1177                         self.fetched_event.wait()
1178                 count = fetch_count - self.last_count
1179                 self.last_count = fetch_count
1180                 self.fetched += count
1181                 return False, count
1182
1183         def Fetch(self, nr):
1184                 if not self.more:
1185                         # -1 inidcates there are no more
1186                         return -1
1187                 result = self.fetched
1188                 extra = result + nr - self.target
1189                 if extra > 0:
1190                         self.target += extra
1191                         # process_target < 0 indicates shutting down
1192                         if self.process_target.value >= 0:
1193                                 self.process_target.value = self.target
1194                         self.wait_event.set()
1195                 return result
1196
1197         def RemoveFromBuffer(self):
1198                 pos = self.local_tail
1199                 if len(self.buffer) - pos < glb_nsz:
1200                         pos = 0
1201                 n = cPickle.loads(self.buffer[pos : pos + glb_nsz])
1202                 if n == 0:
1203                         pos = 0
1204                         n = cPickle.loads(self.buffer[0 : glb_nsz])
1205                 pos += glb_nsz
1206                 obj = cPickle.loads(self.buffer[pos : pos + n])
1207                 self.local_tail = pos + n
1208                 return obj
1209
1210         def ProcessData(self, count):
1211                 for i in xrange(count):
1212                         obj = self.RemoveFromBuffer()
1213                         self.process_data(obj)
1214                 self.tail.value = self.local_tail
1215                 self.wait_event.set()
1216                 self.done.emit(count)
1217
1218 # Fetch more records bar
1219
1220 class FetchMoreRecordsBar():
1221
1222         def __init__(self, model, parent):
1223                 self.model = model
1224
1225                 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1226                 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1227
1228                 self.fetch_count = QSpinBox()
1229                 self.fetch_count.setRange(1, 1000000)
1230                 self.fetch_count.setValue(10)
1231                 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1232
1233                 self.fetch = QPushButton("Go!")
1234                 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1235                 self.fetch.released.connect(self.FetchMoreRecords)
1236
1237                 self.progress = QProgressBar()
1238                 self.progress.setRange(0, 100)
1239                 self.progress.hide()
1240
1241                 self.done_label = QLabel("All records fetched")
1242                 self.done_label.hide()
1243
1244                 self.spacer = QLabel("")
1245
1246                 self.close_button = QToolButton()
1247                 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1248                 self.close_button.released.connect(self.Deactivate)
1249
1250                 self.hbox = QHBoxLayout()
1251                 self.hbox.setContentsMargins(0, 0, 0, 0)
1252
1253                 self.hbox.addWidget(self.label)
1254                 self.hbox.addWidget(self.fetch_count)
1255                 self.hbox.addWidget(self.fetch)
1256                 self.hbox.addWidget(self.spacer)
1257                 self.hbox.addWidget(self.progress)
1258                 self.hbox.addWidget(self.done_label)
1259                 self.hbox.addWidget(self.close_button)
1260
1261                 self.bar = QWidget()
1262                 self.bar.setLayout(self.hbox);
1263                 self.bar.show()
1264
1265                 self.in_progress = False
1266                 self.model.progress.connect(self.Progress)
1267
1268                 self.done = False
1269
1270                 if not model.HasMoreRecords():
1271                         self.Done()
1272
1273         def Widget(self):
1274                 return self.bar
1275
1276         def Activate(self):
1277                 self.bar.show()
1278                 self.fetch.setFocus()
1279
1280         def Deactivate(self):
1281                 self.bar.hide()
1282
1283         def Enable(self, enable):
1284                 self.fetch.setEnabled(enable)
1285                 self.fetch_count.setEnabled(enable)
1286
1287         def Busy(self):
1288                 self.Enable(False)
1289                 self.fetch.hide()
1290                 self.spacer.hide()
1291                 self.progress.show()
1292
1293         def Idle(self):
1294                 self.in_progress = False
1295                 self.Enable(True)
1296                 self.progress.hide()
1297                 self.fetch.show()
1298                 self.spacer.show()
1299
1300         def Target(self):
1301                 return self.fetch_count.value() * glb_chunk_sz
1302
1303         def Done(self):
1304                 self.done = True
1305                 self.Idle()
1306                 self.label.hide()
1307                 self.fetch_count.hide()
1308                 self.fetch.hide()
1309                 self.spacer.hide()
1310                 self.done_label.show()
1311
1312         def Progress(self, count):
1313                 if self.in_progress:
1314                         if count:
1315                                 percent = ((count - self.start) * 100) / self.Target()
1316                                 if percent >= 100:
1317                                         self.Idle()
1318                                 else:
1319                                         self.progress.setValue(percent)
1320                 if not count:
1321                         # Count value of zero means no more records
1322                         self.Done()
1323
1324         def FetchMoreRecords(self):
1325                 if self.done:
1326                         return
1327                 self.progress.setValue(0)
1328                 self.Busy()
1329                 self.in_progress = True
1330                 self.start = self.model.FetchMoreRecords(self.Target())
1331
1332 # Brance data model level two item
1333
1334 class BranchLevelTwoItem():
1335
1336         def __init__(self, row, text, parent_item):
1337                 self.row = row
1338                 self.parent_item = parent_item
1339                 self.data = [""] * 8
1340                 self.data[7] = text
1341                 self.level = 2
1342
1343         def getParentItem(self):
1344                 return self.parent_item
1345
1346         def getRow(self):
1347                 return self.row
1348
1349         def childCount(self):
1350                 return 0
1351
1352         def hasChildren(self):
1353                 return False
1354
1355         def getData(self, column):
1356                 return self.data[column]
1357
1358 # Brance data model level one item
1359
1360 class BranchLevelOneItem():
1361
1362         def __init__(self, glb, row, data, parent_item):
1363                 self.glb = glb
1364                 self.row = row
1365                 self.parent_item = parent_item
1366                 self.child_count = 0
1367                 self.child_items = []
1368                 self.data = data[1:]
1369                 self.dbid = data[0]
1370                 self.level = 1
1371                 self.query_done = False
1372
1373         def getChildItem(self, row):
1374                 return self.child_items[row]
1375
1376         def getParentItem(self):
1377                 return self.parent_item
1378
1379         def getRow(self):
1380                 return self.row
1381
1382         def Select(self):
1383                 self.query_done = True
1384
1385                 if not self.glb.have_disassembler:
1386                         return
1387
1388                 query = QSqlQuery(self.glb.db)
1389
1390                 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1391                                   " FROM samples"
1392                                   " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1393                                   " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1394                                   " WHERE samples.id = " + str(self.dbid))
1395                 if not query.next():
1396                         return
1397                 cpu = query.value(0)
1398                 dso = query.value(1)
1399                 sym = query.value(2)
1400                 if dso == 0 or sym == 0:
1401                         return
1402                 off = query.value(3)
1403                 short_name = query.value(4)
1404                 long_name = query.value(5)
1405                 build_id = query.value(6)
1406                 sym_start = query.value(7)
1407                 ip = query.value(8)
1408
1409                 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1410                                   " FROM samples"
1411                                   " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1412                                   " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1413                                   " ORDER BY samples.id"
1414                                   " LIMIT 1")
1415                 if not query.next():
1416                         return
1417                 if query.value(0) != dso:
1418                         # Cannot disassemble from one dso to another
1419                         return
1420                 bsym = query.value(1)
1421                 boff = query.value(2)
1422                 bsym_start = query.value(3)
1423                 if bsym == 0:
1424                         return
1425                 tot = bsym_start + boff + 1 - sym_start - off
1426                 if tot <= 0 or tot > 16384:
1427                         return
1428
1429                 inst = self.glb.disassembler.Instruction()
1430                 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1431                 if not f:
1432                         return
1433                 mode = 0 if Is64Bit(f) else 1
1434                 self.glb.disassembler.SetMode(inst, mode)
1435
1436                 buf_sz = tot + 16
1437                 buf = create_string_buffer(tot + 16)
1438                 f.seek(sym_start + off)
1439                 buf.value = f.read(buf_sz)
1440                 buf_ptr = addressof(buf)
1441                 i = 0
1442                 while tot > 0:
1443                         cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1444                         if cnt:
1445                                 byte_str = tohex(ip).rjust(16)
1446                                 for k in xrange(cnt):
1447                                         byte_str += " %02x" % ord(buf[i])
1448                                         i += 1
1449                                 while k < 15:
1450                                         byte_str += "   "
1451                                         k += 1
1452                                 self.child_items.append(BranchLevelTwoItem(0, byte_str + " " + text, self))
1453                                 self.child_count += 1
1454                         else:
1455                                 return
1456                         buf_ptr += cnt
1457                         tot -= cnt
1458                         buf_sz -= cnt
1459                         ip += cnt
1460
1461         def childCount(self):
1462                 if not self.query_done:
1463                         self.Select()
1464                         if not self.child_count:
1465                                 return -1
1466                 return self.child_count
1467
1468         def hasChildren(self):
1469                 if not self.query_done:
1470                         return True
1471                 return self.child_count > 0
1472
1473         def getData(self, column):
1474                 return self.data[column]
1475
1476 # Brance data model root item
1477
1478 class BranchRootItem():
1479
1480         def __init__(self):
1481                 self.child_count = 0
1482                 self.child_items = []
1483                 self.level = 0
1484
1485         def getChildItem(self, row):
1486                 return self.child_items[row]
1487
1488         def getParentItem(self):
1489                 return None
1490
1491         def getRow(self):
1492                 return 0
1493
1494         def childCount(self):
1495                 return self.child_count
1496
1497         def hasChildren(self):
1498                 return self.child_count > 0
1499
1500         def getData(self, column):
1501                 return ""
1502
1503 # Branch data preparation
1504
1505 def BranchDataPrep(query):
1506         data = []
1507         for i in xrange(0, 8):
1508                 data.append(query.value(i))
1509         data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1510                         " (" + dsoname(query.value(11)) + ")" + " -> " +
1511                         tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1512                         " (" + dsoname(query.value(15)) + ")")
1513         return data
1514
1515 # Branch data model
1516
1517 class BranchModel(TreeModel):
1518
1519         progress = Signal(object)
1520
1521         def __init__(self, glb, event_id, where_clause, parent=None):
1522                 super(BranchModel, self).__init__(glb, parent)
1523                 self.event_id = event_id
1524                 self.more = True
1525                 self.populated = 0
1526                 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1527                         " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1528                         " ip, symbols.name, sym_offset, dsos.short_name,"
1529                         " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
1530                         " FROM samples"
1531                         " INNER JOIN comms ON comm_id = comms.id"
1532                         " INNER JOIN threads ON thread_id = threads.id"
1533                         " INNER JOIN branch_types ON branch_type = branch_types.id"
1534                         " INNER JOIN symbols ON symbol_id = symbols.id"
1535                         " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1536                         " INNER JOIN dsos ON samples.dso_id = dsos.id"
1537                         " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1538                         " WHERE samples.id > $$last_id$$" + where_clause +
1539                         " AND evsel_id = " + str(self.event_id) +
1540                         " ORDER BY samples.id"
1541                         " LIMIT " + str(glb_chunk_sz))
1542                 self.fetcher = SQLFetcher(glb, sql, BranchDataPrep, self.AddSample)
1543                 self.fetcher.done.connect(self.Update)
1544                 self.fetcher.Fetch(glb_chunk_sz)
1545
1546         def GetRoot(self):
1547                 return BranchRootItem()
1548
1549         def columnCount(self, parent=None):
1550                 return 8
1551
1552         def columnHeader(self, column):
1553                 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
1554
1555         def columnFont(self, column):
1556                 if column != 7:
1557                         return None
1558                 return QFont("Monospace")
1559
1560         def DisplayData(self, item, index):
1561                 if item.level == 1:
1562                         self.FetchIfNeeded(item.row)
1563                 return item.getData(index.column())
1564
1565         def AddSample(self, data):
1566                 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1567                 self.root.child_items.append(child)
1568                 self.populated += 1
1569
1570         def Update(self, fetched):
1571                 if not fetched:
1572                         self.more = False
1573                         self.progress.emit(0)
1574                 child_count = self.root.child_count
1575                 count = self.populated - child_count
1576                 if count > 0:
1577                         parent = QModelIndex()
1578                         self.beginInsertRows(parent, child_count, child_count + count - 1)
1579                         self.insertRows(child_count, count, parent)
1580                         self.root.child_count += count
1581                         self.endInsertRows()
1582                         self.progress.emit(self.root.child_count)
1583
1584         def FetchMoreRecords(self, count):
1585                 current = self.root.child_count
1586                 if self.more:
1587                         self.fetcher.Fetch(count)
1588                 else:
1589                         self.progress.emit(0)
1590                 return current
1591
1592         def HasMoreRecords(self):
1593                 return self.more
1594
1595 # Report Variables
1596
1597 class ReportVars():
1598
1599         def __init__(self, name = "", where_clause = "", limit = ""):
1600                 self.name = name
1601                 self.where_clause = where_clause
1602                 self.limit = limit
1603
1604         def UniqueId(self):
1605                 return str(self.where_clause + ";" + self.limit)
1606
1607 # Branch window
1608
1609 class BranchWindow(QMdiSubWindow):
1610
1611         def __init__(self, glb, event_id, report_vars, parent=None):
1612                 super(BranchWindow, self).__init__(parent)
1613
1614                 model_name = "Branch Events " + str(event_id) +  " " + report_vars.UniqueId()
1615
1616                 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
1617
1618                 self.view = QTreeView()
1619                 self.view.setUniformRowHeights(True)
1620                 self.view.setModel(self.model)
1621
1622                 self.ResizeColumnsToContents()
1623
1624                 self.find_bar = FindBar(self, self, True)
1625
1626                 self.finder = ChildDataItemFinder(self.model.root)
1627
1628                 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1629
1630                 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1631
1632                 self.setWidget(self.vbox.Widget())
1633
1634                 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
1635
1636         def ResizeColumnToContents(self, column, n):
1637                 # Using the view's resizeColumnToContents() here is extrememly slow
1638                 # so implement a crude alternative
1639                 mm = "MM" if column else "MMMM"
1640                 font = self.view.font()
1641                 metrics = QFontMetrics(font)
1642                 max = 0
1643                 for row in xrange(n):
1644                         val = self.model.root.child_items[row].data[column]
1645                         len = metrics.width(str(val) + mm)
1646                         max = len if len > max else max
1647                 val = self.model.columnHeader(column)
1648                 len = metrics.width(str(val) + mm)
1649                 max = len if len > max else max
1650                 self.view.setColumnWidth(column, max)
1651
1652         def ResizeColumnsToContents(self):
1653                 n = min(self.model.root.child_count, 100)
1654                 if n < 1:
1655                         # No data yet, so connect a signal to notify when there is
1656                         self.model.rowsInserted.connect(self.UpdateColumnWidths)
1657                         return
1658                 columns = self.model.columnCount()
1659                 for i in xrange(columns):
1660                         self.ResizeColumnToContents(i, n)
1661
1662         def UpdateColumnWidths(self, *x):
1663                 # This only needs to be done once, so disconnect the signal now
1664                 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1665                 self.ResizeColumnsToContents()
1666
1667         def Find(self, value, direction, pattern, context):
1668                 self.view.setFocus()
1669                 self.find_bar.Busy()
1670                 self.finder.Find(value, direction, pattern, context, self.FindDone)
1671
1672         def FindDone(self, row):
1673                 self.find_bar.Idle()
1674                 if row >= 0:
1675                         self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1676                 else:
1677                         self.find_bar.NotFound()
1678
1679 # Line edit data item
1680
1681 class LineEditDataItem(object):
1682
1683         def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1684                 self.glb = glb
1685                 self.label = label
1686                 self.placeholder_text = placeholder_text
1687                 self.parent = parent
1688                 self.id = id
1689
1690                 self.value = default
1691
1692                 self.widget = QLineEdit(default)
1693                 self.widget.editingFinished.connect(self.Validate)
1694                 self.widget.textChanged.connect(self.Invalidate)
1695                 self.red = False
1696                 self.error = ""
1697                 self.validated = True
1698
1699                 if placeholder_text:
1700                         self.widget.setPlaceholderText(placeholder_text)
1701
1702         def TurnTextRed(self):
1703                 if not self.red:
1704                         palette = QPalette()
1705                         palette.setColor(QPalette.Text,Qt.red)
1706                         self.widget.setPalette(palette)
1707                         self.red = True
1708
1709         def TurnTextNormal(self):
1710                 if self.red:
1711                         palette = QPalette()
1712                         self.widget.setPalette(palette)
1713                         self.red = False
1714
1715         def InvalidValue(self, value):
1716                 self.value = ""
1717                 self.TurnTextRed()
1718                 self.error = self.label + " invalid value '" + value + "'"
1719                 self.parent.ShowMessage(self.error)
1720
1721         def Invalidate(self):
1722                 self.validated = False
1723
1724         def DoValidate(self, input_string):
1725                 self.value = input_string.strip()
1726
1727         def Validate(self):
1728                 self.validated = True
1729                 self.error = ""
1730                 self.TurnTextNormal()
1731                 self.parent.ClearMessage()
1732                 input_string = self.widget.text()
1733                 if not len(input_string.strip()):
1734                         self.value = ""
1735                         return
1736                 self.DoValidate(input_string)
1737
1738         def IsValid(self):
1739                 if not self.validated:
1740                         self.Validate()
1741                 if len(self.error):
1742                         self.parent.ShowMessage(self.error)
1743                         return False
1744                 return True
1745
1746         def IsNumber(self, value):
1747                 try:
1748                         x = int(value)
1749                 except:
1750                         x = 0
1751                 return str(x) == value
1752
1753 # Non-negative integer ranges dialog data item
1754
1755 class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1756
1757         def __init__(self, glb, label, placeholder_text, column_name, parent):
1758                 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1759
1760                 self.column_name = column_name
1761
1762         def DoValidate(self, input_string):
1763                 singles = []
1764                 ranges = []
1765                 for value in [x.strip() for x in input_string.split(",")]:
1766                         if "-" in value:
1767                                 vrange = value.split("-")
1768                                 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1769                                         return self.InvalidValue(value)
1770                                 ranges.append(vrange)
1771                         else:
1772                                 if not self.IsNumber(value):
1773                                         return self.InvalidValue(value)
1774                                 singles.append(value)
1775                 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1776                 if len(singles):
1777                         ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1778                 self.value = " OR ".join(ranges)
1779
1780 # Positive integer dialog data item
1781
1782 class PositiveIntegerDataItem(LineEditDataItem):
1783
1784         def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1785                 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1786
1787         def DoValidate(self, input_string):
1788                 if not self.IsNumber(input_string.strip()):
1789                         return self.InvalidValue(input_string)
1790                 value = int(input_string.strip())
1791                 if value <= 0:
1792                         return self.InvalidValue(input_string)
1793                 self.value = str(value)
1794
1795 # Dialog data item converted and validated using a SQL table
1796
1797 class SQLTableDataItem(LineEditDataItem):
1798
1799         def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1800                 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
1801
1802                 self.table_name = table_name
1803                 self.match_column = match_column
1804                 self.column_name1 = column_name1
1805                 self.column_name2 = column_name2
1806
1807         def ValueToIds(self, value):
1808                 ids = []
1809                 query = QSqlQuery(self.glb.db)
1810                 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1811                 ret = query.exec_(stmt)
1812                 if ret:
1813                         while query.next():
1814                                 ids.append(str(query.value(0)))
1815                 return ids
1816
1817         def DoValidate(self, input_string):
1818                 all_ids = []
1819                 for value in [x.strip() for x in input_string.split(",")]:
1820                         ids = self.ValueToIds(value)
1821                         if len(ids):
1822                                 all_ids.extend(ids)
1823                         else:
1824                                 return self.InvalidValue(value)
1825                 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1826                 if self.column_name2:
1827                         self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1828
1829 # Sample time ranges dialog data item converted and validated using 'samples' SQL table
1830
1831 class SampleTimeRangesDataItem(LineEditDataItem):
1832
1833         def __init__(self, glb, label, placeholder_text, column_name, parent):
1834                 self.column_name = column_name
1835
1836                 self.last_id = 0
1837                 self.first_time = 0
1838                 self.last_time = 2 ** 64
1839
1840                 query = QSqlQuery(glb.db)
1841                 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1842                 if query.next():
1843                         self.last_id = int(query.value(0))
1844                         self.last_time = int(query.value(1))
1845                 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1846                 if query.next():
1847                         self.first_time = int(query.value(0))
1848                 if placeholder_text:
1849                         placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1850
1851                 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1852
1853         def IdBetween(self, query, lower_id, higher_id, order):
1854                 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1855                 if query.next():
1856                         return True, int(query.value(0))
1857                 else:
1858                         return False, 0
1859
1860         def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1861                 query = QSqlQuery(self.glb.db)
1862                 while True:
1863                         next_id = int((lower_id + higher_id) / 2)
1864                         QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1865                         if not query.next():
1866                                 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1867                                 if not ok:
1868                                         ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1869                                         if not ok:
1870                                                 return str(higher_id)
1871                                 next_id = dbid
1872                                 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1873                         next_time = int(query.value(0))
1874                         if get_floor:
1875                                 if target_time > next_time:
1876                                         lower_id = next_id
1877                                 else:
1878                                         higher_id = next_id
1879                                 if higher_id <= lower_id + 1:
1880                                         return str(higher_id)
1881                         else:
1882                                 if target_time >= next_time:
1883                                         lower_id = next_id
1884                                 else:
1885                                         higher_id = next_id
1886                                 if higher_id <= lower_id + 1:
1887                                         return str(lower_id)
1888
1889         def ConvertRelativeTime(self, val):
1890                 mult = 1
1891                 suffix = val[-2:]
1892                 if suffix == "ms":
1893                         mult = 1000000
1894                 elif suffix == "us":
1895                         mult = 1000
1896                 elif suffix == "ns":
1897                         mult = 1
1898                 else:
1899                         return val
1900                 val = val[:-2].strip()
1901                 if not self.IsNumber(val):
1902                         return val
1903                 val = int(val) * mult
1904                 if val >= 0:
1905                         val += self.first_time
1906                 else:
1907                         val += self.last_time
1908                 return str(val)
1909
1910         def ConvertTimeRange(self, vrange):
1911                 if vrange[0] == "":
1912                         vrange[0] = str(self.first_time)
1913                 if vrange[1] == "":
1914                         vrange[1] = str(self.last_time)
1915                 vrange[0] = self.ConvertRelativeTime(vrange[0])
1916                 vrange[1] = self.ConvertRelativeTime(vrange[1])
1917                 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1918                         return False
1919                 beg_range = max(int(vrange[0]), self.first_time)
1920                 end_range = min(int(vrange[1]), self.last_time)
1921                 if beg_range > self.last_time or end_range < self.first_time:
1922                         return False
1923                 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
1924                 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
1925                 return True
1926
1927         def AddTimeRange(self, value, ranges):
1928                 n = value.count("-")
1929                 if n == 1:
1930                         pass
1931                 elif n == 2:
1932                         if value.split("-")[1].strip() == "":
1933                                 n = 1
1934                 elif n == 3:
1935                         n = 2
1936                 else:
1937                         return False
1938                 pos = findnth(value, "-", n)
1939                 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
1940                 if self.ConvertTimeRange(vrange):
1941                         ranges.append(vrange)
1942                         return True
1943                 return False
1944
1945         def DoValidate(self, input_string):
1946                 ranges = []
1947                 for value in [x.strip() for x in input_string.split(",")]:
1948                         if not self.AddTimeRange(value, ranges):
1949                                 return self.InvalidValue(value)
1950                 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1951                 self.value = " OR ".join(ranges)
1952
1953 # Report Dialog Base
1954
1955 class ReportDialogBase(QDialog):
1956
1957         def __init__(self, glb, title, items, partial, parent=None):
1958                 super(ReportDialogBase, self).__init__(parent)
1959
1960                 self.glb = glb
1961
1962                 self.report_vars = ReportVars()
1963
1964                 self.setWindowTitle(title)
1965                 self.setMinimumWidth(600)
1966
1967                 self.data_items = [x(glb, self) for x in items]
1968
1969                 self.partial = partial
1970
1971                 self.grid = QGridLayout()
1972
1973                 for row in xrange(len(self.data_items)):
1974                         self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
1975                         self.grid.addWidget(self.data_items[row].widget, row, 1)
1976
1977                 self.status = QLabel()
1978
1979                 self.ok_button = QPushButton("Ok", self)
1980                 self.ok_button.setDefault(True)
1981                 self.ok_button.released.connect(self.Ok)
1982                 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1983
1984                 self.cancel_button = QPushButton("Cancel", self)
1985                 self.cancel_button.released.connect(self.reject)
1986                 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1987
1988                 self.hbox = QHBoxLayout()
1989                 #self.hbox.addStretch()
1990                 self.hbox.addWidget(self.status)
1991                 self.hbox.addWidget(self.ok_button)
1992                 self.hbox.addWidget(self.cancel_button)
1993
1994                 self.vbox = QVBoxLayout()
1995                 self.vbox.addLayout(self.grid)
1996                 self.vbox.addLayout(self.hbox)
1997
1998                 self.setLayout(self.vbox);
1999
2000         def Ok(self):
2001                 vars = self.report_vars
2002                 for d in self.data_items:
2003                         if d.id == "REPORTNAME":
2004                                 vars.name = d.value
2005                 if not vars.name:
2006                         self.ShowMessage("Report name is required")
2007                         return
2008                 for d in self.data_items:
2009                         if not d.IsValid():
2010                                 return
2011                 for d in self.data_items[1:]:
2012                         if d.id == "LIMIT":
2013                                 vars.limit = d.value
2014                         elif len(d.value):
2015                                 if len(vars.where_clause):
2016                                         vars.where_clause += " AND "
2017                                 vars.where_clause += d.value
2018                 if len(vars.where_clause):
2019                         if self.partial:
2020                                 vars.where_clause = " AND ( " + vars.where_clause + " ) "
2021                         else:
2022                                 vars.where_clause = " WHERE " + vars.where_clause + " "
2023                 self.accept()
2024
2025         def ShowMessage(self, msg):
2026                 self.status.setText("<font color=#FF0000>" + msg)
2027
2028         def ClearMessage(self):
2029                 self.status.setText("")
2030
2031 # Selected branch report creation dialog
2032
2033 class SelectedBranchDialog(ReportDialogBase):
2034
2035         def __init__(self, glb, parent=None):
2036                 title = "Selected Branches"
2037                 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2038                          lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2039                          lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2040                          lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2041                          lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2042                          lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2043                          lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2044                          lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2045                          lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
2046                 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2047
2048 # Event list
2049
2050 def GetEventList(db):
2051         events = []
2052         query = QSqlQuery(db)
2053         QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2054         while query.next():
2055                 events.append(query.value(0))
2056         return events
2057
2058 # Is a table selectable
2059
2060 def IsSelectable(db, table, sql = ""):
2061         query = QSqlQuery(db)
2062         try:
2063                 QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1")
2064         except:
2065                 return False
2066         return True
2067
2068 # SQL data preparation
2069
2070 def SQLTableDataPrep(query, count):
2071         data = []
2072         for i in xrange(count):
2073                 data.append(query.value(i))
2074         return data
2075
2076 # SQL table data model item
2077
2078 class SQLTableItem():
2079
2080         def __init__(self, row, data):
2081                 self.row = row
2082                 self.data = data
2083
2084         def getData(self, column):
2085                 return self.data[column]
2086
2087 # SQL table data model
2088
2089 class SQLTableModel(TableModel):
2090
2091         progress = Signal(object)
2092
2093         def __init__(self, glb, sql, column_headers, parent=None):
2094                 super(SQLTableModel, self).__init__(parent)
2095                 self.glb = glb
2096                 self.more = True
2097                 self.populated = 0
2098                 self.column_headers = column_headers
2099                 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): SQLTableDataPrep(x, y), self.AddSample)
2100                 self.fetcher.done.connect(self.Update)
2101                 self.fetcher.Fetch(glb_chunk_sz)
2102
2103         def DisplayData(self, item, index):
2104                 self.FetchIfNeeded(item.row)
2105                 return item.getData(index.column())
2106
2107         def AddSample(self, data):
2108                 child = SQLTableItem(self.populated, data)
2109                 self.child_items.append(child)
2110                 self.populated += 1
2111
2112         def Update(self, fetched):
2113                 if not fetched:
2114                         self.more = False
2115                         self.progress.emit(0)
2116                 child_count = self.child_count
2117                 count = self.populated - child_count
2118                 if count > 0:
2119                         parent = QModelIndex()
2120                         self.beginInsertRows(parent, child_count, child_count + count - 1)
2121                         self.insertRows(child_count, count, parent)
2122                         self.child_count += count
2123                         self.endInsertRows()
2124                         self.progress.emit(self.child_count)
2125
2126         def FetchMoreRecords(self, count):
2127                 current = self.child_count
2128                 if self.more:
2129                         self.fetcher.Fetch(count)
2130                 else:
2131                         self.progress.emit(0)
2132                 return current
2133
2134         def HasMoreRecords(self):
2135                 return self.more
2136
2137         def columnCount(self, parent=None):
2138                 return len(self.column_headers)
2139
2140         def columnHeader(self, column):
2141                 return self.column_headers[column]
2142
2143 # SQL automatic table data model
2144
2145 class SQLAutoTableModel(SQLTableModel):
2146
2147         def __init__(self, glb, table_name, parent=None):
2148                 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2149                 if table_name == "comm_threads_view":
2150                         # For now, comm_threads_view has no id column
2151                         sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
2152                 column_headers = []
2153                 query = QSqlQuery(glb.db)
2154                 if glb.dbref.is_sqlite3:
2155                         QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2156                         while query.next():
2157                                 column_headers.append(query.value(1))
2158                         if table_name == "sqlite_master":
2159                                 sql = "SELECT * FROM " + table_name
2160                 else:
2161                         if table_name[:19] == "information_schema.":
2162                                 sql = "SELECT * FROM " + table_name
2163                                 select_table_name = table_name[19:]
2164                                 schema = "information_schema"
2165                         else:
2166                                 select_table_name = table_name
2167                                 schema = "public"
2168                         QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2169                         while query.next():
2170                                 column_headers.append(query.value(0))
2171                 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
2172
2173 # Base class for custom ResizeColumnsToContents
2174
2175 class ResizeColumnsToContentsBase(QObject):
2176
2177         def __init__(self, parent=None):
2178                 super(ResizeColumnsToContentsBase, self).__init__(parent)
2179
2180         def ResizeColumnToContents(self, column, n):
2181                 # Using the view's resizeColumnToContents() here is extrememly slow
2182                 # so implement a crude alternative
2183                 font = self.view.font()
2184                 metrics = QFontMetrics(font)
2185                 max = 0
2186                 for row in xrange(n):
2187                         val = self.data_model.child_items[row].data[column]
2188                         len = metrics.width(str(val) + "MM")
2189                         max = len if len > max else max
2190                 val = self.data_model.columnHeader(column)
2191                 len = metrics.width(str(val) + "MM")
2192                 max = len if len > max else max
2193                 self.view.setColumnWidth(column, max)
2194
2195         def ResizeColumnsToContents(self):
2196                 n = min(self.data_model.child_count, 100)
2197                 if n < 1:
2198                         # No data yet, so connect a signal to notify when there is
2199                         self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2200                         return
2201                 columns = self.data_model.columnCount()
2202                 for i in xrange(columns):
2203                         self.ResizeColumnToContents(i, n)
2204
2205         def UpdateColumnWidths(self, *x):
2206                 # This only needs to be done once, so disconnect the signal now
2207                 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2208                 self.ResizeColumnsToContents()
2209
2210 # Table window
2211
2212 class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2213
2214         def __init__(self, glb, table_name, parent=None):
2215                 super(TableWindow, self).__init__(parent)
2216
2217                 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2218
2219                 self.model = QSortFilterProxyModel()
2220                 self.model.setSourceModel(self.data_model)
2221
2222                 self.view = QTableView()
2223                 self.view.setModel(self.model)
2224                 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2225                 self.view.verticalHeader().setVisible(False)
2226                 self.view.sortByColumn(-1, Qt.AscendingOrder)
2227                 self.view.setSortingEnabled(True)
2228
2229                 self.ResizeColumnsToContents()
2230
2231                 self.find_bar = FindBar(self, self, True)
2232
2233                 self.finder = ChildDataItemFinder(self.data_model)
2234
2235                 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2236
2237                 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2238
2239                 self.setWidget(self.vbox.Widget())
2240
2241                 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2242
2243         def Find(self, value, direction, pattern, context):
2244                 self.view.setFocus()
2245                 self.find_bar.Busy()
2246                 self.finder.Find(value, direction, pattern, context, self.FindDone)
2247
2248         def FindDone(self, row):
2249                 self.find_bar.Idle()
2250                 if row >= 0:
2251                         self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
2252                 else:
2253                         self.find_bar.NotFound()
2254
2255 # Table list
2256
2257 def GetTableList(glb):
2258         tables = []
2259         query = QSqlQuery(glb.db)
2260         if glb.dbref.is_sqlite3:
2261                 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2262         else:
2263                 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2264         while query.next():
2265                 tables.append(query.value(0))
2266         if glb.dbref.is_sqlite3:
2267                 tables.append("sqlite_master")
2268         else:
2269                 tables.append("information_schema.tables")
2270                 tables.append("information_schema.views")
2271                 tables.append("information_schema.columns")
2272         return tables
2273
2274 # Top Calls data model
2275
2276 class TopCallsModel(SQLTableModel):
2277
2278         def __init__(self, glb, report_vars, parent=None):
2279                 text = ""
2280                 if not glb.dbref.is_sqlite3:
2281                         text = "::text"
2282                 limit = ""
2283                 if len(report_vars.limit):
2284                         limit = " LIMIT " + report_vars.limit
2285                 sql = ("SELECT comm, pid, tid, name,"
2286                         " CASE"
2287                         " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2288                         " ELSE short_name"
2289                         " END AS dso,"
2290                         " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2291                         " CASE"
2292                         " WHEN (calls.flags = 1) THEN 'no call'" + text +
2293                         " WHEN (calls.flags = 2) THEN 'no return'" + text +
2294                         " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2295                         " ELSE ''" + text +
2296                         " END AS flags"
2297                         " FROM calls"
2298                         " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2299                         " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2300                         " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2301                         " INNER JOIN comms ON calls.comm_id = comms.id"
2302                         " INNER JOIN threads ON calls.thread_id = threads.id" +
2303                         report_vars.where_clause +
2304                         " ORDER BY elapsed_time DESC" +
2305                         limit
2306                         )
2307                 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2308                 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2309                 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2310
2311         def columnAlignment(self, column):
2312                 return self.alignment[column]
2313
2314 # Top Calls report creation dialog
2315
2316 class TopCallsDialog(ReportDialogBase):
2317
2318         def __init__(self, glb, parent=None):
2319                 title = "Top Calls by Elapsed Time"
2320                 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2321                          lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2322                          lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2323                          lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2324                          lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2325                          lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2326                          lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2327                          lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2328                 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2329
2330 # Top Calls window
2331
2332 class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2333
2334         def __init__(self, glb, report_vars, parent=None):
2335                 super(TopCallsWindow, self).__init__(parent)
2336
2337                 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2338                 self.model = self.data_model
2339
2340                 self.view = QTableView()
2341                 self.view.setModel(self.model)
2342                 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2343                 self.view.verticalHeader().setVisible(False)
2344
2345                 self.ResizeColumnsToContents()
2346
2347                 self.find_bar = FindBar(self, self, True)
2348
2349                 self.finder = ChildDataItemFinder(self.model)
2350
2351                 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2352
2353                 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2354
2355                 self.setWidget(self.vbox.Widget())
2356
2357                 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2358
2359         def Find(self, value, direction, pattern, context):
2360                 self.view.setFocus()
2361                 self.find_bar.Busy()
2362                 self.finder.Find(value, direction, pattern, context, self.FindDone)
2363
2364         def FindDone(self, row):
2365                 self.find_bar.Idle()
2366                 if row >= 0:
2367                         self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2368                 else:
2369                         self.find_bar.NotFound()
2370
2371 # Action Definition
2372
2373 def CreateAction(label, tip, callback, parent=None, shortcut=None):
2374         action = QAction(label, parent)
2375         if shortcut != None:
2376                 action.setShortcuts(shortcut)
2377         action.setStatusTip(tip)
2378         action.triggered.connect(callback)
2379         return action
2380
2381 # Typical application actions
2382
2383 def CreateExitAction(app, parent=None):
2384         return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2385
2386 # Typical MDI actions
2387
2388 def CreateCloseActiveWindowAction(mdi_area):
2389         return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2390
2391 def CreateCloseAllWindowsAction(mdi_area):
2392         return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2393
2394 def CreateTileWindowsAction(mdi_area):
2395         return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2396
2397 def CreateCascadeWindowsAction(mdi_area):
2398         return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2399
2400 def CreateNextWindowAction(mdi_area):
2401         return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2402
2403 def CreatePreviousWindowAction(mdi_area):
2404         return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2405
2406 # Typical MDI window menu
2407
2408 class WindowMenu():
2409
2410         def __init__(self, mdi_area, menu):
2411                 self.mdi_area = mdi_area
2412                 self.window_menu = menu.addMenu("&Windows")
2413                 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2414                 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2415                 self.tile_windows = CreateTileWindowsAction(mdi_area)
2416                 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2417                 self.next_window = CreateNextWindowAction(mdi_area)
2418                 self.previous_window = CreatePreviousWindowAction(mdi_area)
2419                 self.window_menu.aboutToShow.connect(self.Update)
2420
2421         def Update(self):
2422                 self.window_menu.clear()
2423                 sub_window_count = len(self.mdi_area.subWindowList())
2424                 have_sub_windows = sub_window_count != 0
2425                 self.close_active_window.setEnabled(have_sub_windows)
2426                 self.close_all_windows.setEnabled(have_sub_windows)
2427                 self.tile_windows.setEnabled(have_sub_windows)
2428                 self.cascade_windows.setEnabled(have_sub_windows)
2429                 self.next_window.setEnabled(have_sub_windows)
2430                 self.previous_window.setEnabled(have_sub_windows)
2431                 self.window_menu.addAction(self.close_active_window)
2432                 self.window_menu.addAction(self.close_all_windows)
2433                 self.window_menu.addSeparator()
2434                 self.window_menu.addAction(self.tile_windows)
2435                 self.window_menu.addAction(self.cascade_windows)
2436                 self.window_menu.addSeparator()
2437                 self.window_menu.addAction(self.next_window)
2438                 self.window_menu.addAction(self.previous_window)
2439                 if sub_window_count == 0:
2440                         return
2441                 self.window_menu.addSeparator()
2442                 nr = 1
2443                 for sub_window in self.mdi_area.subWindowList():
2444                         label = str(nr) + " " + sub_window.name
2445                         if nr < 10:
2446                                 label = "&" + label
2447                         action = self.window_menu.addAction(label)
2448                         action.setCheckable(True)
2449                         action.setChecked(sub_window == self.mdi_area.activeSubWindow())
2450                         action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x))
2451                         self.window_menu.addAction(action)
2452                         nr += 1
2453
2454         def setActiveSubWindow(self, nr):
2455                 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2456
2457 # Help text
2458
2459 glb_help_text = """
2460 <h1>Contents</h1>
2461 <style>
2462 p.c1 {
2463     text-indent: 40px;
2464 }
2465 p.c2 {
2466     text-indent: 80px;
2467 }
2468 }
2469 </style>
2470 <p class=c1><a href=#reports>1. Reports</a></p>
2471 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
2472 <p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2473 <p class=c2><a href=#allbranches>1.3 All branches</a></p>
2474 <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2475 <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
2476 <p class=c1><a href=#tables>2. Tables</a></p>
2477 <h1 id=reports>1. Reports</h1>
2478 <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2479 The result is a GUI window with a tree representing a context-sensitive
2480 call-graph. Expanding a couple of levels of the tree and adjusting column
2481 widths to suit will display something like:
2482 <pre>
2483                                          Call Graph: pt_example
2484 Call Path                          Object      Count   Time(ns)  Time(%)  Branch Count   Branch Count(%)
2485 v- ls
2486     v- 2638:2638
2487         v- _start                  ld-2.19.so    1     10074071   100.0         211135            100.0
2488           |- unknown               unknown       1        13198     0.1              1              0.0
2489           >- _dl_start             ld-2.19.so    1      1400980    13.9          19637              9.3
2490           >- _d_linit_internal     ld-2.19.so    1       448152     4.4          11094              5.3
2491           v-__libc_start_main@plt  ls            1      8211741    81.5         180397             85.4
2492              >- _dl_fixup          ld-2.19.so    1         7607     0.1            108              0.1
2493              >- __cxa_atexit       libc-2.19.so  1        11737     0.1             10              0.0
2494              >- __libc_csu_init    ls            1        10354     0.1             10              0.0
2495              |- _setjmp            libc-2.19.so  1            0     0.0              4              0.0
2496              v- main               ls            1      8182043    99.6         180254             99.9
2497 </pre>
2498 <h3>Points to note:</h3>
2499 <ul>
2500 <li>The top level is a command name (comm)</li>
2501 <li>The next level is a thread (pid:tid)</li>
2502 <li>Subsequent levels are functions</li>
2503 <li>'Count' is the number of calls</li>
2504 <li>'Time' is the elapsed time until the function returns</li>
2505 <li>Percentages are relative to the level above</li>
2506 <li>'Branch Count' is the total number of branches for that function and all functions that it calls
2507 </ul>
2508 <h3>Find</h3>
2509 Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2510 The pattern matching symbols are ? for any character and * for zero or more characters.
2511 <h2 id=calltree>1.2 Call Tree</h2>
2512 The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2513 Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2514 <h2 id=allbranches>1.3 All branches</h2>
2515 The All branches report displays all branches in chronological order.
2516 Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2517 <h3>Disassembly</h3>
2518 Open a branch to display disassembly. This only works if:
2519 <ol>
2520 <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2521 <li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2522 The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2523 One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2524 or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2525 </ol>
2526 <h4 id=xed>Intel XED Setup</h4>
2527 To use Intel XED, libxed.so must be present.  To build and install libxed.so:
2528 <pre>
2529 git clone https://github.com/intelxed/mbuild.git mbuild
2530 git clone https://github.com/intelxed/xed
2531 cd xed
2532 ./mfile.py --share
2533 sudo ./mfile.py --prefix=/usr/local install
2534 sudo ldconfig
2535 </pre>
2536 <h3>Find</h3>
2537 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2538 Refer to Python documentation for the regular expression syntax.
2539 All columns are searched, but only currently fetched rows are searched.
2540 <h2 id=selectedbranches>1.4 Selected branches</h2>
2541 This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2542 by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2543 <h3>1.4.1 Time ranges</h3>
2544 The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2545 ms, us or ns. Also, negative values are relative to the end of trace.  Examples:
2546 <pre>
2547         81073085947329-81073085958238   From 81073085947329 to 81073085958238
2548         100us-200us             From 100us to 200us
2549         10ms-                   From 10ms to the end
2550         -100ns                  The first 100ns
2551         -10ms-                  The last 10ms
2552 </pre>
2553 N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
2554 <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
2555 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.
2556 The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2557 If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
2558 <h1 id=tables>2. Tables</h1>
2559 The Tables menu shows all tables and views in the database. Most tables have an associated view
2560 which displays the information in a more friendly way. Not all data for large tables is fetched
2561 immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2562 but that can be slow for large tables.
2563 <p>There are also tables of database meta-information.
2564 For SQLite3 databases, the sqlite_master table is included.
2565 For PostgreSQL databases, information_schema.tables/views/columns are included.
2566 <h3>Find</h3>
2567 Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2568 Refer to Python documentation for the regular expression syntax.
2569 All columns are searched, but only currently fetched rows are searched.
2570 <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2571 will go to the next/previous result in id order, instead of display order.
2572 """
2573
2574 # Help window
2575
2576 class HelpWindow(QMdiSubWindow):
2577
2578         def __init__(self, glb, parent=None):
2579                 super(HelpWindow, self).__init__(parent)
2580
2581                 self.text = QTextBrowser()
2582                 self.text.setHtml(glb_help_text)
2583                 self.text.setReadOnly(True)
2584                 self.text.setOpenExternalLinks(True)
2585
2586                 self.setWidget(self.text)
2587
2588                 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2589
2590 # Main window that only displays the help text
2591
2592 class HelpOnlyWindow(QMainWindow):
2593
2594         def __init__(self, parent=None):
2595                 super(HelpOnlyWindow, self).__init__(parent)
2596
2597                 self.setMinimumSize(200, 100)
2598                 self.resize(800, 600)
2599                 self.setWindowTitle("Exported SQL Viewer Help")
2600                 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2601
2602                 self.text = QTextBrowser()
2603                 self.text.setHtml(glb_help_text)
2604                 self.text.setReadOnly(True)
2605                 self.text.setOpenExternalLinks(True)
2606
2607                 self.setCentralWidget(self.text)
2608
2609 # Font resize
2610
2611 def ResizeFont(widget, diff):
2612         font = widget.font()
2613         sz = font.pointSize()
2614         font.setPointSize(sz + diff)
2615         widget.setFont(font)
2616
2617 def ShrinkFont(widget):
2618         ResizeFont(widget, -1)
2619
2620 def EnlargeFont(widget):
2621         ResizeFont(widget, 1)
2622
2623 # Unique name for sub-windows
2624
2625 def NumberedWindowName(name, nr):
2626         if nr > 1:
2627                 name += " <" + str(nr) + ">"
2628         return name
2629
2630 def UniqueSubWindowName(mdi_area, name):
2631         nr = 1
2632         while True:
2633                 unique_name = NumberedWindowName(name, nr)
2634                 ok = True
2635                 for sub_window in mdi_area.subWindowList():
2636                         if sub_window.name == unique_name:
2637                                 ok = False
2638                                 break
2639                 if ok:
2640                         return unique_name
2641                 nr += 1
2642
2643 # Add a sub-window
2644
2645 def AddSubWindow(mdi_area, sub_window, name):
2646         unique_name = UniqueSubWindowName(mdi_area, name)
2647         sub_window.setMinimumSize(200, 100)
2648         sub_window.resize(800, 600)
2649         sub_window.setWindowTitle(unique_name)
2650         sub_window.setAttribute(Qt.WA_DeleteOnClose)
2651         sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
2652         sub_window.name = unique_name
2653         mdi_area.addSubWindow(sub_window)
2654         sub_window.show()
2655
2656 # Main window
2657
2658 class MainWindow(QMainWindow):
2659
2660         def __init__(self, glb, parent=None):
2661                 super(MainWindow, self).__init__(parent)
2662
2663                 self.glb = glb
2664
2665                 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
2666                 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
2667                 self.setMinimumSize(200, 100)
2668
2669                 self.mdi_area = QMdiArea()
2670                 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2671                 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
2672
2673                 self.setCentralWidget(self.mdi_area)
2674
2675                 menu = self.menuBar()
2676
2677                 file_menu = menu.addMenu("&File")
2678                 file_menu.addAction(CreateExitAction(glb.app, self))
2679
2680                 edit_menu = menu.addMenu("&Edit")
2681                 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
2682                 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
2683                 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
2684                 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
2685
2686                 reports_menu = menu.addMenu("&Reports")
2687                 if IsSelectable(glb.db, "calls"):
2688                         reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
2689
2690                 if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
2691                         reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
2692
2693                 self.EventMenu(GetEventList(glb.db), reports_menu)
2694
2695                 if IsSelectable(glb.db, "calls"):
2696                         reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
2697
2698                 self.TableMenu(GetTableList(glb), menu)
2699
2700                 self.window_menu = WindowMenu(self.mdi_area, menu)
2701
2702                 help_menu = menu.addMenu("&Help")
2703                 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
2704
2705         def Find(self):
2706                 win = self.mdi_area.activeSubWindow()
2707                 if win:
2708                         try:
2709                                 win.find_bar.Activate()
2710                         except:
2711                                 pass
2712
2713         def FetchMoreRecords(self):
2714                 win = self.mdi_area.activeSubWindow()
2715                 if win:
2716                         try:
2717                                 win.fetch_bar.Activate()
2718                         except:
2719                                 pass
2720
2721         def ShrinkFont(self):
2722                 win = self.mdi_area.activeSubWindow()
2723                 ShrinkFont(win.view)
2724
2725         def EnlargeFont(self):
2726                 win = self.mdi_area.activeSubWindow()
2727                 EnlargeFont(win.view)
2728
2729         def EventMenu(self, events, reports_menu):
2730                 branches_events = 0
2731                 for event in events:
2732                         event = event.split(":")[0]
2733                         if event == "branches":
2734                                 branches_events += 1
2735                 dbid = 0
2736                 for event in events:
2737                         dbid += 1
2738                         event = event.split(":")[0]
2739                         if event == "branches":
2740                                 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
2741                                 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self))
2742                                 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
2743                                 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self))
2744
2745         def TableMenu(self, tables, menu):
2746                 table_menu = menu.addMenu("&Tables")
2747                 for table in tables:
2748                         table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
2749
2750         def NewCallGraph(self):
2751                 CallGraphWindow(self.glb, self)
2752
2753         def NewCallTree(self):
2754                 CallTreeWindow(self.glb, self)
2755
2756         def NewTopCalls(self):
2757                 dialog = TopCallsDialog(self.glb, self)
2758                 ret = dialog.exec_()
2759                 if ret:
2760                         TopCallsWindow(self.glb, dialog.report_vars, self)
2761
2762         def NewBranchView(self, event_id):
2763                 BranchWindow(self.glb, event_id, ReportVars(), self)
2764
2765         def NewSelectedBranchView(self, event_id):
2766                 dialog = SelectedBranchDialog(self.glb, self)
2767                 ret = dialog.exec_()
2768                 if ret:
2769                         BranchWindow(self.glb, event_id, dialog.report_vars, self)
2770
2771         def NewTableView(self, table_name):
2772                 TableWindow(self.glb, table_name, self)
2773
2774         def Help(self):
2775                 HelpWindow(self.glb, self)
2776
2777 # XED Disassembler
2778
2779 class xed_state_t(Structure):
2780
2781         _fields_ = [
2782                 ("mode", c_int),
2783                 ("width", c_int)
2784         ]
2785
2786 class XEDInstruction():
2787
2788         def __init__(self, libxed):
2789                 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
2790                 xedd_t = c_byte * 512
2791                 self.xedd = xedd_t()
2792                 self.xedp = addressof(self.xedd)
2793                 libxed.xed_decoded_inst_zero(self.xedp)
2794                 self.state = xed_state_t()
2795                 self.statep = addressof(self.state)
2796                 # Buffer for disassembled instruction text
2797                 self.buffer = create_string_buffer(256)
2798                 self.bufferp = addressof(self.buffer)
2799
2800 class LibXED():
2801
2802         def __init__(self):
2803                 try:
2804                         self.libxed = CDLL("libxed.so")
2805                 except:
2806                         self.libxed = None
2807                 if not self.libxed:
2808                         self.libxed = CDLL("/usr/local/lib/libxed.so")
2809
2810                 self.xed_tables_init = self.libxed.xed_tables_init
2811                 self.xed_tables_init.restype = None
2812                 self.xed_tables_init.argtypes = []
2813
2814                 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
2815                 self.xed_decoded_inst_zero.restype = None
2816                 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
2817
2818                 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
2819                 self.xed_operand_values_set_mode.restype = None
2820                 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
2821
2822                 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
2823                 self.xed_decoded_inst_zero_keep_mode.restype = None
2824                 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
2825
2826                 self.xed_decode = self.libxed.xed_decode
2827                 self.xed_decode.restype = c_int
2828                 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
2829
2830                 self.xed_format_context = self.libxed.xed_format_context
2831                 self.xed_format_context.restype = c_uint
2832                 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
2833
2834                 self.xed_tables_init()
2835
2836         def Instruction(self):
2837                 return XEDInstruction(self)
2838
2839         def SetMode(self, inst, mode):
2840                 if mode:
2841                         inst.state.mode = 4 # 32-bit
2842                         inst.state.width = 4 # 4 bytes
2843                 else:
2844                         inst.state.mode = 1 # 64-bit
2845                         inst.state.width = 8 # 8 bytes
2846                 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
2847
2848         def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
2849                 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
2850                 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
2851                 if err:
2852                         return 0, ""
2853                 # Use AT&T mode (2), alternative is Intel (3)
2854                 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
2855                 if not ok:
2856                         return 0, ""
2857                 # Return instruction length and the disassembled instruction text
2858                 # For now, assume the length is in byte 166
2859                 return inst.xedd[166], inst.buffer.value
2860
2861 def TryOpen(file_name):
2862         try:
2863                 return open(file_name, "rb")
2864         except:
2865                 return None
2866
2867 def Is64Bit(f):
2868         result = sizeof(c_void_p)
2869         # ELF support only
2870         pos = f.tell()
2871         f.seek(0)
2872         header = f.read(7)
2873         f.seek(pos)
2874         magic = header[0:4]
2875         eclass = ord(header[4])
2876         encoding = ord(header[5])
2877         version = ord(header[6])
2878         if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
2879                 result = True if eclass == 2 else False
2880         return result
2881
2882 # Global data
2883
2884 class Glb():
2885
2886         def __init__(self, dbref, db, dbname):
2887                 self.dbref = dbref
2888                 self.db = db
2889                 self.dbname = dbname
2890                 self.home_dir = os.path.expanduser("~")
2891                 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
2892                 if self.buildid_dir:
2893                         self.buildid_dir += "/.build-id/"
2894                 else:
2895                         self.buildid_dir = self.home_dir + "/.debug/.build-id/"
2896                 self.app = None
2897                 self.mainwindow = None
2898                 self.instances_to_shutdown_on_exit = weakref.WeakSet()
2899                 try:
2900                         self.disassembler = LibXED()
2901                         self.have_disassembler = True
2902                 except:
2903                         self.have_disassembler = False
2904
2905         def FileFromBuildId(self, build_id):
2906                 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
2907                 return TryOpen(file_name)
2908
2909         def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
2910                 # Assume current machine i.e. no support for virtualization
2911                 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
2912                         file_name = os.getenv("PERF_KCORE")
2913                         f = TryOpen(file_name) if file_name else None
2914                         if f:
2915                                 return f
2916                         # For now, no special handling if long_name is /proc/kcore
2917                         f = TryOpen(long_name)
2918                         if f:
2919                                 return f
2920                 f = self.FileFromBuildId(build_id)
2921                 if f:
2922                         return f
2923                 return None
2924
2925         def AddInstanceToShutdownOnExit(self, instance):
2926                 self.instances_to_shutdown_on_exit.add(instance)
2927
2928         # Shutdown any background processes or threads
2929         def ShutdownInstances(self):
2930                 for x in self.instances_to_shutdown_on_exit:
2931                         try:
2932                                 x.Shutdown()
2933                         except:
2934                                 pass
2935
2936 # Database reference
2937
2938 class DBRef():
2939
2940         def __init__(self, is_sqlite3, dbname):
2941                 self.is_sqlite3 = is_sqlite3
2942                 self.dbname = dbname
2943
2944         def Open(self, connection_name):
2945                 dbname = self.dbname
2946                 if self.is_sqlite3:
2947                         db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
2948                 else:
2949                         db = QSqlDatabase.addDatabase("QPSQL", connection_name)
2950                         opts = dbname.split()
2951                         for opt in opts:
2952                                 if "=" in opt:
2953                                         opt = opt.split("=")
2954                                         if opt[0] == "hostname":
2955                                                 db.setHostName(opt[1])
2956                                         elif opt[0] == "port":
2957                                                 db.setPort(int(opt[1]))
2958                                         elif opt[0] == "username":
2959                                                 db.setUserName(opt[1])
2960                                         elif opt[0] == "password":
2961                                                 db.setPassword(opt[1])
2962                                         elif opt[0] == "dbname":
2963                                                 dbname = opt[1]
2964                                 else:
2965                                         dbname = opt
2966
2967                 db.setDatabaseName(dbname)
2968                 if not db.open():
2969                         raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
2970                 return db, dbname
2971
2972 # Main
2973
2974 def Main():
2975         if (len(sys.argv) < 2):
2976                 print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}"
2977                 raise Exception("Too few arguments")
2978
2979         dbname = sys.argv[1]
2980         if dbname == "--help-only":
2981                 app = QApplication(sys.argv)
2982                 mainwindow = HelpOnlyWindow()
2983                 mainwindow.show()
2984                 err = app.exec_()
2985                 sys.exit(err)
2986
2987         is_sqlite3 = False
2988         try:
2989                 f = open(dbname)
2990                 if f.read(15) == "SQLite format 3":
2991                         is_sqlite3 = True
2992                 f.close()
2993         except:
2994                 pass
2995
2996         dbref = DBRef(is_sqlite3, dbname)
2997         db, dbname = dbref.Open("main")
2998         glb = Glb(dbref, db, dbname)
2999         app = QApplication(sys.argv)
3000         glb.app = app
3001         mainwindow = MainWindow(glb)
3002         glb.mainwindow = mainwindow
3003         mainwindow.show()
3004         err = app.exec_()
3005         glb.ShutdownInstances()
3006         db.close()
3007         sys.exit(err)
3008
3009 if __name__ == "__main__":
3010         Main()
This page took 0.236746 seconds and 4 git commands to generate.