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.
Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all
Unified · Split
| @@ -1402,12 +1402,13 @@ class BranchModel(TreeModel): · 1401 unmodified lines | |||
| 1402 | 1402 | ||
| 1403 | class ReportVars(): | 1403 | class ReportVars(): |
| 1404 | 1404 | ||
| 1405 | def __init__(self, name = "", where_clause = ""): | 1405 | def __init__(self, name = "", where_clause = "", limit = ""): |
| 1406 | self.name = name | 1406 | self.name = name |
| 1407 | self.where_clause = where_clause | 1407 | self.where_clause = where_clause |
| 1408 | self.limit = limit | ||
| 1408 | 1409 | ||
| 1409 | def UniqueId(self): | 1410 | def UniqueId(self): |
| 1410 | return str(self.where_clause) | 1411 | return str(self.where_clause + ";" + self.limit) |
| 1411 | 1412 | ||
| 1412 | # Branch window | 1413 | # Branch window |
| 1413 | 1414 | ||
| @@ -1485,16 +1486,16 @@ class BranchWindow(QMdiSubWindow): · 71 unmodified lines | |||
| 1485 | 1486 | ||
| 1486 | class LineEditDataItem(object): | 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 | self.glb = glb | 1490 | self.glb = glb |
| 1490 | self.label = label | 1491 | self.label = label |
| 1491 | self.placeholder_text = placeholder_text | 1492 | self.placeholder_text = placeholder_text |
| 1492 | self.parent = parent | 1493 | self.parent = parent |
| 1493 | self.id = id | 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 | self.widget.editingFinished.connect(self.Validate) | 1499 | self.widget.editingFinished.connect(self.Validate) |
| 1499 | self.widget.textChanged.connect(self.Invalidate) | 1500 | self.widget.textChanged.connect(self.Invalidate) |
| 1500 | self.red = False | 1501 | self.red = False |
| @@ -1582,6 +1583,21 @@ class NonNegativeIntegerRangesDataItem(LineEditDataItem): · 81 unmodified lines | |||
| 1582 | ranges.append(self.column_name + " IN (" + ",".join(singles) + ")") | 1583 | ranges.append(self.column_name + " IN (" + ",".join(singles) + ")") |
| 1583 | self.value = " OR ".join(ranges) | 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 | # Dialog data item converted and validated using a SQL table | 1601 | # Dialog data item converted and validated using a SQL table |
| 1586 | 1602 | ||
| 1587 | class SQLTableDataItem(LineEditDataItem): | 1603 | class SQLTableDataItem(LineEditDataItem): |
| @@ -1799,7 +1815,9 @@ class ReportDialogBase(QDialog): · 211 unmodified lines | |||
| 1799 | if not d.IsValid(): | 1815 | if not d.IsValid(): |
| 1800 | return | 1816 | return |
| 1801 | for d in self.data_items[1:]: | 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 | if len(vars.where_clause): | 1821 | if len(vars.where_clause): |
| 1804 | vars.where_clause += " AND " | 1822 | vars.where_clause += " AND " |
| 1805 | vars.where_clause += d.value | 1823 | vars.where_clause += d.value |
| @@ -2059,6 +2077,103 @@ def GetTableList(glb): · 253 unmodified lines | |||
| 2059 | tables.append("information_schema.columns") | 2077 | tables.append("information_schema.columns") |
| 2060 | return tables | 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 | # Action Definition | 2177 | # Action Definition |
| 2063 | 2178 | ||
| 2064 | def CreateAction(label, tip, callback, parent=None, shortcut=None): | 2179 | def CreateAction(label, tip, callback, parent=None, shortcut=None): |
| @@ -2162,6 +2277,7 @@ p.c2 { · 97 unmodified lines | |||
| 2162 | <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p> | 2277 | <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p> |
| 2163 | <p class=c2><a href=#allbranches>1.2 All branches</a></p> | 2278 | <p class=c2><a href=#allbranches>1.2 All branches</a></p> |
| 2164 | <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p> | 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 | <p class=c1><a href=#tables>2. Tables</a></p> | 2281 | <p class=c1><a href=#tables>2. Tables</a></p> |
| 2166 | <h1 id=reports>1. Reports</h1> | 2282 | <h1 id=reports>1. Reports</h1> |
| 2167 | <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2> | 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 | -10ms- The last 10ms | 2353 | -10ms- The last 10ms |
| 2238 | </pre> | 2354 | </pre> |
| 2239 | N.B. Due to the granularity of timestamps, there could be no branches in any given time range. | 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 | <h1 id=tables>2. Tables</h1> | 2360 | <h1 id=tables>2. Tables</h1> |
| 2241 | The Tables menu shows all tables and views in the database. Most tables have an associated view | 2361 | The Tables menu shows all tables and views in the database. Most tables have an associated view |
| 2242 | which displays the information in a more friendly way. Not all data for large tables is fetched | 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 | self.EventMenu(GetEventList(glb.db), reports_menu) | 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 | self.TableMenu(GetTableList(glb), menu) | 2497 | self.TableMenu(GetTableList(glb), menu) |
| 2375 | 2498 | ||
| 2376 | self.window_menu = WindowMenu(self.mdi_area, menu) | 2499 | self.window_menu = WindowMenu(self.mdi_area, menu) |
| @@ -2426,6 +2549,12 @@ class MainWindow(QMainWindow): · 49 unmodified lines | |||
| 2426 | def NewCallGraph(self): | 2549 | def NewCallGraph(self): |
| 2427 | CallGraphWindow(self.glb, self) | 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 | def NewBranchView(self, event_id): | 2558 | def NewBranchView(self, event_id): |
| 2430 | BranchWindow(self.glb, event_id, ReportVars(), self) | 2559 | BranchWindow(self.glb, event_id, ReportVars(), self) |
| 2431 | 2560 | ||