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