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