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 | ||