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