diff options
Diffstat (limited to 'tools/perf/scripts/python/exported-sql-viewer.py')
| -rwxr-xr-x | tools/perf/scripts/python/exported-sql-viewer.py | 857 |
1 files changed, 626 insertions, 231 deletions
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py index f278ce5ebab7..afec9479ca7f 100755 --- a/tools/perf/scripts/python/exported-sql-viewer.py +++ b/tools/perf/scripts/python/exported-sql-viewer.py | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | #!/usr/bin/python2 | 1 | #!/usr/bin/env python2 |
| 2 | # SPDX-License-Identifier: GPL-2.0 | 2 | # SPDX-License-Identifier: GPL-2.0 |
| 3 | # exported-sql-viewer.py: view data from sql database | 3 | # exported-sql-viewer.py: view data from sql database |
| 4 | # Copyright (c) 2014-2018, Intel Corporation. | 4 | # Copyright (c) 2014-2018, Intel Corporation. |
| @@ -167,9 +167,10 @@ class Thread(QThread): | |||
| 167 | 167 | ||
| 168 | class TreeModel(QAbstractItemModel): | 168 | class TreeModel(QAbstractItemModel): |
| 169 | 169 | ||
| 170 | def __init__(self, root, parent=None): | 170 | def __init__(self, glb, parent=None): |
| 171 | super(TreeModel, self).__init__(parent) | 171 | super(TreeModel, self).__init__(parent) |
| 172 | self.root = root | 172 | self.glb = glb |
| 173 | self.root = self.GetRoot() | ||
| 173 | self.last_row_read = 0 | 174 | self.last_row_read = 0 |
| 174 | 175 | ||
| 175 | def Item(self, parent): | 176 | def Item(self, parent): |
| @@ -557,24 +558,12 @@ class CallGraphRootItem(CallGraphLevelItemBase): | |||
| 557 | self.child_items.append(child_item) | 558 | self.child_items.append(child_item) |
| 558 | self.child_count += 1 | 559 | self.child_count += 1 |
| 559 | 560 | ||
| 560 | # Context-sensitive call graph data model | 561 | # Context-sensitive call graph data model base |
| 561 | 562 | ||
| 562 | class CallGraphModel(TreeModel): | 563 | class CallGraphModelBase(TreeModel): |
| 563 | 564 | ||
| 564 | def __init__(self, glb, parent=None): | 565 | def __init__(self, glb, parent=None): |
| 565 | super(CallGraphModel, self).__init__(CallGraphRootItem(glb), parent) | 566 | super(CallGraphModelBase, self).__init__(glb, parent) |
| 566 | self.glb = glb | ||
| 567 | |||
| 568 | def columnCount(self, parent=None): | ||
| 569 | return 7 | ||
| 570 | |||
| 571 | def columnHeader(self, column): | ||
| 572 | headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "] | ||
| 573 | return headers[column] | ||
| 574 | |||
| 575 | def columnAlignment(self, column): | ||
| 576 | alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] | ||
| 577 | return alignment[column] | ||
| 578 | 567 | ||
| 579 | def FindSelect(self, value, pattern, query): | 568 | def FindSelect(self, value, pattern, query): |
| 580 | if pattern: | 569 | if pattern: |
| @@ -594,34 +583,7 @@ class CallGraphModel(TreeModel): | |||
| 594 | match = " GLOB '" + str(value) + "'" | 583 | match = " GLOB '" + str(value) + "'" |
| 595 | else: | 584 | else: |
| 596 | match = " = '" + str(value) + "'" | 585 | match = " = '" + str(value) + "'" |
| 597 | QueryExec(query, "SELECT call_path_id, comm_id, thread_id" | 586 | self.DoFindSelect(query, match) |
| 598 | " FROM calls" | ||
| 599 | " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" | ||
| 600 | " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" | ||
| 601 | " WHERE symbols.name" + match + | ||
| 602 | " GROUP BY comm_id, thread_id, call_path_id" | ||
| 603 | " ORDER BY comm_id, thread_id, call_path_id") | ||
| 604 | |||
| 605 | def FindPath(self, query): | ||
| 606 | # Turn the query result into a list of ids that the tree view can walk | ||
| 607 | # to open the tree at the right place. | ||
| 608 | ids = [] | ||
| 609 | parent_id = query.value(0) | ||
| 610 | while parent_id: | ||
| 611 | ids.insert(0, parent_id) | ||
| 612 | q2 = QSqlQuery(self.glb.db) | ||
| 613 | QueryExec(q2, "SELECT parent_id" | ||
| 614 | " FROM call_paths" | ||
| 615 | " WHERE id = " + str(parent_id)) | ||
| 616 | if not q2.next(): | ||
| 617 | break | ||
| 618 | parent_id = q2.value(0) | ||
| 619 | # The call path root is not used | ||
| 620 | if ids[0] == 1: | ||
| 621 | del ids[0] | ||
| 622 | ids.insert(0, query.value(2)) | ||
| 623 | ids.insert(0, query.value(1)) | ||
| 624 | return ids | ||
| 625 | 587 | ||
| 626 | def Found(self, query, found): | 588 | def Found(self, query, found): |
| 627 | if found: | 589 | if found: |
| @@ -675,6 +637,201 @@ class CallGraphModel(TreeModel): | |||
| 675 | def FindDone(self, thread, callback, ids): | 637 | def FindDone(self, thread, callback, ids): |
| 676 | callback(ids) | 638 | callback(ids) |
| 677 | 639 | ||
| 640 | # Context-sensitive call graph data model | ||
| 641 | |||
| 642 | class CallGraphModel(CallGraphModelBase): | ||
| 643 | |||
| 644 | def __init__(self, glb, parent=None): | ||
| 645 | super(CallGraphModel, self).__init__(glb, parent) | ||
| 646 | |||
| 647 | def GetRoot(self): | ||
| 648 | return CallGraphRootItem(self.glb) | ||
| 649 | |||
| 650 | def columnCount(self, parent=None): | ||
| 651 | return 7 | ||
| 652 | |||
| 653 | def columnHeader(self, column): | ||
| 654 | headers = ["Call Path", "Object", "Count ", "Time (ns) ", "Time (%) ", "Branch Count ", "Branch Count (%) "] | ||
| 655 | return headers[column] | ||
| 656 | |||
| 657 | def columnAlignment(self, column): | ||
| 658 | alignment = [ Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight, Qt.AlignRight ] | ||
| 659 | return alignment[column] | ||
| 660 | |||
| 661 | def DoFindSelect(self, query, match): | ||
| 662 | QueryExec(query, "SELECT call_path_id, comm_id, thread_id" | ||
| 663 | " FROM calls" | ||
| 664 | " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" | ||
| 665 | " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" | ||
| 666 | " WHERE symbols.name" + match + | ||
| 667 | " GROUP BY comm_id, thread_id, call_path_id" | ||
| 668 | " ORDER BY comm_id, thread_id, call_path_id") | ||
| 669 | |||
| 670 | def FindPath(self, query): | ||
| 671 | # Turn the query result into a list of ids that the tree view can walk | ||
| 672 | # to open the tree at the right place. | ||
| 673 | ids = [] | ||
| 674 | parent_id = query.value(0) | ||
| 675 | while parent_id: | ||
| 676 | ids.insert(0, parent_id) | ||
| 677 | q2 = QSqlQuery(self.glb.db) | ||
| 678 | QueryExec(q2, "SELECT parent_id" | ||
| 679 | " FROM call_paths" | ||
| 680 | " WHERE id = " + str(parent_id)) | ||
| 681 | if not q2.next(): | ||
| 682 | break | ||
| 683 | parent_id = q2.value(0) | ||
| 684 | # The call path root is not used | ||
| 685 | if ids[0] == 1: | ||
| 686 | del ids[0] | ||
| 687 | ids.insert(0, query.value(2)) | ||
| 688 | ids.insert(0, query.value(1)) | ||
| 689 | return ids | ||
| 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 | |||
| 678 | # Vertical widget layout | 835 | # Vertical widget layout |
| 679 | 836 | ||
| 680 | class VBox(): | 837 | class VBox(): |
| @@ -693,28 +850,16 @@ class VBox(): | |||
| 693 | def Widget(self): | 850 | def Widget(self): |
| 694 | return self.vbox | 851 | return self.vbox |
| 695 | 852 | ||
| 696 | # Context-sensitive call graph window | 853 | # Tree window base |
| 697 | |||
| 698 | class CallGraphWindow(QMdiSubWindow): | ||
| 699 | |||
| 700 | def __init__(self, glb, parent=None): | ||
| 701 | super(CallGraphWindow, self).__init__(parent) | ||
| 702 | |||
| 703 | self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x)) | ||
| 704 | |||
| 705 | self.view = QTreeView() | ||
| 706 | self.view.setModel(self.model) | ||
| 707 | |||
| 708 | for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): | ||
| 709 | self.view.setColumnWidth(c, w) | ||
| 710 | |||
| 711 | self.find_bar = FindBar(self, self) | ||
| 712 | 854 | ||
| 713 | self.vbox = VBox(self.view, self.find_bar.Widget()) | 855 | class TreeWindowBase(QMdiSubWindow): |
| 714 | 856 | ||
| 715 | self.setWidget(self.vbox.Widget()) | 857 | def __init__(self, parent=None): |
| 858 | super(TreeWindowBase, self).__init__(parent) | ||
| 716 | 859 | ||
| 717 | AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph") | 860 | self.model = None |
| 861 | self.view = None | ||
| 862 | self.find_bar = None | ||
| 718 | 863 | ||
| 719 | def DisplayFound(self, ids): | 864 | def DisplayFound(self, ids): |
| 720 | if not len(ids): | 865 | if not len(ids): |
| @@ -747,6 +892,53 @@ class CallGraphWindow(QMdiSubWindow): | |||
| 747 | if not found: | 892 | if not found: |
| 748 | self.find_bar.NotFound() | 893 | self.find_bar.NotFound() |
| 749 | 894 | ||
| 895 | |||
| 896 | # Context-sensitive call graph window | ||
| 897 | |||
| 898 | class CallGraphWindow(TreeWindowBase): | ||
| 899 | |||
| 900 | def __init__(self, glb, parent=None): | ||
| 901 | super(CallGraphWindow, self).__init__(parent) | ||
| 902 | |||
| 903 | self.model = LookupCreateModel("Context-Sensitive Call Graph", lambda x=glb: CallGraphModel(x)) | ||
| 904 | |||
| 905 | self.view = QTreeView() | ||
| 906 | self.view.setModel(self.model) | ||
| 907 | |||
| 908 | for c, w in ((0, 250), (1, 100), (2, 60), (3, 70), (4, 70), (5, 100)): | ||
| 909 | self.view.setColumnWidth(c, w) | ||
| 910 | |||
| 911 | self.find_bar = FindBar(self, self) | ||
| 912 | |||
| 913 | self.vbox = VBox(self.view, self.find_bar.Widget()) | ||
| 914 | |||
| 915 | self.setWidget(self.vbox.Widget()) | ||
| 916 | |||
| 917 | AddSubWindow(glb.mainwindow.mdi_area, self, "Context-Sensitive Call Graph") | ||
| 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 | |||
| 750 | # Child data item finder | 942 | # Child data item finder |
| 751 | 943 | ||
| 752 | class ChildDataItemFinder(): | 944 | class ChildDataItemFinder(): |
| @@ -1327,8 +1519,7 @@ class BranchModel(TreeModel): | |||
| 1327 | progress = Signal(object) | 1519 | progress = Signal(object) |
| 1328 | 1520 | ||
| 1329 | def __init__(self, glb, event_id, where_clause, parent=None): | 1521 | def __init__(self, glb, event_id, where_clause, parent=None): |
| 1330 | super(BranchModel, self).__init__(BranchRootItem(), parent) | 1522 | super(BranchModel, self).__init__(glb, parent) |
| 1331 | self.glb = glb | ||
| 1332 | self.event_id = event_id | 1523 | self.event_id = event_id |
| 1333 | self.more = True | 1524 | self.more = True |
| 1334 | self.populated = 0 | 1525 | self.populated = 0 |
| @@ -1352,6 +1543,9 @@ class BranchModel(TreeModel): | |||
| 1352 | self.fetcher.done.connect(self.Update) | 1543 | self.fetcher.done.connect(self.Update) |
| 1353 | self.fetcher.Fetch(glb_chunk_sz) | 1544 | self.fetcher.Fetch(glb_chunk_sz) |
| 1354 | 1545 | ||
| 1546 | def GetRoot(self): | ||
| 1547 | return BranchRootItem() | ||
| 1548 | |||
| 1355 | def columnCount(self, parent=None): | 1549 | def columnCount(self, parent=None): |
| 1356 | return 8 | 1550 | return 8 |
| 1357 | 1551 | ||
| @@ -1398,18 +1592,28 @@ class BranchModel(TreeModel): | |||
| 1398 | def HasMoreRecords(self): | 1592 | def HasMoreRecords(self): |
| 1399 | return self.more | 1593 | return self.more |
| 1400 | 1594 | ||
| 1595 | # Report Variables | ||
| 1596 | |||
| 1597 | class ReportVars(): | ||
| 1598 | |||
| 1599 | def __init__(self, name = "", where_clause = "", limit = ""): | ||
| 1600 | self.name = name | ||
| 1601 | self.where_clause = where_clause | ||
| 1602 | self.limit = limit | ||
| 1603 | |||
| 1604 | def UniqueId(self): | ||
| 1605 | return str(self.where_clause + ";" + self.limit) | ||
| 1606 | |||
| 1401 | # Branch window | 1607 | # Branch window |
| 1402 | 1608 | ||
| 1403 | class BranchWindow(QMdiSubWindow): | 1609 | class BranchWindow(QMdiSubWindow): |
| 1404 | 1610 | ||
| 1405 | def __init__(self, glb, event_id, name, where_clause, parent=None): | 1611 | def __init__(self, glb, event_id, report_vars, parent=None): |
| 1406 | super(BranchWindow, self).__init__(parent) | 1612 | super(BranchWindow, self).__init__(parent) |
| 1407 | 1613 | ||
| 1408 | model_name = "Branch Events " + str(event_id) | 1614 | model_name = "Branch Events " + str(event_id) + " " + report_vars.UniqueId() |
| 1409 | if len(where_clause): | ||
| 1410 | model_name = where_clause + " " + model_name | ||
| 1411 | 1615 | ||
| 1412 | self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, where_clause)) | 1616 | self.model = LookupCreateModel(model_name, lambda: BranchModel(glb, event_id, report_vars.where_clause)) |
| 1413 | 1617 | ||
| 1414 | self.view = QTreeView() | 1618 | self.view = QTreeView() |
| 1415 | self.view.setUniformRowHeights(True) | 1619 | self.view.setUniformRowHeights(True) |
| @@ -1427,7 +1631,7 @@ class BranchWindow(QMdiSubWindow): | |||
| 1427 | 1631 | ||
| 1428 | self.setWidget(self.vbox.Widget()) | 1632 | self.setWidget(self.vbox.Widget()) |
| 1429 | 1633 | ||
| 1430 | AddSubWindow(glb.mainwindow.mdi_area, self, name + " Branch Events") | 1634 | AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name + " Branch Events") |
| 1431 | 1635 | ||
| 1432 | def ResizeColumnToContents(self, column, n): | 1636 | def ResizeColumnToContents(self, column, n): |
| 1433 | # Using the view's resizeColumnToContents() here is extrememly slow | 1637 | # Using the view's resizeColumnToContents() here is extrememly slow |
| @@ -1472,47 +1676,134 @@ class BranchWindow(QMdiSubWindow): | |||
| 1472 | else: | 1676 | else: |
| 1473 | self.find_bar.NotFound() | 1677 | self.find_bar.NotFound() |
| 1474 | 1678 | ||
| 1475 | # Dialog data item converted and validated using a SQL table | 1679 | # Line edit data item |
| 1476 | 1680 | ||
| 1477 | class SQLTableDialogDataItem(): | 1681 | class LineEditDataItem(object): |
| 1478 | 1682 | ||
| 1479 | def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent): | 1683 | def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""): |
| 1480 | self.glb = glb | 1684 | self.glb = glb |
| 1481 | self.label = label | 1685 | self.label = label |
| 1482 | self.placeholder_text = placeholder_text | 1686 | self.placeholder_text = placeholder_text |
| 1483 | self.table_name = table_name | ||
| 1484 | self.match_column = match_column | ||
| 1485 | self.column_name1 = column_name1 | ||
| 1486 | self.column_name2 = column_name2 | ||
| 1487 | self.parent = parent | 1687 | self.parent = parent |
| 1688 | self.id = id | ||
| 1488 | 1689 | ||
| 1489 | self.value = "" | 1690 | self.value = default |
| 1490 | 1691 | ||
| 1491 | self.widget = QLineEdit() | 1692 | self.widget = QLineEdit(default) |
| 1492 | self.widget.editingFinished.connect(self.Validate) | 1693 | self.widget.editingFinished.connect(self.Validate) |
| 1493 | self.widget.textChanged.connect(self.Invalidate) | 1694 | self.widget.textChanged.connect(self.Invalidate) |
| 1494 | self.red = False | 1695 | self.red = False |
| 1495 | self.error = "" | 1696 | self.error = "" |
| 1496 | self.validated = True | 1697 | self.validated = True |
| 1497 | 1698 | ||
| 1498 | self.last_id = 0 | ||
| 1499 | self.first_time = 0 | ||
| 1500 | self.last_time = 2 ** 64 | ||
| 1501 | if self.table_name == "<timeranges>": | ||
| 1502 | query = QSqlQuery(self.glb.db) | ||
| 1503 | QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1") | ||
| 1504 | if query.next(): | ||
| 1505 | self.last_id = int(query.value(0)) | ||
| 1506 | self.last_time = int(query.value(1)) | ||
| 1507 | QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1") | ||
| 1508 | if query.next(): | ||
| 1509 | self.first_time = int(query.value(0)) | ||
| 1510 | if placeholder_text: | ||
| 1511 | placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time) | ||
| 1512 | |||
| 1513 | if placeholder_text: | 1699 | if placeholder_text: |
| 1514 | self.widget.setPlaceholderText(placeholder_text) | 1700 | self.widget.setPlaceholderText(placeholder_text) |
| 1515 | 1701 | ||
| 1702 | def TurnTextRed(self): | ||
| 1703 | if not self.red: | ||
| 1704 | palette = QPalette() | ||
| 1705 | palette.setColor(QPalette.Text,Qt.red) | ||
| 1706 | self.widget.setPalette(palette) | ||
| 1707 | self.red = True | ||
| 1708 | |||
| 1709 | def TurnTextNormal(self): | ||
| 1710 | if self.red: | ||
| 1711 | palette = QPalette() | ||
| 1712 | self.widget.setPalette(palette) | ||
| 1713 | self.red = False | ||
| 1714 | |||
| 1715 | def InvalidValue(self, value): | ||
| 1716 | self.value = "" | ||
| 1717 | self.TurnTextRed() | ||
| 1718 | self.error = self.label + " invalid value '" + value + "'" | ||
| 1719 | self.parent.ShowMessage(self.error) | ||
| 1720 | |||
| 1721 | def Invalidate(self): | ||
| 1722 | self.validated = False | ||
| 1723 | |||
| 1724 | def DoValidate(self, input_string): | ||
| 1725 | self.value = input_string.strip() | ||
| 1726 | |||
| 1727 | def Validate(self): | ||
| 1728 | self.validated = True | ||
| 1729 | self.error = "" | ||
| 1730 | self.TurnTextNormal() | ||
| 1731 | self.parent.ClearMessage() | ||
| 1732 | input_string = self.widget.text() | ||
| 1733 | if not len(input_string.strip()): | ||
| 1734 | self.value = "" | ||
| 1735 | return | ||
| 1736 | self.DoValidate(input_string) | ||
| 1737 | |||
| 1738 | def IsValid(self): | ||
| 1739 | if not self.validated: | ||
| 1740 | self.Validate() | ||
| 1741 | if len(self.error): | ||
| 1742 | self.parent.ShowMessage(self.error) | ||
| 1743 | return False | ||
| 1744 | return True | ||
| 1745 | |||
| 1746 | def IsNumber(self, value): | ||
| 1747 | try: | ||
| 1748 | x = int(value) | ||
| 1749 | except: | ||
| 1750 | x = 0 | ||
| 1751 | return str(x) == value | ||
| 1752 | |||
| 1753 | # Non-negative integer ranges dialog data item | ||
| 1754 | |||
| 1755 | class NonNegativeIntegerRangesDataItem(LineEditDataItem): | ||
| 1756 | |||
| 1757 | def __init__(self, glb, label, placeholder_text, column_name, parent): | ||
| 1758 | super(NonNegativeIntegerRangesDataItem, self).__init__(glb, label, placeholder_text, parent) | ||
| 1759 | |||
| 1760 | self.column_name = column_name | ||
| 1761 | |||
| 1762 | def DoValidate(self, input_string): | ||
| 1763 | singles = [] | ||
| 1764 | ranges = [] | ||
| 1765 | for value in [x.strip() for x in input_string.split(",")]: | ||
| 1766 | if "-" in value: | ||
| 1767 | vrange = value.split("-") | ||
| 1768 | if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): | ||
| 1769 | return self.InvalidValue(value) | ||
| 1770 | ranges.append(vrange) | ||
| 1771 | else: | ||
| 1772 | if not self.IsNumber(value): | ||
| 1773 | return self.InvalidValue(value) | ||
| 1774 | singles.append(value) | ||
| 1775 | ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges] | ||
| 1776 | if len(singles): | ||
| 1777 | ranges.append(self.column_name + " IN (" + ",".join(singles) + ")") | ||
| 1778 | self.value = " OR ".join(ranges) | ||
| 1779 | |||
| 1780 | # Positive integer dialog data item | ||
| 1781 | |||
| 1782 | class PositiveIntegerDataItem(LineEditDataItem): | ||
| 1783 | |||
| 1784 | def __init__(self, glb, label, placeholder_text, parent, id = "", default = ""): | ||
| 1785 | super(PositiveIntegerDataItem, self).__init__(glb, label, placeholder_text, parent, id, default) | ||
| 1786 | |||
| 1787 | def DoValidate(self, input_string): | ||
| 1788 | if not self.IsNumber(input_string.strip()): | ||
| 1789 | return self.InvalidValue(input_string) | ||
| 1790 | value = int(input_string.strip()) | ||
| 1791 | if value <= 0: | ||
| 1792 | return self.InvalidValue(input_string) | ||
| 1793 | self.value = str(value) | ||
| 1794 | |||
| 1795 | # Dialog data item converted and validated using a SQL table | ||
| 1796 | |||
| 1797 | class SQLTableDataItem(LineEditDataItem): | ||
| 1798 | |||
| 1799 | def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent): | ||
| 1800 | super(SQLTableDataItem, self).__init__(glb, label, placeholder_text, parent) | ||
| 1801 | |||
| 1802 | self.table_name = table_name | ||
| 1803 | self.match_column = match_column | ||
| 1804 | self.column_name1 = column_name1 | ||
| 1805 | self.column_name2 = column_name2 | ||
| 1806 | |||
| 1516 | def ValueToIds(self, value): | 1807 | def ValueToIds(self, value): |
| 1517 | ids = [] | 1808 | ids = [] |
| 1518 | query = QSqlQuery(self.glb.db) | 1809 | query = QSqlQuery(self.glb.db) |
| @@ -1523,6 +1814,42 @@ class SQLTableDialogDataItem(): | |||
| 1523 | ids.append(str(query.value(0))) | 1814 | ids.append(str(query.value(0))) |
| 1524 | return ids | 1815 | return ids |
| 1525 | 1816 | ||
| 1817 | def DoValidate(self, input_string): | ||
| 1818 | all_ids = [] | ||
| 1819 | for value in [x.strip() for x in input_string.split(",")]: | ||
| 1820 | ids = self.ValueToIds(value) | ||
| 1821 | if len(ids): | ||
| 1822 | all_ids.extend(ids) | ||
| 1823 | else: | ||
| 1824 | return self.InvalidValue(value) | ||
| 1825 | self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")" | ||
| 1826 | if self.column_name2: | ||
| 1827 | self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )" | ||
| 1828 | |||
| 1829 | # Sample time ranges dialog data item converted and validated using 'samples' SQL table | ||
| 1830 | |||
| 1831 | class SampleTimeRangesDataItem(LineEditDataItem): | ||
| 1832 | |||
| 1833 | def __init__(self, glb, label, placeholder_text, column_name, parent): | ||
| 1834 | self.column_name = column_name | ||
| 1835 | |||
| 1836 | self.last_id = 0 | ||
| 1837 | self.first_time = 0 | ||
| 1838 | self.last_time = 2 ** 64 | ||
| 1839 | |||
| 1840 | query = QSqlQuery(glb.db) | ||
| 1841 | QueryExec(query, "SELECT id, time FROM samples ORDER BY id DESC LIMIT 1") | ||
| 1842 | if query.next(): | ||
| 1843 | self.last_id = int(query.value(0)) | ||
| 1844 | self.last_time = int(query.value(1)) | ||
| 1845 | QueryExec(query, "SELECT time FROM samples WHERE time != 0 ORDER BY id LIMIT 1") | ||
| 1846 | if query.next(): | ||
| 1847 | self.first_time = int(query.value(0)) | ||
| 1848 | if placeholder_text: | ||
| 1849 | placeholder_text += ", between " + str(self.first_time) + " and " + str(self.last_time) | ||
| 1850 | |||
| 1851 | super(SampleTimeRangesDataItem, self).__init__(glb, label, placeholder_text, parent) | ||
| 1852 | |||
| 1526 | def IdBetween(self, query, lower_id, higher_id, order): | 1853 | def IdBetween(self, query, lower_id, higher_id, order): |
| 1527 | QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1") | 1854 | QueryExec(query, "SELECT id FROM samples WHERE id > " + str(lower_id) + " AND id < " + str(higher_id) + " ORDER BY id " + order + " LIMIT 1") |
| 1528 | if query.next(): | 1855 | if query.next(): |
| @@ -1560,7 +1887,6 @@ class SQLTableDialogDataItem(): | |||
| 1560 | return str(lower_id) | 1887 | return str(lower_id) |
| 1561 | 1888 | ||
| 1562 | def ConvertRelativeTime(self, val): | 1889 | def ConvertRelativeTime(self, val): |
| 1563 | print "val ", val | ||
| 1564 | mult = 1 | 1890 | mult = 1 |
| 1565 | suffix = val[-2:] | 1891 | suffix = val[-2:] |
| 1566 | if suffix == "ms": | 1892 | if suffix == "ms": |
| @@ -1582,29 +1908,23 @@ class SQLTableDialogDataItem(): | |||
| 1582 | return str(val) | 1908 | return str(val) |
| 1583 | 1909 | ||
| 1584 | def ConvertTimeRange(self, vrange): | 1910 | def ConvertTimeRange(self, vrange): |
| 1585 | print "vrange ", vrange | ||
| 1586 | if vrange[0] == "": | 1911 | if vrange[0] == "": |
| 1587 | vrange[0] = str(self.first_time) | 1912 | vrange[0] = str(self.first_time) |
| 1588 | if vrange[1] == "": | 1913 | if vrange[1] == "": |
| 1589 | vrange[1] = str(self.last_time) | 1914 | vrange[1] = str(self.last_time) |
| 1590 | vrange[0] = self.ConvertRelativeTime(vrange[0]) | 1915 | vrange[0] = self.ConvertRelativeTime(vrange[0]) |
| 1591 | vrange[1] = self.ConvertRelativeTime(vrange[1]) | 1916 | vrange[1] = self.ConvertRelativeTime(vrange[1]) |
| 1592 | print "vrange2 ", vrange | ||
| 1593 | if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): | 1917 | if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): |
| 1594 | return False | 1918 | return False |
| 1595 | print "ok1" | ||
| 1596 | beg_range = max(int(vrange[0]), self.first_time) | 1919 | beg_range = max(int(vrange[0]), self.first_time) |
| 1597 | end_range = min(int(vrange[1]), self.last_time) | 1920 | end_range = min(int(vrange[1]), self.last_time) |
| 1598 | if beg_range > self.last_time or end_range < self.first_time: | 1921 | if beg_range > self.last_time or end_range < self.first_time: |
| 1599 | return False | 1922 | return False |
| 1600 | print "ok2" | ||
| 1601 | vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True) | 1923 | vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True) |
| 1602 | vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False) | 1924 | vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False) |
| 1603 | print "vrange3 ", vrange | ||
| 1604 | return True | 1925 | return True |
| 1605 | 1926 | ||
| 1606 | def AddTimeRange(self, value, ranges): | 1927 | def AddTimeRange(self, value, ranges): |
| 1607 | print "value ", value | ||
| 1608 | n = value.count("-") | 1928 | n = value.count("-") |
| 1609 | if n == 1: | 1929 | if n == 1: |
| 1610 | pass | 1930 | pass |
| @@ -1622,111 +1942,31 @@ class SQLTableDialogDataItem(): | |||
| 1622 | return True | 1942 | return True |
| 1623 | return False | 1943 | return False |
| 1624 | 1944 | ||
| 1625 | def InvalidValue(self, value): | 1945 | def DoValidate(self, input_string): |
| 1626 | self.value = "" | 1946 | ranges = [] |
| 1627 | palette = QPalette() | 1947 | for value in [x.strip() for x in input_string.split(",")]: |
| 1628 | palette.setColor(QPalette.Text,Qt.red) | 1948 | if not self.AddTimeRange(value, ranges): |
| 1629 | self.widget.setPalette(palette) | 1949 | return self.InvalidValue(value) |
| 1630 | self.red = True | 1950 | ranges = [("(" + self.column_name + " >= " + r[0] + " AND " + self.column_name + " <= " + r[1] + ")") for r in ranges] |
| 1631 | self.error = self.label + " invalid value '" + value + "'" | 1951 | self.value = " OR ".join(ranges) |
| 1632 | self.parent.ShowMessage(self.error) | ||
| 1633 | |||
| 1634 | def IsNumber(self, value): | ||
| 1635 | try: | ||
| 1636 | x = int(value) | ||
| 1637 | except: | ||
| 1638 | x = 0 | ||
| 1639 | return str(x) == value | ||
| 1640 | 1952 | ||
| 1641 | def Invalidate(self): | 1953 | # Report Dialog Base |
| 1642 | self.validated = False | ||
| 1643 | 1954 | ||
| 1644 | def Validate(self): | 1955 | class ReportDialogBase(QDialog): |
| 1645 | input_string = self.widget.text() | ||
| 1646 | self.validated = True | ||
| 1647 | if self.red: | ||
| 1648 | palette = QPalette() | ||
| 1649 | self.widget.setPalette(palette) | ||
| 1650 | self.red = False | ||
| 1651 | if not len(input_string.strip()): | ||
| 1652 | self.error = "" | ||
| 1653 | self.value = "" | ||
| 1654 | return | ||
| 1655 | if self.table_name == "<timeranges>": | ||
| 1656 | ranges = [] | ||
| 1657 | for value in [x.strip() for x in input_string.split(",")]: | ||
| 1658 | if not self.AddTimeRange(value, ranges): | ||
| 1659 | return self.InvalidValue(value) | ||
| 1660 | ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges] | ||
| 1661 | self.value = " OR ".join(ranges) | ||
| 1662 | elif self.table_name == "<ranges>": | ||
| 1663 | singles = [] | ||
| 1664 | ranges = [] | ||
| 1665 | for value in [x.strip() for x in input_string.split(",")]: | ||
| 1666 | if "-" in value: | ||
| 1667 | vrange = value.split("-") | ||
| 1668 | if len(vrange) != 2 or not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): | ||
| 1669 | return self.InvalidValue(value) | ||
| 1670 | ranges.append(vrange) | ||
| 1671 | else: | ||
| 1672 | if not self.IsNumber(value): | ||
| 1673 | return self.InvalidValue(value) | ||
| 1674 | singles.append(value) | ||
| 1675 | ranges = [("(" + self.column_name1 + " >= " + r[0] + " AND " + self.column_name1 + " <= " + r[1] + ")") for r in ranges] | ||
| 1676 | if len(singles): | ||
| 1677 | ranges.append(self.column_name1 + " IN (" + ",".join(singles) + ")") | ||
| 1678 | self.value = " OR ".join(ranges) | ||
| 1679 | elif self.table_name: | ||
| 1680 | all_ids = [] | ||
| 1681 | for value in [x.strip() for x in input_string.split(",")]: | ||
| 1682 | ids = self.ValueToIds(value) | ||
| 1683 | if len(ids): | ||
| 1684 | all_ids.extend(ids) | ||
| 1685 | else: | ||
| 1686 | return self.InvalidValue(value) | ||
| 1687 | self.value = self.column_name1 + " IN (" + ",".join(all_ids) + ")" | ||
| 1688 | if self.column_name2: | ||
| 1689 | self.value = "( " + self.value + " OR " + self.column_name2 + " IN (" + ",".join(all_ids) + ") )" | ||
| 1690 | else: | ||
| 1691 | self.value = input_string.strip() | ||
| 1692 | self.error = "" | ||
| 1693 | self.parent.ClearMessage() | ||
| 1694 | 1956 | ||
| 1695 | def IsValid(self): | 1957 | def __init__(self, glb, title, items, partial, parent=None): |
| 1696 | if not self.validated: | 1958 | super(ReportDialogBase, self).__init__(parent) |
| 1697 | self.Validate() | ||
| 1698 | if len(self.error): | ||
| 1699 | self.parent.ShowMessage(self.error) | ||
| 1700 | return False | ||
| 1701 | return True | ||
| 1702 | |||
| 1703 | # Selected branch report creation dialog | ||
| 1704 | |||
| 1705 | class SelectedBranchDialog(QDialog): | ||
| 1706 | |||
| 1707 | def __init__(self, glb, parent=None): | ||
| 1708 | super(SelectedBranchDialog, self).__init__(parent) | ||
| 1709 | 1959 | ||
| 1710 | self.glb = glb | 1960 | self.glb = glb |
| 1711 | 1961 | ||
| 1712 | self.name = "" | 1962 | self.report_vars = ReportVars() |
| 1713 | self.where_clause = "" | ||
| 1714 | 1963 | ||
| 1715 | self.setWindowTitle("Selected Branches") | 1964 | self.setWindowTitle(title) |
| 1716 | self.setMinimumWidth(600) | 1965 | self.setMinimumWidth(600) |
| 1717 | 1966 | ||
| 1718 | items = ( | 1967 | self.data_items = [x(glb, self) for x in items] |
| 1719 | ("Report name:", "Enter a name to appear in the window title bar", "", "", "", ""), | 1968 | |
| 1720 | ("Time ranges:", "Enter time ranges", "<timeranges>", "", "samples.id", ""), | 1969 | self.partial = partial |
| 1721 | ("CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "<ranges>", "", "cpu", ""), | ||
| 1722 | ("Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", ""), | ||
| 1723 | ("PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", ""), | ||
| 1724 | ("TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", ""), | ||
| 1725 | ("DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id"), | ||
| 1726 | ("Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id"), | ||
| 1727 | ("Raw SQL clause: ", "Enter a raw SQL WHERE clause", "", "", "", ""), | ||
| 1728 | ) | ||
| 1729 | self.data_items = [SQLTableDialogDataItem(glb, *x, parent=self) for x in items] | ||
| 1730 | 1970 | ||
| 1731 | self.grid = QGridLayout() | 1971 | self.grid = QGridLayout() |
| 1732 | 1972 | ||
| @@ -1758,23 +1998,28 @@ class SelectedBranchDialog(QDialog): | |||
| 1758 | self.setLayout(self.vbox); | 1998 | self.setLayout(self.vbox); |
| 1759 | 1999 | ||
| 1760 | def Ok(self): | 2000 | def Ok(self): |
| 1761 | self.name = self.data_items[0].value | 2001 | vars = self.report_vars |
| 1762 | if not self.name: | 2002 | for d in self.data_items: |
| 2003 | if d.id == "REPORTNAME": | ||
| 2004 | vars.name = d.value | ||
| 2005 | if not vars.name: | ||
| 1763 | self.ShowMessage("Report name is required") | 2006 | self.ShowMessage("Report name is required") |
| 1764 | return | 2007 | return |
| 1765 | for d in self.data_items: | 2008 | for d in self.data_items: |
| 1766 | if not d.IsValid(): | 2009 | if not d.IsValid(): |
| 1767 | return | 2010 | return |
| 1768 | for d in self.data_items[1:]: | 2011 | for d in self.data_items[1:]: |
| 1769 | if len(d.value): | 2012 | if d.id == "LIMIT": |
| 1770 | if len(self.where_clause): | 2013 | vars.limit = d.value |
| 1771 | self.where_clause += " AND " | 2014 | elif len(d.value): |
| 1772 | self.where_clause += d.value | 2015 | if len(vars.where_clause): |
| 1773 | if len(self.where_clause): | 2016 | vars.where_clause += " AND " |
| 1774 | self.where_clause = " AND ( " + self.where_clause + " ) " | 2017 | vars.where_clause += d.value |
| 1775 | else: | 2018 | if len(vars.where_clause): |
| 1776 | self.ShowMessage("No selection") | 2019 | if self.partial: |
| 1777 | return | 2020 | vars.where_clause = " AND ( " + vars.where_clause + " ) " |
| 2021 | else: | ||
| 2022 | vars.where_clause = " WHERE " + vars.where_clause + " " | ||
| 1778 | self.accept() | 2023 | self.accept() |
| 1779 | 2024 | ||
| 1780 | def ShowMessage(self, msg): | 2025 | def ShowMessage(self, msg): |
| @@ -1783,6 +2028,23 @@ class SelectedBranchDialog(QDialog): | |||
| 1783 | def ClearMessage(self): | 2028 | def ClearMessage(self): |
| 1784 | self.status.setText("") | 2029 | self.status.setText("") |
| 1785 | 2030 | ||
| 2031 | # Selected branch report creation dialog | ||
| 2032 | |||
| 2033 | class SelectedBranchDialog(ReportDialogBase): | ||
| 2034 | |||
| 2035 | def __init__(self, glb, parent=None): | ||
| 2036 | title = "Selected Branches" | ||
| 2037 | items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"), | ||
| 2038 | lambda g, p: SampleTimeRangesDataItem(g, "Time ranges:", "Enter time ranges", "samples.id", p), | ||
| 2039 | lambda g, p: NonNegativeIntegerRangesDataItem(g, "CPUs:", "Enter CPUs or ranges e.g. 0,5-6", "cpu", p), | ||
| 2040 | lambda g, p: SQLTableDataItem(g, "Commands:", "Only branches with these commands will be included", "comms", "comm", "comm_id", "", p), | ||
| 2041 | lambda g, p: SQLTableDataItem(g, "PIDs:", "Only branches with these process IDs will be included", "threads", "pid", "thread_id", "", p), | ||
| 2042 | lambda g, p: SQLTableDataItem(g, "TIDs:", "Only branches with these thread IDs will be included", "threads", "tid", "thread_id", "", p), | ||
| 2043 | lambda g, p: SQLTableDataItem(g, "DSOs:", "Only branches with these DSOs will be included", "dsos", "short_name", "samples.dso_id", "to_dso_id", p), | ||
| 2044 | lambda g, p: SQLTableDataItem(g, "Symbols:", "Only branches with these symbols will be included", "symbols", "name", "symbol_id", "to_symbol_id", p), | ||
| 2045 | lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p)) | ||
| 2046 | super(SelectedBranchDialog, self).__init__(glb, title, items, True, parent) | ||
| 2047 | |||
| 1786 | # Event list | 2048 | # Event list |
| 1787 | 2049 | ||
| 1788 | def GetEventList(db): | 2050 | def GetEventList(db): |
| @@ -1793,6 +2055,16 @@ def GetEventList(db): | |||
| 1793 | events.append(query.value(0)) | 2055 | events.append(query.value(0)) |
| 1794 | return events | 2056 | return events |
| 1795 | 2057 | ||
| 2058 | # Is a table selectable | ||
| 2059 | |||
| 2060 | def IsSelectable(db, table, sql = ""): | ||
| 2061 | query = QSqlQuery(db) | ||
| 2062 | try: | ||
| 2063 | QueryExec(query, "SELECT * FROM " + table + " " + sql + " LIMIT 1") | ||
| 2064 | except: | ||
| 2065 | return False | ||
| 2066 | return True | ||
| 2067 | |||
| 1796 | # SQL data preparation | 2068 | # SQL data preparation |
| 1797 | 2069 | ||
| 1798 | def SQLTableDataPrep(query, count): | 2070 | def SQLTableDataPrep(query, count): |
| @@ -1818,12 +2090,13 @@ class SQLTableModel(TableModel): | |||
| 1818 | 2090 | ||
| 1819 | progress = Signal(object) | 2091 | progress = Signal(object) |
| 1820 | 2092 | ||
| 1821 | def __init__(self, glb, sql, column_count, parent=None): | 2093 | def __init__(self, glb, sql, column_headers, parent=None): |
| 1822 | super(SQLTableModel, self).__init__(parent) | 2094 | super(SQLTableModel, self).__init__(parent) |
| 1823 | self.glb = glb | 2095 | self.glb = glb |
| 1824 | self.more = True | 2096 | self.more = True |
| 1825 | self.populated = 0 | 2097 | self.populated = 0 |
| 1826 | self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample) | 2098 | self.column_headers = column_headers |
| 2099 | self.fetcher = SQLFetcher(glb, sql, lambda x, y=len(column_headers): SQLTableDataPrep(x, y), self.AddSample) | ||
| 1827 | self.fetcher.done.connect(self.Update) | 2100 | self.fetcher.done.connect(self.Update) |
| 1828 | self.fetcher.Fetch(glb_chunk_sz) | 2101 | self.fetcher.Fetch(glb_chunk_sz) |
| 1829 | 2102 | ||
| @@ -1861,6 +2134,12 @@ class SQLTableModel(TableModel): | |||
| 1861 | def HasMoreRecords(self): | 2134 | def HasMoreRecords(self): |
| 1862 | return self.more | 2135 | return self.more |
| 1863 | 2136 | ||
| 2137 | def columnCount(self, parent=None): | ||
| 2138 | return len(self.column_headers) | ||
| 2139 | |||
| 2140 | def columnHeader(self, column): | ||
| 2141 | return self.column_headers[column] | ||
| 2142 | |||
| 1864 | # SQL automatic table data model | 2143 | # SQL automatic table data model |
| 1865 | 2144 | ||
| 1866 | class SQLAutoTableModel(SQLTableModel): | 2145 | class SQLAutoTableModel(SQLTableModel): |
| @@ -1870,12 +2149,12 @@ class SQLAutoTableModel(SQLTableModel): | |||
| 1870 | if table_name == "comm_threads_view": | 2149 | if table_name == "comm_threads_view": |
| 1871 | # For now, comm_threads_view has no id column | 2150 | # For now, comm_threads_view has no id column |
| 1872 | sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz) | 2151 | sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz) |
| 1873 | self.column_headers = [] | 2152 | column_headers = [] |
| 1874 | query = QSqlQuery(glb.db) | 2153 | query = QSqlQuery(glb.db) |
| 1875 | if glb.dbref.is_sqlite3: | 2154 | if glb.dbref.is_sqlite3: |
| 1876 | QueryExec(query, "PRAGMA table_info(" + table_name + ")") | 2155 | QueryExec(query, "PRAGMA table_info(" + table_name + ")") |
| 1877 | while query.next(): | 2156 | while query.next(): |
| 1878 | self.column_headers.append(query.value(1)) | 2157 | column_headers.append(query.value(1)) |
| 1879 | if table_name == "sqlite_master": | 2158 | if table_name == "sqlite_master": |
| 1880 | sql = "SELECT * FROM " + table_name | 2159 | sql = "SELECT * FROM " + table_name |
| 1881 | else: | 2160 | else: |
| @@ -1888,14 +2167,8 @@ class SQLAutoTableModel(SQLTableModel): | |||
| 1888 | schema = "public" | 2167 | schema = "public" |
| 1889 | QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'") | 2168 | QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'") |
| 1890 | while query.next(): | 2169 | while query.next(): |
| 1891 | self.column_headers.append(query.value(0)) | 2170 | column_headers.append(query.value(0)) |
| 1892 | super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent) | 2171 | super(SQLAutoTableModel, self).__init__(glb, sql, column_headers, parent) |
| 1893 | |||
| 1894 | def columnCount(self, parent=None): | ||
| 1895 | return len(self.column_headers) | ||
| 1896 | |||
| 1897 | def columnHeader(self, column): | ||
| 1898 | return self.column_headers[column] | ||
| 1899 | 2172 | ||
| 1900 | # Base class for custom ResizeColumnsToContents | 2173 | # Base class for custom ResizeColumnsToContents |
| 1901 | 2174 | ||
| @@ -1998,6 +2271,103 @@ def GetTableList(glb): | |||
| 1998 | tables.append("information_schema.columns") | 2271 | tables.append("information_schema.columns") |
| 1999 | return tables | 2272 | return tables |
| 2000 | 2273 | ||
| 2274 | # Top Calls data model | ||
| 2275 | |||
| 2276 | class TopCallsModel(SQLTableModel): | ||
| 2277 | |||
| 2278 | def __init__(self, glb, report_vars, parent=None): | ||
| 2279 | text = "" | ||
| 2280 | if not glb.dbref.is_sqlite3: | ||
| 2281 | text = "::text" | ||
| 2282 | limit = "" | ||
| 2283 | if len(report_vars.limit): | ||
| 2284 | limit = " LIMIT " + report_vars.limit | ||
| 2285 | sql = ("SELECT comm, pid, tid, name," | ||
| 2286 | " CASE" | ||
| 2287 | " WHEN (short_name = '[kernel.kallsyms]') THEN '[kernel]'" + text + | ||
| 2288 | " ELSE short_name" | ||
| 2289 | " END AS dso," | ||
| 2290 | " call_time, return_time, (return_time - call_time) AS elapsed_time, branch_count, " | ||
| 2291 | " CASE" | ||
| 2292 | " WHEN (calls.flags = 1) THEN 'no call'" + text + | ||
| 2293 | " WHEN (calls.flags = 2) THEN 'no return'" + text + | ||
| 2294 | " WHEN (calls.flags = 3) THEN 'no call/return'" + text + | ||
| 2295 | " ELSE ''" + text + | ||
| 2296 | " END AS flags" | ||
| 2297 | " FROM calls" | ||
| 2298 | " INNER JOIN call_paths ON calls.call_path_id = call_paths.id" | ||
| 2299 | " INNER JOIN symbols ON call_paths.symbol_id = symbols.id" | ||
| 2300 | " INNER JOIN dsos ON symbols.dso_id = dsos.id" | ||
| 2301 | " INNER JOIN comms ON calls.comm_id = comms.id" | ||
| 2302 | " INNER JOIN threads ON calls.thread_id = threads.id" + | ||
| 2303 | report_vars.where_clause + | ||
| 2304 | " ORDER BY elapsed_time DESC" + | ||
| 2305 | limit | ||
| 2306 | ) | ||
| 2307 | column_headers = ("Command", "PID", "TID", "Symbol", "Object", "Call Time", "Return Time", "Elapsed Time (ns)", "Branch Count", "Flags") | ||
| 2308 | self.alignment = (Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignLeft, Qt.AlignRight, Qt.AlignRight, Qt.AlignLeft) | ||
| 2309 | super(TopCallsModel, self).__init__(glb, sql, column_headers, parent) | ||
| 2310 | |||
| 2311 | def columnAlignment(self, column): | ||
| 2312 | return self.alignment[column] | ||
| 2313 | |||
| 2314 | # Top Calls report creation dialog | ||
| 2315 | |||
| 2316 | class TopCallsDialog(ReportDialogBase): | ||
| 2317 | |||
| 2318 | def __init__(self, glb, parent=None): | ||
| 2319 | title = "Top Calls by Elapsed Time" | ||
| 2320 | items = (lambda g, p: LineEditDataItem(g, "Report name:", "Enter a name to appear in the window title bar", p, "REPORTNAME"), | ||
| 2321 | lambda g, p: SQLTableDataItem(g, "Commands:", "Only calls with these commands will be included", "comms", "comm", "comm_id", "", p), | ||
| 2322 | lambda g, p: SQLTableDataItem(g, "PIDs:", "Only calls with these process IDs will be included", "threads", "pid", "thread_id", "", p), | ||
| 2323 | lambda g, p: SQLTableDataItem(g, "TIDs:", "Only calls with these thread IDs will be included", "threads", "tid", "thread_id", "", p), | ||
| 2324 | lambda g, p: SQLTableDataItem(g, "DSOs:", "Only calls with these DSOs will be included", "dsos", "short_name", "dso_id", "", p), | ||
| 2325 | lambda g, p: SQLTableDataItem(g, "Symbols:", "Only calls with these symbols will be included", "symbols", "name", "symbol_id", "", p), | ||
| 2326 | lambda g, p: LineEditDataItem(g, "Raw SQL clause: ", "Enter a raw SQL WHERE clause", p), | ||
| 2327 | lambda g, p: PositiveIntegerDataItem(g, "Record limit:", "Limit selection to this number of records", p, "LIMIT", "100")) | ||
| 2328 | super(TopCallsDialog, self).__init__(glb, title, items, False, parent) | ||
| 2329 | |||
| 2330 | # Top Calls window | ||
| 2331 | |||
| 2332 | class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase): | ||
| 2333 | |||
| 2334 | def __init__(self, glb, report_vars, parent=None): | ||
| 2335 | super(TopCallsWindow, self).__init__(parent) | ||
| 2336 | |||
| 2337 | self.data_model = LookupCreateModel("Top Calls " + report_vars.UniqueId(), lambda: TopCallsModel(glb, report_vars)) | ||
| 2338 | self.model = self.data_model | ||
| 2339 | |||
| 2340 | self.view = QTableView() | ||
| 2341 | self.view.setModel(self.model) | ||
| 2342 | self.view.setEditTriggers(QAbstractItemView.NoEditTriggers) | ||
| 2343 | self.view.verticalHeader().setVisible(False) | ||
| 2344 | |||
| 2345 | self.ResizeColumnsToContents() | ||
| 2346 | |||
| 2347 | self.find_bar = FindBar(self, self, True) | ||
| 2348 | |||
| 2349 | self.finder = ChildDataItemFinder(self.model) | ||
| 2350 | |||
| 2351 | self.fetch_bar = FetchMoreRecordsBar(self.data_model, self) | ||
| 2352 | |||
| 2353 | self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget()) | ||
| 2354 | |||
| 2355 | self.setWidget(self.vbox.Widget()) | ||
| 2356 | |||
| 2357 | AddSubWindow(glb.mainwindow.mdi_area, self, report_vars.name) | ||
| 2358 | |||
| 2359 | def Find(self, value, direction, pattern, context): | ||
| 2360 | self.view.setFocus() | ||
| 2361 | self.find_bar.Busy() | ||
| 2362 | self.finder.Find(value, direction, pattern, context, self.FindDone) | ||
| 2363 | |||
| 2364 | def FindDone(self, row): | ||
| 2365 | self.find_bar.Idle() | ||
| 2366 | if row >= 0: | ||
| 2367 | self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) | ||
| 2368 | else: | ||
| 2369 | self.find_bar.NotFound() | ||
| 2370 | |||
| 2001 | # Action Definition | 2371 | # Action Definition |
| 2002 | 2372 | ||
| 2003 | def CreateAction(label, tip, callback, parent=None, shortcut=None): | 2373 | def CreateAction(label, tip, callback, parent=None, shortcut=None): |
| @@ -2099,8 +2469,10 @@ p.c2 { | |||
| 2099 | </style> | 2469 | </style> |
| 2100 | <p class=c1><a href=#reports>1. Reports</a></p> | 2470 | <p class=c1><a href=#reports>1. Reports</a></p> |
| 2101 | <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> |
| 2102 | <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> |
| 2103 | <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> |
| 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> | ||
| 2104 | <p class=c1><a href=#tables>2. Tables</a></p> | 2476 | <p class=c1><a href=#tables>2. Tables</a></p> |
| 2105 | <h1 id=reports>1. Reports</h1> | 2477 | <h1 id=reports>1. Reports</h1> |
| 2106 | <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2> | 2478 | <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2> |
| @@ -2136,7 +2508,10 @@ v- ls | |||
| 2136 | <h3>Find</h3> | 2508 | <h3>Find</h3> |
| 2137 | 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. |
| 2138 | 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. |
| 2139 | <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> | ||
| 2140 | The All branches report displays all branches in chronological order. | 2515 | The All branches report displays all branches in chronological order. |
| 2141 | 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. |
| 2142 | <h3>Disassembly</h3> | 2517 | <h3>Disassembly</h3> |
| @@ -2162,10 +2537,10 @@ sudo ldconfig | |||
| 2162 | 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. |
| 2163 | Refer to Python documentation for the regular expression syntax. | 2538 | Refer to Python documentation for the regular expression syntax. |
| 2164 | All columns are searched, but only currently fetched rows are searched. | 2539 | All columns are searched, but only currently fetched rows are searched. |
| 2165 | <h2 id=selectedbranches>1.3 Selected branches</h2> | 2540 | <h2 id=selectedbranches>1.4 Selected branches</h2> |
| 2166 | 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 |
| 2167 | 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. |
| 2168 | <h3>1.3.1 Time ranges</h3> | 2543 | <h3>1.4.1 Time ranges</h3> |
| 2169 | 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 |
| 2170 | 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: |
| 2171 | <pre> | 2546 | <pre> |
| @@ -2176,6 +2551,10 @@ ms, us or ns. Also, negative values are relative to the end of trace. Examples: | |||
| 2176 | -10ms- The last 10ms | 2551 | -10ms- The last 10ms |
| 2177 | </pre> | 2552 | </pre> |
| 2178 | 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. |
| 2554 | <h2 id=topcallsbyelapsedtime>1.5 Top calls by elapsed time</h2> | ||
| 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. | ||
| 2556 | The data is reduced by various selection criteria. A dialog box displays available criteria which are AND'ed together. | ||
| 2557 | If not all data is fetched, a Fetch bar is provided. Ctrl-F displays a Find bar. | ||
| 2179 | <h1 id=tables>2. Tables</h1> | 2558 | <h1 id=tables>2. Tables</h1> |
| 2180 | The Tables menu shows all tables and views in the database. Most tables have an associated view | 2559 | The Tables menu shows all tables and views in the database. Most tables have an associated view |
| 2181 | which displays the information in a more friendly way. Not all data for large tables is fetched | 2560 | which displays the information in a more friendly way. Not all data for large tables is fetched |
| @@ -2305,10 +2684,17 @@ class MainWindow(QMainWindow): | |||
| 2305 | edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")])) | 2684 | edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")])) |
| 2306 | 2685 | ||
| 2307 | reports_menu = menu.addMenu("&Reports") | 2686 | reports_menu = menu.addMenu("&Reports") |
| 2308 | reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) | 2687 | if IsSelectable(glb.db, "calls"): |
| 2688 | reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self)) | ||
| 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)) | ||
| 2309 | 2692 | ||
| 2310 | self.EventMenu(GetEventList(glb.db), reports_menu) | 2693 | self.EventMenu(GetEventList(glb.db), reports_menu) |
| 2311 | 2694 | ||
| 2695 | if IsSelectable(glb.db, "calls"): | ||
| 2696 | reports_menu.addAction(CreateAction("&Top calls by elapsed time", "Create a new window displaying top calls by elapsed time", self.NewTopCalls, self)) | ||
| 2697 | |||
| 2312 | self.TableMenu(GetTableList(glb), menu) | 2698 | self.TableMenu(GetTableList(glb), menu) |
| 2313 | 2699 | ||
| 2314 | self.window_menu = WindowMenu(self.mdi_area, menu) | 2700 | self.window_menu = WindowMenu(self.mdi_area, menu) |
| @@ -2364,14 +2750,23 @@ class MainWindow(QMainWindow): | |||
| 2364 | def NewCallGraph(self): | 2750 | def NewCallGraph(self): |
| 2365 | CallGraphWindow(self.glb, self) | 2751 | CallGraphWindow(self.glb, self) |
| 2366 | 2752 | ||
| 2753 | def NewCallTree(self): | ||
| 2754 | CallTreeWindow(self.glb, self) | ||
| 2755 | |||
| 2756 | def NewTopCalls(self): | ||
| 2757 | dialog = TopCallsDialog(self.glb, self) | ||
| 2758 | ret = dialog.exec_() | ||
| 2759 | if ret: | ||
| 2760 | TopCallsWindow(self.glb, dialog.report_vars, self) | ||
| 2761 | |||
| 2367 | def NewBranchView(self, event_id): | 2762 | def NewBranchView(self, event_id): |
| 2368 | BranchWindow(self.glb, event_id, "", "", self) | 2763 | BranchWindow(self.glb, event_id, ReportVars(), self) |
| 2369 | 2764 | ||
| 2370 | def NewSelectedBranchView(self, event_id): | 2765 | def NewSelectedBranchView(self, event_id): |
| 2371 | dialog = SelectedBranchDialog(self.glb, self) | 2766 | dialog = SelectedBranchDialog(self.glb, self) |
| 2372 | ret = dialog.exec_() | 2767 | ret = dialog.exec_() |
| 2373 | if ret: | 2768 | if ret: |
| 2374 | BranchWindow(self.glb, event_id, dialog.name, dialog.where_clause, self) | 2769 | BranchWindow(self.glb, event_id, dialog.report_vars, self) |
| 2375 | 2770 | ||
| 2376 | def NewTableView(self, table_name): | 2771 | def NewTableView(self, table_name): |
| 2377 | TableWindow(self.glb, table_name, self) | 2772 | TableWindow(self.glb, table_name, self) |
