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