blob: a607235c8cd9cd2f1c061afec0bebcca06b7abca [file] [log] [blame]
Adrian Hunterc6aba1b2019-04-12 14:38:23 +03001#!/usr/bin/env python
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
Tony Jonesbeda0e72019-03-08 16:05:15 -080091from __future__ import print_function
92
Adrian Hunter4b715d22015-07-17 19:33:45 +030093import sys
Adrian Hunter1ed7f472019-04-12 14:38:24 +030094import argparse
Adrian Hunter1beb5c72018-10-01 09:28:47 +030095import weakref
96import threading
Adrian Hunterebd70c72018-10-01 09:28:48 +030097import string
Tony Jonesbeda0e72019-03-08 16:05:15 -080098try:
99 # Python2
100 import cPickle as pickle
101 # size of pickled integer big enough for record size
102 glb_nsz = 8
103except ImportError:
104 import pickle
105 glb_nsz = 16
Adrian Hunter8392b742018-10-01 09:28:50 +0300106import re
107import os
Adrian Hunterdf8ea222019-04-12 14:38:25 +0300108
Adrian Hunter8453c932019-03-27 09:28:25 +0200109pyside_version_1 = True
Adrian Hunterdf8ea222019-04-12 14:38:25 +0300110if not "--pyside-version-1" in sys.argv:
111 try:
112 from PySide2.QtCore import *
113 from PySide2.QtGui import *
114 from PySide2.QtSql import *
115 from PySide2.QtWidgets import *
116 pyside_version_1 = False
117 except:
118 pass
119
120if pyside_version_1:
121 from PySide.QtCore import *
122 from PySide.QtGui import *
123 from PySide.QtSql import *
124
Adrian Hunter4b715d22015-07-17 19:33:45 +0300125from decimal import *
Adrian Hunter8392b742018-10-01 09:28:50 +0300126from ctypes import *
127from multiprocessing import Process, Array, Value, Event
Adrian Hunter4b715d22015-07-17 19:33:45 +0300128
Tony Jonesbeda0e72019-03-08 16:05:15 -0800129# xrange is range in Python3
130try:
131 xrange
132except NameError:
133 xrange = range
134
135def printerr(*args, **keyword_args):
136 print(*args, file=sys.stderr, **keyword_args)
137
Adrian Hunter4be9ace2018-10-01 09:28:44 +0300138# Data formatting helpers
139
Adrian Hunter76099f92018-10-23 10:59:49 +0300140def tohex(ip):
141 if ip < 0:
142 ip += 1 << 64
143 return "%x" % ip
144
145def offstr(offset):
146 if offset:
147 return "+0x%x" % offset
148 return ""
149
Adrian Hunter4be9ace2018-10-01 09:28:44 +0300150def dsoname(name):
151 if name == "[kernel.kallsyms]":
152 return "[kernel]"
153 return name
154
Adrian Hunter210cf1f2018-11-04 17:12:36 +0200155def findnth(s, sub, n, offs=0):
156 pos = s.find(sub)
157 if pos < 0:
158 return pos
159 if n <= 1:
160 return offs + pos
161 return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1)
162
Adrian Hunter4be9ace2018-10-01 09:28:44 +0300163# Percent to one decimal place
164
165def PercentToOneDP(n, d):
166 if not d:
167 return "0.0"
168 x = (n * Decimal(100)) / d
169 return str(x.quantize(Decimal(".1"), rounding=ROUND_HALF_UP))
170
171# Helper for queries that must not fail
172
173def QueryExec(query, stmt):
174 ret = query.exec_(stmt)
175 if not ret:
176 raise Exception("Query failed: " + query.lastError().text())
177
Adrian Hunterebd70c72018-10-01 09:28:48 +0300178# Background thread
179
180class Thread(QThread):
181
182 done = Signal(object)
183
184 def __init__(self, task, param=None, parent=None):
185 super(Thread, self).__init__(parent)
186 self.task = task
187 self.param = param
188
189 def run(self):
190 while True:
191 if self.param is None:
192 done, result = self.task()
193 else:
194 done, result = self.task(self.param)
195 self.done.emit(result)
196 if done:
197 break
198
Adrian Hunter70d831e2018-10-01 09:28:43 +0300199# Tree data model
200
Adrian Hunter4b715d22015-07-17 19:33:45 +0300201class TreeModel(QAbstractItemModel):
202
Adrian Huntera448ba22019-02-28 15:00:29 +0200203 def __init__(self, glb, parent=None):
Adrian Hunter4b715d22015-07-17 19:33:45 +0300204 super(TreeModel, self).__init__(parent)
Adrian Huntera448ba22019-02-28 15:00:29 +0200205 self.glb = glb
206 self.root = self.GetRoot()
Adrian Hunter70d831e2018-10-01 09:28:43 +0300207 self.last_row_read = 0
Adrian Hunter4b715d22015-07-17 19:33:45 +0300208
Adrian Hunter70d831e2018-10-01 09:28:43 +0300209 def Item(self, parent):
210 if parent.isValid():
211 return parent.internalPointer()
212 else:
213 return self.root
Adrian Hunter4b715d22015-07-17 19:33:45 +0300214
215 def rowCount(self, parent):
Adrian Hunter70d831e2018-10-01 09:28:43 +0300216 result = self.Item(parent).childCount()
217 if result < 0:
218 result = 0
219 self.dataChanged.emit(parent, parent)
220 return result
221
222 def hasChildren(self, parent):
223 return self.Item(parent).hasChildren()
Adrian Hunter4b715d22015-07-17 19:33:45 +0300224
225 def headerData(self, section, orientation, role):
226 if role == Qt.TextAlignmentRole:
Adrian Hunter70d831e2018-10-01 09:28:43 +0300227 return self.columnAlignment(section)
Adrian Hunter4b715d22015-07-17 19:33:45 +0300228 if role != Qt.DisplayRole:
229 return None
230 if orientation != Qt.Horizontal:
231 return None
Adrian Hunter70d831e2018-10-01 09:28:43 +0300232 return self.columnHeader(section)
Adrian Hunter4b715d22015-07-17 19:33:45 +0300233
234 def parent(self, child):
235 child_item = child.internalPointer()
236 if child_item is self.root:
237 return QModelIndex()
238 parent_item = child_item.getParentItem()
239 return self.createIndex(parent_item.getRow(), 0, parent_item)
240
241 def index(self, row, column, parent):
Adrian Hunter70d831e2018-10-01 09:28:43 +0300242 child_item = self.Item(parent).getChildItem(row)
Adrian Hunter4b715d22015-07-17 19:33:45 +0300243 return self.createIndex(row, column, child_item)
244
Adrian Hunter70d831e2018-10-01 09:28:43 +0300245 def DisplayData(self, item, index):
246 return item.getData(index.column())
247
Adrian Hunter8392b742018-10-01 09:28:50 +0300248 def FetchIfNeeded(self, row):
249 if row > self.last_row_read:
250 self.last_row_read = row
251 if row + 10 >= self.root.child_count:
252 self.fetcher.Fetch(glb_chunk_sz)
253
254 def columnAlignment(self, column):
255 return Qt.AlignLeft
256
257 def columnFont(self, column):
258 return None
259
260 def data(self, index, role):
261 if role == Qt.TextAlignmentRole:
262 return self.columnAlignment(index.column())
263 if role == Qt.FontRole:
264 return self.columnFont(index.column())
265 if role != Qt.DisplayRole:
266 return None
267 item = index.internalPointer()
268 return self.DisplayData(item, index)
269
270# Table data model
271
272class TableModel(QAbstractTableModel):
273
274 def __init__(self, parent=None):
275 super(TableModel, self).__init__(parent)
276 self.child_count = 0
277 self.child_items = []
278 self.last_row_read = 0
279
280 def Item(self, parent):
281 if parent.isValid():
282 return parent.internalPointer()
283 else:
284 return self
285
286 def rowCount(self, parent):
287 return self.child_count
288
289 def headerData(self, section, orientation, role):
290 if role == Qt.TextAlignmentRole:
291 return self.columnAlignment(section)
292 if role != Qt.DisplayRole:
293 return None
294 if orientation != Qt.Horizontal:
295 return None
296 return self.columnHeader(section)
297
298 def index(self, row, column, parent):
299 return self.createIndex(row, column, self.child_items[row])
300
301 def DisplayData(self, item, index):
302 return item.getData(index.column())
303
304 def FetchIfNeeded(self, row):
305 if row > self.last_row_read:
306 self.last_row_read = row
307 if row + 10 >= self.child_count:
308 self.fetcher.Fetch(glb_chunk_sz)
309
Adrian Hunter70d831e2018-10-01 09:28:43 +0300310 def columnAlignment(self, column):
311 return Qt.AlignLeft
312
313 def columnFont(self, column):
314 return None
315
Adrian Hunter4b715d22015-07-17 19:33:45 +0300316 def data(self, index, role):
317 if role == Qt.TextAlignmentRole:
Adrian Hunter70d831e2018-10-01 09:28:43 +0300318 return self.columnAlignment(index.column())
319 if role == Qt.FontRole:
320 return self.columnFont(index.column())
Adrian Hunter4b715d22015-07-17 19:33:45 +0300321 if role != Qt.DisplayRole:
322 return None
Adrian Hunter70d831e2018-10-01 09:28:43 +0300323 item = index.internalPointer()
324 return self.DisplayData(item, index)
325
Adrian Hunter1beb5c72018-10-01 09:28:47 +0300326# Model cache
327
328model_cache = weakref.WeakValueDictionary()
329model_cache_lock = threading.Lock()
330
331def LookupCreateModel(model_name, create_fn):
332 model_cache_lock.acquire()
333 try:
334 model = model_cache[model_name]
335 except:
336 model = None
337 if model is None:
338 model = create_fn()
339 model_cache[model_name] = model
340 model_cache_lock.release()
341 return model
342
Adrian Hunterebd70c72018-10-01 09:28:48 +0300343# Find bar
344
345class FindBar():
346
347 def __init__(self, parent, finder, is_reg_expr=False):
348 self.finder = finder
349 self.context = []
350 self.last_value = None
351 self.last_pattern = None
352
353 label = QLabel("Find:")
354 label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
355
356 self.textbox = QComboBox()
357 self.textbox.setEditable(True)
358 self.textbox.currentIndexChanged.connect(self.ValueChanged)
359
360 self.progress = QProgressBar()
361 self.progress.setRange(0, 0)
362 self.progress.hide()
363
364 if is_reg_expr:
365 self.pattern = QCheckBox("Regular Expression")
366 else:
367 self.pattern = QCheckBox("Pattern")
368 self.pattern.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
369
370 self.next_button = QToolButton()
371 self.next_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowDown))
372 self.next_button.released.connect(lambda: self.NextPrev(1))
373
374 self.prev_button = QToolButton()
375 self.prev_button.setIcon(parent.style().standardIcon(QStyle.SP_ArrowUp))
376 self.prev_button.released.connect(lambda: self.NextPrev(-1))
377
378 self.close_button = QToolButton()
379 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
380 self.close_button.released.connect(self.Deactivate)
381
382 self.hbox = QHBoxLayout()
383 self.hbox.setContentsMargins(0, 0, 0, 0)
384
385 self.hbox.addWidget(label)
386 self.hbox.addWidget(self.textbox)
387 self.hbox.addWidget(self.progress)
388 self.hbox.addWidget(self.pattern)
389 self.hbox.addWidget(self.next_button)
390 self.hbox.addWidget(self.prev_button)
391 self.hbox.addWidget(self.close_button)
392
393 self.bar = QWidget()
394 self.bar.setLayout(self.hbox);
395 self.bar.hide()
396
397 def Widget(self):
398 return self.bar
399
400 def Activate(self):
401 self.bar.show()
402 self.textbox.setFocus()
403
404 def Deactivate(self):
405 self.bar.hide()
406
407 def Busy(self):
408 self.textbox.setEnabled(False)
409 self.pattern.hide()
410 self.next_button.hide()
411 self.prev_button.hide()
412 self.progress.show()
413
414 def Idle(self):
415 self.textbox.setEnabled(True)
416 self.progress.hide()
417 self.pattern.show()
418 self.next_button.show()
419 self.prev_button.show()
420
421 def Find(self, direction):
422 value = self.textbox.currentText()
423 pattern = self.pattern.isChecked()
424 self.last_value = value
425 self.last_pattern = pattern
426 self.finder.Find(value, direction, pattern, self.context)
427
428 def ValueChanged(self):
429 value = self.textbox.currentText()
430 pattern = self.pattern.isChecked()
431 index = self.textbox.currentIndex()
432 data = self.textbox.itemData(index)
433 # Store the pattern in the combo box to keep it with the text value
434 if data == None:
435 self.textbox.setItemData(index, pattern)
436 else:
437 self.pattern.setChecked(data)
438 self.Find(0)
439
440 def NextPrev(self, direction):
441 value = self.textbox.currentText()
442 pattern = self.pattern.isChecked()
443 if value != self.last_value:
444 index = self.textbox.findText(value)
445 # Allow for a button press before the value has been added to the combo box
446 if index < 0:
447 index = self.textbox.count()
448 self.textbox.addItem(value, pattern)
449 self.textbox.setCurrentIndex(index)
450 return
451 else:
452 self.textbox.setItemData(index, pattern)
453 elif pattern != self.last_pattern:
454 # Keep the pattern recorded in the combo box up to date
455 index = self.textbox.currentIndex()
456 self.textbox.setItemData(index, pattern)
457 self.Find(direction)
458
459 def NotFound(self):
460 QMessageBox.information(self.bar, "Find", "'" + self.textbox.currentText() + "' not found")
461
Adrian Hunter341e73c2018-10-01 09:28:45 +0300462# Context-sensitive call graph data model item base
463
464class CallGraphLevelItemBase(object):
465
466 def __init__(self, glb, row, parent_item):
467 self.glb = glb
468 self.row = row
469 self.parent_item = parent_item
470 self.query_done = False;
471 self.child_count = 0
472 self.child_items = []
Adrian Hunter3ac641f2019-05-03 15:08:25 +0300473 if parent_item:
474 self.level = parent_item.level + 1
475 else:
476 self.level = 0
Adrian Hunter341e73c2018-10-01 09:28:45 +0300477
478 def getChildItem(self, row):
479 return self.child_items[row]
480
481 def getParentItem(self):
482 return self.parent_item
483
484 def getRow(self):
485 return self.row
486
487 def childCount(self):
488 if not self.query_done:
489 self.Select()
490 if not self.child_count:
491 return -1
492 return self.child_count
493
494 def hasChildren(self):
495 if not self.query_done:
496 return True
497 return self.child_count > 0
498
499 def getData(self, column):
500 return self.data[column]
501
502# Context-sensitive call graph data model level 2+ item base
503
504class CallGraphLevelTwoPlusItemBase(CallGraphLevelItemBase):
505
506 def __init__(self, glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item):
507 super(CallGraphLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
508 self.comm_id = comm_id
509 self.thread_id = thread_id
510 self.call_path_id = call_path_id
511 self.branch_count = branch_count
512 self.time = time
513
514 def Select(self):
515 self.query_done = True;
516 query = QSqlQuery(self.glb.db)
517 QueryExec(query, "SELECT call_path_id, name, short_name, COUNT(calls.id), SUM(return_time - call_time), SUM(branch_count)"
518 " FROM calls"
519 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
520 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
521 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
522 " WHERE parent_call_path_id = " + str(self.call_path_id) +
523 " AND comm_id = " + str(self.comm_id) +
524 " AND thread_id = " + str(self.thread_id) +
525 " GROUP BY call_path_id, name, short_name"
526 " ORDER BY call_path_id")
527 while query.next():
528 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)
529 self.child_items.append(child_item)
530 self.child_count += 1
531
532# Context-sensitive call graph data model level three item
533
534class CallGraphLevelThreeItem(CallGraphLevelTwoPlusItemBase):
535
536 def __init__(self, glb, row, comm_id, thread_id, call_path_id, name, dso, count, time, branch_count, parent_item):
537 super(CallGraphLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, call_path_id, time, branch_count, parent_item)
538 dso = dsoname(dso)
539 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
540 self.dbid = call_path_id
541
542# Context-sensitive call graph data model level two item
543
544class CallGraphLevelTwoItem(CallGraphLevelTwoPlusItemBase):
545
546 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
547 super(CallGraphLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 1, 0, 0, parent_item)
548 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
549 self.dbid = thread_id
550
551 def Select(self):
552 super(CallGraphLevelTwoItem, self).Select()
553 for child_item in self.child_items:
554 self.time += child_item.time
555 self.branch_count += child_item.branch_count
556 for child_item in self.child_items:
557 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
558 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
559
560# Context-sensitive call graph data model level one item
561
562class CallGraphLevelOneItem(CallGraphLevelItemBase):
563
564 def __init__(self, glb, row, comm_id, comm, parent_item):
565 super(CallGraphLevelOneItem, self).__init__(glb, row, parent_item)
566 self.data = [comm, "", "", "", "", "", ""]
567 self.dbid = comm_id
568
569 def Select(self):
570 self.query_done = True;
571 query = QSqlQuery(self.glb.db)
572 QueryExec(query, "SELECT thread_id, pid, tid"
573 " FROM comm_threads"
574 " INNER JOIN threads ON thread_id = threads.id"
575 " WHERE comm_id = " + str(self.dbid))
576 while query.next():
577 child_item = CallGraphLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
578 self.child_items.append(child_item)
579 self.child_count += 1
580
581# Context-sensitive call graph data model root item
582
583class CallGraphRootItem(CallGraphLevelItemBase):
584
585 def __init__(self, glb):
586 super(CallGraphRootItem, self).__init__(glb, 0, None)
587 self.dbid = 0
588 self.query_done = True;
589 query = QSqlQuery(glb.db)
590 QueryExec(query, "SELECT id, comm FROM comms")
591 while query.next():
592 if not query.value(0):
593 continue
594 child_item = CallGraphLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
595 self.child_items.append(child_item)
596 self.child_count += 1
597
Adrian Hunter254c0d82019-02-28 15:00:30 +0200598# Context-sensitive call graph data model base
Adrian Hunter70d831e2018-10-01 09:28:43 +0300599
Adrian Hunter254c0d82019-02-28 15:00:30 +0200600class CallGraphModelBase(TreeModel):
Adrian Hunter70d831e2018-10-01 09:28:43 +0300601
602 def __init__(self, glb, parent=None):
Adrian Hunter254c0d82019-02-28 15:00:30 +0200603 super(CallGraphModelBase, self).__init__(glb, parent)
Adrian Hunter70d831e2018-10-01 09:28:43 +0300604
Adrian Hunterebd70c72018-10-01 09:28:48 +0300605 def FindSelect(self, value, pattern, query):
606 if pattern:
607 # postgresql and sqlite pattern patching differences:
608 # postgresql LIKE is case sensitive but sqlite LIKE is not
609 # postgresql LIKE allows % and _ to be escaped with \ but sqlite LIKE does not
610 # postgresql supports ILIKE which is case insensitive
611 # sqlite supports GLOB (text only) which uses * and ? and is case sensitive
612 if not self.glb.dbref.is_sqlite3:
613 # Escape % and _
614 s = value.replace("%", "\%")
615 s = s.replace("_", "\_")
616 # Translate * and ? into SQL LIKE pattern characters % and _
617 trans = string.maketrans("*?", "%_")
618 match = " LIKE '" + str(s).translate(trans) + "'"
619 else:
620 match = " GLOB '" + str(value) + "'"
621 else:
622 match = " = '" + str(value) + "'"
Adrian Hunter254c0d82019-02-28 15:00:30 +0200623 self.DoFindSelect(query, match)
Adrian Hunterebd70c72018-10-01 09:28:48 +0300624
625 def Found(self, query, found):
626 if found:
627 return self.FindPath(query)
628 return []
629
630 def FindValue(self, value, pattern, query, last_value, last_pattern):
631 if last_value == value and pattern == last_pattern:
632 found = query.first()
633 else:
634 self.FindSelect(value, pattern, query)
635 found = query.next()
636 return self.Found(query, found)
637
638 def FindNext(self, query):
639 found = query.next()
640 if not found:
641 found = query.first()
642 return self.Found(query, found)
643
644 def FindPrev(self, query):
645 found = query.previous()
646 if not found:
647 found = query.last()
648 return self.Found(query, found)
649
650 def FindThread(self, c):
651 if c.direction == 0 or c.value != c.last_value or c.pattern != c.last_pattern:
652 ids = self.FindValue(c.value, c.pattern, c.query, c.last_value, c.last_pattern)
653 elif c.direction > 0:
654 ids = self.FindNext(c.query)
655 else:
656 ids = self.FindPrev(c.query)
657 return (True, ids)
658
659 def Find(self, value, direction, pattern, context, callback):
660 class Context():
661 def __init__(self, *x):
662 self.value, self.direction, self.pattern, self.query, self.last_value, self.last_pattern = x
663 def Update(self, *x):
664 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = x + (self.value, self.pattern)
665 if len(context):
666 context[0].Update(value, direction, pattern)
667 else:
668 context.append(Context(value, direction, pattern, QSqlQuery(self.glb.db), None, None))
669 # Use a thread so the UI is not blocked during the SELECT
670 thread = Thread(self.FindThread, context[0])
671 thread.done.connect(lambda ids, t=thread, c=callback: self.FindDone(t, c, ids), Qt.QueuedConnection)
672 thread.start()
673
674 def FindDone(self, thread, callback, ids):
675 callback(ids)
676
Adrian Hunter254c0d82019-02-28 15:00:30 +0200677# Context-sensitive call graph data model
678
679class CallGraphModel(CallGraphModelBase):
680
681 def __init__(self, glb, parent=None):
682 super(CallGraphModel, self).__init__(glb, parent)
683
684 def GetRoot(self):
685 return CallGraphRootItem(self.glb)
686
687 def columnCount(self, parent=None):
688 return 7
689
690 def columnHeader(self, column):
691 headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
692 return headers[column]
693
694 def columnAlignment(self, column):
695 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
696 return alignment[column]
697
698 def DoFindSelect(self, query, match):
699 QueryExec(query, "SELECT call_path_id, comm_id, thread_id"
700 " FROM calls"
701 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
702 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
703 " WHERE symbols.name" + match +
704 " GROUP BY comm_id, thread_id, call_path_id"
705 " ORDER BY comm_id, thread_id, call_path_id")
706
707 def FindPath(self, query):
708 # Turn the query result into a list of ids that the tree view can walk
709 # to open the tree at the right place.
710 ids = []
711 parent_id = query.value(0)
712 while parent_id:
713 ids.insert(0, parent_id)
714 q2 = QSqlQuery(self.glb.db)
715 QueryExec(q2, "SELECT parent_id"
716 " FROM call_paths"
717 " WHERE id = " + str(parent_id))
718 if not q2.next():
719 break
720 parent_id = q2.value(0)
721 # The call path root is not used
722 if ids[0] == 1:
723 del ids[0]
724 ids.insert(0, query.value(2))
725 ids.insert(0, query.value(1))
726 return ids
727
Adrian Hunterae8b8872019-02-28 15:00:31 +0200728# Call tree data model level 2+ item base
729
730class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase):
731
732 def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item):
733 super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item)
734 self.comm_id = comm_id
735 self.thread_id = thread_id
736 self.calls_id = calls_id
737 self.branch_count = branch_count
738 self.time = time
739
740 def Select(self):
741 self.query_done = True;
742 if self.calls_id == 0:
743 comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id)
744 else:
745 comm_thread = ""
746 query = QSqlQuery(self.glb.db)
747 QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count"
748 " FROM calls"
749 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
750 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
751 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
752 " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread +
753 " ORDER BY call_time, calls.id")
754 while query.next():
755 child_item = CallTreeLevelThreeItem(self.glb, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), int(query.value(5)), self)
756 self.child_items.append(child_item)
757 self.child_count += 1
758
759# Call tree data model level three item
760
761class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase):
762
763 def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item):
764 super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item)
765 dso = dsoname(dso)
766 self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ]
767 self.dbid = calls_id
768
769# Call tree data model level two item
770
771class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase):
772
773 def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item):
774 super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item)
775 self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""]
776 self.dbid = thread_id
777
778 def Select(self):
779 super(CallTreeLevelTwoItem, self).Select()
780 for child_item in self.child_items:
781 self.time += child_item.time
782 self.branch_count += child_item.branch_count
783 for child_item in self.child_items:
784 child_item.data[4] = PercentToOneDP(child_item.time, self.time)
785 child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count)
786
787# Call tree data model level one item
788
789class CallTreeLevelOneItem(CallGraphLevelItemBase):
790
791 def __init__(self, glb, row, comm_id, comm, parent_item):
792 super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item)
793 self.data = [comm, "", "", "", "", "", ""]
794 self.dbid = comm_id
795
796 def Select(self):
797 self.query_done = True;
798 query = QSqlQuery(self.glb.db)
799 QueryExec(query, "SELECT thread_id, pid, tid"
800 " FROM comm_threads"
801 " INNER JOIN threads ON thread_id = threads.id"
802 " WHERE comm_id = " + str(self.dbid))
803 while query.next():
804 child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self)
805 self.child_items.append(child_item)
806 self.child_count += 1
807
808# Call tree data model root item
809
810class CallTreeRootItem(CallGraphLevelItemBase):
811
812 def __init__(self, glb):
813 super(CallTreeRootItem, self).__init__(glb, 0, None)
814 self.dbid = 0
815 self.query_done = True;
816 query = QSqlQuery(glb.db)
817 QueryExec(query, "SELECT id, comm FROM comms")
818 while query.next():
819 if not query.value(0):
820 continue
821 child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self)
822 self.child_items.append(child_item)
823 self.child_count += 1
824
825# Call Tree data model
826
827class CallTreeModel(CallGraphModelBase):
828
829 def __init__(self, glb, parent=None):
830 super(CallTreeModel, self).__init__(glb, parent)
831
832 def GetRoot(self):
833 return CallTreeRootItem(self.glb)
834
835 def columnCount(self, parent=None):
836 return 7
837
838 def columnHeader(self, column):
839 headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "]
840 return headers[column]
841
842 def columnAlignment(self, column):
843 alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ]
844 return alignment[column]
845
846 def DoFindSelect(self, query, match):
847 QueryExec(query, "SELECT calls.id, comm_id, thread_id"
848 " FROM calls"
849 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
850 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
851 " WHERE symbols.name" + match +
852 " ORDER BY comm_id, thread_id, call_time, calls.id")
853
854 def FindPath(self, query):
855 # Turn the query result into a list of ids that the tree view can walk
856 # to open the tree at the right place.
857 ids = []
858 parent_id = query.value(0)
859 while parent_id:
860 ids.insert(0, parent_id)
861 q2 = QSqlQuery(self.glb.db)
862 QueryExec(q2, "SELECT parent_id"
863 " FROM calls"
864 " WHERE id = " + str(parent_id))
865 if not q2.next():
866 break
867 parent_id = q2.value(0)
868 ids.insert(0, query.value(2))
869 ids.insert(0, query.value(1))
870 return ids
871
Adrian Hunterebd70c72018-10-01 09:28:48 +0300872# Vertical widget layout
873
874class VBox():
875
876 def __init__(self, w1, w2, w3=None):
877 self.vbox = QWidget()
878 self.vbox.setLayout(QVBoxLayout());
879
880 self.vbox.layout().setContentsMargins(0, 0, 0, 0)
881
882 self.vbox.layout().addWidget(w1)
883 self.vbox.layout().addWidget(w2)
884 if w3:
885 self.vbox.layout().addWidget(w3)
886
887 def Widget(self):
888 return self.vbox
889
Adrian Huntera731cc42019-02-28 15:00:28 +0200890# Tree window base
Adrian Hunter1beb5c72018-10-01 09:28:47 +0300891
Adrian Huntera731cc42019-02-28 15:00:28 +0200892class TreeWindowBase(QMdiSubWindow):
Adrian Hunter1beb5c72018-10-01 09:28:47 +0300893
Adrian Huntera731cc42019-02-28 15:00:28 +0200894 def __init__(self, parent=None):
895 super(TreeWindowBase, self).__init__(parent)
Adrian Hunter1beb5c72018-10-01 09:28:47 +0300896
Adrian Huntera731cc42019-02-28 15:00:28 +0200897 self.model = None
Adrian Huntera731cc42019-02-28 15:00:28 +0200898 self.find_bar = None
Adrian Hunter1beb5c72018-10-01 09:28:47 +0300899
Adrian Hunterbe6e7472019-05-03 15:08:24 +0300900 self.view = QTreeView()
Adrian Hunter96c43b92019-05-03 15:08:26 +0300901 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
902 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
Adrian Hunterbe6e7472019-05-03 15:08:24 +0300903
Adrian Hunter9bc4e4b2019-05-03 15:08:27 +0300904 self.context_menu = TreeContextMenu(self.view)
905
Adrian Hunterebd70c72018-10-01 09:28:48 +0300906 def DisplayFound(self, ids):
907 if not len(ids):
908 return False
909 parent = QModelIndex()
910 for dbid in ids:
911 found = False
912 n = self.model.rowCount(parent)
913 for row in xrange(n):
914 child = self.model.index(row, 0, parent)
915 if child.internalPointer().dbid == dbid:
916 found = True
917 self.view.setCurrentIndex(child)
918 parent = child
919 break
920 if not found:
921 break
922 return found
923
924 def Find(self, value, direction, pattern, context):
925 self.view.setFocus()
926 self.find_bar.Busy()
927 self.model.Find(value, direction, pattern, context, self.FindDone)
928
929 def FindDone(self, ids):
930 found = True
931 if not self.DisplayFound(ids):
932 found = False
933 self.find_bar.Idle()
934 if not found:
935 self.find_bar.NotFound()
936
Adrian Huntera731cc42019-02-28 15:00:28 +0200937
938# Context-sensitive call graph window
939
940class CallGraphWindow(TreeWindowBase):
941
942 def __init__(self, glb, parent=None):
943 super(CallGraphWindow, self).__init__(parent)
944
945 self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x))
946
Adrian Huntera731cc42019-02-28 15:00:28 +0200947 self.view.setModel(self.model)
948
949 for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)):
950 self.view.setColumnWidth(c, w)
951
952 self.find_bar = FindBar(self, self)
953
954 self.vbox = VBox(self.view, self.find_bar.Widget())
955
956 self.setWidget(self.vbox.Widget())
957
958 AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph")
959
Adrian Hunterae8b8872019-02-28 15:00:31 +0200960# Call tree window
961
962class CallTreeWindow(TreeWindowBase):
963
964 def __init__(self, glb, parent=None):
965 super(CallTreeWindow, self).__init__(parent)
966
967 self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x))
968
Adrian Hunterae8b8872019-02-28 15:00:31 +0200969 self.view.setModel(self.model)
970
971 for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)):
972 self.view.setColumnWidth(c, w)
973
974 self.find_bar = FindBar(self, self)
975
976 self.vbox = VBox(self.view, self.find_bar.Widget())
977
978 self.setWidget(self.vbox.Widget())
979
980 AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree")
981
Adrian Hunter8392b742018-10-01 09:28:50 +0300982# Child data item finder
983
984class ChildDataItemFinder():
985
986 def __init__(self, root):
987 self.root = root
988 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
989 self.rows = []
990 self.pos = 0
991
992 def FindSelect(self):
993 self.rows = []
994 if self.pattern:
995 pattern = re.compile(self.value)
996 for child in self.root.child_items:
997 for column_data in child.data:
998 if re.search(pattern, str(column_data)) is not None:
999 self.rows.append(child.row)
1000 break
1001 else:
1002 for child in self.root.child_items:
1003 for column_data in child.data:
1004 if self.value in str(column_data):
1005 self.rows.append(child.row)
1006 break
1007
1008 def FindValue(self):
1009 self.pos = 0
1010 if self.last_value != self.value or self.pattern != self.last_pattern:
1011 self.FindSelect()
1012 if not len(self.rows):
1013 return -1
1014 return self.rows[self.pos]
1015
1016 def FindThread(self):
1017 if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
1018 row = self.FindValue()
1019 elif len(self.rows):
1020 if self.direction > 0:
1021 self.pos += 1
1022 if self.pos >= len(self.rows):
1023 self.pos = 0
1024 else:
1025 self.pos -= 1
1026 if self.pos < 0:
1027 self.pos = len(self.rows) - 1
1028 row = self.rows[self.pos]
1029 else:
1030 row = -1
1031 return (True, row)
1032
1033 def Find(self, value, direction, pattern, context, callback):
1034 self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
1035 # Use a thread so the UI is not blocked
1036 thread = Thread(self.FindThread)
1037 thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
1038 thread.start()
1039
1040 def FindDone(self, thread, callback, row):
1041 callback(row)
1042
1043# Number of database records to fetch in one go
1044
1045glb_chunk_sz = 10000
1046
Adrian Hunter8392b742018-10-01 09:28:50 +03001047# Background process for SQL data fetcher
1048
1049class SQLFetcherProcess():
1050
1051 def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
1052 # Need a unique connection name
1053 conn_name = "SQLFetcher" + str(os.getpid())
1054 self.db, dbname = dbref.Open(conn_name)
1055 self.sql = sql
1056 self.buffer = buffer
1057 self.head = head
1058 self.tail = tail
1059 self.fetch_count = fetch_count
1060 self.fetching_done = fetching_done
1061 self.process_target = process_target
1062 self.wait_event = wait_event
1063 self.fetched_event = fetched_event
1064 self.prep = prep
1065 self.query = QSqlQuery(self.db)
1066 self.query_limit = 0 if "$$last_id$$" in sql else 2
1067 self.last_id = -1
1068 self.fetched = 0
1069 self.more = True
1070 self.local_head = self.head.value
1071 self.local_tail = self.tail.value
1072
1073 def Select(self):
1074 if self.query_limit:
1075 if self.query_limit == 1:
1076 return
1077 self.query_limit -= 1
1078 stmt = self.sql.replace("$$last_id$$", str(self.last_id))
1079 QueryExec(self.query, stmt)
1080
1081 def Next(self):
1082 if not self.query.next():
1083 self.Select()
1084 if not self.query.next():
1085 return None
1086 self.last_id = self.query.value(0)
1087 return self.prep(self.query)
1088
1089 def WaitForTarget(self):
1090 while True:
1091 self.wait_event.clear()
1092 target = self.process_target.value
1093 if target > self.fetched or target < 0:
1094 break
1095 self.wait_event.wait()
1096 return target
1097
1098 def HasSpace(self, sz):
1099 if self.local_tail <= self.local_head:
1100 space = len(self.buffer) - self.local_head
1101 if space > sz:
1102 return True
1103 if space >= glb_nsz:
1104 # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
Tony Jonesbeda0e72019-03-08 16:05:15 -08001105 nd = pickle.dumps(0, pickle.HIGHEST_PROTOCOL)
Adrian Hunter8392b742018-10-01 09:28:50 +03001106 self.buffer[self.local_head : self.local_head + len(nd)] = nd
1107 self.local_head = 0
1108 if self.local_tail - self.local_head > sz:
1109 return True
1110 return False
1111
1112 def WaitForSpace(self, sz):
1113 if self.HasSpace(sz):
1114 return
1115 while True:
1116 self.wait_event.clear()
1117 self.local_tail = self.tail.value
1118 if self.HasSpace(sz):
1119 return
1120 self.wait_event.wait()
1121
1122 def AddToBuffer(self, obj):
Tony Jonesbeda0e72019-03-08 16:05:15 -08001123 d = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
Adrian Hunter8392b742018-10-01 09:28:50 +03001124 n = len(d)
Tony Jonesbeda0e72019-03-08 16:05:15 -08001125 nd = pickle.dumps(n, pickle.HIGHEST_PROTOCOL)
Adrian Hunter8392b742018-10-01 09:28:50 +03001126 sz = n + glb_nsz
1127 self.WaitForSpace(sz)
1128 pos = self.local_head
1129 self.buffer[pos : pos + len(nd)] = nd
1130 self.buffer[pos + glb_nsz : pos + sz] = d
1131 self.local_head += sz
1132
1133 def FetchBatch(self, batch_size):
1134 fetched = 0
1135 while batch_size > fetched:
1136 obj = self.Next()
1137 if obj is None:
1138 self.more = False
1139 break
1140 self.AddToBuffer(obj)
1141 fetched += 1
1142 if fetched:
1143 self.fetched += fetched
1144 with self.fetch_count.get_lock():
1145 self.fetch_count.value += fetched
1146 self.head.value = self.local_head
1147 self.fetched_event.set()
1148
1149 def Run(self):
1150 while self.more:
1151 target = self.WaitForTarget()
1152 if target < 0:
1153 break
1154 batch_size = min(glb_chunk_sz, target - self.fetched)
1155 self.FetchBatch(batch_size)
1156 self.fetching_done.value = True
1157 self.fetched_event.set()
1158
1159def SQLFetcherFn(*x):
1160 process = SQLFetcherProcess(*x)
1161 process.Run()
1162
1163# SQL data fetcher
1164
1165class SQLFetcher(QObject):
1166
1167 done = Signal(object)
1168
1169 def __init__(self, glb, sql, prep, process_data, parent=None):
1170 super(SQLFetcher, self).__init__(parent)
1171 self.process_data = process_data
1172 self.more = True
1173 self.target = 0
1174 self.last_target = 0
1175 self.fetched = 0
1176 self.buffer_size = 16 * 1024 * 1024
1177 self.buffer = Array(c_char, self.buffer_size, lock=False)
1178 self.head = Value(c_longlong)
1179 self.tail = Value(c_longlong)
1180 self.local_tail = 0
1181 self.fetch_count = Value(c_longlong)
1182 self.fetching_done = Value(c_bool)
1183 self.last_count = 0
1184 self.process_target = Value(c_longlong)
1185 self.wait_event = Event()
1186 self.fetched_event = Event()
1187 glb.AddInstanceToShutdownOnExit(self)
1188 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))
1189 self.process.start()
1190 self.thread = Thread(self.Thread)
1191 self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
1192 self.thread.start()
1193
1194 def Shutdown(self):
1195 # Tell the thread and process to exit
1196 self.process_target.value = -1
1197 self.wait_event.set()
1198 self.more = False
1199 self.fetching_done.value = True
1200 self.fetched_event.set()
1201
1202 def Thread(self):
1203 if not self.more:
1204 return True, 0
1205 while True:
1206 self.fetched_event.clear()
1207 fetch_count = self.fetch_count.value
1208 if fetch_count != self.last_count:
1209 break
1210 if self.fetching_done.value:
1211 self.more = False
1212 return True, 0
1213 self.fetched_event.wait()
1214 count = fetch_count - self.last_count
1215 self.last_count = fetch_count
1216 self.fetched += count
1217 return False, count
1218
1219 def Fetch(self, nr):
1220 if not self.more:
1221 # -1 inidcates there are no more
1222 return -1
1223 result = self.fetched
1224 extra = result + nr - self.target
1225 if extra > 0:
1226 self.target += extra
1227 # process_target < 0 indicates shutting down
1228 if self.process_target.value >= 0:
1229 self.process_target.value = self.target
1230 self.wait_event.set()
1231 return result
1232
1233 def RemoveFromBuffer(self):
1234 pos = self.local_tail
1235 if len(self.buffer) - pos < glb_nsz:
1236 pos = 0
Tony Jonesbeda0e72019-03-08 16:05:15 -08001237 n = pickle.loads(self.buffer[pos : pos + glb_nsz])
Adrian Hunter8392b742018-10-01 09:28:50 +03001238 if n == 0:
1239 pos = 0
Tony Jonesbeda0e72019-03-08 16:05:15 -08001240 n = pickle.loads(self.buffer[0 : glb_nsz])
Adrian Hunter8392b742018-10-01 09:28:50 +03001241 pos += glb_nsz
Tony Jonesbeda0e72019-03-08 16:05:15 -08001242 obj = pickle.loads(self.buffer[pos : pos + n])
Adrian Hunter8392b742018-10-01 09:28:50 +03001243 self.local_tail = pos + n
1244 return obj
1245
1246 def ProcessData(self, count):
1247 for i in xrange(count):
1248 obj = self.RemoveFromBuffer()
1249 self.process_data(obj)
1250 self.tail.value = self.local_tail
1251 self.wait_event.set()
1252 self.done.emit(count)
1253
1254# Fetch more records bar
1255
1256class FetchMoreRecordsBar():
1257
1258 def __init__(self, model, parent):
1259 self.model = model
1260
1261 self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
1262 self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1263
1264 self.fetch_count = QSpinBox()
1265 self.fetch_count.setRange(1, 1000000)
1266 self.fetch_count.setValue(10)
1267 self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1268
1269 self.fetch = QPushButton("Go!")
1270 self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1271 self.fetch.released.connect(self.FetchMoreRecords)
1272
1273 self.progress = QProgressBar()
1274 self.progress.setRange(0, 100)
1275 self.progress.hide()
1276
1277 self.done_label = QLabel("All records fetched")
1278 self.done_label.hide()
1279
1280 self.spacer = QLabel("")
1281
1282 self.close_button = QToolButton()
1283 self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
1284 self.close_button.released.connect(self.Deactivate)
1285
1286 self.hbox = QHBoxLayout()
1287 self.hbox.setContentsMargins(0, 0, 0, 0)
1288
1289 self.hbox.addWidget(self.label)
1290 self.hbox.addWidget(self.fetch_count)
1291 self.hbox.addWidget(self.fetch)
1292 self.hbox.addWidget(self.spacer)
1293 self.hbox.addWidget(self.progress)
1294 self.hbox.addWidget(self.done_label)
1295 self.hbox.addWidget(self.close_button)
1296
1297 self.bar = QWidget()
1298 self.bar.setLayout(self.hbox);
1299 self.bar.show()
1300
1301 self.in_progress = False
1302 self.model.progress.connect(self.Progress)
1303
1304 self.done = False
1305
1306 if not model.HasMoreRecords():
1307 self.Done()
1308
1309 def Widget(self):
1310 return self.bar
1311
1312 def Activate(self):
1313 self.bar.show()
1314 self.fetch.setFocus()
1315
1316 def Deactivate(self):
1317 self.bar.hide()
1318
1319 def Enable(self, enable):
1320 self.fetch.setEnabled(enable)
1321 self.fetch_count.setEnabled(enable)
1322
1323 def Busy(self):
1324 self.Enable(False)
1325 self.fetch.hide()
1326 self.spacer.hide()
1327 self.progress.show()
1328
1329 def Idle(self):
1330 self.in_progress = False
1331 self.Enable(True)
1332 self.progress.hide()
1333 self.fetch.show()
1334 self.spacer.show()
1335
1336 def Target(self):
1337 return self.fetch_count.value() * glb_chunk_sz
1338
1339 def Done(self):
1340 self.done = True
1341 self.Idle()
1342 self.label.hide()
1343 self.fetch_count.hide()
1344 self.fetch.hide()
1345 self.spacer.hide()
1346 self.done_label.show()
1347
1348 def Progress(self, count):
1349 if self.in_progress:
1350 if count:
1351 percent = ((count - self.start) * 100) / self.Target()
1352 if percent >= 100:
1353 self.Idle()
1354 else:
1355 self.progress.setValue(percent)
1356 if not count:
1357 # Count value of zero means no more records
1358 self.Done()
1359
1360 def FetchMoreRecords(self):
1361 if self.done:
1362 return
1363 self.progress.setValue(0)
1364 self.Busy()
1365 self.in_progress = True
1366 self.start = self.model.FetchMoreRecords(self.Target())
1367
Adrian Hunter76099f92018-10-23 10:59:49 +03001368# Brance data model level two item
1369
1370class BranchLevelTwoItem():
1371
Adrian Hunter530e22f2019-05-20 14:37:24 +03001372 def __init__(self, row, col, text, parent_item):
Adrian Hunter76099f92018-10-23 10:59:49 +03001373 self.row = row
1374 self.parent_item = parent_item
Adrian Hunter530e22f2019-05-20 14:37:24 +03001375 self.data = [""] * (col + 1)
1376 self.data[col] = text
Adrian Hunter76099f92018-10-23 10:59:49 +03001377 self.level = 2
1378
1379 def getParentItem(self):
1380 return self.parent_item
1381
1382 def getRow(self):
1383 return self.row
1384
1385 def childCount(self):
1386 return 0
1387
1388 def hasChildren(self):
1389 return False
1390
1391 def getData(self, column):
1392 return self.data[column]
1393
1394# Brance data model level one item
1395
1396class BranchLevelOneItem():
1397
1398 def __init__(self, glb, row, data, parent_item):
1399 self.glb = glb
1400 self.row = row
1401 self.parent_item = parent_item
1402 self.child_count = 0
1403 self.child_items = []
1404 self.data = data[1:]
1405 self.dbid = data[0]
1406 self.level = 1
1407 self.query_done = False
Adrian Hunter530e22f2019-05-20 14:37:24 +03001408 self.br_col = len(self.data) - 1
Adrian Hunter76099f92018-10-23 10:59:49 +03001409
1410 def getChildItem(self, row):
1411 return self.child_items[row]
1412
1413 def getParentItem(self):
1414 return self.parent_item
1415
1416 def getRow(self):
1417 return self.row
1418
1419 def Select(self):
1420 self.query_done = True
1421
1422 if not self.glb.have_disassembler:
1423 return
1424
1425 query = QSqlQuery(self.glb.db)
1426
1427 QueryExec(query, "SELECT cpu, to_dso_id, to_symbol_id, to_sym_offset, short_name, long_name, build_id, sym_start, to_ip"
1428 " FROM samples"
1429 " INNER JOIN dsos ON samples.to_dso_id = dsos.id"
1430 " INNER JOIN symbols ON samples.to_symbol_id = symbols.id"
1431 " WHERE samples.id = " + str(self.dbid))
1432 if not query.next():
1433 return
1434 cpu = query.value(0)
1435 dso = query.value(1)
1436 sym = query.value(2)
1437 if dso == 0 or sym == 0:
1438 return
1439 off = query.value(3)
1440 short_name = query.value(4)
1441 long_name = query.value(5)
1442 build_id = query.value(6)
1443 sym_start = query.value(7)
1444 ip = query.value(8)
1445
1446 QueryExec(query, "SELECT samples.dso_id, symbol_id, sym_offset, sym_start"
1447 " FROM samples"
1448 " INNER JOIN symbols ON samples.symbol_id = symbols.id"
1449 " WHERE samples.id > " + str(self.dbid) + " AND cpu = " + str(cpu) +
1450 " ORDER BY samples.id"
1451 " LIMIT 1")
1452 if not query.next():
1453 return
1454 if query.value(0) != dso:
1455 # Cannot disassemble from one dso to another
1456 return
1457 bsym = query.value(1)
1458 boff = query.value(2)
1459 bsym_start = query.value(3)
1460 if bsym == 0:
1461 return
1462 tot = bsym_start + boff + 1 - sym_start - off
1463 if tot <= 0 or tot > 16384:
1464 return
1465
1466 inst = self.glb.disassembler.Instruction()
1467 f = self.glb.FileFromNamesAndBuildId(short_name, long_name, build_id)
1468 if not f:
1469 return
1470 mode = 0 if Is64Bit(f) else 1
1471 self.glb.disassembler.SetMode(inst, mode)
1472
1473 buf_sz = tot + 16
1474 buf = create_string_buffer(tot + 16)
1475 f.seek(sym_start + off)
1476 buf.value = f.read(buf_sz)
1477 buf_ptr = addressof(buf)
1478 i = 0
1479 while tot > 0:
1480 cnt, text = self.glb.disassembler.DisassembleOne(inst, buf_ptr, buf_sz, ip)
1481 if cnt:
1482 byte_str = tohex(ip).rjust(16)
1483 for k in xrange(cnt):
1484 byte_str += " %02x" % ord(buf[i])
1485 i += 1
1486 while k < 15:
1487 byte_str += " "
1488 k += 1
Adrian Hunter530e22f2019-05-20 14:37:24 +03001489 self.child_items.append(BranchLevelTwoItem(0, self.br_col, byte_str + " " + text, self))
Adrian Hunter76099f92018-10-23 10:59:49 +03001490 self.child_count += 1
1491 else:
1492 return
1493 buf_ptr += cnt
1494 tot -= cnt
1495 buf_sz -= cnt
1496 ip += cnt
1497
1498 def childCount(self):
1499 if not self.query_done:
1500 self.Select()
1501 if not self.child_count:
1502 return -1
1503 return self.child_count
1504
1505 def hasChildren(self):
1506 if not self.query_done:
1507 return True
1508 return self.child_count > 0
1509
1510 def getData(self, column):
1511 return self.data[column]
1512
1513# Brance data model root item
1514
1515class BranchRootItem():
1516
1517 def __init__(self):
1518 self.child_count = 0
1519 self.child_items = []
1520 self.level = 0
1521
1522 def getChildItem(self, row):
1523 return self.child_items[row]
1524
1525 def getParentItem(self):
1526 return None
1527
1528 def getRow(self):
1529 return 0
1530
1531 def childCount(self):
1532 return self.child_count
1533
1534 def hasChildren(self):
1535 return self.child_count > 0
1536
1537 def getData(self, column):
1538 return ""
1539
Adrian Hunter530e22f2019-05-20 14:37:24 +03001540# Calculate instructions per cycle
1541
1542def CalcIPC(cyc_cnt, insn_cnt):
1543 if cyc_cnt and insn_cnt:
1544 ipc = Decimal(float(insn_cnt) / cyc_cnt)
1545 ipc = str(ipc.quantize(Decimal(".01"), rounding=ROUND_HALF_UP))
1546 else:
1547 ipc = "0"
1548 return ipc
1549
Adrian Hunter76099f92018-10-23 10:59:49 +03001550# Branch data preparation
1551
Adrian Hunter530e22f2019-05-20 14:37:24 +03001552def BranchDataPrepBr(query, data):
1553 data.append(tohex(query.value(8)).rjust(16) + " " + query.value(9) + offstr(query.value(10)) +
1554 " (" + dsoname(query.value(11)) + ")" + " -> " +
1555 tohex(query.value(12)) + " " + query.value(13) + offstr(query.value(14)) +
1556 " (" + dsoname(query.value(15)) + ")")
1557
1558def BranchDataPrepIPC(query, data):
1559 insn_cnt = query.value(16)
1560 cyc_cnt = query.value(17)
1561 ipc = CalcIPC(cyc_cnt, insn_cnt)
1562 data.append(insn_cnt)
1563 data.append(cyc_cnt)
1564 data.append(ipc)
1565
Adrian Hunter76099f92018-10-23 10:59:49 +03001566def BranchDataPrep(query):
1567 data = []
1568 for i in xrange(0, 8):
1569 data.append(query.value(i))
Adrian Hunter530e22f2019-05-20 14:37:24 +03001570 BranchDataPrepBr(query, data)
Adrian Hunter76099f92018-10-23 10:59:49 +03001571 return data
1572
Adrian Hunter8453c932019-03-27 09:28:25 +02001573def BranchDataPrepWA(query):
1574 data = []
1575 data.append(query.value(0))
1576 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1577 data.append("{:>19}".format(query.value(1)))
1578 for i in xrange(2, 8):
1579 data.append(query.value(i))
Adrian Hunter530e22f2019-05-20 14:37:24 +03001580 BranchDataPrepBr(query, data)
1581 return data
1582
1583def BranchDataWithIPCPrep(query):
1584 data = []
1585 for i in xrange(0, 8):
1586 data.append(query.value(i))
1587 BranchDataPrepIPC(query, data)
1588 BranchDataPrepBr(query, data)
1589 return data
1590
1591def BranchDataWithIPCPrepWA(query):
1592 data = []
1593 data.append(query.value(0))
1594 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
1595 data.append("{:>19}".format(query.value(1)))
1596 for i in xrange(2, 8):
1597 data.append(query.value(i))
1598 BranchDataPrepIPC(query, data)
1599 BranchDataPrepBr(query, data)
Adrian Hunter8453c932019-03-27 09:28:25 +02001600 return data
1601
Adrian Hunter76099f92018-10-23 10:59:49 +03001602# Branch data model
1603
1604class BranchModel(TreeModel):
1605
1606 progress = Signal(object)
1607
1608 def __init__(self, glb, event_id, where_clause, parent=None):
Adrian Huntera448ba22019-02-28 15:00:29 +02001609 super(BranchModel, self).__init__(glb, parent)
Adrian Hunter76099f92018-10-23 10:59:49 +03001610 self.event_id = event_id
1611 self.more = True
1612 self.populated = 0
Adrian Hunter530e22f2019-05-20 14:37:24 +03001613 self.have_ipc = IsSelectable(glb.db, "samples", columns = "insn_count, cyc_count")
1614 if self.have_ipc:
1615 select_ipc = ", insn_count, cyc_count"
1616 prep_fn = BranchDataWithIPCPrep
1617 prep_wa_fn = BranchDataWithIPCPrepWA
1618 else:
1619 select_ipc = ""
1620 prep_fn = BranchDataPrep
1621 prep_wa_fn = BranchDataPrepWA
Adrian Hunter76099f92018-10-23 10:59:49 +03001622 sql = ("SELECT samples.id, time, cpu, comm, pid, tid, branch_types.name,"
1623 " CASE WHEN in_tx = '0' THEN 'No' ELSE 'Yes' END,"
1624 " ip, symbols.name, sym_offset, dsos.short_name,"
1625 " to_ip, to_symbols.name, to_sym_offset, to_dsos.short_name"
Adrian Hunter530e22f2019-05-20 14:37:24 +03001626 + select_ipc +
Adrian Hunter76099f92018-10-23 10:59:49 +03001627 " FROM samples"
1628 " INNER JOIN comms ON comm_id = comms.id"
1629 " INNER JOIN threads ON thread_id = threads.id"
1630 " INNER JOIN branch_types ON branch_type = branch_types.id"
1631 " INNER JOIN symbols ON symbol_id = symbols.id"
1632 " INNER JOIN symbols to_symbols ON to_symbol_id = to_symbols.id"
1633 " INNER JOIN dsos ON samples.dso_id = dsos.id"
1634 " INNER JOIN dsos AS to_dsos ON samples.to_dso_id = to_dsos.id"
1635 " WHERE samples.id > $$last_id$$" + where_clause +
1636 " AND evsel_id = " + str(self.event_id) +
1637 " ORDER BY samples.id"
1638 " LIMIT " + str(glb_chunk_sz))
Adrian Hunter8453c932019-03-27 09:28:25 +02001639 if pyside_version_1 and sys.version_info[0] == 3:
Adrian Hunter530e22f2019-05-20 14:37:24 +03001640 prep = prep_fn
Adrian Hunter8453c932019-03-27 09:28:25 +02001641 else:
Adrian Hunter530e22f2019-05-20 14:37:24 +03001642 prep = prep_wa_fn
Adrian Hunter8453c932019-03-27 09:28:25 +02001643 self.fetcher = SQLFetcher(glb, sql, prep, self.AddSample)
Adrian Hunter76099f92018-10-23 10:59:49 +03001644 self.fetcher.done.connect(self.Update)
1645 self.fetcher.Fetch(glb_chunk_sz)
1646
Adrian Huntera448ba22019-02-28 15:00:29 +02001647 def GetRoot(self):
1648 return BranchRootItem()
1649
Adrian Hunter76099f92018-10-23 10:59:49 +03001650 def columnCount(self, parent=None):
Adrian Hunter530e22f2019-05-20 14:37:24 +03001651 if self.have_ipc:
1652 return 11
1653 else:
1654 return 8
Adrian Hunter76099f92018-10-23 10:59:49 +03001655
1656 def columnHeader(self, column):
Adrian Hunter530e22f2019-05-20 14:37:24 +03001657 if self.have_ipc:
1658 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Insn Cnt", "Cyc Cnt", "IPC", "Branch")[column]
1659 else:
1660 return ("Time", "CPU", "Command", "PID", "TID", "Branch Type", "In Tx", "Branch")[column]
Adrian Hunter76099f92018-10-23 10:59:49 +03001661
1662 def columnFont(self, column):
Adrian Hunter530e22f2019-05-20 14:37:24 +03001663 if self.have_ipc:
1664 br_col = 10
1665 else:
1666 br_col = 7
1667 if column != br_col:
Adrian Hunter76099f92018-10-23 10:59:49 +03001668 return None
1669 return QFont("Monospace")
1670
1671 def DisplayData(self, item, index):
1672 if item.level == 1:
1673 self.FetchIfNeeded(item.row)
1674 return item.getData(index.column())
1675
1676 def AddSample(self, data):
1677 child = BranchLevelOneItem(self.glb, self.populated, data, self.root)
1678 self.root.child_items.append(child)
1679 self.populated += 1
1680
1681 def Update(self, fetched):
1682 if not fetched:
1683 self.more = False
1684 self.progress.emit(0)
1685 child_count = self.root.child_count
1686 count = self.populated - child_count
1687 if count > 0:
1688 parent = QModelIndex()
1689 self.beginInsertRows(parent, child_count, child_count + count - 1)
1690 self.insertRows(child_count, count, parent)
1691 self.root.child_count += count
1692 self.endInsertRows()
1693 self.progress.emit(self.root.child_count)
1694
1695 def FetchMoreRecords(self, count):
1696 current = self.root.child_count
1697 if self.more:
1698 self.fetcher.Fetch(count)
1699 else:
1700 self.progress.emit(0)
1701 return current
1702
1703 def HasMoreRecords(self):
1704 return self.more
1705
Adrian Hunter0bf09472019-02-22 09:27:23 +02001706# Report Variables
1707
1708class ReportVars():
1709
Adrian Huntercd358012019-02-22 09:27:28 +02001710 def __init__(self, name = "", where_clause = "", limit = ""):
Adrian Hunter947cc382019-02-22 09:27:24 +02001711 self.name = name
Adrian Hunter0bf09472019-02-22 09:27:23 +02001712 self.where_clause = where_clause
Adrian Huntercd358012019-02-22 09:27:28 +02001713 self.limit = limit
Adrian Hunter0bf09472019-02-22 09:27:23 +02001714
1715 def UniqueId(self):
Adrian Huntercd358012019-02-22 09:27:28 +02001716 return str(self.where_clause + ";" + self.limit)
Adrian Hunter0bf09472019-02-22 09:27:23 +02001717
Adrian Hunter76099f92018-10-23 10:59:49 +03001718# Branch window
1719
1720class BranchWindow(QMdiSubWindow):
1721
Adrian Hunter947cc382019-02-22 09:27:24 +02001722 def __init__(self, glb, event_id, report_vars, parent=None):
Adrian Hunter76099f92018-10-23 10:59:49 +03001723 super(BranchWindow, self).__init__(parent)
1724
Adrian Hunter0bf09472019-02-22 09:27:23 +02001725 model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId()
Adrian Hunter76099f92018-10-23 10:59:49 +03001726
Adrian Hunter0bf09472019-02-22 09:27:23 +02001727 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause))
Adrian Hunter76099f92018-10-23 10:59:49 +03001728
1729 self.view = QTreeView()
1730 self.view.setUniformRowHeights(True)
Adrian Hunter96c43b92019-05-03 15:08:26 +03001731 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
1732 self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
Adrian Hunter76099f92018-10-23 10:59:49 +03001733 self.view.setModel(self.model)
1734
1735 self.ResizeColumnsToContents()
1736
Adrian Hunter9bc4e4b2019-05-03 15:08:27 +03001737 self.context_menu = TreeContextMenu(self.view)
1738
Adrian Hunter76099f92018-10-23 10:59:49 +03001739 self.find_bar = FindBar(self, self, True)
1740
1741 self.finder = ChildDataItemFinder(self.model.root)
1742
1743 self.fetch_bar = FetchMoreRecordsBar(self.model, self)
1744
1745 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1746
1747 self.setWidget(self.vbox.Widget())
1748
Adrian Hunter947cc382019-02-22 09:27:24 +02001749 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events")
Adrian Hunter76099f92018-10-23 10:59:49 +03001750
1751 def ResizeColumnToContents(self, column, n):
1752 # Using the view's resizeColumnToContents() here is extrememly slow
1753 # so implement a crude alternative
1754 mm = "MM" if column else "MMMM"
1755 font = self.view.font()
1756 metrics = QFontMetrics(font)
1757 max = 0
1758 for row in xrange(n):
1759 val = self.model.root.child_items[row].data[column]
1760 len = metrics.width(str(val) + mm)
1761 max = len if len > max else max
1762 val = self.model.columnHeader(column)
1763 len = metrics.width(str(val) + mm)
1764 max = len if len > max else max
1765 self.view.setColumnWidth(column, max)
1766
1767 def ResizeColumnsToContents(self):
1768 n = min(self.model.root.child_count, 100)
1769 if n < 1:
1770 # No data yet, so connect a signal to notify when there is
1771 self.model.rowsInserted.connect(self.UpdateColumnWidths)
1772 return
1773 columns = self.model.columnCount()
1774 for i in xrange(columns):
1775 self.ResizeColumnToContents(i, n)
1776
1777 def UpdateColumnWidths(self, *x):
1778 # This only needs to be done once, so disconnect the signal now
1779 self.model.rowsInserted.disconnect(self.UpdateColumnWidths)
1780 self.ResizeColumnsToContents()
1781
1782 def Find(self, value, direction, pattern, context):
1783 self.view.setFocus()
1784 self.find_bar.Busy()
1785 self.finder.Find(value, direction, pattern, context, self.FindDone)
1786
1787 def FindDone(self, row):
1788 self.find_bar.Idle()
1789 if row >= 0:
1790 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1791 else:
1792 self.find_bar.NotFound()
1793
Adrian Hunter1c3ca1b2019-02-22 09:27:25 +02001794# Line edit data item
1795
1796class LineEditDataItem(object):
1797
Adrian Huntercd358012019-02-22 09:27:28 +02001798 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
Adrian Hunter1c3ca1b2019-02-22 09:27:25 +02001799 self.glb = glb
1800 self.label = label
1801 self.placeholder_text = placeholder_text
1802 self.parent = parent
1803 self.id = id
1804
Adrian Huntercd358012019-02-22 09:27:28 +02001805 self.value = default
Adrian Hunter1c3ca1b2019-02-22 09:27:25 +02001806
Adrian Huntercd358012019-02-22 09:27:28 +02001807 self.widget = QLineEdit(default)
Adrian Hunter1c3ca1b2019-02-22 09:27:25 +02001808 self.widget.editingFinished.connect(self.Validate)
1809 self.widget.textChanged.connect(self.Invalidate)
1810 self.red = False
1811 self.error = ""
1812 self.validated = True
1813
1814 if placeholder_text:
1815 self.widget.setPlaceholderText(placeholder_text)
1816
1817 def TurnTextRed(self):
1818 if not self.red:
1819 palette = QPalette()
1820 palette.setColor(QPalette.Text,Qt.red)
1821 self.widget.setPalette(palette)
1822 self.red = True
1823
1824 def TurnTextNormal(self):
1825 if self.red:
1826 palette = QPalette()
1827 self.widget.setPalette(palette)
1828 self.red = False
1829
1830 def InvalidValue(self, value):
1831 self.value = ""
1832 self.TurnTextRed()
1833 self.error = self.label + " invalid value '" + value + "'"
1834 self.parent.ShowMessage(self.error)
1835
1836 def Invalidate(self):
1837 self.validated = False
1838
1839 def DoValidate(self, input_string):
1840 self.value = input_string.strip()
1841
1842 def Validate(self):
1843 self.validated = True
1844 self.error = ""
1845 self.TurnTextNormal()
1846 self.parent.ClearMessage()
1847 input_string = self.widget.text()
1848 if not len(input_string.strip()):
1849 self.value = ""
1850 return
1851 self.DoValidate(input_string)
1852
1853 def IsValid(self):
1854 if not self.validated:
1855 self.Validate()
1856 if len(self.error):
1857 self.parent.ShowMessage(self.error)
1858 return False
1859 return True
1860
1861 def IsNumber(self, value):
1862 try:
1863 x = int(value)
1864 except:
1865 x = 0
1866 return str(x) == value
1867
1868# Non-negative integer ranges dialog data item
1869
1870class NonNegativeIntegerRangesDataItem(LineEditDataItem):
1871
1872 def __init__(self, glb, label, placeholder_text, column_name, parent):
1873 super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1874
1875 self.column_name = column_name
1876
1877 def DoValidate(self, input_string):
1878 singles = []
1879 ranges = []
1880 for value in [x.strip() for x in input_string.split(",")]:
1881 if "-" in value:
1882 vrange = value.split("-")
1883 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
1884 return self.InvalidValue(value)
1885 ranges.append(vrange)
1886 else:
1887 if not self.IsNumber(value):
1888 return self.InvalidValue(value)
1889 singles.append(value)
1890 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
1891 if len(singles):
1892 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
1893 self.value = " OR ".join(ranges)
1894
Adrian Huntercd358012019-02-22 09:27:28 +02001895# Positive integer dialog data item
1896
1897class PositiveIntegerDataItem(LineEditDataItem):
1898
1899 def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
1900 super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default)
1901
1902 def DoValidate(self, input_string):
1903 if not self.IsNumber(input_string.strip()):
1904 return self.InvalidValue(input_string)
1905 value = int(input_string.strip())
1906 if value <= 0:
1907 return self.InvalidValue(input_string)
1908 self.value = str(value)
1909
Adrian Hunter1c3ca1b2019-02-22 09:27:25 +02001910# Dialog data item converted and validated using a SQL table
1911
1912class SQLTableDataItem(LineEditDataItem):
1913
1914 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent):
1915 super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent)
1916
1917 self.table_name = table_name
1918 self.match_column = match_column
1919 self.column_name1 = column_name1
1920 self.column_name2 = column_name2
1921
1922 def ValueToIds(self, value):
1923 ids = []
1924 query = QSqlQuery(self.glb.db)
1925 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'"
1926 ret = query.exec_(stmt)
1927 if ret:
1928 while query.next():
1929 ids.append(str(query.value(0)))
1930 return ids
1931
1932 def DoValidate(self, input_string):
1933 all_ids = []
1934 for value in [x.strip() for x in input_string.split(",")]:
1935 ids = self.ValueToIds(value)
1936 if len(ids):
1937 all_ids.extend(ids)
1938 else:
1939 return self.InvalidValue(value)
1940 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")"
1941 if self.column_name2:
1942 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )"
1943
1944# Sample time ranges dialog data item converted and validated using 'samples' SQL table
1945
1946class SampleTimeRangesDataItem(LineEditDataItem):
1947
1948 def __init__(self, glb, label, placeholder_text, column_name, parent):
1949 self.column_name = column_name
1950
1951 self.last_id = 0
1952 self.first_time = 0
1953 self.last_time = 2 ** 64
1954
1955 query = QSqlQuery(glb.db)
1956 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1")
1957 if query.next():
1958 self.last_id = int(query.value(0))
1959 self.last_time = int(query.value(1))
1960 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1")
1961 if query.next():
1962 self.first_time = int(query.value(0))
1963 if placeholder_text:
1964 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time)
1965
1966 super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent)
1967
1968 def IdBetween(self, query, lower_id, higher_id, order):
1969 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1")
1970 if query.next():
1971 return True, int(query.value(0))
1972 else:
1973 return False, 0
1974
1975 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor):
1976 query = QSqlQuery(self.glb.db)
1977 while True:
1978 next_id = int((lower_id + higher_id) / 2)
1979 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1980 if not query.next():
1981 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC")
1982 if not ok:
1983 ok, dbid = self.IdBetween(query, next_id, higher_id, "")
1984 if not ok:
1985 return str(higher_id)
1986 next_id = dbid
1987 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id))
1988 next_time = int(query.value(0))
1989 if get_floor:
1990 if target_time > next_time:
1991 lower_id = next_id
1992 else:
1993 higher_id = next_id
1994 if higher_id <= lower_id + 1:
1995 return str(higher_id)
1996 else:
1997 if target_time >= next_time:
1998 lower_id = next_id
1999 else:
2000 higher_id = next_id
2001 if higher_id <= lower_id + 1:
2002 return str(lower_id)
2003
2004 def ConvertRelativeTime(self, val):
2005 mult = 1
2006 suffix = val[-2:]
2007 if suffix == "ms":
2008 mult = 1000000
2009 elif suffix == "us":
2010 mult = 1000
2011 elif suffix == "ns":
2012 mult = 1
2013 else:
2014 return val
2015 val = val[:-2].strip()
2016 if not self.IsNumber(val):
2017 return val
2018 val = int(val) * mult
2019 if val >= 0:
2020 val += self.first_time
2021 else:
2022 val += self.last_time
2023 return str(val)
2024
2025 def ConvertTimeRange(self, vrange):
2026 if vrange[0] == "":
2027 vrange[0] = str(self.first_time)
2028 if vrange[1] == "":
2029 vrange[1] = str(self.last_time)
2030 vrange[0] = self.ConvertRelativeTime(vrange[0])
2031 vrange[1] = self.ConvertRelativeTime(vrange[1])
2032 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]):
2033 return False
2034 beg_range = max(int(vrange[0]), self.first_time)
2035 end_range = min(int(vrange[1]), self.last_time)
2036 if beg_range > self.last_time or end_range < self.first_time:
2037 return False
2038 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True)
2039 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False)
2040 return True
2041
2042 def AddTimeRange(self, value, ranges):
2043 n = value.count("-")
2044 if n == 1:
2045 pass
2046 elif n == 2:
2047 if value.split("-")[1].strip() == "":
2048 n = 1
2049 elif n == 3:
2050 n = 2
2051 else:
2052 return False
2053 pos = findnth(value, "-", n)
2054 vrange = [value[:pos].strip() ,value[pos+1:].strip()]
2055 if self.ConvertTimeRange(vrange):
2056 ranges.append(vrange)
2057 return True
2058 return False
2059
2060 def DoValidate(self, input_string):
2061 ranges = []
2062 for value in [x.strip() for x in input_string.split(",")]:
2063 if not self.AddTimeRange(value, ranges):
2064 return self.InvalidValue(value)
2065 ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges]
2066 self.value = " OR ".join(ranges)
2067
Adrian Hunter0924cd62019-02-22 09:27:22 +02002068# Report Dialog Base
Adrian Hunter210cf1f2018-11-04 17:12:36 +02002069
Adrian Hunter0924cd62019-02-22 09:27:22 +02002070class ReportDialogBase(QDialog):
Adrian Hunter210cf1f2018-11-04 17:12:36 +02002071
Adrian Hunter0924cd62019-02-22 09:27:22 +02002072 def __init__(self, glb, title, items, partial, parent=None):
2073 super(ReportDialogBase, self).__init__(parent)
Adrian Hunter210cf1f2018-11-04 17:12:36 +02002074
2075 self.glb = glb
2076
Adrian Hunter0bf09472019-02-22 09:27:23 +02002077 self.report_vars = ReportVars()
Adrian Hunter210cf1f2018-11-04 17:12:36 +02002078
Adrian Hunter0924cd62019-02-22 09:27:22 +02002079 self.setWindowTitle(title)
Adrian Hunter210cf1f2018-11-04 17:12:36 +02002080 self.setMinimumWidth(600)
2081
Adrian Hunter1c3ca1b2019-02-22 09:27:25 +02002082 self.data_items = [x(glb, self) for x in items]
Adrian Hunter210cf1f2018-11-04 17:12:36 +02002083
Adrian Hunter0924cd62019-02-22 09:27:22 +02002084 self.partial = partial
2085
Adrian Hunter210cf1f2018-11-04 17:12:36 +02002086 self.grid = QGridLayout()
2087
2088 for row in xrange(len(self.data_items)):
2089 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0)
2090 self.grid.addWidget(self.data_items[row].widget, row, 1)
2091
2092 self.status = QLabel()
2093
2094 self.ok_button = QPushButton("Ok", self)
2095 self.ok_button.setDefault(True)
2096 self.ok_button.released.connect(self.Ok)
2097 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2098
2099 self.cancel_button = QPushButton("Cancel", self)
2100 self.cancel_button.released.connect(self.reject)
2101 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
2102
2103 self.hbox = QHBoxLayout()
2104 #self.hbox.addStretch()
2105 self.hbox.addWidget(self.status)
2106 self.hbox.addWidget(self.ok_button)
2107 self.hbox.addWidget(self.cancel_button)
2108
2109 self.vbox = QVBoxLayout()
2110 self.vbox.addLayout(self.grid)
2111 self.vbox.addLayout(self.hbox)
2112
2113 self.setLayout(self.vbox);
2114
2115 def Ok(self):
Adrian Hunter0bf09472019-02-22 09:27:23 +02002116 vars = self.report_vars
Adrian Hunter1c3ca1b2019-02-22 09:27:25 +02002117 for d in self.data_items:
2118 if d.id == "REPORTNAME":
2119 vars.name = d.value
Adrian Hunter947cc382019-02-22 09:27:24 +02002120 if not vars.name:
Adrian Hunter210cf1f2018-11-04 17:12:36 +02002121 self.ShowMessage("Report name is required")
2122 return
2123 for d in self.data_items:
2124 if not d.IsValid():
2125 return
2126 for d in self.data_items[1:]:
Adrian Huntercd358012019-02-22 09:27:28 +02002127 if d.id == "LIMIT":
2128 vars.limit = d.value
2129 elif len(d.value):
Adrian Hunter0bf09472019-02-22 09:27:23 +02002130 if len(vars.where_clause):
2131 vars.where_clause += " AND "
2132 vars.where_clause += d.value
2133 if len(vars.where_clause):
Adrian Hunter0924cd62019-02-22 09:27:22 +02002134 if self.partial:
Adrian Hunter0bf09472019-02-22 09:27:23 +02002135 vars.where_clause = " AND ( " + vars.where_clause + " ) "
Adrian Hunter0924cd62019-02-22 09:27:22 +02002136 else:
Adrian Hunter0bf09472019-02-22 09:27:23 +02002137 vars.where_clause = " WHERE " + vars.where_clause + " "
Adrian Hunter210cf1f2018-11-04 17:12:36 +02002138 self.accept()
2139
2140 def ShowMessage(self, msg):
2141 self.status.setText("<font color=#FF0000>" + msg)
2142
2143 def ClearMessage(self):
2144 self.status.setText("")
2145
Adrian Hunter0924cd62019-02-22 09:27:22 +02002146# Selected branch report creation dialog
2147
2148class SelectedBranchDialog(ReportDialogBase):
2149
2150 def __init__(self, glb, parent=None):
2151 title = "Selected Branches"
Adrian Hunter1c3ca1b2019-02-22 09:27:25 +02002152 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2153 lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p),
2154 lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p),
2155 lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p),
2156 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2157 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2158 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p),
2159 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p),
2160 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p))
Adrian Hunter0924cd62019-02-22 09:27:22 +02002161 super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent)
2162
Adrian Hunter76099f92018-10-23 10:59:49 +03002163# Event list
2164
2165def GetEventList(db):
2166 events = []
2167 query = QSqlQuery(db)
2168 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id")
2169 while query.next():
2170 events.append(query.value(0))
2171 return events
2172
Adrian Hunter655cb9522019-02-22 09:27:20 +02002173# Is a table selectable
2174
Adrian Hunter530e22f2019-05-20 14:37:24 +03002175def IsSelectable(db, table, sql = "", columns = "*"):
Adrian Hunter655cb9522019-02-22 09:27:20 +02002176 query = QSqlQuery(db)
2177 try:
Adrian Hunter530e22f2019-05-20 14:37:24 +03002178 QueryExec(query, "SELECT " + columns + " FROM " + table + " " + sql + " LIMIT 1")
Adrian Hunter655cb9522019-02-22 09:27:20 +02002179 except:
2180 return False
2181 return True
2182
Adrian Hunter8392b742018-10-01 09:28:50 +03002183# SQL table data model item
2184
2185class SQLTableItem():
2186
2187 def __init__(self, row, data):
2188 self.row = row
2189 self.data = data
2190
2191 def getData(self, column):
2192 return self.data[column]
2193
2194# SQL table data model
2195
2196class SQLTableModel(TableModel):
2197
2198 progress = Signal(object)
2199
Adrian Hunter8c90fef2019-02-22 09:27:21 +02002200 def __init__(self, glb, sql, column_headers, parent=None):
Adrian Hunter8392b742018-10-01 09:28:50 +03002201 super(SQLTableModel, self).__init__(parent)
2202 self.glb = glb
2203 self.more = True
2204 self.populated = 0
Adrian Hunter8c90fef2019-02-22 09:27:21 +02002205 self.column_headers = column_headers
Adrian Hunter8453c932019-03-27 09:28:25 +02002206 self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): self.SQLTableDataPrep(x, y), self.AddSample)
Adrian Hunter8392b742018-10-01 09:28:50 +03002207 self.fetcher.done.connect(self.Update)
2208 self.fetcher.Fetch(glb_chunk_sz)
2209
2210 def DisplayData(self, item, index):
2211 self.FetchIfNeeded(item.row)
2212 return item.getData(index.column())
2213
2214 def AddSample(self, data):
2215 child = SQLTableItem(self.populated, data)
2216 self.child_items.append(child)
2217 self.populated += 1
2218
2219 def Update(self, fetched):
2220 if not fetched:
2221 self.more = False
2222 self.progress.emit(0)
2223 child_count = self.child_count
2224 count = self.populated - child_count
2225 if count > 0:
2226 parent = QModelIndex()
2227 self.beginInsertRows(parent, child_count, child_count + count - 1)
2228 self.insertRows(child_count, count, parent)
2229 self.child_count += count
2230 self.endInsertRows()
2231 self.progress.emit(self.child_count)
2232
2233 def FetchMoreRecords(self, count):
2234 current = self.child_count
2235 if self.more:
2236 self.fetcher.Fetch(count)
2237 else:
2238 self.progress.emit(0)
2239 return current
2240
2241 def HasMoreRecords(self):
2242 return self.more
2243
Adrian Hunter8c90fef2019-02-22 09:27:21 +02002244 def columnCount(self, parent=None):
2245 return len(self.column_headers)
2246
2247 def columnHeader(self, column):
2248 return self.column_headers[column]
2249
Adrian Hunter8453c932019-03-27 09:28:25 +02002250 def SQLTableDataPrep(self, query, count):
2251 data = []
2252 for i in xrange(count):
2253 data.append(query.value(i))
2254 return data
2255
Adrian Hunter8392b742018-10-01 09:28:50 +03002256# SQL automatic table data model
2257
2258class SQLAutoTableModel(SQLTableModel):
2259
2260 def __init__(self, glb, table_name, parent=None):
2261 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
2262 if table_name == "comm_threads_view":
2263 # For now, comm_threads_view has no id column
2264 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 +02002265 column_headers = []
Adrian Hunter8392b742018-10-01 09:28:50 +03002266 query = QSqlQuery(glb.db)
2267 if glb.dbref.is_sqlite3:
2268 QueryExec(query, "PRAGMA table_info(" + table_name + ")")
2269 while query.next():
Adrian Hunter8c90fef2019-02-22 09:27:21 +02002270 column_headers.append(query.value(1))
Adrian Hunter8392b742018-10-01 09:28:50 +03002271 if table_name == "sqlite_master":
2272 sql = "SELECT * FROM " + table_name
2273 else:
2274 if table_name[:19] == "information_schema.":
2275 sql = "SELECT * FROM " + table_name
2276 select_table_name = table_name[19:]
2277 schema = "information_schema"
2278 else:
2279 select_table_name = table_name
2280 schema = "public"
2281 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
2282 while query.next():
Adrian Hunter8c90fef2019-02-22 09:27:21 +02002283 column_headers.append(query.value(0))
Adrian Hunter8453c932019-03-27 09:28:25 +02002284 if pyside_version_1 and sys.version_info[0] == 3:
2285 if table_name == "samples_view":
2286 self.SQLTableDataPrep = self.samples_view_DataPrep
2287 if table_name == "samples":
2288 self.SQLTableDataPrep = self.samples_DataPrep
Adrian Hunter8c90fef2019-02-22 09:27:21 +02002289 super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent)
Adrian Hunter8392b742018-10-01 09:28:50 +03002290
Adrian Hunter8453c932019-03-27 09:28:25 +02002291 def samples_view_DataPrep(self, query, count):
2292 data = []
2293 data.append(query.value(0))
2294 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2295 data.append("{:>19}".format(query.value(1)))
2296 for i in xrange(2, count):
2297 data.append(query.value(i))
2298 return data
2299
2300 def samples_DataPrep(self, query, count):
2301 data = []
2302 for i in xrange(9):
2303 data.append(query.value(i))
2304 # Workaround pyside failing to handle large integers (i.e. time) in python3 by converting to a string
2305 data.append("{:>19}".format(query.value(9)))
2306 for i in xrange(10, count):
2307 data.append(query.value(i))
2308 return data
2309
Adrian Hunter8392b742018-10-01 09:28:50 +03002310# Base class for custom ResizeColumnsToContents
2311
2312class ResizeColumnsToContentsBase(QObject):
2313
2314 def __init__(self, parent=None):
2315 super(ResizeColumnsToContentsBase, self).__init__(parent)
2316
2317 def ResizeColumnToContents(self, column, n):
2318 # Using the view's resizeColumnToContents() here is extrememly slow
2319 # so implement a crude alternative
2320 font = self.view.font()
2321 metrics = QFontMetrics(font)
2322 max = 0
2323 for row in xrange(n):
2324 val = self.data_model.child_items[row].data[column]
2325 len = metrics.width(str(val) + "MM")
2326 max = len if len > max else max
2327 val = self.data_model.columnHeader(column)
2328 len = metrics.width(str(val) + "MM")
2329 max = len if len > max else max
2330 self.view.setColumnWidth(column, max)
2331
2332 def ResizeColumnsToContents(self):
2333 n = min(self.data_model.child_count, 100)
2334 if n < 1:
2335 # No data yet, so connect a signal to notify when there is
2336 self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
2337 return
2338 columns = self.data_model.columnCount()
2339 for i in xrange(columns):
2340 self.ResizeColumnToContents(i, n)
2341
2342 def UpdateColumnWidths(self, *x):
2343 # This only needs to be done once, so disconnect the signal now
2344 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
2345 self.ResizeColumnsToContents()
2346
Adrian Hunter96c43b92019-05-03 15:08:26 +03002347# Convert value to CSV
2348
2349def ToCSValue(val):
2350 if '"' in val:
2351 val = val.replace('"', '""')
2352 if "," in val or '"' in val:
2353 val = '"' + val + '"'
2354 return val
2355
2356# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
2357
2358glb_max_cols = 1000
2359
2360def RowColumnKey(a):
2361 return a.row() * glb_max_cols + a.column()
2362
2363# Copy selected table cells to clipboard
2364
2365def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
2366 indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
2367 idx_cnt = len(indexes)
2368 if not idx_cnt:
2369 return
2370 if idx_cnt == 1:
2371 with_hdr=False
2372 min_row = indexes[0].row()
2373 max_row = indexes[0].row()
2374 min_col = indexes[0].column()
2375 max_col = indexes[0].column()
2376 for i in indexes:
2377 min_row = min(min_row, i.row())
2378 max_row = max(max_row, i.row())
2379 min_col = min(min_col, i.column())
2380 max_col = max(max_col, i.column())
2381 if max_col > glb_max_cols:
2382 raise RuntimeError("glb_max_cols is too low")
2383 max_width = [0] * (1 + max_col - min_col)
2384 for i in indexes:
2385 c = i.column() - min_col
2386 max_width[c] = max(max_width[c], len(str(i.data())))
2387 text = ""
2388 pad = ""
2389 sep = ""
2390 if with_hdr:
2391 model = indexes[0].model()
2392 for col in range(min_col, max_col + 1):
2393 val = model.headerData(col, Qt.Horizontal)
2394 if as_csv:
2395 text += sep + ToCSValue(val)
2396 sep = ","
2397 else:
2398 c = col - min_col
2399 max_width[c] = max(max_width[c], len(val))
2400 width = max_width[c]
2401 align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
2402 if align & Qt.AlignRight:
2403 val = val.rjust(width)
2404 text += pad + sep + val
2405 pad = " " * (width - len(val))
2406 sep = " "
2407 text += "\n"
2408 pad = ""
2409 sep = ""
2410 last_row = min_row
2411 for i in indexes:
2412 if i.row() > last_row:
2413 last_row = i.row()
2414 text += "\n"
2415 pad = ""
2416 sep = ""
2417 if as_csv:
2418 text += sep + ToCSValue(str(i.data()))
2419 sep = ","
2420 else:
2421 width = max_width[i.column() - min_col]
2422 if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2423 val = str(i.data()).rjust(width)
2424 else:
2425 val = str(i.data())
2426 text += pad + sep + val
2427 pad = " " * (width - len(val))
2428 sep = " "
2429 QApplication.clipboard().setText(text)
2430
2431def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
2432 indexes = view.selectedIndexes()
2433 if not len(indexes):
2434 return
2435
2436 selection = view.selectionModel()
2437
2438 first = None
2439 for i in indexes:
2440 above = view.indexAbove(i)
2441 if not selection.isSelected(above):
2442 first = i
2443 break
2444
2445 if first is None:
2446 raise RuntimeError("CopyTreeCellsToClipboard internal error")
2447
2448 model = first.model()
2449 row_cnt = 0
2450 col_cnt = model.columnCount(first)
2451 max_width = [0] * col_cnt
2452
2453 indent_sz = 2
2454 indent_str = " " * indent_sz
2455
2456 expanded_mark_sz = 2
2457 if sys.version_info[0] == 3:
2458 expanded_mark = "\u25BC "
2459 not_expanded_mark = "\u25B6 "
2460 else:
2461 expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
2462 not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
2463 leaf_mark = " "
2464
2465 if not as_csv:
2466 pos = first
2467 while True:
2468 row_cnt += 1
2469 row = pos.row()
2470 for c in range(col_cnt):
2471 i = pos.sibling(row, c)
2472 if c:
2473 n = len(str(i.data()))
2474 else:
2475 n = len(str(i.data()).strip())
2476 n += (i.internalPointer().level - 1) * indent_sz
2477 n += expanded_mark_sz
2478 max_width[c] = max(max_width[c], n)
2479 pos = view.indexBelow(pos)
2480 if not selection.isSelected(pos):
2481 break
2482
2483 text = ""
2484 pad = ""
2485 sep = ""
2486 if with_hdr:
2487 for c in range(col_cnt):
2488 val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
2489 if as_csv:
2490 text += sep + ToCSValue(val)
2491 sep = ","
2492 else:
2493 max_width[c] = max(max_width[c], len(val))
2494 width = max_width[c]
2495 align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
2496 if align & Qt.AlignRight:
2497 val = val.rjust(width)
2498 text += pad + sep + val
2499 pad = " " * (width - len(val))
2500 sep = " "
2501 text += "\n"
2502 pad = ""
2503 sep = ""
2504
2505 pos = first
2506 while True:
2507 row = pos.row()
2508 for c in range(col_cnt):
2509 i = pos.sibling(row, c)
2510 val = str(i.data())
2511 if not c:
2512 if model.hasChildren(i):
2513 if view.isExpanded(i):
2514 mark = expanded_mark
2515 else:
2516 mark = not_expanded_mark
2517 else:
2518 mark = leaf_mark
2519 val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
2520 if as_csv:
2521 text += sep + ToCSValue(val)
2522 sep = ","
2523 else:
2524 width = max_width[c]
2525 if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
2526 val = val.rjust(width)
2527 text += pad + sep + val
2528 pad = " " * (width - len(val))
2529 sep = " "
2530 pos = view.indexBelow(pos)
2531 if not selection.isSelected(pos):
2532 break
2533 text = text.rstrip() + "\n"
2534 pad = ""
2535 sep = ""
2536
2537 QApplication.clipboard().setText(text)
2538
2539def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
2540 view.CopyCellsToClipboard(view, as_csv, with_hdr)
2541
2542def CopyCellsToClipboardHdr(view):
2543 CopyCellsToClipboard(view, False, True)
2544
2545def CopyCellsToClipboardCSV(view):
2546 CopyCellsToClipboard(view, True, True)
2547
Adrian Hunter9bc4e4b2019-05-03 15:08:27 +03002548# Context menu
2549
2550class ContextMenu(object):
2551
2552 def __init__(self, view):
2553 self.view = view
2554 self.view.setContextMenuPolicy(Qt.CustomContextMenu)
2555 self.view.customContextMenuRequested.connect(self.ShowContextMenu)
2556
2557 def ShowContextMenu(self, pos):
2558 menu = QMenu(self.view)
2559 self.AddActions(menu)
2560 menu.exec_(self.view.mapToGlobal(pos))
2561
2562 def AddCopy(self, menu):
2563 menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view))
2564 menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view))
2565
2566 def AddActions(self, menu):
2567 self.AddCopy(menu)
2568
2569class TreeContextMenu(ContextMenu):
2570
2571 def __init__(self, view):
2572 super(TreeContextMenu, self).__init__(view)
2573
2574 def AddActions(self, menu):
2575 i = self.view.currentIndex()
2576 text = str(i.data()).strip()
2577 if len(text):
2578 menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view))
2579 self.AddCopy(menu)
2580
Adrian Hunter8392b742018-10-01 09:28:50 +03002581# Table window
2582
2583class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2584
2585 def __init__(self, glb, table_name, parent=None):
2586 super(TableWindow, self).__init__(parent)
2587
2588 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
2589
2590 self.model = QSortFilterProxyModel()
2591 self.model.setSourceModel(self.data_model)
2592
2593 self.view = QTableView()
2594 self.view.setModel(self.model)
2595 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2596 self.view.verticalHeader().setVisible(False)
2597 self.view.sortByColumn(-1, Qt.AscendingOrder)
2598 self.view.setSortingEnabled(True)
Adrian Hunter96c43b92019-05-03 15:08:26 +03002599 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2600 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
Adrian Hunter8392b742018-10-01 09:28:50 +03002601
2602 self.ResizeColumnsToContents()
2603
Adrian Hunter9bc4e4b2019-05-03 15:08:27 +03002604 self.context_menu = ContextMenu(self.view)
2605
Adrian Hunter8392b742018-10-01 09:28:50 +03002606 self.find_bar = FindBar(self, self, True)
2607
2608 self.finder = ChildDataItemFinder(self.data_model)
2609
2610 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2611
2612 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2613
2614 self.setWidget(self.vbox.Widget())
2615
2616 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
2617
2618 def Find(self, value, direction, pattern, context):
2619 self.view.setFocus()
2620 self.find_bar.Busy()
2621 self.finder.Find(value, direction, pattern, context, self.FindDone)
2622
2623 def FindDone(self, row):
2624 self.find_bar.Idle()
2625 if row >= 0:
Adrian Hunter35fa1ce2018-11-04 17:12:38 +02002626 self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex())))
Adrian Hunter8392b742018-10-01 09:28:50 +03002627 else:
2628 self.find_bar.NotFound()
2629
2630# Table list
2631
2632def GetTableList(glb):
2633 tables = []
2634 query = QSqlQuery(glb.db)
2635 if glb.dbref.is_sqlite3:
2636 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
2637 else:
2638 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
2639 while query.next():
2640 tables.append(query.value(0))
2641 if glb.dbref.is_sqlite3:
2642 tables.append("sqlite_master")
2643 else:
2644 tables.append("information_schema.tables")
2645 tables.append("information_schema.views")
2646 tables.append("information_schema.columns")
2647 return tables
2648
Adrian Huntercd358012019-02-22 09:27:28 +02002649# Top Calls data model
2650
2651class TopCallsModel(SQLTableModel):
2652
2653 def __init__(self, glb, report_vars, parent=None):
2654 text = ""
2655 if not glb.dbref.is_sqlite3:
2656 text = "::text"
2657 limit = ""
2658 if len(report_vars.limit):
2659 limit = " LIMIT " + report_vars.limit
2660 sql = ("SELECT comm, pid, tid, name,"
2661 " CASE"
2662 " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text +
2663 " ELSE short_name"
2664 " END AS dso,"
2665 " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, "
2666 " CASE"
2667 " WHEN (calls.flags = 1) THEN 'no call'" + text +
2668 " WHEN (calls.flags = 2) THEN 'no return'" + text +
2669 " WHEN (calls.flags = 3) THEN 'no call/return'" + text +
2670 " ELSE ''" + text +
2671 " END AS flags"
2672 " FROM calls"
2673 " INNER JOIN call_paths ON calls.call_path_id = call_paths.id"
2674 " INNER JOIN symbols ON call_paths.symbol_id = symbols.id"
2675 " INNER JOIN dsos ON symbols.dso_id = dsos.id"
2676 " INNER JOIN comms ON calls.comm_id = comms.id"
2677 " INNER JOIN threads ON calls.thread_id = threads.id" +
2678 report_vars.where_clause +
2679 " ORDER BY elapsed_time DESC" +
2680 limit
2681 )
2682 column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags")
2683 self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft)
2684 super(TopCallsModel, self).__init__(glb, sql, column_headers, parent)
2685
2686 def columnAlignment(self, column):
2687 return self.alignment[column]
2688
2689# Top Calls report creation dialog
2690
2691class TopCallsDialog(ReportDialogBase):
2692
2693 def __init__(self, glb, parent=None):
2694 title = "Top Calls by Elapsed Time"
2695 items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"),
2696 lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p),
2697 lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p),
2698 lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p),
2699 lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p),
2700 lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p),
2701 lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p),
2702 lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100"))
2703 super(TopCallsDialog, self).__init__(glb, title, items, False, parent)
2704
2705# Top Calls window
2706
2707class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
2708
2709 def __init__(self, glb, report_vars, parent=None):
2710 super(TopCallsWindow, self).__init__(parent)
2711
2712 self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars))
2713 self.model = self.data_model
2714
2715 self.view = QTableView()
2716 self.view.setModel(self.model)
2717 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
2718 self.view.verticalHeader().setVisible(False)
Adrian Hunter96c43b92019-05-03 15:08:26 +03002719 self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
2720 self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
Adrian Huntercd358012019-02-22 09:27:28 +02002721
Adrian Hunter9bc4e4b2019-05-03 15:08:27 +03002722 self.context_menu = ContextMenu(self.view)
2723
Adrian Huntercd358012019-02-22 09:27:28 +02002724 self.ResizeColumnsToContents()
2725
2726 self.find_bar = FindBar(self, self, True)
2727
2728 self.finder = ChildDataItemFinder(self.model)
2729
2730 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
2731
2732 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
2733
2734 self.setWidget(self.vbox.Widget())
2735
2736 AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name)
2737
2738 def Find(self, value, direction, pattern, context):
2739 self.view.setFocus()
2740 self.find_bar.Busy()
2741 self.finder.Find(value, direction, pattern, context, self.FindDone)
2742
2743 def FindDone(self, row):
2744 self.find_bar.Idle()
2745 if row >= 0:
2746 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
2747 else:
2748 self.find_bar.NotFound()
2749
Adrian Hunter1beb5c72018-10-01 09:28:47 +03002750# Action Definition
2751
2752def CreateAction(label, tip, callback, parent=None, shortcut=None):
2753 action = QAction(label, parent)
2754 if shortcut != None:
2755 action.setShortcuts(shortcut)
2756 action.setStatusTip(tip)
2757 action.triggered.connect(callback)
2758 return action
2759
2760# Typical application actions
2761
2762def CreateExitAction(app, parent=None):
2763 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit)
2764
2765# Typical MDI actions
2766
2767def CreateCloseActiveWindowAction(mdi_area):
2768 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area)
2769
2770def CreateCloseAllWindowsAction(mdi_area):
2771 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area)
2772
2773def CreateTileWindowsAction(mdi_area):
2774 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area)
2775
2776def CreateCascadeWindowsAction(mdi_area):
2777 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area)
2778
2779def CreateNextWindowAction(mdi_area):
2780 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild)
2781
2782def CreatePreviousWindowAction(mdi_area):
2783 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild)
2784
2785# Typical MDI window menu
2786
2787class WindowMenu():
2788
2789 def __init__(self, mdi_area, menu):
2790 self.mdi_area = mdi_area
2791 self.window_menu = menu.addMenu("&Windows")
2792 self.close_active_window = CreateCloseActiveWindowAction(mdi_area)
2793 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area)
2794 self.tile_windows = CreateTileWindowsAction(mdi_area)
2795 self.cascade_windows = CreateCascadeWindowsAction(mdi_area)
2796 self.next_window = CreateNextWindowAction(mdi_area)
2797 self.previous_window = CreatePreviousWindowAction(mdi_area)
2798 self.window_menu.aboutToShow.connect(self.Update)
2799
2800 def Update(self):
2801 self.window_menu.clear()
2802 sub_window_count = len(self.mdi_area.subWindowList())
2803 have_sub_windows = sub_window_count != 0
2804 self.close_active_window.setEnabled(have_sub_windows)
2805 self.close_all_windows.setEnabled(have_sub_windows)
2806 self.tile_windows.setEnabled(have_sub_windows)
2807 self.cascade_windows.setEnabled(have_sub_windows)
2808 self.next_window.setEnabled(have_sub_windows)
2809 self.previous_window.setEnabled(have_sub_windows)
2810 self.window_menu.addAction(self.close_active_window)
2811 self.window_menu.addAction(self.close_all_windows)
2812 self.window_menu.addSeparator()
2813 self.window_menu.addAction(self.tile_windows)
2814 self.window_menu.addAction(self.cascade_windows)
2815 self.window_menu.addSeparator()
2816 self.window_menu.addAction(self.next_window)
2817 self.window_menu.addAction(self.previous_window)
2818 if sub_window_count == 0:
2819 return
2820 self.window_menu.addSeparator()
2821 nr = 1
2822 for sub_window in self.mdi_area.subWindowList():
2823 label = str(nr) + " " + sub_window.name
2824 if nr < 10:
2825 label = "&" + label
2826 action = self.window_menu.addAction(label)
2827 action.setCheckable(True)
2828 action.setChecked(sub_window == self.mdi_area.activeSubWindow())
Adrian Hunterdf8ea222019-04-12 14:38:25 +03002829 action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x))
Adrian Hunter1beb5c72018-10-01 09:28:47 +03002830 self.window_menu.addAction(action)
2831 nr += 1
2832
2833 def setActiveSubWindow(self, nr):
2834 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1])
2835
Adrian Hunter65b24292018-11-04 17:12:37 +02002836# Help text
2837
2838glb_help_text = """
2839<h1>Contents</h1>
2840<style>
2841p.c1 {
2842 text-indent: 40px;
2843}
2844p.c2 {
2845 text-indent: 80px;
2846}
2847}
2848</style>
2849<p class=c1><a href=#reports>1. Reports</a></p>
2850<p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
Adrian Hunterae8b8872019-02-28 15:00:31 +02002851<p class=c2><a href=#calltree>1.2 Call Tree</a></p>
2852<p class=c2><a href=#allbranches>1.3 All branches</a></p>
2853<p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p>
2854<p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p>
Adrian Hunter65b24292018-11-04 17:12:37 +02002855<p class=c1><a href=#tables>2. Tables</a></p>
2856<h1 id=reports>1. Reports</h1>
2857<h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>
2858The result is a GUI window with a tree representing a context-sensitive
2859call-graph. Expanding a couple of levels of the tree and adjusting column
2860widths to suit will display something like:
2861<pre>
2862 Call Graph: pt_example
2863Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)
2864v- ls
2865 v- 2638:2638
2866 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0
2867 |- unknown unknown 1 13198 0.1 1 0.0
2868 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3
2869 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3
2870 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4
2871 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1
2872 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0
2873 >- __libc_csu_init ls 1 10354 0.1 10 0.0
2874 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0
2875 v- main ls 1 8182043 99.6 180254 99.9
2876</pre>
2877<h3>Points to note:</h3>
2878<ul>
2879<li>The top level is a command name (comm)</li>
2880<li>The next level is a thread (pid:tid)</li>
2881<li>Subsequent levels are functions</li>
2882<li>'Count' is the number of calls</li>
2883<li>'Time' is the elapsed time until the function returns</li>
2884<li>Percentages are relative to the level above</li>
2885<li>'Branch Count' is the total number of branches for that function and all functions that it calls
2886</ul>
2887<h3>Find</h3>
2888Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.
2889The pattern matching symbols are ? for any character and * for zero or more characters.
Adrian Hunterae8b8872019-02-28 15:00:31 +02002890<h2 id=calltree>1.2 Call Tree</h2>
2891The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.
2892Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.
2893<h2 id=allbranches>1.3 All branches</h2>
Adrian Hunter65b24292018-11-04 17:12:37 +02002894The All branches report displays all branches in chronological order.
2895Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.
2896<h3>Disassembly</h3>
2897Open a branch to display disassembly. This only works if:
2898<ol>
2899<li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li>
2900<li>The object code is available. Currently, only the perf build ID cache is searched for object code.
2901The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR.
2902One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu),
2903or alternatively, set environment variable PERF_KCORE to the kcore file name.</li>
2904</ol>
2905<h4 id=xed>Intel XED Setup</h4>
2906To use Intel XED, libxed.so must be present. To build and install libxed.so:
2907<pre>
2908git clone https://github.com/intelxed/mbuild.git mbuild
2909git clone https://github.com/intelxed/xed
2910cd xed
2911./mfile.py --share
2912sudo ./mfile.py --prefix=/usr/local install
2913sudo ldconfig
2914</pre>
Adrian Hunter530e22f2019-05-20 14:37:24 +03002915<h3>Instructions per Cycle (IPC)</h3>
2916If available, IPC information is displayed in columns 'insn_cnt', 'cyc_cnt' and 'IPC'.
2917<p><b>Intel PT note:</b> The information applies to the blocks of code ending with, and including, that branch.
2918Due to the granularity of timing information, the number of cycles for some code blocks will not be known.
2919In that case, 'insn_cnt', 'cyc_cnt' and 'IPC' are zero, but when 'IPC' is displayed it covers the period
2920since the previous displayed 'IPC'.
Adrian Hunter65b24292018-11-04 17:12:37 +02002921<h3>Find</h3>
2922Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2923Refer to Python documentation for the regular expression syntax.
2924All columns are searched, but only currently fetched rows are searched.
Adrian Hunterae8b8872019-02-28 15:00:31 +02002925<h2 id=selectedbranches>1.4 Selected branches</h2>
Adrian Hunter65b24292018-11-04 17:12:37 +02002926This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced
2927by various selection criteria. A dialog box displays available criteria which are AND'ed together.
Adrian Hunterae8b8872019-02-28 15:00:31 +02002928<h3>1.4.1 Time ranges</h3>
Adrian Hunter65b24292018-11-04 17:12:37 +02002929The time ranges hint text shows the total time range. Relative time ranges can also be entered in
2930ms, us or ns. Also, negative values are relative to the end of trace. Examples:
2931<pre>
2932 81073085947329-81073085958238 From 81073085947329 to 81073085958238
2933 100us-200us From 100us to 200us
2934 10ms- From 10ms to the end
2935 -100ns The first 100ns
2936 -10ms- The last 10ms
2937</pre>
2938N.B. Due to the granularity of timestamps, there could be no branches in any given time range.
Adrian Hunterae8b8872019-02-28 15:00:31 +02002939<h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2>
Adrian Huntercd358012019-02-22 09:27:28 +02002940The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned.
2941The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together.
2942If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar.
Adrian Hunter65b24292018-11-04 17:12:37 +02002943<h1 id=tables>2. Tables</h1>
2944The Tables menu shows all tables and views in the database. Most tables have an associated view
2945which displays the information in a more friendly way. Not all data for large tables is fetched
2946immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted,
2947but that can be slow for large tables.
2948<p>There are also tables of database meta-information.
2949For SQLite3 databases, the sqlite_master table is included.
2950For PostgreSQL databases, information_schema.tables/views/columns are included.
2951<h3>Find</h3>
2952Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match.
2953Refer to Python documentation for the regular expression syntax.
2954All columns are searched, but only currently fetched rows are searched.
Adrian Hunter35fa1ce2018-11-04 17:12:38 +02002955<p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous
2956will go to the next/previous result in id order, instead of display order.
Adrian Hunter65b24292018-11-04 17:12:37 +02002957"""
2958
2959# Help window
2960
2961class HelpWindow(QMdiSubWindow):
2962
2963 def __init__(self, glb, parent=None):
2964 super(HelpWindow, self).__init__(parent)
2965
2966 self.text = QTextBrowser()
2967 self.text.setHtml(glb_help_text)
2968 self.text.setReadOnly(True)
2969 self.text.setOpenExternalLinks(True)
2970
2971 self.setWidget(self.text)
2972
2973 AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help")
2974
2975# Main window that only displays the help text
2976
2977class HelpOnlyWindow(QMainWindow):
2978
2979 def __init__(self, parent=None):
2980 super(HelpOnlyWindow, self).__init__(parent)
2981
2982 self.setMinimumSize(200, 100)
2983 self.resize(800, 600)
2984 self.setWindowTitle("Exported SQL Viewer Help")
2985 self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation))
2986
2987 self.text = QTextBrowser()
2988 self.text.setHtml(glb_help_text)
2989 self.text.setReadOnly(True)
2990 self.text.setOpenExternalLinks(True)
2991
2992 self.setCentralWidget(self.text)
2993
Adrian Hunterb62d18a2019-05-03 15:08:28 +03002994# PostqreSQL server version
2995
2996def PostqreSQLServerVersion(db):
2997 query = QSqlQuery(db)
2998 QueryExec(query, "SELECT VERSION()")
2999 if query.next():
3000 v_str = query.value(0)
3001 v_list = v_str.strip().split(" ")
3002 if v_list[0] == "PostgreSQL" and v_list[2] == "on":
3003 return v_list[1]
3004 return v_str
3005 return "Unknown"
3006
3007# SQLite version
3008
3009def SQLiteVersion(db):
3010 query = QSqlQuery(db)
3011 QueryExec(query, "SELECT sqlite_version()")
3012 if query.next():
3013 return query.value(0)
3014 return "Unknown"
3015
3016# About dialog
3017
3018class AboutDialog(QDialog):
3019
3020 def __init__(self, glb, parent=None):
3021 super(AboutDialog, self).__init__(parent)
3022
3023 self.setWindowTitle("About Exported SQL Viewer")
3024 self.setMinimumWidth(300)
3025
3026 pyside_version = "1" if pyside_version_1 else "2"
3027
3028 text = "<pre>"
3029 text += "Python version: " + sys.version.split(" ")[0] + "\n"
3030 text += "PySide version: " + pyside_version + "\n"
3031 text += "Qt version: " + qVersion() + "\n"
3032 if glb.dbref.is_sqlite3:
3033 text += "SQLite version: " + SQLiteVersion(glb.db) + "\n"
3034 else:
3035 text += "PostqreSQL version: " + PostqreSQLServerVersion(glb.db) + "\n"
3036 text += "</pre>"
3037
3038 self.text = QTextBrowser()
3039 self.text.setHtml(text)
3040 self.text.setReadOnly(True)
3041 self.text.setOpenExternalLinks(True)
3042
3043 self.vbox = QVBoxLayout()
3044 self.vbox.addWidget(self.text)
3045
3046 self.setLayout(self.vbox);
3047
Adrian Hunter82f68e22018-10-01 09:28:49 +03003048# Font resize
3049
3050def ResizeFont(widget, diff):
3051 font = widget.font()
3052 sz = font.pointSize()
3053 font.setPointSize(sz + diff)
3054 widget.setFont(font)
3055
3056def ShrinkFont(widget):
3057 ResizeFont(widget, -1)
3058
3059def EnlargeFont(widget):
3060 ResizeFont(widget, 1)
3061
Adrian Hunter1beb5c72018-10-01 09:28:47 +03003062# Unique name for sub-windows
3063
3064def NumberedWindowName(name, nr):
3065 if nr > 1:
3066 name += " <" + str(nr) + ">"
3067 return name
3068
3069def UniqueSubWindowName(mdi_area, name):
3070 nr = 1
3071 while True:
3072 unique_name = NumberedWindowName(name, nr)
3073 ok = True
3074 for sub_window in mdi_area.subWindowList():
3075 if sub_window.name == unique_name:
3076 ok = False
3077 break
3078 if ok:
3079 return unique_name
3080 nr += 1
3081
3082# Add a sub-window
3083
3084def AddSubWindow(mdi_area, sub_window, name):
3085 unique_name = UniqueSubWindowName(mdi_area, name)
3086 sub_window.setMinimumSize(200, 100)
3087 sub_window.resize(800, 600)
3088 sub_window.setWindowTitle(unique_name)
3089 sub_window.setAttribute(Qt.WA_DeleteOnClose)
3090 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon))
3091 sub_window.name = unique_name
3092 mdi_area.addSubWindow(sub_window)
3093 sub_window.show()
3094
Adrian Hunter70d831e2018-10-01 09:28:43 +03003095# Main window
Adrian Hunter4b715d22015-07-17 19:33:45 +03003096
3097class MainWindow(QMainWindow):
3098
Adrian Hunter5f9dfef2018-10-01 09:28:41 +03003099 def __init__(self, glb, parent=None):
Adrian Hunter4b715d22015-07-17 19:33:45 +03003100 super(MainWindow, self).__init__(parent)
3101
Adrian Hunter5f9dfef2018-10-01 09:28:41 +03003102 self.glb = glb
3103
Adrian Hunter1beb5c72018-10-01 09:28:47 +03003104 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname)
Adrian Hunter99a097c2018-10-01 09:28:38 +03003105 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
Adrian Hunter3c4ef452018-10-01 09:28:37 +03003106 self.setMinimumSize(200, 100)
Adrian Hunter4b715d22015-07-17 19:33:45 +03003107
Adrian Hunter1beb5c72018-10-01 09:28:47 +03003108 self.mdi_area = QMdiArea()
3109 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
3110 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
Adrian Hunter4b715d22015-07-17 19:33:45 +03003111
Adrian Hunter1beb5c72018-10-01 09:28:47 +03003112 self.setCentralWidget(self.mdi_area)
Adrian Hunter4b715d22015-07-17 19:33:45 +03003113
Adrian Hunter1beb5c72018-10-01 09:28:47 +03003114 menu = self.menuBar()
Adrian Hunter1d865c02018-10-01 09:28:36 +03003115
Adrian Hunter1beb5c72018-10-01 09:28:47 +03003116 file_menu = menu.addMenu("&File")
3117 file_menu.addAction(CreateExitAction(glb.app, self))
3118
Adrian Hunterebd70c72018-10-01 09:28:48 +03003119 edit_menu = menu.addMenu("&Edit")
Adrian Hunter96c43b92019-05-03 15:08:26 +03003120 edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
3121 edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
Adrian Hunterebd70c72018-10-01 09:28:48 +03003122 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
Adrian Hunter8392b742018-10-01 09:28:50 +03003123 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
Adrian Hunter82f68e22018-10-01 09:28:49 +03003124 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
3125 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
Adrian Hunterebd70c72018-10-01 09:28:48 +03003126
Adrian Hunter1beb5c72018-10-01 09:28:47 +03003127 reports_menu = menu.addMenu("&Reports")
Adrian Hunter655cb9522019-02-22 09:27:20 +02003128 if IsSelectable(glb.db, "calls"):
3129 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 +03003130
Adrian Hunterae8b8872019-02-28 15:00:31 +02003131 if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"):
3132 reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self))
3133
Adrian Hunter76099f92018-10-23 10:59:49 +03003134 self.EventMenu(GetEventList(glb.db), reports_menu)
3135
Adrian Huntercd358012019-02-22 09:27:28 +02003136 if IsSelectable(glb.db, "calls"):
3137 reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self))
3138
Adrian Hunter8392b742018-10-01 09:28:50 +03003139 self.TableMenu(GetTableList(glb), menu)
3140
Adrian Hunter1beb5c72018-10-01 09:28:47 +03003141 self.window_menu = WindowMenu(self.mdi_area, menu)
3142
Adrian Hunter65b24292018-11-04 17:12:37 +02003143 help_menu = menu.addMenu("&Help")
3144 help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents))
Adrian Hunterb62d18a2019-05-03 15:08:28 +03003145 help_menu.addAction(CreateAction("&About Exported SQL Viewer", "About this application", self.About, self))
Adrian Hunter65b24292018-11-04 17:12:37 +02003146
Adrian Hunter4b208452019-05-03 15:08:23 +03003147 def Try(self, fn):
3148 win = self.mdi_area.activeSubWindow()
3149 if win:
3150 try:
3151 fn(win.view)
3152 except:
3153 pass
3154
Adrian Hunter96c43b92019-05-03 15:08:26 +03003155 def CopyToClipboard(self):
3156 self.Try(CopyCellsToClipboardHdr)
3157
3158 def CopyToClipboardCSV(self):
3159 self.Try(CopyCellsToClipboardCSV)
3160
Adrian Hunterebd70c72018-10-01 09:28:48 +03003161 def Find(self):
3162 win = self.mdi_area.activeSubWindow()
3163 if win:
3164 try:
3165 win.find_bar.Activate()
3166 except:
3167 pass
3168
Adrian Hunter8392b742018-10-01 09:28:50 +03003169 def FetchMoreRecords(self):
3170 win = self.mdi_area.activeSubWindow()
3171 if win:
3172 try:
3173 win.fetch_bar.Activate()
3174 except:
3175 pass
3176
Adrian Hunter82f68e22018-10-01 09:28:49 +03003177 def ShrinkFont(self):
Adrian Hunter4b208452019-05-03 15:08:23 +03003178 self.Try(ShrinkFont)
Adrian Hunter82f68e22018-10-01 09:28:49 +03003179
3180 def EnlargeFont(self):
Adrian Hunter4b208452019-05-03 15:08:23 +03003181 self.Try(EnlargeFont)
Adrian Hunter82f68e22018-10-01 09:28:49 +03003182
Adrian Hunter76099f92018-10-23 10:59:49 +03003183 def EventMenu(self, events, reports_menu):
3184 branches_events = 0
3185 for event in events:
3186 event = event.split(":")[0]
3187 if event == "branches":
3188 branches_events += 1
3189 dbid = 0
3190 for event in events:
3191 dbid += 1
3192 event = event.split(":")[0]
3193 if event == "branches":
3194 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
Adrian Hunterdf8ea222019-04-12 14:38:25 +03003195 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewBranchView(x), self))
Adrian Hunter210cf1f2018-11-04 17:12:36 +02003196 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")"
Adrian Hunterdf8ea222019-04-12 14:38:25 +03003197 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda a=None,x=dbid: self.NewSelectedBranchView(x), self))
Adrian Hunter76099f92018-10-23 10:59:49 +03003198
Adrian Hunter8392b742018-10-01 09:28:50 +03003199 def TableMenu(self, tables, menu):
3200 table_menu = menu.addMenu("&Tables")
3201 for table in tables:
Adrian Hunterdf8ea222019-04-12 14:38:25 +03003202 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda a=None,t=table: self.NewTableView(t), self))
Adrian Hunter8392b742018-10-01 09:28:50 +03003203
Adrian Hunter1beb5c72018-10-01 09:28:47 +03003204 def NewCallGraph(self):
3205 CallGraphWindow(self.glb, self)
Adrian Hunter4b715d22015-07-17 19:33:45 +03003206
Adrian Hunterae8b8872019-02-28 15:00:31 +02003207 def NewCallTree(self):
3208 CallTreeWindow(self.glb, self)
3209
Adrian Huntercd358012019-02-22 09:27:28 +02003210 def NewTopCalls(self):
3211 dialog = TopCallsDialog(self.glb, self)
3212 ret = dialog.exec_()
3213 if ret:
3214 TopCallsWindow(self.glb, dialog.report_vars, self)
3215
Adrian Hunter76099f92018-10-23 10:59:49 +03003216 def NewBranchView(self, event_id):
Adrian Hunter947cc382019-02-22 09:27:24 +02003217 BranchWindow(self.glb, event_id, ReportVars(), self)
Adrian Hunter76099f92018-10-23 10:59:49 +03003218
Adrian Hunter210cf1f2018-11-04 17:12:36 +02003219 def NewSelectedBranchView(self, event_id):
3220 dialog = SelectedBranchDialog(self.glb, self)
3221 ret = dialog.exec_()
3222 if ret:
Adrian Hunter947cc382019-02-22 09:27:24 +02003223 BranchWindow(self.glb, event_id, dialog.report_vars, self)
Adrian Hunter210cf1f2018-11-04 17:12:36 +02003224
Adrian Hunter8392b742018-10-01 09:28:50 +03003225 def NewTableView(self, table_name):
3226 TableWindow(self.glb, table_name, self)
3227
Adrian Hunter65b24292018-11-04 17:12:37 +02003228 def Help(self):
3229 HelpWindow(self.glb, self)
3230
Adrian Hunterb62d18a2019-05-03 15:08:28 +03003231 def About(self):
3232 dialog = AboutDialog(self.glb, self)
3233 dialog.exec_()
3234
Adrian Hunter76099f92018-10-23 10:59:49 +03003235# XED Disassembler
3236
3237class xed_state_t(Structure):
3238
3239 _fields_ = [
3240 ("mode", c_int),
3241 ("width", c_int)
3242 ]
3243
3244class XEDInstruction():
3245
3246 def __init__(self, libxed):
3247 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
3248 xedd_t = c_byte * 512
3249 self.xedd = xedd_t()
3250 self.xedp = addressof(self.xedd)
3251 libxed.xed_decoded_inst_zero(self.xedp)
3252 self.state = xed_state_t()
3253 self.statep = addressof(self.state)
3254 # Buffer for disassembled instruction text
3255 self.buffer = create_string_buffer(256)
3256 self.bufferp = addressof(self.buffer)
3257
3258class LibXED():
3259
3260 def __init__(self):
Adrian Hunter5ed44192018-11-04 17:12:35 +02003261 try:
3262 self.libxed = CDLL("libxed.so")
3263 except:
3264 self.libxed = None
3265 if not self.libxed:
3266 self.libxed = CDLL("/usr/local/lib/libxed.so")
Adrian Hunter76099f92018-10-23 10:59:49 +03003267
3268 self.xed_tables_init = self.libxed.xed_tables_init
3269 self.xed_tables_init.restype = None
3270 self.xed_tables_init.argtypes = []
3271
3272 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
3273 self.xed_decoded_inst_zero.restype = None
3274 self.xed_decoded_inst_zero.argtypes = [ c_void_p ]
3275
3276 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
3277 self.xed_operand_values_set_mode.restype = None
3278 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ]
3279
3280 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
3281 self.xed_decoded_inst_zero_keep_mode.restype = None
3282 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ]
3283
3284 self.xed_decode = self.libxed.xed_decode
3285 self.xed_decode.restype = c_int
3286 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ]
3287
3288 self.xed_format_context = self.libxed.xed_format_context
3289 self.xed_format_context.restype = c_uint
3290 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ]
3291
3292 self.xed_tables_init()
3293
3294 def Instruction(self):
3295 return XEDInstruction(self)
3296
3297 def SetMode(self, inst, mode):
3298 if mode:
3299 inst.state.mode = 4 # 32-bit
3300 inst.state.width = 4 # 4 bytes
3301 else:
3302 inst.state.mode = 1 # 64-bit
3303 inst.state.width = 8 # 8 bytes
3304 self.xed_operand_values_set_mode(inst.xedp, inst.statep)
3305
3306 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip):
3307 self.xed_decoded_inst_zero_keep_mode(inst.xedp)
3308 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
3309 if err:
3310 return 0, ""
3311 # Use AT&T mode (2), alternative is Intel (3)
3312 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
3313 if not ok:
3314 return 0, ""
Adrian Hunter606bd602019-03-27 09:28:26 +02003315 if sys.version_info[0] == 2:
3316 result = inst.buffer.value
3317 else:
3318 result = inst.buffer.value.decode()
Adrian Hunter76099f92018-10-23 10:59:49 +03003319 # Return instruction length and the disassembled instruction text
3320 # For now, assume the length is in byte 166
Adrian Hunter606bd602019-03-27 09:28:26 +02003321 return inst.xedd[166], result
Adrian Hunter76099f92018-10-23 10:59:49 +03003322
3323def TryOpen(file_name):
3324 try:
3325 return open(file_name, "rb")
3326 except:
3327 return None
3328
3329def Is64Bit(f):
3330 result = sizeof(c_void_p)
3331 # ELF support only
3332 pos = f.tell()
3333 f.seek(0)
3334 header = f.read(7)
3335 f.seek(pos)
3336 magic = header[0:4]
Adrian Hunter606bd602019-03-27 09:28:26 +02003337 if sys.version_info[0] == 2:
3338 eclass = ord(header[4])
3339 encoding = ord(header[5])
3340 version = ord(header[6])
3341 else:
3342 eclass = header[4]
3343 encoding = header[5]
3344 version = header[6]
Adrian Hunter76099f92018-10-23 10:59:49 +03003345 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1:
3346 result = True if eclass == 2 else False
3347 return result
3348
Adrian Hunter5f9dfef2018-10-01 09:28:41 +03003349# Global data
3350
3351class Glb():
3352
3353 def __init__(self, dbref, db, dbname):
3354 self.dbref = dbref
3355 self.db = db
3356 self.dbname = dbname
Adrian Hunter76099f92018-10-23 10:59:49 +03003357 self.home_dir = os.path.expanduser("~")
3358 self.buildid_dir = os.getenv("PERF_BUILDID_DIR")
3359 if self.buildid_dir:
3360 self.buildid_dir += "/.build-id/"
3361 else:
3362 self.buildid_dir = self.home_dir + "/.debug/.build-id/"
Adrian Hunter5f9dfef2018-10-01 09:28:41 +03003363 self.app = None
3364 self.mainwindow = None
Adrian Hunter8392b742018-10-01 09:28:50 +03003365 self.instances_to_shutdown_on_exit = weakref.WeakSet()
Adrian Hunter76099f92018-10-23 10:59:49 +03003366 try:
3367 self.disassembler = LibXED()
3368 self.have_disassembler = True
3369 except:
3370 self.have_disassembler = False
3371
3372 def FileFromBuildId(self, build_id):
3373 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf"
3374 return TryOpen(file_name)
3375
3376 def FileFromNamesAndBuildId(self, short_name, long_name, build_id):
3377 # Assume current machine i.e. no support for virtualization
3378 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore":
3379 file_name = os.getenv("PERF_KCORE")
3380 f = TryOpen(file_name) if file_name else None
3381 if f:
3382 return f
3383 # For now, no special handling if long_name is /proc/kcore
3384 f = TryOpen(long_name)
3385 if f:
3386 return f
3387 f = self.FileFromBuildId(build_id)
3388 if f:
3389 return f
3390 return None
Adrian Hunter8392b742018-10-01 09:28:50 +03003391
3392 def AddInstanceToShutdownOnExit(self, instance):
3393 self.instances_to_shutdown_on_exit.add(instance)
3394
3395 # Shutdown any background processes or threads
3396 def ShutdownInstances(self):
3397 for x in self.instances_to_shutdown_on_exit:
3398 try:
3399 x.Shutdown()
3400 except:
3401 pass
Adrian Hunter5f9dfef2018-10-01 09:28:41 +03003402
Adrian Hunterb2556c42018-10-01 09:28:40 +03003403# Database reference
3404
3405class DBRef():
3406
3407 def __init__(self, is_sqlite3, dbname):
3408 self.is_sqlite3 = is_sqlite3
3409 self.dbname = dbname
3410
3411 def Open(self, connection_name):
3412 dbname = self.dbname
3413 if self.is_sqlite3:
3414 db = QSqlDatabase.addDatabase("QSQLITE", connection_name)
3415 else:
3416 db = QSqlDatabase.addDatabase("QPSQL", connection_name)
3417 opts = dbname.split()
3418 for opt in opts:
3419 if "=" in opt:
3420 opt = opt.split("=")
3421 if opt[0] == "hostname":
3422 db.setHostName(opt[1])
3423 elif opt[0] == "port":
3424 db.setPort(int(opt[1]))
3425 elif opt[0] == "username":
3426 db.setUserName(opt[1])
3427 elif opt[0] == "password":
3428 db.setPassword(opt[1])
3429 elif opt[0] == "dbname":
3430 dbname = opt[1]
3431 else:
3432 dbname = opt
3433
3434 db.setDatabaseName(dbname)
3435 if not db.open():
3436 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text())
3437 return db, dbname
3438
Adrian Hunter7e4fc932018-10-01 09:28:39 +03003439# Main
3440
3441def Main():
Adrian Hunter1ed7f472019-04-12 14:38:24 +03003442 usage_str = "exported-sql-viewer.py [--pyside-version-1] <database name>\n" \
3443 " or: exported-sql-viewer.py --help-only"
3444 ap = argparse.ArgumentParser(usage = usage_str, add_help = False)
Adrian Hunterdf8ea222019-04-12 14:38:25 +03003445 ap.add_argument("--pyside-version-1", action='store_true')
Adrian Hunter1ed7f472019-04-12 14:38:24 +03003446 ap.add_argument("dbname", nargs="?")
3447 ap.add_argument("--help-only", action='store_true')
3448 args = ap.parse_args()
Adrian Hunter4b715d22015-07-17 19:33:45 +03003449
Adrian Hunter1ed7f472019-04-12 14:38:24 +03003450 if args.help_only:
Adrian Hunter65b24292018-11-04 17:12:37 +02003451 app = QApplication(sys.argv)
3452 mainwindow = HelpOnlyWindow()
3453 mainwindow.show()
3454 err = app.exec_()
3455 sys.exit(err)
Adrian Hunter4b715d22015-07-17 19:33:45 +03003456
Adrian Hunter1ed7f472019-04-12 14:38:24 +03003457 dbname = args.dbname
3458 if dbname is None:
3459 ap.print_usage()
3460 print("Too few arguments")
3461 sys.exit(1)
3462
Adrian Hunter1fe03b52017-08-03 11:31:30 +03003463 is_sqlite3 = False
3464 try:
Tony Jonesbeda0e72019-03-08 16:05:15 -08003465 f = open(dbname, "rb")
3466 if f.read(15) == b'SQLite format 3':
Adrian Hunter1fe03b52017-08-03 11:31:30 +03003467 is_sqlite3 = True
3468 f.close()
3469 except:
3470 pass
Adrian Hunter4b715d22015-07-17 19:33:45 +03003471
Adrian Hunterb2556c42018-10-01 09:28:40 +03003472 dbref = DBRef(is_sqlite3, dbname)
3473 db, dbname = dbref.Open("main")
Adrian Hunter5f9dfef2018-10-01 09:28:41 +03003474 glb = Glb(dbref, db, dbname)
Adrian Hunter4b715d22015-07-17 19:33:45 +03003475 app = QApplication(sys.argv)
Adrian Hunter5f9dfef2018-10-01 09:28:41 +03003476 glb.app = app
3477 mainwindow = MainWindow(glb)
3478 glb.mainwindow = mainwindow
3479 mainwindow.show()
Adrian Hunter4b715d22015-07-17 19:33:45 +03003480 err = app.exec_()
Adrian Hunter8392b742018-10-01 09:28:50 +03003481 glb.ShutdownInstances()
Adrian Hunter4b715d22015-07-17 19:33:45 +03003482 db.close()
3483 sys.exit(err)
Adrian Hunter7e4fc932018-10-01 09:28:39 +03003484
3485if __name__ == "__main__":
3486 Main()