1#!/usr/bin/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# Branch window 1402 1403class BranchWindow(QMdiSubWindow): 1404 1405 def __init__(self, glb, event_id, name, where_clause, parent=None): 1406 super(BranchWindow, self).__init__(parent) 1407 1408 model_name = "Branch Events " + str(event_id) 1409 if len(where_clause): 1410 model_name = where_clause + " " + model_name 1411 1412 self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, where_clause)) 1413 1414 self.view = QTreeView() 1415 self.view.setUniformRowHeights(True) 1416 self.view.setModel(self.model) 1417 1418 self.ResizeColumnsToContents() 1419 1420 self.find_bar = FindBar(self, self, True) 1421 1422 self.finder = ChildDataItemFinder(self.model.root) 1423 1424 self.fetch_bar = FetchMoreRecordsBar(self.model, self) 1425 1426 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) 1427 1428 self.setWidget(self.vbox.Widget()) 1429 1430 AddSubWindow(glb.mainwindow.mdi_area, self, name + " Branch Events") 1431 1432 def ResizeColumnToContents(self, column, n): 1433 # Using the view's resizeColumnToContents() here is extrememly slow 1434 # so implement a crude alternative 1435 mm = "MM" if column else "MMMM" 1436 font = self.view.font() 1437 metrics = QFontMetrics(font) 1438 max = 0 1439 for row in xrange(n): 1440 val = self.model.root.child_items[row].data[column] 1441 len = metrics.width(str(val) + mm) 1442 max = len if len > max else max 1443 val = self.model.columnHeader(column) 1444 len = metrics.width(str(val) + mm) 1445 max = len if len > max else max 1446 self.view.setColumnWidth(column, max) 1447 1448 def ResizeColumnsToContents(self): 1449 n = min(self.model.root.child_count, 100) 1450 if n < 1: 1451 # No data yet, so connect a signal to notify when there is 1452 self.model.rowsInserted.connect(self.UpdateColumnWidths) 1453 return 1454 columns = self.model.columnCount() 1455 for i in xrange(columns): 1456 self.ResizeColumnToContents(i, n) 1457 1458 def UpdateColumnWidths(self, *x): 1459 # This only needs to be done once, so disconnect the signal now 1460 self.model.rowsInserted.disconnect(self.UpdateColumnWidths) 1461 self.ResizeColumnsToContents() 1462 1463 def Find(self, value, direction, pattern, context): 1464 self.view.setFocus() 1465 self.find_bar.Busy() 1466 self.finder.Find(value, direction, pattern, context, self.FindDone) 1467 1468 def FindDone(self, row): 1469 self.find_bar.Idle() 1470 if row >= 0: 1471 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) 1472 else: 1473 self.find_bar.NotFound() 1474 1475# Dialog data item converted and validated using a SQL table 1476 1477class SQLTableDialogDataItem(): 1478 1479 def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent): 1480 self.glb = glb 1481 self.label = label 1482 self.placeholder_text = placeholder_text 1483 self.table_name = table_name 1484 self.match_column = match_column 1485 self.column_name1 = column_name1 1486 self.column_name2 = column_name2 1487 self.parent = parent 1488 1489 self.value = "" 1490 1491 self.widget = QLineEdit() 1492 self.widget.editingFinished.connect(self.Validate) 1493 self.widget.textChanged.connect(self.Invalidate) 1494 self.red = False 1495 self.error = "" 1496 self.validated = True 1497 1498 self.last_id = 0 1499 self.first_time = 0 1500 self.last_time = 2 ** 64 1501 if self.table_name == "<timeranges>": 1502 query = QSqlQuery(self.glb.db) 1503 QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1") 1504 if query.next(): 1505 self.last_id = int(query.value(0)) 1506 self.last_time = int(query.value(1)) 1507 QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1") 1508 if query.next(): 1509 self.first_time = int(query.value(0)) 1510 if placeholder_text: 1511 placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time) 1512 1513 if placeholder_text: 1514 self.widget.setPlaceholderText(placeholder_text) 1515 1516 def ValueToIds(self, value): 1517 ids = [] 1518 query = QSqlQuery(self.glb.db) 1519 stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'" 1520 ret = query.exec_(stmt) 1521 if ret: 1522 while query.next(): 1523 ids.append(str(query.value(0))) 1524 return ids 1525 1526 def IdBetween(self, query, lower_id, higher_id, order): 1527 QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1") 1528 if query.next(): 1529 return True, int(query.value(0)) 1530 else: 1531 return False, 0 1532 1533 def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor): 1534 query = QSqlQuery(self.glb.db) 1535 while True: 1536 next_id = int((lower_id + higher_id) / 2) 1537 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) 1538 if not query.next(): 1539 ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC") 1540 if not ok: 1541 ok, dbid = self.IdBetween(query, next_id, higher_id, "") 1542 if not ok: 1543 return str(higher_id) 1544 next_id = dbid 1545 QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) 1546 next_time = int(query.value(0)) 1547 if get_floor: 1548 if target_time > next_time: 1549 lower_id = next_id 1550 else: 1551 higher_id = next_id 1552 if higher_id <= lower_id + 1: 1553 return str(higher_id) 1554 else: 1555 if target_time >= next_time: 1556 lower_id = next_id 1557 else: 1558 higher_id = next_id 1559 if higher_id <= lower_id + 1: 1560 return str(lower_id) 1561 1562 def ConvertRelativeTime(self, val): 1563 print "val ", val 1564 mult = 1 1565 suffix = val[-2:] 1566 if suffix == "ms": 1567 mult = 1000000 1568 elif suffix == "us": 1569 mult = 1000 1570 elif suffix == "ns": 1571 mult = 1 1572 else: 1573 return val 1574 val = val[:-2].strip() 1575 if not self.IsNumber(val): 1576 return val 1577 val = int(val) * mult 1578 if val >= 0: 1579 val += self.first_time 1580 else: 1581 val += self.last_time 1582 return str(val) 1583 1584 def ConvertTimeRange(self, vrange): 1585 print "vrange ", vrange 1586 if vrange[0] == "": 1587 vrange[0] = str(self.first_time) 1588 if vrange[1] == "": 1589 vrange[1] = str(self.last_time) 1590 vrange[0] = self.ConvertRelativeTime(vrange[0]) 1591 vrange[1] = self.ConvertRelativeTime(vrange[1]) 1592 print "vrange2 ", vrange 1593 if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): 1594 return False 1595 print "ok1" 1596 beg_range = max(int(vrange[0]), self.first_time) 1597 end_range = min(int(vrange[1]), self.last_time) 1598 if beg_range > self.last_time or end_range < self.first_time: 1599 return False 1600 print "ok2" 1601 vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True) 1602 vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False) 1603 print "vrange3 ", vrange 1604 return True 1605 1606 def AddTimeRange(self, value, ranges): 1607 print "value ", value 1608 n = value.count("-") 1609 if n == 1: 1610 pass 1611 elif n == 2: 1612 if value.split("-")[1].strip() == "": 1613 n = 1 1614 elif n == 3: 1615 n = 2 1616 else: 1617 return False 1618 pos = findnth(value, "-", n) 1619 vrange = [value[:pos].strip() ,value[pos+1:].strip()] 1620 if self.ConvertTimeRange(vrange): 1621 ranges.append(vrange) 1622 return True 1623 return False 1624 1625 def InvalidValue(self, value): 1626 self.value = "" 1627 palette = QPalette() 1628 palette.setColor(QPalette.Text,Qt.red) 1629 self.widget.setPalette(palette) 1630 self.red = True 1631 self.error = self.label + " invalid value '" + value + "'" 1632 self.parent.ShowMessage(self.error) 1633 1634 def IsNumber(self, value): 1635 try: 1636 x = int(value) 1637 except: 1638 x = 0 1639 return str(x) == value 1640 1641 def Invalidate(self): 1642 self.validated = False 1643 1644 def Validate(self): 1645 input_string = self.widget.text() 1646 self.validated = True 1647 if self.red: 1648 palette = QPalette() 1649 self.widget.setPalette(palette) 1650 self.red = False 1651 if not len(input_string.strip()): 1652 self.error = "" 1653 self.value = "" 1654 return 1655 if self.table_name == "<timeranges>": 1656 ranges = [] 1657 for value in [x.strip() for x in input_string.split(",")]: 1658 if not self.AddTimeRange(value, ranges): 1659 return self.InvalidValue(value) 1660 ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges] 1661 self.value = " OR ".join(ranges) 1662 elif self.table_name == "<ranges>": 1663 singles = [] 1664 ranges = [] 1665 for value in [x.strip() for x in input_string.split(",")]: 1666 if "-" in value: 1667 vrange = value.split("-") 1668 if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): 1669 return self.InvalidValue(value) 1670 ranges.append(vrange) 1671 else: 1672 if not self.IsNumber(value): 1673 return self.InvalidValue(value) 1674 singles.append(value) 1675 ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges] 1676 if len(singles): 1677 ranges.append(self.column_name1 + " IN (" + ",".join(singles) + ")") 1678 self.value = " OR ".join(ranges) 1679 elif self.table_name: 1680 all_ids = [] 1681 for value in [x.strip() for x in input_string.split(",")]: 1682 ids = self.ValueToIds(value) 1683 if len(ids): 1684 all_ids.extend(ids) 1685 else: 1686 return self.InvalidValue(value) 1687 self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")" 1688 if self.column_name2: 1689 self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )" 1690 else: 1691 self.value = input_string.strip() 1692 self.error = "" 1693 self.parent.ClearMessage() 1694 1695 def IsValid(self): 1696 if not self.validated: 1697 self.Validate() 1698 if len(self.error): 1699 self.parent.ShowMessage(self.error) 1700 return False 1701 return True 1702 1703# Selected branch report creation dialog 1704 1705class SelectedBranchDialog(QDialog): 1706 1707 def __init__(self, glb, parent=None): 1708 super(SelectedBranchDialog, self).__init__(parent) 1709 1710 self.glb = glb 1711 1712 self.name = "" 1713 self.where_clause = "" 1714 1715 self.setWindowTitle("Selected Branches") 1716 self.setMinimumWidth(600) 1717 1718 items = ( 1719 ("Report name:", "Enter a name to appear in the window title bar", "", "", "", ""), 1720 ("Time ranges:", "Enter time ranges", "<timeranges>", "", "samples.id", ""), 1721 ("CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "<ranges>", "", "cpu", ""), 1722 ("Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", ""), 1723 ("PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", ""), 1724 ("TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", ""), 1725 ("DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id"), 1726 ("Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id"), 1727 ("Raw SQL clause: ", "Enter a raw SQL WHERE clause", "", "", "", ""), 1728 ) 1729 self.data_items = [SQLTableDialogDataItem(glb, *x, parent=self) for x in items] 1730 1731 self.grid = QGridLayout() 1732 1733 for row in xrange(len(self.data_items)): 1734 self.grid.addWidget(QLabel(self.data_items[row].label), row, 0) 1735 self.grid.addWidget(self.data_items[row].widget, row, 1) 1736 1737 self.status = QLabel() 1738 1739 self.ok_button = QPushButton("Ok", self) 1740 self.ok_button.setDefault(True) 1741 self.ok_button.released.connect(self.Ok) 1742 self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 1743 1744 self.cancel_button = QPushButton("Cancel", self) 1745 self.cancel_button.released.connect(self.reject) 1746 self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 1747 1748 self.hbox = QHBoxLayout() 1749 #self.hbox.addStretch() 1750 self.hbox.addWidget(self.status) 1751 self.hbox.addWidget(self.ok_button) 1752 self.hbox.addWidget(self.cancel_button) 1753 1754 self.vbox = QVBoxLayout() 1755 self.vbox.addLayout(self.grid) 1756 self.vbox.addLayout(self.hbox) 1757 1758 self.setLayout(self.vbox); 1759 1760 def Ok(self): 1761 self.name = self.data_items[0].value 1762 if not self.name: 1763 self.ShowMessage("Report name is required") 1764 return 1765 for d in self.data_items: 1766 if not d.IsValid(): 1767 return 1768 for d in self.data_items[1:]: 1769 if len(d.value): 1770 if len(self.where_clause): 1771 self.where_clause += " AND " 1772 self.where_clause += d.value 1773 if len(self.where_clause): 1774 self.where_clause = " AND ( " + self.where_clause + " ) " 1775 else: 1776 self.ShowMessage("No selection") 1777 return 1778 self.accept() 1779 1780 def ShowMessage(self, msg): 1781 self.status.setText("<font color=#FF0000>" + msg) 1782 1783 def ClearMessage(self): 1784 self.status.setText("") 1785 1786# Event list 1787 1788def GetEventList(db): 1789 events = [] 1790 query = QSqlQuery(db) 1791 QueryExec(query, "SELECT name FROM selected_events WHERE id > 0 ORDER BY id") 1792 while query.next(): 1793 events.append(query.value(0)) 1794 return events 1795 1796# SQL data preparation 1797 1798def SQLTableDataPrep(query, count): 1799 data = [] 1800 for i in xrange(count): 1801 data.append(query.value(i)) 1802 return data 1803 1804# SQL table data model item 1805 1806class SQLTableItem(): 1807 1808 def __init__(self, row, data): 1809 self.row = row 1810 self.data = data 1811 1812 def getData(self, column): 1813 return self.data[column] 1814 1815# SQL table data model 1816 1817class SQLTableModel(TableModel): 1818 1819 progress = Signal(object) 1820 1821 def __init__(self, glb, sql, column_count, parent=None): 1822 super(SQLTableModel, self).__init__(parent) 1823 self.glb = glb 1824 self.more = True 1825 self.populated = 0 1826 self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample) 1827 self.fetcher.done.connect(self.Update) 1828 self.fetcher.Fetch(glb_chunk_sz) 1829 1830 def DisplayData(self, item, index): 1831 self.FetchIfNeeded(item.row) 1832 return item.getData(index.column()) 1833 1834 def AddSample(self, data): 1835 child = SQLTableItem(self.populated, data) 1836 self.child_items.append(child) 1837 self.populated += 1 1838 1839 def Update(self, fetched): 1840 if not fetched: 1841 self.more = False 1842 self.progress.emit(0) 1843 child_count = self.child_count 1844 count = self.populated - child_count 1845 if count > 0: 1846 parent = QModelIndex() 1847 self.beginInsertRows(parent, child_count, child_count + count - 1) 1848 self.insertRows(child_count, count, parent) 1849 self.child_count += count 1850 self.endInsertRows() 1851 self.progress.emit(self.child_count) 1852 1853 def FetchMoreRecords(self, count): 1854 current = self.child_count 1855 if self.more: 1856 self.fetcher.Fetch(count) 1857 else: 1858 self.progress.emit(0) 1859 return current 1860 1861 def HasMoreRecords(self): 1862 return self.more 1863 1864# SQL automatic table data model 1865 1866class SQLAutoTableModel(SQLTableModel): 1867 1868 def __init__(self, glb, table_name, parent=None): 1869 sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz) 1870 if table_name == "comm_threads_view": 1871 # For now, comm_threads_view has no id column 1872 sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz) 1873 self.column_headers = [] 1874 query = QSqlQuery(glb.db) 1875 if glb.dbref.is_sqlite3: 1876 QueryExec(query, "PRAGMA table_info(" + table_name + ")") 1877 while query.next(): 1878 self.column_headers.append(query.value(1)) 1879 if table_name == "sqlite_master": 1880 sql = "SELECT * FROM " + table_name 1881 else: 1882 if table_name[:19] == "information_schema.": 1883 sql = "SELECT * FROM " + table_name 1884 select_table_name = table_name[19:] 1885 schema = "information_schema" 1886 else: 1887 select_table_name = table_name 1888 schema = "public" 1889 QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'") 1890 while query.next(): 1891 self.column_headers.append(query.value(0)) 1892 super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent) 1893 1894 def columnCount(self, parent=None): 1895 return len(self.column_headers) 1896 1897 def columnHeader(self, column): 1898 return self.column_headers[column] 1899 1900# Base class for custom ResizeColumnsToContents 1901 1902class ResizeColumnsToContentsBase(QObject): 1903 1904 def __init__(self, parent=None): 1905 super(ResizeColumnsToContentsBase, self).__init__(parent) 1906 1907 def ResizeColumnToContents(self, column, n): 1908 # Using the view's resizeColumnToContents() here is extrememly slow 1909 # so implement a crude alternative 1910 font = self.view.font() 1911 metrics = QFontMetrics(font) 1912 max = 0 1913 for row in xrange(n): 1914 val = self.data_model.child_items[row].data[column] 1915 len = metrics.width(str(val) + "MM") 1916 max = len if len > max else max 1917 val = self.data_model.columnHeader(column) 1918 len = metrics.width(str(val) + "MM") 1919 max = len if len > max else max 1920 self.view.setColumnWidth(column, max) 1921 1922 def ResizeColumnsToContents(self): 1923 n = min(self.data_model.child_count, 100) 1924 if n < 1: 1925 # No data yet, so connect a signal to notify when there is 1926 self.data_model.rowsInserted.connect(self.UpdateColumnWidths) 1927 return 1928 columns = self.data_model.columnCount() 1929 for i in xrange(columns): 1930 self.ResizeColumnToContents(i, n) 1931 1932 def UpdateColumnWidths(self, *x): 1933 # This only needs to be done once, so disconnect the signal now 1934 self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths) 1935 self.ResizeColumnsToContents() 1936 1937# Table window 1938 1939class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase): 1940 1941 def __init__(self, glb, table_name, parent=None): 1942 super(TableWindow, self).__init__(parent) 1943 1944 self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name)) 1945 1946 self.model = QSortFilterProxyModel() 1947 self.model.setSourceModel(self.data_model) 1948 1949 self.view = QTableView() 1950 self.view.setModel(self.model) 1951 self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) 1952 self.view.verticalHeader().setVisible(False) 1953 self.view.sortByColumn(-1, Qt.AscendingOrder) 1954 self.view.setSortingEnabled(True) 1955 1956 self.ResizeColumnsToContents() 1957 1958 self.find_bar = FindBar(self, self, True) 1959 1960 self.finder = ChildDataItemFinder(self.data_model) 1961 1962 self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) 1963 1964 self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) 1965 1966 self.setWidget(self.vbox.Widget()) 1967 1968 AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table") 1969 1970 def Find(self, value, direction, pattern, context): 1971 self.view.setFocus() 1972 self.find_bar.Busy() 1973 self.finder.Find(value, direction, pattern, context, self.FindDone) 1974 1975 def FindDone(self, row): 1976 self.find_bar.Idle() 1977 if row >= 0: 1978 self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) 1979 else: 1980 self.find_bar.NotFound() 1981 1982# Table list 1983 1984def GetTableList(glb): 1985 tables = [] 1986 query = QSqlQuery(glb.db) 1987 if glb.dbref.is_sqlite3: 1988 QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name") 1989 else: 1990 QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name") 1991 while query.next(): 1992 tables.append(query.value(0)) 1993 if glb.dbref.is_sqlite3: 1994 tables.append("sqlite_master") 1995 else: 1996 tables.append("information_schema.tables") 1997 tables.append("information_schema.views") 1998 tables.append("information_schema.columns") 1999 return tables 2000 2001# Action Definition 2002 2003def CreateAction(label, tip, callback, parent=None, shortcut=None): 2004 action = QAction(label, parent) 2005 if shortcut != None: 2006 action.setShortcuts(shortcut) 2007 action.setStatusTip(tip) 2008 action.triggered.connect(callback) 2009 return action 2010 2011# Typical application actions 2012 2013def CreateExitAction(app, parent=None): 2014 return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit) 2015 2016# Typical MDI actions 2017 2018def CreateCloseActiveWindowAction(mdi_area): 2019 return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area) 2020 2021def CreateCloseAllWindowsAction(mdi_area): 2022 return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area) 2023 2024def CreateTileWindowsAction(mdi_area): 2025 return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area) 2026 2027def CreateCascadeWindowsAction(mdi_area): 2028 return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area) 2029 2030def CreateNextWindowAction(mdi_area): 2031 return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild) 2032 2033def CreatePreviousWindowAction(mdi_area): 2034 return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild) 2035 2036# Typical MDI window menu 2037 2038class WindowMenu(): 2039 2040 def __init__(self, mdi_area, menu): 2041 self.mdi_area = mdi_area 2042 self.window_menu = menu.addMenu("&Windows") 2043 self.close_active_window = CreateCloseActiveWindowAction(mdi_area) 2044 self.close_all_windows = CreateCloseAllWindowsAction(mdi_area) 2045 self.tile_windows = CreateTileWindowsAction(mdi_area) 2046 self.cascade_windows = CreateCascadeWindowsAction(mdi_area) 2047 self.next_window = CreateNextWindowAction(mdi_area) 2048 self.previous_window = CreatePreviousWindowAction(mdi_area) 2049 self.window_menu.aboutToShow.connect(self.Update) 2050 2051 def Update(self): 2052 self.window_menu.clear() 2053 sub_window_count = len(self.mdi_area.subWindowList()) 2054 have_sub_windows = sub_window_count != 0 2055 self.close_active_window.setEnabled(have_sub_windows) 2056 self.close_all_windows.setEnabled(have_sub_windows) 2057 self.tile_windows.setEnabled(have_sub_windows) 2058 self.cascade_windows.setEnabled(have_sub_windows) 2059 self.next_window.setEnabled(have_sub_windows) 2060 self.previous_window.setEnabled(have_sub_windows) 2061 self.window_menu.addAction(self.close_active_window) 2062 self.window_menu.addAction(self.close_all_windows) 2063 self.window_menu.addSeparator() 2064 self.window_menu.addAction(self.tile_windows) 2065 self.window_menu.addAction(self.cascade_windows) 2066 self.window_menu.addSeparator() 2067 self.window_menu.addAction(self.next_window) 2068 self.window_menu.addAction(self.previous_window) 2069 if sub_window_count == 0: 2070 return 2071 self.window_menu.addSeparator() 2072 nr = 1 2073 for sub_window in self.mdi_area.subWindowList(): 2074 label = str(nr) + " " + sub_window.name 2075 if nr < 10: 2076 label = "&" + label 2077 action = self.window_menu.addAction(label) 2078 action.setCheckable(True) 2079 action.setChecked(sub_window == self.mdi_area.activeSubWindow()) 2080 action.triggered.connect(lambda x=nr: self.setActiveSubWindow(x)) 2081 self.window_menu.addAction(action) 2082 nr += 1 2083 2084 def setActiveSubWindow(self, nr): 2085 self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) 2086 2087# Font resize 2088 2089def ResizeFont(widget, diff): 2090 font = widget.font() 2091 sz = font.pointSize() 2092 font.setPointSize(sz + diff) 2093 widget.setFont(font) 2094 2095def ShrinkFont(widget): 2096 ResizeFont(widget, -1) 2097 2098def EnlargeFont(widget): 2099 ResizeFont(widget, 1) 2100 2101# Unique name for sub-windows 2102 2103def NumberedWindowName(name, nr): 2104 if nr > 1: 2105 name += " <" + str(nr) + ">" 2106 return name 2107 2108def UniqueSubWindowName(mdi_area, name): 2109 nr = 1 2110 while True: 2111 unique_name = NumberedWindowName(name, nr) 2112 ok = True 2113 for sub_window in mdi_area.subWindowList(): 2114 if sub_window.name == unique_name: 2115 ok = False 2116 break 2117 if ok: 2118 return unique_name 2119 nr += 1 2120 2121# Add a sub-window 2122 2123def AddSubWindow(mdi_area, sub_window, name): 2124 unique_name = UniqueSubWindowName(mdi_area, name) 2125 sub_window.setMinimumSize(200, 100) 2126 sub_window.resize(800, 600) 2127 sub_window.setWindowTitle(unique_name) 2128 sub_window.setAttribute(Qt.WA_DeleteOnClose) 2129 sub_window.setWindowIcon(sub_window.style().standardIcon(QStyle.SP_FileIcon)) 2130 sub_window.name = unique_name 2131 mdi_area.addSubWindow(sub_window) 2132 sub_window.show() 2133 2134# Main window 2135 2136class MainWindow(QMainWindow): 2137 2138 def __init__(self, glb, parent=None): 2139 super(MainWindow, self).__init__(parent) 2140 2141 self.glb = glb 2142 2143 self.setWindowTitle("Exported SQL Viewer: " + glb.dbname) 2144 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) 2145 self.setMinimumSize(200, 100) 2146 2147 self.mdi_area = QMdiArea() 2148 self.mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) 2149 self.mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) 2150 2151 self.setCentralWidget(self.mdi_area) 2152 2153 menu = self.menuBar() 2154 2155 file_menu = menu.addMenu("&File") 2156 file_menu.addAction(CreateExitAction(glb.app, self)) 2157 2158 edit_menu = menu.addMenu("&Edit") 2159 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find)) 2160 edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)])) 2161 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")])) 2162 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")])) 2163 2164 reports_menu = menu.addMenu("&Reports") 2165 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) 2166 2167 self.EventMenu(GetEventList(glb.db), reports_menu) 2168 2169 self.TableMenu(GetTableList(glb), menu) 2170 2171 self.window_menu = WindowMenu(self.mdi_area, menu) 2172 2173 def Find(self): 2174 win = self.mdi_area.activeSubWindow() 2175 if win: 2176 try: 2177 win.find_bar.Activate() 2178 except: 2179 pass 2180 2181 def FetchMoreRecords(self): 2182 win = self.mdi_area.activeSubWindow() 2183 if win: 2184 try: 2185 win.fetch_bar.Activate() 2186 except: 2187 pass 2188 2189 def ShrinkFont(self): 2190 win = self.mdi_area.activeSubWindow() 2191 ShrinkFont(win.view) 2192 2193 def EnlargeFont(self): 2194 win = self.mdi_area.activeSubWindow() 2195 EnlargeFont(win.view) 2196 2197 def EventMenu(self, events, reports_menu): 2198 branches_events = 0 2199 for event in events: 2200 event = event.split(":")[0] 2201 if event == "branches": 2202 branches_events += 1 2203 dbid = 0 2204 for event in events: 2205 dbid += 1 2206 event = event.split(":")[0] 2207 if event == "branches": 2208 label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")" 2209 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self)) 2210 label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")" 2211 reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self)) 2212 2213 def TableMenu(self, tables, menu): 2214 table_menu = menu.addMenu("&Tables") 2215 for table in tables: 2216 table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self)) 2217 2218 def NewCallGraph(self): 2219 CallGraphWindow(self.glb, self) 2220 2221 def NewBranchView(self, event_id): 2222 BranchWindow(self.glb, event_id, "", "", self) 2223 2224 def NewSelectedBranchView(self, event_id): 2225 dialog = SelectedBranchDialog(self.glb, self) 2226 ret = dialog.exec_() 2227 if ret: 2228 BranchWindow(self.glb, event_id, dialog.name, dialog.where_clause, self) 2229 2230 def NewTableView(self, table_name): 2231 TableWindow(self.glb, table_name, self) 2232 2233# XED Disassembler 2234 2235class xed_state_t(Structure): 2236 2237 _fields_ = [ 2238 ("mode", c_int), 2239 ("width", c_int) 2240 ] 2241 2242class XEDInstruction(): 2243 2244 def __init__(self, libxed): 2245 # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion 2246 xedd_t = c_byte * 512 2247 self.xedd = xedd_t() 2248 self.xedp = addressof(self.xedd) 2249 libxed.xed_decoded_inst_zero(self.xedp) 2250 self.state = xed_state_t() 2251 self.statep = addressof(self.state) 2252 # Buffer for disassembled instruction text 2253 self.buffer = create_string_buffer(256) 2254 self.bufferp = addressof(self.buffer) 2255 2256class LibXED(): 2257 2258 def __init__(self): 2259 try: 2260 self.libxed = CDLL("libxed.so") 2261 except: 2262 self.libxed = None 2263 if not self.libxed: 2264 self.libxed = CDLL("/usr/local/lib/libxed.so") 2265 2266 self.xed_tables_init = self.libxed.xed_tables_init 2267 self.xed_tables_init.restype = None 2268 self.xed_tables_init.argtypes = [] 2269 2270 self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero 2271 self.xed_decoded_inst_zero.restype = None 2272 self.xed_decoded_inst_zero.argtypes = [ c_void_p ] 2273 2274 self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode 2275 self.xed_operand_values_set_mode.restype = None 2276 self.xed_operand_values_set_mode.argtypes = [ c_void_p, c_void_p ] 2277 2278 self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode 2279 self.xed_decoded_inst_zero_keep_mode.restype = None 2280 self.xed_decoded_inst_zero_keep_mode.argtypes = [ c_void_p ] 2281 2282 self.xed_decode = self.libxed.xed_decode 2283 self.xed_decode.restype = c_int 2284 self.xed_decode.argtypes = [ c_void_p, c_void_p, c_uint ] 2285 2286 self.xed_format_context = self.libxed.xed_format_context 2287 self.xed_format_context.restype = c_uint 2288 self.xed_format_context.argtypes = [ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p ] 2289 2290 self.xed_tables_init() 2291 2292 def Instruction(self): 2293 return XEDInstruction(self) 2294 2295 def SetMode(self, inst, mode): 2296 if mode: 2297 inst.state.mode = 4 # 32-bit 2298 inst.state.width = 4 # 4 bytes 2299 else: 2300 inst.state.mode = 1 # 64-bit 2301 inst.state.width = 8 # 8 bytes 2302 self.xed_operand_values_set_mode(inst.xedp, inst.statep) 2303 2304 def DisassembleOne(self, inst, bytes_ptr, bytes_cnt, ip): 2305 self.xed_decoded_inst_zero_keep_mode(inst.xedp) 2306 err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt) 2307 if err: 2308 return 0, "" 2309 # Use AT&T mode (2), alternative is Intel (3) 2310 ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0) 2311 if not ok: 2312 return 0, "" 2313 # Return instruction length and the disassembled instruction text 2314 # For now, assume the length is in byte 166 2315 return inst.xedd[166], inst.buffer.value 2316 2317def TryOpen(file_name): 2318 try: 2319 return open(file_name, "rb") 2320 except: 2321 return None 2322 2323def Is64Bit(f): 2324 result = sizeof(c_void_p) 2325 # ELF support only 2326 pos = f.tell() 2327 f.seek(0) 2328 header = f.read(7) 2329 f.seek(pos) 2330 magic = header[0:4] 2331 eclass = ord(header[4]) 2332 encoding = ord(header[5]) 2333 version = ord(header[6]) 2334 if magic == chr(127) + "ELF" and eclass > 0 and eclass < 3 and encoding > 0 and encoding < 3 and version == 1: 2335 result = True if eclass == 2 else False 2336 return result 2337 2338# Global data 2339 2340class Glb(): 2341 2342 def __init__(self, dbref, db, dbname): 2343 self.dbref = dbref 2344 self.db = db 2345 self.dbname = dbname 2346 self.home_dir = os.path.expanduser("~") 2347 self.buildid_dir = os.getenv("PERF_BUILDID_DIR") 2348 if self.buildid_dir: 2349 self.buildid_dir += "/.build-id/" 2350 else: 2351 self.buildid_dir = self.home_dir + "/.debug/.build-id/" 2352 self.app = None 2353 self.mainwindow = None 2354 self.instances_to_shutdown_on_exit = weakref.WeakSet() 2355 try: 2356 self.disassembler = LibXED() 2357 self.have_disassembler = True 2358 except: 2359 self.have_disassembler = False 2360 2361 def FileFromBuildId(self, build_id): 2362 file_name = self.buildid_dir + build_id[0:2] + "/" + build_id[2:] + "/elf" 2363 return TryOpen(file_name) 2364 2365 def FileFromNamesAndBuildId(self, short_name, long_name, build_id): 2366 # Assume current machine i.e. no support for virtualization 2367 if short_name[0:7] == "[kernel" and os.path.basename(long_name) == "kcore": 2368 file_name = os.getenv("PERF_KCORE") 2369 f = TryOpen(file_name) if file_name else None 2370 if f: 2371 return f 2372 # For now, no special handling if long_name is /proc/kcore 2373 f = TryOpen(long_name) 2374 if f: 2375 return f 2376 f = self.FileFromBuildId(build_id) 2377 if f: 2378 return f 2379 return None 2380 2381 def AddInstanceToShutdownOnExit(self, instance): 2382 self.instances_to_shutdown_on_exit.add(instance) 2383 2384 # Shutdown any background processes or threads 2385 def ShutdownInstances(self): 2386 for x in self.instances_to_shutdown_on_exit: 2387 try: 2388 x.Shutdown() 2389 except: 2390 pass 2391 2392# Database reference 2393 2394class DBRef(): 2395 2396 def __init__(self, is_sqlite3, dbname): 2397 self.is_sqlite3 = is_sqlite3 2398 self.dbname = dbname 2399 2400 def Open(self, connection_name): 2401 dbname = self.dbname 2402 if self.is_sqlite3: 2403 db = QSqlDatabase.addDatabase("QSQLITE", connection_name) 2404 else: 2405 db = QSqlDatabase.addDatabase("QPSQL", connection_name) 2406 opts = dbname.split() 2407 for opt in opts: 2408 if "=" in opt: 2409 opt = opt.split("=") 2410 if opt[0] == "hostname": 2411 db.setHostName(opt[1]) 2412 elif opt[0] == "port": 2413 db.setPort(int(opt[1])) 2414 elif opt[0] == "username": 2415 db.setUserName(opt[1]) 2416 elif opt[0] == "password": 2417 db.setPassword(opt[1]) 2418 elif opt[0] == "dbname": 2419 dbname = opt[1] 2420 else: 2421 dbname = opt 2422 2423 db.setDatabaseName(dbname) 2424 if not db.open(): 2425 raise Exception("Failed to open database " + dbname + " error: " + db.lastError().text()) 2426 return db, dbname 2427 2428# Main 2429 2430def Main(): 2431 if (len(sys.argv) < 2): 2432 print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>" 2433 raise Exception("Too few arguments") 2434 2435 dbname = sys.argv[1] 2436 2437 is_sqlite3 = False 2438 try: 2439 f = open(dbname) 2440 if f.read(15) == "SQLite format 3": 2441 is_sqlite3 = True 2442 f.close() 2443 except: 2444 pass 2445 2446 dbref = DBRef(is_sqlite3, dbname) 2447 db, dbname = dbref.Open("main") 2448 glb = Glb(dbref, db, dbname) 2449 app = QApplication(sys.argv) 2450 glb.app = app 2451 mainwindow = MainWindow(glb) 2452 glb.mainwindow = mainwindow 2453 mainwindow.show() 2454 err = app.exec_() 2455 glb.ShutdownInstances() 2456 db.close() 2457 sys.exit(err) 2458 2459if __name__ == "__main__": 2460 Main() 2461