History

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.

Adrian Hunter committed 7 years ago

Commit cd358011 parent fc2c77a1 file · +135 −6Browse files

Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all

Unified · Split

tools/perf/scripts/python/exported-sql-viewer.py+135−6
@@ -1402,12 +1402,13 @@ class BranchModel(TreeModel): · 1401 unmodified lines
14021402
14031403 class ReportVars():
14041404
1405− def __init__(self, name = "", where_clause = ""):
1405+ def __init__(self, name = "", where_clause = "", limit = ""):
14061406 self.name = name
14071407 self.where_clause = where_clause
1408+ self.limit = limit
14081409
14091410 def UniqueId(self):
1410− return str(self.where_clause)
1411+ return str(self.where_clause + ";" + self.limit)
14111412
14121413 # Branch window
14131414
@@ -1485,16 +1486,16 @@ class BranchWindow(QMdiSubWindow): · 71 unmodified lines
14851486
14861487 class LineEditDataItem(object):
14871488
1488− def __init__(self, glb, label, placeholder_text, parent, id = ""):
1489+ def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""):
14891490 self.glb = glb
14901491 self.label = label
14911492 self.placeholder_text = placeholder_text
14921493 self.parent = parent
14931494 self.id = id
14941495
1495− self.value = ""
1496+ self.value = default
14961497
1497− self.widget = QLineEdit()
1498+ self.widget = QLineEdit(default)
14981499 self.widget.editingFinished.connect(self.Validate)
14991500 self.widget.textChanged.connect(self.Invalidate)
15001501 self.red = False
@@ -1582,6 +1583,21 @@ class NonNegativeIntegerRangesDataItem(LineEditDataItem): · 81 unmodified lines
15821583 ranges.append(self.column_name + " IN (" + ",".join(singles) + ")")
15831584 self.value = " OR ".join(ranges)
15841585
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+
15851601 # Dialog data item converted and validated using a SQL table
15861602
15871603 class SQLTableDataItem(LineEditDataItem):
@@ -1799,7 +1815,9 @@ class ReportDialogBase(QDialog): · 211 unmodified lines
17991815 if not d.IsValid():
18001816 return
18011817 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):
18031821 if len(vars.where_clause):
18041822 vars.where_clause += " AND "
18051823 vars.where_clause += d.value
@@ -2059,6 +2077,103 @@ def GetTableList(glb): · 253 unmodified lines
20592077 tables.append("information_schema.columns")
20602078 return tables
20612079
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+
20622177 # Action Definition
20632178
20642179 def CreateAction(label, tip, callback, parent=None, shortcut=None):
@@ -2162,6 +2277,7 @@ p.c2 { · 97 unmodified lines
21622277 <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p>
21632278 <p class=c2><a href=#allbranches>1.2 All branches</a></p>
21642279 <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>
21652281 <p class=c1><a href=#tables>2. Tables</a></p>
21662282 <h1 id=reports>1. Reports</h1>
21672283 <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
22372353 -10ms- The last 10ms
22382354 </pre>
22392355 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.
22402360 <h1 id=tables>2. Tables</h1>
22412361 The Tables menu shows all tables and views in the database. Most tables have an associated view
22422362 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
23712491
23722492 self.EventMenu(GetEventList(glb.db), reports_menu)
23732493
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+
23742497 self.TableMenu(GetTableList(glb), menu)
23752498
23762499 self.window_menu = WindowMenu(self.mdi_area, menu)
@@ -2426,6 +2549,12 @@ class MainWindow(QMainWindow): · 49 unmodified lines
24262549 def NewCallGraph(self):
24272550 CallGraphWindow(self.glb, self)
24282551
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+
24292558 def NewBranchView(self, event_id):
24302559 BranchWindow(self.glb, event_id, ReportVars(), self)
24312560