diff options
author | Adrian Hunter <adrian.hunter@intel.com> | 2019-02-28 08:00:31 -0500 |
---|---|---|
committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2019-03-01 13:04:16 -0500 |
commit | ae8b887c00d3fe4ca8c2cba16ae452b5df4c19e2 (patch) | |
tree | 038e8ad65996b1018fcb1b0e45a635efb1259b44 /tools/perf/scripts/python/exported-sql-viewer.py | |
parent | 254c0d820b86d7712e03750c58ab104e06e3655d (diff) |
perf scripts python: exported-sql-viewer.py: Add call tree
Add a new report to display a call tree. The Call Tree report is very
similar to the Context-Sensitive Call Graph, but the data is not
aggregated. Also the 'Count' column, which would be always 1, is replaced
by the 'Call Time'.
Committer testing:
$ cat simple-retpoline.c
/*
https://lkml.kernel.org/r/20190109091835.5570-6-adrian.hunter@intel.com
$ gcc -ggdb3 -Wall -Wextra -O2 -o simple-retpoline simple-retpoline.c
$ objdump -d simple-retpoline
*/
__attribute__((noinline)) int bar(void)
{
return -1;
}
int foo(void)
{
return bar() + 1;
}
__attribute__((indirect_branch("thunk"))) int main()
{
int (*volatile fn)(void) = foo;
fn();
return fn();
}
$
$ perf record -o simple-retpoline.perf.data -e intel_pt/cyc/u ./simple-retpoline
$ perf script -i simple-retpoline.perf.data --itrace=be -s ~acme/libexec/perf-core/scripts/python/export-to-sqlite.py simple-retpoline.db branches calls
$ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py simple-retpoline.db
And in the GUI select:
"Reports"
"Call Tree"
Call Path | Object | Call Time (ns) | Time (ns) | Time (%) | Branch Count | Brach Count (%) |
> simple-retpolin
> PID:TID
> _start ld-2.28.so 2193855505777 156267 100.0 10602 100.0
unknown unknown 2193855506010 2276 1.5 1 0.0
> _dl_start ld-2.28.so 2193855508286 137047 87.7 10088 95.2
> _dl_init ld-2.28.so 2193855645444 9142 5.9 326 3.1
> _start simple-retpoline 2193855654587 7457 4.8 182 1.7
> __libc_start_main <SNIP>
<SNIP>
> main simple-retpoline 2193855657493 32 0.5 12 6.7
> foo simple-retpoline 2193855657493 14 43.8 5 41.7
<SNIP>
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: https://lkml.kernel.org/n/tip-enf0w96gqzfpv4fi16pw9ovc@git.kernel.org
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 | 195 |
1 files changed, 186 insertions, 9 deletions
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py index c4a2134d85f5..afec9479ca7f 100755 --- a/tools/perf/scripts/python/exported-sql-viewer.py +++ b/tools/perf/scripts/python/exported-sql-viewer.py | |||
@@ -688,6 +688,150 @@ class CallGraphModel(CallGraphModelBase): | |||
688 | ids.insert(0, query.value(1)) | 688 | ids.insert(0, query.value(1)) |
689 | return ids | 689 | return ids |
690 | 690 | ||
691 | # Call tree data model level 2+ item base | ||
692 | |||
693 | class CallTreeLevelTwoPlusItemBase(CallGraphLevelItemBase): | ||
694 | |||
695 | def __init__(self, glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item): | ||
696 | super(CallTreeLevelTwoPlusItemBase, self).__init__(glb, row, parent_item) | ||
697 | self.comm_id = comm_id | ||
698 | self.thread_id = thread_id | ||
699 | self.calls_id = calls_id | ||
700 | self.branch_count = branch_count | ||
701 | self.time = time | ||
702 | |||
703 | def Select(self): | ||
704 | self.query_done = True; | ||
705 | if self.calls_id == 0: | ||
706 | comm_thread = " AND comm_id = " + str(self.comm_id) + " AND thread_id = " + str(self.thread_id) | ||
707 | else: | ||
708 | comm_thread = "" | ||
709 | query = QSqlQuery(self.glb.db) | ||
710 | QueryExec(query, "SELECT calls.id, name, short_name, call_time, return_time - call_time, branch_count" | ||
711 | " FROM calls" | ||
712 | " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" | ||
713 | " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" | ||
714 | " INNER JOIN dsos ON symbols.dso_id = dsos.id" | ||
715 | " WHERE calls.parent_id = " + str(self.calls_id) + comm_thread + | ||
716 | " ORDER BY call_time, calls.id") | ||
717 | while query.next(): | ||
718 | child_item = CallTreeLevelThreeItem(self.glb, self.child_count, self.comm_id, self.thread_id, query.value(0), query.value(1), query.value(2), query.value(3), int(query.value(4)), int(query.value(5)), self) | ||
719 | self.child_items.append(child_item) | ||
720 | self.child_count += 1 | ||
721 | |||
722 | # Call tree data model level three item | ||
723 | |||
724 | class CallTreeLevelThreeItem(CallTreeLevelTwoPlusItemBase): | ||
725 | |||
726 | def __init__(self, glb, row, comm_id, thread_id, calls_id, name, dso, count, time, branch_count, parent_item): | ||
727 | super(CallTreeLevelThreeItem, self).__init__(glb, row, comm_id, thread_id, calls_id, time, branch_count, parent_item) | ||
728 | dso = dsoname(dso) | ||
729 | self.data = [ name, dso, str(count), str(time), PercentToOneDP(time, parent_item.time), str(branch_count), PercentToOneDP(branch_count, parent_item.branch_count) ] | ||
730 | self.dbid = calls_id | ||
731 | |||
732 | # Call tree data model level two item | ||
733 | |||
734 | class CallTreeLevelTwoItem(CallTreeLevelTwoPlusItemBase): | ||
735 | |||
736 | def __init__(self, glb, row, comm_id, thread_id, pid, tid, parent_item): | ||
737 | super(CallTreeLevelTwoItem, self).__init__(glb, row, comm_id, thread_id, 0, 0, 0, parent_item) | ||
738 | self.data = [str(pid) + ":" + str(tid), "", "", "", "", "", ""] | ||
739 | self.dbid = thread_id | ||
740 | |||
741 | def Select(self): | ||
742 | super(CallTreeLevelTwoItem, self).Select() | ||
743 | for child_item in self.child_items: | ||
744 | self.time += child_item.time | ||
745 | self.branch_count += child_item.branch_count | ||
746 | for child_item in self.child_items: | ||
747 | child_item.data[4] = PercentToOneDP(child_item.time, self.time) | ||
748 | child_item.data[6] = PercentToOneDP(child_item.branch_count, self.branch_count) | ||
749 | |||
750 | # Call tree data model level one item | ||
751 | |||
752 | class CallTreeLevelOneItem(CallGraphLevelItemBase): | ||
753 | |||
754 | def __init__(self, glb, row, comm_id, comm, parent_item): | ||
755 | super(CallTreeLevelOneItem, self).__init__(glb, row, parent_item) | ||
756 | self.data = [comm, "", "", "", "", "", ""] | ||
757 | self.dbid = comm_id | ||
758 | |||
759 | def Select(self): | ||
760 | self.query_done = True; | ||
761 | query = QSqlQuery(self.glb.db) | ||
762 | QueryExec(query, "SELECT thread_id, pid, tid" | ||
763 | " FROM comm_threads" | ||
764 | " INNER JOIN threads ON thread_id = threads.id" | ||
765 | " WHERE comm_id = " + str(self.dbid)) | ||
766 | while query.next(): | ||
767 | child_item = CallTreeLevelTwoItem(self.glb, self.child_count, self.dbid, query.value(0), query.value(1), query.value(2), self) | ||
768 | self.child_items.append(child_item) | ||
769 | self.child_count += 1 | ||
770 | |||
771 | # Call tree data model root item | ||
772 | |||
773 | class CallTreeRootItem(CallGraphLevelItemBase): | ||
774 | |||
775 | def __init__(self, glb): | ||
776 | super(CallTreeRootItem, self).__init__(glb, 0, None) | ||
777 | self.dbid = 0 | ||
778 | self.query_done = True; | ||
779 | query = QSqlQuery(glb.db) | ||
780 | QueryExec(query, "SELECT id, comm FROM comms") | ||
781 | while query.next(): | ||
782 | if not query.value(0): | ||
783 | continue | ||
784 | child_item = CallTreeLevelOneItem(glb, self.child_count, query.value(0), query.value(1), self) | ||
785 | self.child_items.append(child_item) | ||
786 | self.child_count += 1 | ||
787 | |||
788 | # Call Tree data model | ||
789 | |||
790 | class CallTreeModel(CallGraphModelBase): | ||
791 | |||
792 | def __init__(self, glb, parent=None): | ||
793 | super(CallTreeModel, self).__init__(glb, parent) | ||
794 | |||
795 | def GetRoot(self): | ||
796 | return CallTreeRootItem(self.glb) | ||
797 | |||
798 | def columnCount(self, parent=None): | ||
799 | return 7 | ||
800 | |||
801 | def columnHeader(self, column): | ||
802 | headers = ["Call Path", "Object", "Call Time", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "] | ||
803 | return headers[column] | ||
804 | |||
805 | def columnAlignment(self, column): | ||
806 | alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] | ||
807 | return alignment[column] | ||
808 | |||
809 | def DoFindSelect(self, query, match): | ||
810 | QueryExec(query, "SELECT calls.id, comm_id, thread_id" | ||
811 | " FROM calls" | ||
812 | " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" | ||
813 | " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" | ||
814 | " WHERE symbols.name" + match + | ||
815 | " ORDER BY comm_id, thread_id, call_time, calls.id") | ||
816 | |||
817 | def FindPath(self, query): | ||
818 | # Turn the query result into a list of ids that the tree view can walk | ||
819 | # to open the tree at the right place. | ||
820 | ids = [] | ||
821 | parent_id = query.value(0) | ||
822 | while parent_id: | ||
823 | ids.insert(0, parent_id) | ||
824 | q2 = QSqlQuery(self.glb.db) | ||
825 | QueryExec(q2, "SELECT parent_id" | ||
826 | " FROM calls" | ||
827 | " WHERE id = " + str(parent_id)) | ||
828 | if not q2.next(): | ||
829 | break | ||
830 | parent_id = q2.value(0) | ||
831 | ids.insert(0, query.value(2)) | ||
832 | ids.insert(0, query.value(1)) | ||
833 | return ids | ||
834 | |||
691 | # Vertical widget layout | 835 | # Vertical widget layout |
692 | 836 | ||
693 | class VBox(): | 837 | class VBox(): |
@@ -772,6 +916,29 @@ class CallGraphWindow(TreeWindowBase): | |||
772 | 916 | ||
773 | AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph") | 917 | AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph") |
774 | 918 | ||
919 | # Call tree window | ||
920 | |||
921 | class CallTreeWindow(TreeWindowBase): | ||
922 | |||
923 | def __init__(self, glb, parent=None): | ||
924 | super(CallTreeWindow, self).__init__(parent) | ||
925 | |||
926 | self.model = LookupCreateModel("Call Tree", lambda x=glb: CallTreeModel(x)) | ||
927 | |||
928 | self.view = QTreeView() | ||
929 | self.view.setModel(self.model) | ||
930 | |||
931 | for c, w in ((0, 230), (1, 100), (2, 100), (3, 70), (4, 70), (5, 100)): | ||
932 | self.view.setColumnWidth(c, w) | ||
933 | |||
934 | self.find_bar = FindBar(self, self) | ||
935 | |||
936 | self.vbox = VBox(self.view, self.find_bar.Widget()) | ||
937 | |||
938 | self.setWidget(self.vbox.Widget()) | ||
939 | |||
940 | AddSubWindow(glb.mainwindow.mdi_area, self, "Call Tree") | ||
941 | |||
775 | # Child data item finder | 942 | # Child data item finder |
776 | 943 | ||
777 | class ChildDataItemFinder(): | 944 | class ChildDataItemFinder(): |
@@ -1890,10 +2057,10 @@ def GetEventList(db): | |||
1890 | 2057 | ||
1891 | # Is a table selectable | 2058 | # Is a table selectable |
1892 | 2059 | ||
1893 | def IsSelectable(db, table): | 2060 | def IsSelectable(db, table, sql = ""): |
1894 | query = QSqlQuery(db) | 2061 | query = QSqlQuery(db) |
1895 | try: | 2062 | try: |
1896 | QueryExec(query, "SELECT * FROM " + table + " LIMIT 1") | 2063 | QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1") |
1897 | except: | 2064 | except: |
1898 | return False | 2065 | return False |
1899 | return True | 2066 | return True |
@@ -2302,9 +2469,10 @@ p.c2 { | |||
2302 | </style> | 2469 | </style> |
2303 | <p class=c1><a href=#reports>1. Reports</a></p> | 2470 | <p class=c1><a href=#reports>1. Reports</a></p> |
2304 | <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p> | 2471 | <p class=c2><a href=#callgraph>1.1 Context-Sensitive Call Graph</a></p> |
2305 | <p class=c2><a href=#allbranches>1.2 All branches</a></p> | 2472 | <p class=c2><a href=#calltree>1.2 Call Tree</a></p> |
2306 | <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p> | 2473 | <p class=c2><a href=#allbranches>1.3 All branches</a></p> |
2307 | <p class=c2><a href=#topcallsbyelapsedtime>1.4 Top calls by elapsed time</a></p> | 2474 | <p class=c2><a href=#selectedbranches>1.4 Selected branches</a></p> |
2475 | <p class=c2><a href=#topcallsbyelapsedtime>1.5 Top calls by elapsed time</a></p> | ||
2308 | <p class=c1><a href=#tables>2. Tables</a></p> | 2476 | <p class=c1><a href=#tables>2. Tables</a></p> |
2309 | <h1 id=reports>1. Reports</h1> | 2477 | <h1 id=reports>1. Reports</h1> |
2310 | <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2> | 2478 | <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2> |
@@ -2340,7 +2508,10 @@ v- ls | |||
2340 | <h3>Find</h3> | 2508 | <h3>Find</h3> |
2341 | Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match. | 2509 | Ctrl-F displays a Find bar which finds function names by either an exact match or a pattern match. |
2342 | The pattern matching symbols are ? for any character and * for zero or more characters. | 2510 | The pattern matching symbols are ? for any character and * for zero or more characters. |
2343 | <h2 id=allbranches>1.2 All branches</h2> | 2511 | <h2 id=calltree>1.2 Call Tree</h2> |
2512 | The Call Tree report is very similar to the Context-Sensitive Call Graph, but the data is not aggregated. | ||
2513 | Also the 'Count' column, which would be always 1, is replaced by the 'Call Time'. | ||
2514 | <h2 id=allbranches>1.3 All branches</h2> | ||
2344 | The All branches report displays all branches in chronological order. | 2515 | The All branches report displays all branches in chronological order. |
2345 | Not all data is fetched immediately. More records can be fetched using the Fetch bar provided. | 2516 | Not all data is fetched immediately. More records can be fetched using the Fetch bar provided. |
2346 | <h3>Disassembly</h3> | 2517 | <h3>Disassembly</h3> |
@@ -2366,10 +2537,10 @@ sudo ldconfig | |||
2366 | Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match. | 2537 | Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match. |
2367 | Refer to Python documentation for the regular expression syntax. | 2538 | Refer to Python documentation for the regular expression syntax. |
2368 | All columns are searched, but only currently fetched rows are searched. | 2539 | All columns are searched, but only currently fetched rows are searched. |
2369 | <h2 id=selectedbranches>1.3 Selected branches</h2> | 2540 | <h2 id=selectedbranches>1.4 Selected branches</h2> |
2370 | This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced | 2541 | This is the same as the <a href=#allbranches>All branches</a> report but with the data reduced |
2371 | by various selection criteria. A dialog box displays available criteria which are AND'ed together. | 2542 | by various selection criteria. A dialog box displays available criteria which are AND'ed together. |
2372 | <h3>1.3.1 Time ranges</h3> | 2543 | <h3>1.4.1 Time ranges</h3> |
2373 | The time ranges hint text shows the total time range. Relative time ranges can also be entered in | 2544 | The time ranges hint text shows the total time range. Relative time ranges can also be entered in |
2374 | ms, us or ns. Also, negative values are relative to the end of trace. Examples: | 2545 | ms, us or ns. Also, negative values are relative to the end of trace. Examples: |
2375 | <pre> | 2546 | <pre> |
@@ -2380,7 +2551,7 @@ ms, us or ns. Also, negative values are relative to the end of trace. Examples: | |||
2380 | -10ms- The last 10ms | 2551 | -10ms- The last 10ms |
2381 | </pre> | 2552 | </pre> |
2382 | N.B. Due to the granularity of timestamps, there could be no branches in any given time range. | 2553 | N.B. Due to the granularity of timestamps, there could be no branches in any given time range. |
2383 | <h2 id=topcallsbyelapsedtime>1.4 Top calls by elapsed time</h2> | 2554 | <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2> |
2384 | 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. | 2555 | 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. |
2385 | The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together. | 2556 | The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together. |
2386 | If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar. | 2557 | If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar. |
@@ -2516,6 +2687,9 @@ class MainWindow(QMainWindow): | |||
2516 | if IsSelectable(glb.db, "calls"): | 2687 | if IsSelectable(glb.db, "calls"): |
2517 | reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) | 2688 | reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) |
2518 | 2689 | ||
2690 | if IsSelectable(glb.db, "calls", "WHERE parent_id >= 0"): | ||
2691 | reports_menu.addAction(CreateAction("Call &Tree", "Create a new window containing a call tree", self.NewCallTree, self)) | ||
2692 | |||
2519 | self.EventMenu(GetEventList(glb.db), reports_menu) | 2693 | self.EventMenu(GetEventList(glb.db), reports_menu) |
2520 | 2694 | ||
2521 | if IsSelectable(glb.db, "calls"): | 2695 | if IsSelectable(glb.db, "calls"): |
@@ -2576,6 +2750,9 @@ class MainWindow(QMainWindow): | |||
2576 | def NewCallGraph(self): | 2750 | def NewCallGraph(self): |
2577 | CallGraphWindow(self.glb, self) | 2751 | CallGraphWindow(self.glb, self) |
2578 | 2752 | ||
2753 | def NewCallTree(self): | ||
2754 | CallTreeWindow(self.glb, self) | ||
2755 | |||
2579 | def NewTopCalls(self): | 2756 | def NewTopCalls(self): |
2580 | dialog = TopCallsDialog(self.glb, self) | 2757 | dialog = TopCallsDialog(self.glb, self) |
2581 | ret = dialog.exec_() | 2758 | ret = dialog.exec_() |