Add a new report to display top calls by elapsed time. It displays calls in descending order of time elapsed between when the function was called and when it returned.
Unified · Split
| @@ -1402,12 +1402,13 @@ class BranchModel(TreeModel): · 1401 unmodified lines | ||
| 1402 | 1402 | |
| 1403 | 1403 | class ReportVars(): |
| 1404 | 1404 | |
| 1405 | − def __init__(self, name = "", where_clause = ""): | |
| 1405 | + def __init__(self, name = "", where_clause = "", limit = ""): | |
| 1406 | 1406 | self.name = name |
| 1407 | 1407 | self.where_clause = where_clause |
| 1408 | + self.limit = limit | |
| 1408 | 1409 | |
| 1409 | 1410 | def UniqueId(self): |
| 1410 | − return str(self.where_clause) | |
| 1411 | + return str(self.where_clause + ";" + self.limit) | |
| 1411 | 1412 | |
| 1412 | 1413 | # Branch window |
| 1413 | 1414 | |
| @@ -1485,16 +1486,16 @@ class BranchWindow(QMdiSubWindow): · 71 unmodified lines | ||
| 1485 | 1486 | |
| 1486 | 1487 | class LineEditDataItem(object): |
| 1487 | 1488 | |
| 1488 | − def __init__(self, glb, label, placeholder_text, parent, id = ""): | |
| 1489 | + def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""): | |
| 1489 | 1490 | self.glb = glb |
| 1490 | 1491 | self.label = label |
| 1491 | 1492 | self.placeholder_text = placeholder_text |
| 1492 | 1493 | self.parent = parent |
| 1493 | 1494 | self.id = id |
| 1494 | 1495 | |
| 1495 | − self.value = "" | |
| 1496 | + self.value = default | |
| 1496 | 1497 | |
| 1497 | − self.widget = QLineEdit() | |
| 1498 | + self.widget = QLineEdit(default) | |
| 1498 | 1499 | self.widget.editingFinished.connect(self.Validate) |
| 1499 | 1500 | self.widget.textChanged.connect(self.Invalidate) |
| 1500 | 1501 | self.red = False |
| @@ -1582,6 +1583,21 @@ class NonNegativeIntegerRangesDataItem(LineEditDataItem): · 81 unmodified lines | ||
| 1582 | 1583 | ranges.append(self.column_name + " IN (" + ",".join(singles) + ")") |
| 1583 | 1584 | self.value = " OR ".join(ranges) |
| 1584 | 1585 | |
| 1586 | +# Positive integer dialog data item | |
| 1587 | + | |
| 1588 | +class PositiveIntegerDataItem(LineEditDataItem): | |
| 1589 | + | |
| 1590 | + def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""): | |
| 1591 | + super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default) | |
| 1592 | + | |
| 1593 | + def DoValidate(self, input_string): | |
| 1594 | + if not self.IsNumber(input_string.strip()): | |
| 1595 | + return self.InvalidValue(input_string) | |
| 1596 | + value = int(input_string.strip()) | |
| 1597 | + if value <= 0: | |
| 1598 | + return self.InvalidValue(input_string) | |
| 1599 | + self.value = str(value) | |
| 1600 | + | |
| 1585 | 1601 | # Dialog data item converted and validated using a SQL table |
| 1586 | 1602 | |
| 1587 | 1603 | class SQLTableDataItem(LineEditDataItem): |
| @@ -1799,7 +1815,9 @@ class ReportDialogBase(QDialog): · 211 unmodified lines | ||
| 1799 | 1815 | if not d.IsValid(): |
| 1800 | 1816 | return |
| 1801 | 1817 | for d in self.data_items[1:]: |
| 1802 | − if len(d.value): | |
| 1818 | + if d.id == "LIMIT": | |
| 1819 | + vars.limit = d.value | |
| 1820 | + elif len(d.value): | |
| 1803 | 1821 | if len(vars.where_clause): |
| 1804 | 1822 | vars.where_clause += " AND " |
| 1805 | 1823 | vars.where_clause += d.value |
| @@ -2059,6 +2077,103 @@ def GetTableList(glb): · 253 unmodified lines | ||
| 2059 | 2077 | tables.append("information_schema.columns") |
| 2060 | 2078 | return tables |
| 2061 | 2079 | |
| 2080 | +# Top Calls data model | |
| 2081 | + | |
| 2082 | +class TopCallsModel(SQLTableModel): | |
| 2083 | + | |
| 2084 | + def __init__(self, glb, report_vars, parent=None): | |
| 2085 | + text = "" | |
| 2086 | + if not glb.dbref.is_sqlite3: | |
| 2087 | + text = "::text" | |
| 2088 | + limit = "" | |
| 2089 | + if len(report_vars.limit): | |
| 2090 | + limit = " LIMIT " + report_vars.limit | |
| 2091 | + sql = ("SELECT comm, pid, tid, name," | |
| 2092 | + " CASE" | |
| 2093 | + " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text + | |
| 2094 | + " ELSE short_name" | |
| 2095 | + " END AS dso," | |
| 2096 | + " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, " | |
| 2097 | + " CASE" | |
| 2098 | + " WHEN (calls.flags = 1) THEN 'no call'" + text + | |
| 2099 | + " WHEN (calls.flags = 2) THEN 'no return'" + text + | |
| 2100 | + " WHEN (calls.flags = 3) THEN 'no call/return'" + text + | |
| 2101 | + " ELSE ''" + text + | |
| 2102 | + " END AS flags" | |
| 2103 | + " FROM calls" | |
| 2104 | + " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" | |
| 2105 | + " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" | |
| 2106 | + " INNER JOIN dsos ON symbols.dso_id = dsos.id" | |
| 2107 | + " INNER JOIN comms ON calls.comm_id = comms.id" | |
| 2108 | + " INNER JOIN threads ON calls.thread_id = threads.id" + | |
| 2109 | + report_vars.where_clause + | |
| 2110 | + " ORDER BY elapsed_time DESC" + | |
| 2111 | + limit | |
| 2112 | + ) | |
| 2113 | + column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags") | |
| 2114 | + self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft) | |
| 2115 | + super(TopCallsModel, self).__init__(glb, sql, column_headers, parent) | |
| 2116 | + | |
| 2117 | + def columnAlignment(self, column): | |
| 2118 | + return self.alignment[column] | |
| 2119 | + | |
| 2120 | +# Top Calls report creation dialog | |
| 2121 | + | |
| 2122 | +class TopCallsDialog(ReportDialogBase): | |
| 2123 | + | |
| 2124 | + def __init__(self, glb, parent=None): | |
| 2125 | + title = "Top Calls by Elapsed Time" | |
| 2126 | + items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"), | |
| 2127 | + lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p), | |
| 2128 | + lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p), | |
| 2129 | + lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p), | |
| 2130 | + lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p), | |
| 2131 | + lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p), | |
| 2132 | + lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p), | |
| 2133 | + lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100")) | |
| 2134 | + super(TopCallsDialog, self).__init__(glb, title, items, False, parent) | |
| 2135 | + | |
| 2136 | +# Top Calls window | |
| 2137 | + | |
| 2138 | +class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase): | |
| 2139 | + | |
| 2140 | + def __init__(self, glb, report_vars, parent=None): | |
| 2141 | + super(TopCallsWindow, self).__init__(parent) | |
| 2142 | + | |
| 2143 | + self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars)) | |
| 2144 | + self.model = self.data_model | |
| 2145 | + | |
| 2146 | + self.view = QTableView() | |
| 2147 | + self.view.setModel(self.model) | |
| 2148 | + self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) | |
| 2149 | + self.view.verticalHeader().setVisible(False) | |
| 2150 | + | |
| 2151 | + self.ResizeColumnsToContents() | |
| 2152 | + | |
| 2153 | + self.find_bar = FindBar(self, self, True) | |
| 2154 | + | |
| 2155 | + self.finder = ChildDataItemFinder(self.model) | |
| 2156 | + | |
| 2157 | + self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) | |
| 2158 | + | |
| 2159 | + self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) | |
| 2160 | + | |
| 2161 | + self.setWidget(self.vbox.Widget()) | |
| 2162 | + | |
| 2163 | + AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name) | |
| 2164 | + | |
| 2165 | + def Find(self, value, direction, pattern, context): | |
| 2166 | + self.view.setFocus() | |
| 2167 | + self.find_bar.Busy() | |
| 2168 | + self.finder.Find(value, direction, pattern, context, self.FindDone) | |
| 2169 | + | |
| 2170 | + def FindDone(self, row): | |
| 2171 | + self.find_bar.Idle() | |
| 2172 | + if row >= 0: | |
| 2173 | + self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) | |
| 2174 | + else: | |
| 2175 | + self.find_bar.NotFound() | |
| 2176 | + | |
| 2062 | 2177 | # Action Definition |
| 2063 | 2178 | |
| 2064 | 2179 | def CreateAction(label, tip, callback, parent=None, shortcut=None): |
| @@ -2162,6 +2277,7 @@ p.c2 { · 97 unmodified lines | ||
| 2162 | 2277 | <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p> |
| 2163 | 2278 | <p class=c2><a href=#allbranches>1.2 All branches</a></p> |
| 2164 | 2279 | <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p> |
| 2280 | +<p class=c2><a href=#topcallsbyelapsedtime>1.4 Top calls by elapsed time</a></p> | |
| 2165 | 2281 | <p class=c1><a href=#tables>2. Tables</a></p> |
| 2166 | 2282 | <h1 id=reports>1. Reports</h1> |
| 2167 | 2283 | <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2> |
| @@ -2237,6 +2353,10 @@ ms, us or ns. Also, negative values are relative to the end of trace. Examples: · 69 unmodified lines | ||
| 2237 | 2353 | -10ms- The last 10ms |
| 2238 | 2354 | </pre> |
| 2239 | 2355 | N.B. Due to the granularity of timestamps, there could be no branches in any given time range. |
| 2356 | +<h2 id=topcallsbyelapsedtime>1.4 Top calls by elapsed time</h2> | |
| 2357 | +The Top calls by elapsed time report displays calls in descending order of time elapsed between when the function was called and when it returned. | |
| 2358 | +The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together. | |
| 2359 | +If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar. | |
| 2240 | 2360 | <h1 id=tables>2. Tables</h1> |
| 2241 | 2361 | The Tables menu shows all tables and views in the database. Most tables have an associated view |
| 2242 | 2362 | which displays the information in a more friendly way. Not all data for large tables is fetched |
| @@ -2371,6 +2491,9 @@ class MainWindow(QMainWindow): · 128 unmodified lines | ||
| 2371 | 2491 | |
| 2372 | 2492 | self.EventMenu(GetEventList(glb.db), reports_menu) |
| 2373 | 2493 | |
| 2494 | + if IsSelectable(glb.db, "calls"): | |
| 2495 | + reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self)) | |
| 2496 | + | |
| 2374 | 2497 | self.TableMenu(GetTableList(glb), menu) |
| 2375 | 2498 | |
| 2376 | 2499 | self.window_menu = WindowMenu(self.mdi_area, menu) |
| @@ -2426,6 +2549,12 @@ class MainWindow(QMainWindow): · 49 unmodified lines | ||
| 2426 | 2549 | def NewCallGraph(self): |
| 2427 | 2550 | CallGraphWindow(self.glb, self) |
| 2428 | 2551 | |
| 2552 | + def NewTopCalls(self): | |
| 2553 | + dialog = TopCallsDialog(self.glb, self) | |
| 2554 | + ret = dialog.exec_() | |
| 2555 | + if ret: | |
| 2556 | + TopCallsWindow(self.glb, dialog.report_vars, self) | |
| 2557 | + | |
| 2429 | 2558 | def NewBranchView(self, event_id): |
| 2430 | 2559 | BranchWindow(self.glb, event_id, ReportVars(), self) |
| 2431 | 2560 | |