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