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