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