diff options
| author | Adrian Hunter <adrian.hunter@intel.com> | 2019-02-22 02:27:28 -0500 |
|---|---|---|
| committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2019-02-22 14:52:07 -0500 |
| commit | cd358012ba20d4193c225d89cd1d0c11bc54b1bc (patch) | |
| tree | fb598c601d41d7b0db13966ea2f1e94ae6ec6901 /tools/perf/scripts/python/exported-sql-viewer.py | |
| parent | fc2c77aa8437855d2992d3f3c6a1dff681789a07 (diff) | |
perf scripts python: exported-sql-viewer.py: Add top calls report
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.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/scripts/python/exported-sql-viewer.py')
| -rwxr-xr-x | tools/perf/scripts/python/exported-sql-viewer.py | 141 |
1 files changed, 135 insertions, 6 deletions
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py index 728200e3a691..09ce73b07d35 100755 --- a/tools/perf/scripts/python/exported-sql-viewer.py +++ b/tools/perf/scripts/python/exported-sql-viewer.py | |||
| @@ -1402,12 +1402,13 @@ class BranchModel(TreeModel): | |||
| 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): | |||
| 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): | |||
| 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): | |||
| 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): | |||
| 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 { | |||
| 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: | |||
| 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): | |||
| 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): | |||
| 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 | ||
