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