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