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