max_width[c] = max(max_width[c], len(val)) width = max_width[c] align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole) if align & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " text += "\n" pad = "" sep = "" last_row = min_row for i in indexes: if i.row() > last_row: last_row = i.row() text += "\n" pad = "" sep = "" if as_csv: text += sep + ToCSValue(str(i.data())) sep = "," else: width = max_width[i.column() - min_col] if i.data(Qt.TextAlignmentRole) & Qt.AlignRight: val = str(i.data()).rjust(width) else: val = str(i.data()) text += pad + sep + val pad = " " * (width - len(val)) sep = " " QApplication.clipboard().setText(text) def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False): indexes = view.selectedIndexes() if not len(indexes): return selection = view.selectionModel() first = None for i in indexes: above = view.indexAbove(i) if not selection.isSelected(above): first = i break if first is None: raise RuntimeError("CopyTreeCellsToClipboard internal error") model = first.model() row_cnt = 0 col_cnt = model.columnCount(first) max_width = [0] * col_cnt indent_sz = 2 indent_str = " " * indent_sz expanded_mark_sz = 2 if sys.version_info[0] == 3: expanded_mark = "\u25BC " not_expanded_mark = "\u25B6 " else: expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8") not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8") leaf_mark = " " if not as_csv: pos = first while True: row_cnt += 1 row = pos.row() for c in range(col_cnt): i = pos.sibling(row, c) if c: n = len(str(i.data())) else: n = len(str(i.data()).strip()) n += (i.internalPointer().level - 1) * indent_sz n += expanded_mark_sz max_width[c] = max(max_width[c], n) pos = view.indexBelow(pos) if not selection.isSelected(pos): break text = "" pad = "" sep = "" if with_hdr: for c in range(col_cnt): val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip() if as_csv: text += sep + ToCSValue(val) sep = "," else: max_width[c] = max(max_width[c], len(val)) width = max_width[c] align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole) if align & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " text += "\n" pad = "" sep = "" pos = first while True: row = pos.row() for c in range(col_cnt): i = pos.sibling(row, c) val = str(i.data()) if not c: if model.hasChildren(i): if view.isExpanded(i): mark = expanded_mark else: mark = not_expanded_mark else: mark = leaf_mark val = indent_str * (i.internalPointer().level - 1) + mark + val.strip() if as_csv: text += sep + ToCSValue(val) sep = "," else: width = max_width[c] if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " pos = view.indexBelow(pos) if not selection.isSelected(pos): break text = text.rstrip() + "\n" pad = "" sep = "" QApplication.clipboard().setText(text) def CopyCellsToClipboard(view, as_csv=False, with_hdr=False): view.CopyCellsToClipboard(view, as_csv, with_hdr) def CopyCellsToClipboardHdr(view): CopyCellsToClipboard(view, False, True) def CopyCellsToClipboardCSV(view): CopyCellsToClipboard(view, True, True) # Context menu class ContextMenu(object): def __init__(self, view): self.view = view self.view.setContextMenuPolicy(Qt.CustomContextMenu) self.view.customContextMenuRequested.connect(self.ShowContextMenu) def ShowContextMenu(self, pos): menu = QMenu(self.view) self.AddActions(menu) menu.exec_(self.view.mapToGlobal(pos)) def AddCopy(self, menu): menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view)) menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view)) def AddActions(self, menu): self.AddCopy(menu) class TreeContextMenu(ContextMenu): def __init__(self, view): super(TreeContextMenu, self).__init__(view) def AddActions(self, menu): i = self.view.currentIndex() text = str(i.data()).strip() if len(text): menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view)) self.AddCopy(menu) # Table window class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase): def __init__(self, glb, table_name, parent=None): super(TableWindow, self).__init__(parent) self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name)) self.model = QSortFilterProxyModel() self.model.setSourceModel(self.data_model) self.view = QTableView() self.view.setModel(self.model) self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) self.view.verticalHeader().setVisible(False) self.view.sortByColumn(-1, Qt.AscendingOrder) self.view.setSortingEnabled(True) self.view.setSelectionMode(QAbstractItemView.ContiguousSelection) self.view.CopyCellsToClipboard = CopyTableCellsToClipboard self.ResizeColumnsToContents() self.context_menu = ContextMenu(self.view) self.find_bar = FindBar(self, self, True) self.finder = ChildDataItemFinder(self.data_model) self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) self.setWidget(self.vbox.Widget()) AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table") def Find(self, value, direction, pattern, context): self.view.setFocus() self.find_bar.Busy() self.finder.Find(value, direction, pattern, context, self.FindDone) def FindDone(self, row): self.find_bar.Idle() if row >= 0: self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex()))) else: self.find_bar.NotFound() # Table list def GetTableList(glb): tables = [] query = QSqlQuery(glb.db) if glb.dbref.is_sqlite3: QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name") else: QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name") while query.next(): tables.append(query.value(0)) if glb.dbref.is_sqlite3: tables.append("sqlite_master") else: tables.append("information_schema.tables") tables.append("information_schema.views") tables.append("information_schema.columns") return tables # Top Calls data model class TopCallsModel(SQLTableModel): def __init__(self, glb, report_vars, parent=None): text = "" if not glb.dbref.is_sqlite3: text = "::text" limit = "" if len(report_vars.limit): limit = " LIMIT " + report_vars.limit sql = ("SELECT comm, pid, tid, name," " CASE" " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text + " ELSE short_name" " END AS dso," " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, " " CASE" " WHEN (calls.flags = 1) THEN 'no call'" + text + " WHEN (calls.flags = 2) THEN 'no return'" + text + " WHEN (calls.flags = 3) THEN 'no call/return'" + text + " ELSE ''" + text + " END AS flags" " FROM calls" " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" " INNER JOIN dsos ON symbols.dso_id = dsos.id" " INNER JOIN comms ON calls.comm_id = comms.id" " INNER JOIN threads ON calls.thread_id = threads.id" + report_vars.where_clause + " ORDER BY elapsed_time DESC" + limit ) column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags") self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft) super(TopCallsModel, self).__init__(glb, sql, column_headers, parent) def columnAlignment(self, column): return self.alignment[column] # Top Calls report creation dialog class TopCallsDialog(ReportDialogBase): def __init__(self, glb, parent=None): title = "Top Calls by Elapsed Time" items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"), lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p), lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p), lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p), lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p), lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p), lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p), lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100")) super(TopCallsDialog, self).__init__(glb, title, items, False, parent) # Top Calls window class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase): def __init__(self, glb, report_vars, parent=None): super(TopCallsWindow, self).__init__(parent) self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars)) self.model = self.data_model self.view = QTableView() self.view.setModel(self.model) self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) self.view.verticalHeader().setVisible(False) self.view.setSelectionMode(QAbstractItemView.ContiguousSelection) self.view.CopyCellsToClipboard = CopyTableCellsToClipboard self.context_menu = ContextMenu(self.view) self.ResizeColumnsToContents() self.find_bar = FindBar(self, self, True) self.finder = ChildDataItemFinder(self.model) self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) self.setWidget(self.vbox.Widget()) AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name) def Find(self, value, direction, pattern, context): self.view.setFocus() self.find_bar.Busy() self.finder.Find(value, direction, pattern, context, self.FindDone) def FindDone(self, row): self.find_bar.Idle() if row >= 0: self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) else: self.find_bar.NotFound() # Action Definition def CreateAction(label, tip, callback, parent=None, shortcut=None): action = QAction(label, parent) if shortcut != None: action.setShortcuts(shortcut) action.setStatusTip(tip) action.triggered.connect(callback) return action # Typical application actions def CreateExitAction(app, parent=None): return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit) # Typical MDI actions def CreateCloseActiveWindowAction(mdi_area): return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area) def CreateCloseAllWindowsAction(mdi_area): return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area) def CreateTileWindowsAction(mdi_area): return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area) def CreateCascadeWindowsAction(mdi_area): return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area) def CreateNextWindowAction(mdi_area): return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild) def CreatePreviousWindowAction(mdi_area): return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild) # Typical MDI window menu class WindowMenu(): def __init__(self, mdi_area, menu): self.mdi_area = mdi_area self.window_menu = menu.addMenu("&Windows") self.close_active_window = CreateCloseActiveWindowAction(mdi_area) self.close_all_windows = CreateCloseAllWindowsAction(mdi_area) self.tile_windows = CreateTileWindowsAction(mdi_area) self.cascade_windows = CreateCascadeWindowsAction(mdi_area) self.next_window = CreateNextWindowAction(mdi_area) self.previous_window = CreatePreviousWindowAction(mdi_area) self.window_menu.aboutToShow.connect(self.Update) def Update(self): self.window_menu.clear() sub_window_count = len(self.mdi_area.subWindowList()) have_sub_windows = sub_window_count != 0 self.close_active_window.setEnabled(have_sub_windows) self.close_all_windows.setEnabled(have_sub_windows) self.tile_windows.setEnabled(have_sub_windows) self.cascade_windows.setEnabled(have_sub_windows) self.next_window.setEnabled(have_sub_windows) self.previous_window.setEnabled(have_sub_windows) self.window_menu.addAction(self.close_active_window) self.window_menu.addAction(self.close_all_windows) self.window_menu.addSeparator() self.window_menu.addAction(self.tile_windows) self.window_menu.addAction(self.cascade_windows) self.window_menu.addSeparator() self.window_menu.addAction(self.next_window) self.window_menu.addAction(self.previous_window) if sub_window_count == 0: return self.window_menu.addSeparator() nr = 1 for sub_window in self.mdi_area.subWindowList(): label = str(nr) + " " + sub_window.name if nr < 10: label = "&" + label action = self.window_menu.addAction(label) action.setCheckable(True) action.setChecked(sub_window == self.mdi_area.activeSubWindow()) action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x)) self.window_menu.addAction(action) nr += 1 def setActiveSubWindow(self, nr): self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) # Help text glb_help_text = """<h1>Contents</h1><style>p.c1 { text-indent: 40px;}p.c2 { text-indent: 80px;}}</style><p class=c1><a href=#reports>1. Reports</a></p><p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p><p class=c2><a href=#calltree>1.2 Call Tree</a></p><p class=c2><a href=#allbranches>1.3 All branches</a></p><p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p><p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p><p class=c1><a href=#tables>2. Tables</a></p><h1 id=reports>1. Reports</h1><h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2>The result is a GUI window with a tree representing a context-sensitivecall-graph. Expanding a couple of levels of the tree and adjusting columnwidths to suit will display something like:<pre> Call Graph: pt_exampleCall Path Object Count Time(ns) Time(%) Branch Count Branch Count(%)v- ls v- 2638:2638 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0 |- unknown unknown 1 13198 0.1 1 0.0 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0 >- __libc_csu_init ls 1 10354 0.1 10 0.0 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0 v- main ls 1 8182043 99.6 180254 99.9</pre><h3>Points to note:</h3><ul><li>The top level is a command name (comm)</li><li>The next level is a thread (pid:tid)</li><li>Subsequent levels are functions</li><li>'Count' is the number of calls</li><li>'Time' is the elapsed time until the function returns</li><li>Percentages are relative to the level above</li><li>'Branch Count' is the total number of branches for that function and all functions that it calls</ul><h3>Find</h3>Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match.The pattern matching symbols are ? for any character and * for zero or more characters.<h2 id=calltree>1.2 Call Tree</h2>The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated.Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'.<h2 id=allbranches>1.3 All branches</h2>The All branches report displays all branches in chronological order.Not all data is fetched immediately. More records can be fetched using the Fetch bar provided.<h3>Disassembly</h3>Open a branch to display disassembly. This only works if:<ol>Lines 2501–3000 of 3588. Previous lines Load more
max_width[c] = max(max_width[c], len(val)) width = max_width[c] align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole) if align & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " text += "\n" pad = "" sep = "" last_row = min_row for i in indexes: if i.row() > last_row: last_row = i.row() text += "\n" pad = "" sep = "" if as_csv: text += sep + ToCSValue(str(i.data())) sep = "," else: width = max_width[i.column() - min_col] if i.data(Qt.TextAlignmentRole) & Qt.AlignRight: val = str(i.data()).rjust(width) else: val = str(i.data()) text += pad + sep + val pad = " " * (width - len(val)) sep = " " QApplication.clipboard().setText(text) def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False): indexes = view.selectedIndexes() if not len(indexes): return selection = view.selectionModel() first = None for i in indexes: above = view.indexAbove(i) if not selection.isSelected(above): first = i break if first is None: raise RuntimeError("CopyTreeCellsToClipboard internal error") model = first.model() row_cnt = 0 col_cnt = model.columnCount(first) max_width = [0] * col_cnt indent_sz = 2 indent_str = " " * indent_sz expanded_mark_sz = 2 if sys.version_info[0] == 3: expanded_mark = "\u25BC " not_expanded_mark = "\u25B6 " else: expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8") not_expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8") leaf_mark = " " if not as_csv: pos = first while True: row_cnt += 1 row = pos.row() for c in range(col_cnt): i = pos.sibling(row, c) if c: n = len(str(i.data())) else: n = len(str(i.data()).strip()) n += (i.internalPointer().level - 1) * indent_sz n += expanded_mark_sz max_width[c] = max(max_width[c], n) pos = view.indexBelow(pos) if not selection.isSelected(pos): break text = "" pad = "" sep = "" if with_hdr: for c in range(col_cnt): val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip() if as_csv: text += sep + ToCSValue(val) sep = "," else: max_width[c] = max(max_width[c], len(val)) width = max_width[c] align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole) if align & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " text += "\n" pad = "" sep = "" pos = first while True: row = pos.row() for c in range(col_cnt): i = pos.sibling(row, c) val = str(i.data()) if not c: if model.hasChildren(i): if view.isExpanded(i): mark = expanded_mark else: mark = not_expanded_mark else: mark = leaf_mark val = indent_str * (i.internalPointer().level - 1) + mark + val.strip() if as_csv: text += sep + ToCSValue(val) sep = "," else: width = max_width[c] if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight: val = val.rjust(width) text += pad + sep + val pad = " " * (width - len(val)) sep = " " pos = view.indexBelow(pos) if not selection.isSelected(pos): break text = text.rstrip() + "\n" pad = "" sep = "" QApplication.clipboard().setText(text) def CopyCellsToClipboard(view, as_csv=False, with_hdr=False): view.CopyCellsToClipboard(view, as_csv, with_hdr) def CopyCellsToClipboardHdr(view): CopyCellsToClipboard(view, False, True) def CopyCellsToClipboardCSV(view): CopyCellsToClipboard(view, True, True) # Context menu class ContextMenu(object): def __init__(self, view): self.view = view self.view.setContextMenuPolicy(Qt.CustomContextMenu) self.view.customContextMenuRequested.connect(self.ShowContextMenu) def ShowContextMenu(self, pos): menu = QMenu(self.view) self.AddActions(menu) menu.exec_(self.view.mapToGlobal(pos)) def AddCopy(self, menu): menu.addAction(CreateAction("&Copy selection", "Copy to clipboard", lambda: CopyCellsToClipboardHdr(self.view), self.view)) menu.addAction(CreateAction("Copy selection as CS&V", "Copy to clipboard as CSV", lambda: CopyCellsToClipboardCSV(self.view), self.view)) def AddActions(self, menu): self.AddCopy(menu) class TreeContextMenu(ContextMenu): def __init__(self, view): super(TreeContextMenu, self).__init__(view) def AddActions(self, menu): i = self.view.currentIndex() text = str(i.data()).strip() if len(text): menu.addAction(CreateAction('Copy "' + text + '"', "Copy to clipboard", lambda: QApplication.clipboard().setText(text), self.view)) self.AddCopy(menu) # Table window class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase): def __init__(self, glb, table_name, parent=None): super(TableWindow, self).__init__(parent) self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name)) self.model = QSortFilterProxyModel() self.model.setSourceModel(self.data_model) self.view = QTableView() self.view.setModel(self.model) self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) self.view.verticalHeader().setVisible(False) self.view.sortByColumn(-1, Qt.AscendingOrder) self.view.setSortingEnabled(True) self.view.setSelectionMode(QAbstractItemView.ContiguousSelection) self.view.CopyCellsToClipboard = CopyTableCellsToClipboard self.ResizeColumnsToContents() self.context_menu = ContextMenu(self.view) self.find_bar = FindBar(self, self, True) self.finder = ChildDataItemFinder(self.data_model) self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) self.setWidget(self.vbox.Widget()) AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table") def Find(self, value, direction, pattern, context): self.view.setFocus() self.find_bar.Busy() self.finder.Find(value, direction, pattern, context, self.FindDone) def FindDone(self, row): self.find_bar.Idle() if row >= 0: self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex()))) else: self.find_bar.NotFound() # Table list def GetTableList(glb): tables = [] query = QSqlQuery(glb.db) if glb.dbref.is_sqlite3: QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name") else: QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name") while query.next(): tables.append(query.value(0)) if glb.dbref.is_sqlite3: tables.append("sqlite_master") else: tables.append("information_schema.tables") tables.append("information_schema.views") tables.append("information_schema.columns") return tables # Top Calls data model class TopCallsModel(SQLTableModel): def __init__(self, glb, report_vars, parent=None): text = "" if not glb.dbref.is_sqlite3: text = "::text" limit = "" if len(report_vars.limit): limit = " LIMIT " + report_vars.limit sql = ("SELECT comm, pid, tid, name," " CASE" " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text + " ELSE short_name" " END AS dso," " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, " " CASE" " WHEN (calls.flags = 1) THEN 'no call'" + text + " WHEN (calls.flags = 2) THEN 'no return'" + text + " WHEN (calls.flags = 3) THEN 'no call/return'" + text + " ELSE ''" + text + " END AS flags" " FROM calls" " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" " INNER JOIN dsos ON symbols.dso_id = dsos.id" " INNER JOIN comms ON calls.comm_id = comms.id" " INNER JOIN threads ON calls.thread_id = threads.id" + report_vars.where_clause + " ORDER BY elapsed_time DESC" + limit ) column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags") self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft) super(TopCallsModel, self).__init__(glb, sql, column_headers, parent) def columnAlignment(self, column): return self.alignment[column] # Top Calls report creation dialog class TopCallsDialog(ReportDialogBase): def __init__(self, glb, parent=None): title = "Top Calls by Elapsed Time" items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"), lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p), lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p), lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p), lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p), lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p), lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p), lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100")) super(TopCallsDialog, self).__init__(glb, title, items, False, parent) # Top Calls window class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase): def __init__(self, glb, report_vars, parent=None): super(TopCallsWindow, self).__init__(parent) self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars)) self.model = self.data_model self.view = QTableView() self.view.setModel(self.model) self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) self.view.verticalHeader().setVisible(False) self.view.setSelectionMode(QAbstractItemView.ContiguousSelection) self.view.CopyCellsToClipboard = CopyTableCellsToClipboard self.context_menu = ContextMenu(self.view) self.ResizeColumnsToContents() self.find_bar = FindBar(self, self, True) self.finder = ChildDataItemFinder(self.model) self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) self.setWidget(self.vbox.Widget()) AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name) def Find(self, value, direction, pattern, context): self.view.setFocus() self.find_bar.Busy() self.finder.Find(value, direction, pattern, context, self.FindDone) def FindDone(self, row): self.find_bar.Idle() if row >= 0: self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) else: self.find_bar.NotFound() # Action Definition def CreateAction(label, tip, callback, parent=None, shortcut=None): action = QAction(label, parent) if shortcut != None: action.setShortcuts(shortcut) action.setStatusTip(tip) action.triggered.connect(callback) return action # Typical application actions def CreateExitAction(app, parent=None): return CreateAction("&Quit", "Exit the application", app.closeAllWindows, parent, QKeySequence.Quit) # Typical MDI actions def CreateCloseActiveWindowAction(mdi_area): return CreateAction("Cl&ose", "Close the active window", mdi_area.closeActiveSubWindow, mdi_area) def CreateCloseAllWindowsAction(mdi_area): return CreateAction("Close &All", "Close all the windows", mdi_area.closeAllSubWindows, mdi_area) def CreateTileWindowsAction(mdi_area): return CreateAction("&Tile", "Tile the windows", mdi_area.tileSubWindows, mdi_area) def CreateCascadeWindowsAction(mdi_area): return CreateAction("&Cascade", "Cascade the windows", mdi_area.cascadeSubWindows, mdi_area) def CreateNextWindowAction(mdi_area): return CreateAction("Ne&xt", "Move the focus to the next window", mdi_area.activateNextSubWindow, mdi_area, QKeySequence.NextChild) def CreatePreviousWindowAction(mdi_area): return CreateAction("Pre&vious", "Move the focus to the previous window", mdi_area.activatePreviousSubWindow, mdi_area, QKeySequence.PreviousChild) # Typical MDI window menu class WindowMenu(): def __init__(self, mdi_area, menu): self.mdi_area = mdi_area self.window_menu = menu.addMenu("&Windows") self.close_active_window = CreateCloseActiveWindowAction(mdi_area) self.close_all_windows = CreateCloseAllWindowsAction(mdi_area) self.tile_windows = CreateTileWindowsAction(mdi_area) self.cascade_windows = CreateCascadeWindowsAction(mdi_area) self.next_window = CreateNextWindowAction(mdi_area) self.previous_window = CreatePreviousWindowAction(mdi_area) self.window_menu.aboutToShow.connect(self.Update) def Update(self): self.window_menu.clear() sub_window_count = len(self.mdi_area.subWindowList()) have_sub_windows = sub_window_count != 0 self.close_active_window.setEnabled(have_sub_windows) self.close_all_windows.setEnabled(have_sub_windows) self.tile_windows.setEnabled(have_sub_windows) self.cascade_windows.setEnabled(have_sub_windows) self.next_window.setEnabled(have_sub_windows) self.previous_window.setEnabled(have_sub_windows) self.window_menu.addAction(self.close_active_window) self.window_menu.addAction(self.close_all_windows) self.window_menu.addSeparator() self.window_menu.addAction(self.tile_windows) self.window_menu.addAction(self.cascade_windows) self.window_menu.addSeparator() self.window_menu.addAction(self.next_window) self.window_menu.addAction(self.previous_window) if sub_window_count == 0: return self.window_menu.addSeparator() nr = 1 for sub_window in self.mdi_area.subWindowList(): label = str(nr) + " " + sub_window.name if nr < 10: label = "&" + label action = self.window_menu.addAction(label) action.setCheckable(True) action.setChecked(sub_window == self.mdi_area.activeSubWindow()) action.triggered.connect(lambda a=None,x=nr: self.setActiveSubWindow(x)) self.window_menu.addAction(action) nr += 1 def setActiveSubWindow(self, nr): self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) # Help text glb_help_text = """ <h1>Contents</h1> <style> p.c1 { text-indent: 40px; } p.c2 { text-indent: 80px; } } </style> <p class=c1><a href=#reports>1. Reports</a></p> <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p> <p class=c2><a href=#calltree>1.2 Call Tree</a></p> <p class=c2><a href=#allbranches>1.3 All branches</a></p> <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p> <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p> <p class=c1><a href=#tables>2. Tables</a></p> <h1 id=reports>1. Reports</h1> <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2> The result is a GUI window with a tree representing a context-sensitive call-graph. Expanding a couple of levels of the tree and adjusting column widths to suit will display something like: <pre> Call Graph: pt_example Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%) v- ls v- 2638:2638 v- _start ld-2.19.so 1 10074071 100.0 211135 100.0 |- unknown unknown 1 13198 0.1 1 0.0 >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3 >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3 v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4 >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1 >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0 >- __libc_csu_init ls 1 10354 0.1 10 0.0 |- _setjmp libc-2.19.so 1 0 0.0 4 0.0 v- main ls 1 8182043 99.6 180254 99.9 </pre> <h3>Points to note:</h3> <ul> <li>The top level is a command name (comm)</li> <li>The next level is a thread (pid:tid)</li> <li>Subsequent levels are functions</li> <li>'Count' is the number of calls</li> <li>'Time' is the elapsed time until the function returns</li> <li>Percentages are relative to the level above</li> <li>'Branch Count' is the total number of branches for that function and all functions that it calls </ul> <h3>Find</h3> Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match. The pattern matching symbols are ? for any character and * for zero or more characters. <h2 id=calltree>1.2 Call Tree</h2> The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated. Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'. <h2 id=allbranches>1.3 All branches</h2> The All branches report displays all branches in chronological order. Not all data is fetched immediately. More records can be fetched using the Fetch bar provided. <h3>Disassembly</h3> Open a branch to display disassembly. This only works if: <ol>