diff options
Diffstat (limited to 'tools/perf/scripts/python/exported-sql-viewer.py')
-rwxr-xr-x | tools/perf/scripts/python/exported-sql-viewer.py | 493 |
1 files changed, 490 insertions, 3 deletions
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py index 24cb0bd56afa..f278ce5ebab7 100755 --- a/tools/perf/scripts/python/exported-sql-viewer.py +++ b/tools/perf/scripts/python/exported-sql-viewer.py | |||
@@ -119,6 +119,14 @@ def dsoname(name): | |||
119 | return "[kernel]" | 119 | return "[kernel]" |
120 | return name | 120 | return name |
121 | 121 | ||
122 | def findnth(s, sub, n, offs=0): | ||
123 | pos = s.find(sub) | ||
124 | if pos < 0: | ||
125 | return pos | ||
126 | if n <= 1: | ||
127 | return offs + pos | ||
128 | return findnth(s[pos + 1:], sub, n - 1, offs + pos + 1) | ||
129 | |||
122 | # Percent to one decimal place | 130 | # Percent to one decimal place |
123 | 131 | ||
124 | def PercentToOneDP(n, d): | 132 | def PercentToOneDP(n, d): |
@@ -1464,6 +1472,317 @@ class BranchWindow(QMdiSubWindow): | |||
1464 | else: | 1472 | else: |
1465 | self.find_bar.NotFound() | 1473 | self.find_bar.NotFound() |
1466 | 1474 | ||
1475 | # Dialog data item converted and validated using a SQL table | ||
1476 | |||
1477 | class SQLTableDialogDataItem(): | ||
1478 | |||
1479 | def __init__(self, glb, label, placeholder_text, table_name, match_column, column_name1, column_name2, parent): | ||
1480 | self.glb = glb | ||
1481 | self.label = label | ||
1482 | 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 | ||
1488 | |||
1489 | self.value = "" | ||
1490 | |||
1491 | self.widget = QLineEdit() | ||
1492 | self.widget.editingFinished.connect(self.Validate) | ||
1493 | self.widget.textChanged.connect(self.Invalidate) | ||
1494 | self.red = False | ||
1495 | self.error = "" | ||
1496 | self.validated = True | ||
1497 | |||
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: | ||
1514 | self.widget.setPlaceholderText(placeholder_text) | ||
1515 | |||
1516 | def ValueToIds(self, value): | ||
1517 | ids = [] | ||
1518 | query = QSqlQuery(self.glb.db) | ||
1519 | stmt = "SELECT id FROM " + self.table_name + " WHERE " + self.match_column + " = '" + value + "'" | ||
1520 | ret = query.exec_(stmt) | ||
1521 | if ret: | ||
1522 | while query.next(): | ||
1523 | ids.append(str(query.value(0))) | ||
1524 | return ids | ||
1525 | |||
1526 | 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") | ||
1528 | if query.next(): | ||
1529 | return True, int(query.value(0)) | ||
1530 | else: | ||
1531 | return False, 0 | ||
1532 | |||
1533 | def BinarySearchTime(self, lower_id, higher_id, target_time, get_floor): | ||
1534 | query = QSqlQuery(self.glb.db) | ||
1535 | while True: | ||
1536 | next_id = int((lower_id + higher_id) / 2) | ||
1537 | QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) | ||
1538 | if not query.next(): | ||
1539 | ok, dbid = self.IdBetween(query, lower_id, next_id, "DESC") | ||
1540 | if not ok: | ||
1541 | ok, dbid = self.IdBetween(query, next_id, higher_id, "") | ||
1542 | if not ok: | ||
1543 | return str(higher_id) | ||
1544 | next_id = dbid | ||
1545 | QueryExec(query, "SELECT time FROM samples WHERE id = " + str(next_id)) | ||
1546 | next_time = int(query.value(0)) | ||
1547 | if get_floor: | ||
1548 | if target_time > next_time: | ||
1549 | lower_id = next_id | ||
1550 | else: | ||
1551 | higher_id = next_id | ||
1552 | if higher_id <= lower_id + 1: | ||
1553 | return str(higher_id) | ||
1554 | else: | ||
1555 | if target_time >= next_time: | ||
1556 | lower_id = next_id | ||
1557 | else: | ||
1558 | higher_id = next_id | ||
1559 | if higher_id <= lower_id + 1: | ||
1560 | return str(lower_id) | ||
1561 | |||
1562 | def ConvertRelativeTime(self, val): | ||
1563 | print "val ", val | ||
1564 | mult = 1 | ||
1565 | suffix = val[-2:] | ||
1566 | if suffix == "ms": | ||
1567 | mult = 1000000 | ||
1568 | elif suffix == "us": | ||
1569 | mult = 1000 | ||
1570 | elif suffix == "ns": | ||
1571 | mult = 1 | ||
1572 | else: | ||
1573 | return val | ||
1574 | val = val[:-2].strip() | ||
1575 | if not self.IsNumber(val): | ||
1576 | return val | ||
1577 | val = int(val) * mult | ||
1578 | if val >= 0: | ||
1579 | val += self.first_time | ||
1580 | else: | ||
1581 | val += self.last_time | ||
1582 | return str(val) | ||
1583 | |||
1584 | def ConvertTimeRange(self, vrange): | ||
1585 | print "vrange ", vrange | ||
1586 | if vrange[0] == "": | ||
1587 | vrange[0] = str(self.first_time) | ||
1588 | if vrange[1] == "": | ||
1589 | vrange[1] = str(self.last_time) | ||
1590 | vrange[0] = self.ConvertRelativeTime(vrange[0]) | ||
1591 | vrange[1] = self.ConvertRelativeTime(vrange[1]) | ||
1592 | print "vrange2 ", vrange | ||
1593 | if not self.IsNumber(vrange[0]) or not self.IsNumber(vrange[1]): | ||
1594 | return False | ||
1595 | print "ok1" | ||
1596 | beg_range = max(int(vrange[0]), self.first_time) | ||
1597 | end_range = min(int(vrange[1]), self.last_time) | ||
1598 | if beg_range > self.last_time or end_range < self.first_time: | ||
1599 | return False | ||
1600 | print "ok2" | ||
1601 | vrange[0] = self.BinarySearchTime(0, self.last_id, beg_range, True) | ||
1602 | vrange[1] = self.BinarySearchTime(1, self.last_id + 1, end_range, False) | ||
1603 | print "vrange3 ", vrange | ||
1604 | return True | ||
1605 | |||
1606 | def AddTimeRange(self, value, ranges): | ||
1607 | print "value ", value | ||
1608 | n = value.count("-") | ||
1609 | if n == 1: | ||
1610 | pass | ||
1611 | elif n == 2: | ||
1612 | if value.split("-")[1].strip() == "": | ||
1613 | n = 1 | ||
1614 | elif n == 3: | ||
1615 | n = 2 | ||
1616 | else: | ||
1617 | return False | ||
1618 | pos = findnth(value, "-", n) | ||
1619 | vrange = [value[:pos].strip() ,value[pos+1:].strip()] | ||
1620 | if self.ConvertTimeRange(vrange): | ||
1621 | ranges.append(vrange) | ||
1622 | return True | ||
1623 | return False | ||
1624 | |||
1625 | def InvalidValue(self, value): | ||
1626 | self.value = "" | ||
1627 | palette = QPalette() | ||
1628 | palette.setColor(QPalette.Text,Qt.red) | ||
1629 | self.widget.setPalette(palette) | ||
1630 | self.red = True | ||
1631 | self.error = self.label + " invalid value '" + value + "'" | ||
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 | |||
1641 | def Invalidate(self): | ||
1642 | self.validated = False | ||
1643 | |||
1644 | def Validate(self): | ||
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 | |||
1695 | def IsValid(self): | ||
1696 | if not self.validated: | ||
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 | |||
1710 | self.glb = glb | ||
1711 | |||
1712 | self.name = "" | ||
1713 | self.where_clause = "" | ||
1714 | |||
1715 | self.setWindowTitle("Selected Branches") | ||
1716 | self.setMinimumWidth(600) | ||
1717 | |||
1718 | items = ( | ||
1719 | ("Report name:", "Enter a name to appear in the window title bar", "", "", "", ""), | ||
1720 | ("Time ranges:", "Enter time ranges", "<timeranges>", "", "samples.id", ""), | ||
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 | |||
1731 | self.grid = QGridLayout() | ||
1732 | |||
1733 | for row in xrange(len(self.data_items)): | ||
1734 | self.grid.addWidget(QLabel(self.data_items[row].label), row, 0) | ||
1735 | self.grid.addWidget(self.data_items[row].widget, row, 1) | ||
1736 | |||
1737 | self.status = QLabel() | ||
1738 | |||
1739 | self.ok_button = QPushButton("Ok", self) | ||
1740 | self.ok_button.setDefault(True) | ||
1741 | self.ok_button.released.connect(self.Ok) | ||
1742 | self.ok_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) | ||
1743 | |||
1744 | self.cancel_button = QPushButton("Cancel", self) | ||
1745 | self.cancel_button.released.connect(self.reject) | ||
1746 | self.cancel_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) | ||
1747 | |||
1748 | self.hbox = QHBoxLayout() | ||
1749 | #self.hbox.addStretch() | ||
1750 | self.hbox.addWidget(self.status) | ||
1751 | self.hbox.addWidget(self.ok_button) | ||
1752 | self.hbox.addWidget(self.cancel_button) | ||
1753 | |||
1754 | self.vbox = QVBoxLayout() | ||
1755 | self.vbox.addLayout(self.grid) | ||
1756 | self.vbox.addLayout(self.hbox) | ||
1757 | |||
1758 | self.setLayout(self.vbox); | ||
1759 | |||
1760 | def Ok(self): | ||
1761 | self.name = self.data_items[0].value | ||
1762 | if not self.name: | ||
1763 | self.ShowMessage("Report name is required") | ||
1764 | return | ||
1765 | for d in self.data_items: | ||
1766 | if not d.IsValid(): | ||
1767 | return | ||
1768 | for d in self.data_items[1:]: | ||
1769 | if len(d.value): | ||
1770 | if len(self.where_clause): | ||
1771 | self.where_clause += " AND " | ||
1772 | self.where_clause += d.value | ||
1773 | if len(self.where_clause): | ||
1774 | self.where_clause = " AND ( " + self.where_clause + " ) " | ||
1775 | else: | ||
1776 | self.ShowMessage("No selection") | ||
1777 | return | ||
1778 | self.accept() | ||
1779 | |||
1780 | def ShowMessage(self, msg): | ||
1781 | self.status.setText("<font color=#FF0000>" + msg) | ||
1782 | |||
1783 | def ClearMessage(self): | ||
1784 | self.status.setText("") | ||
1785 | |||
1467 | # Event list | 1786 | # Event list |
1468 | 1787 | ||
1469 | def GetEventList(db): | 1788 | def GetEventList(db): |
@@ -1656,7 +1975,7 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase): | |||
1656 | def FindDone(self, row): | 1975 | def FindDone(self, row): |
1657 | self.find_bar.Idle() | 1976 | self.find_bar.Idle() |
1658 | if row >= 0: | 1977 | if row >= 0: |
1659 | self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex())) | 1978 | self.view.setCurrentIndex(self.model.mapFromSource(self.data_model.index(row, 0, QModelIndex()))) |
1660 | else: | 1979 | else: |
1661 | self.find_bar.NotFound() | 1980 | self.find_bar.NotFound() |
1662 | 1981 | ||
@@ -1765,6 +2084,149 @@ class WindowMenu(): | |||
1765 | def setActiveSubWindow(self, nr): | 2084 | def setActiveSubWindow(self, nr): |
1766 | self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) | 2085 | self.mdi_area.setActiveSubWindow(self.mdi_area.subWindowList()[nr - 1]) |
1767 | 2086 | ||
2087 | # Help text | ||
2088 | |||
2089 | glb_help_text = """ | ||
2090 | <h1>Contents</h1> | ||
2091 | <style> | ||
2092 | p.c1 { | ||
2093 | text-indent: 40px; | ||
2094 | } | ||
2095 | p.c2 { | ||
2096 | text-indent: 80px; | ||
2097 | } | ||
2098 | } | ||
2099 | </style> | ||
2100 | <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> | ||
2102 | <p class=c2><a href=#allbranches>1.2 All branches</a></p> | ||
2103 | <p class=c2><a href=#selectedbranches>1.3 Selected branches</a></p> | ||
2104 | <p class=c1><a href=#tables>2. Tables</a></p> | ||
2105 | <h1 id=reports>1. Reports</h1> | ||
2106 | <h2 id=callgraph>1.1 Context-Sensitive Call Graph</h2> | ||
2107 | The result is a GUI window with a tree representing a context-sensitive | ||
2108 | call-graph. Expanding a couple of levels of the tree and adjusting column | ||
2109 | widths to suit will display something like: | ||
2110 | <pre> | ||
2111 | Call Graph: pt_example | ||
2112 | Call Path Object Count Time(ns) Time(%) Branch Count Branch Count(%) | ||
2113 | v- ls | ||
2114 | v- 2638:2638 | ||
2115 | v- _start ld-2.19.so 1 10074071 100.0 211135 100.0 | ||
2116 | |- unknown unknown 1 13198 0.1 1 0.0 | ||
2117 | >- _dl_start ld-2.19.so 1 1400980 13.9 19637 9.3 | ||
2118 | >- _d_linit_internal ld-2.19.so 1 448152 4.4 11094 5.3 | ||
2119 | v-__libc_start_main@plt ls 1 8211741 81.5 180397 85.4 | ||
2120 | >- _dl_fixup ld-2.19.so 1 7607 0.1 108 0.1 | ||
2121 | >- __cxa_atexit libc-2.19.so 1 11737 0.1 10 0.0 | ||
2122 | >- __libc_csu_init ls 1 10354 0.1 10 0.0 | ||
2123 | |- _setjmp libc-2.19.so 1 0 0.0 4 0.0 | ||
2124 | v- main ls 1 8182043 99.6 180254 99.9 | ||
2125 | </pre> | ||
2126 | <h3>Points to note:</h3> | ||
2127 | <ul> | ||
2128 | <li>The top level is a command name (comm)</li> | ||
2129 | <li>The next level is a thread (pid:tid)</li> | ||
2130 | <li>Subsequent levels are functions</li> | ||
2131 | <li>'Count' is the number of calls</li> | ||
2132 | <li>'Time' is the elapsed time until the function returns</li> | ||
2133 | <li>Percentages are relative to the level above</li> | ||
2134 | <li>'Branch Count' is the total number of branches for that function and all functions that it calls | ||
2135 | </ul> | ||
2136 | <h3>Find</h3> | ||
2137 | 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. | ||
2139 | <h2 id=allbranches>1.2 All branches</h2> | ||
2140 | 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. | ||
2142 | <h3>Disassembly</h3> | ||
2143 | Open a branch to display disassembly. This only works if: | ||
2144 | <ol> | ||
2145 | <li>The disassembler is available. Currently, only Intel XED is supported - see <a href=#xed>Intel XED Setup</a></li> | ||
2146 | <li>The object code is available. Currently, only the perf build ID cache is searched for object code. | ||
2147 | The default directory ~/.debug can be overridden by setting environment variable PERF_BUILDID_DIR. | ||
2148 | One exception is kcore where the DSO long name is used (refer dsos_view on the Tables menu), | ||
2149 | or alternatively, set environment variable PERF_KCORE to the kcore file name.</li> | ||
2150 | </ol> | ||
2151 | <h4 id=xed>Intel XED Setup</h4> | ||
2152 | To use Intel XED, libxed.so must be present. To build and install libxed.so: | ||
2153 | <pre> | ||
2154 | git clone https://github.com/intelxed/mbuild.git mbuild | ||
2155 | git clone https://github.com/intelxed/xed | ||
2156 | cd xed | ||
2157 | ./mfile.py --share | ||
2158 | sudo ./mfile.py --prefix=/usr/local install | ||
2159 | sudo ldconfig | ||
2160 | </pre> | ||
2161 | <h3>Find</h3> | ||
2162 | 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. | ||
2164 | All columns are searched, but only currently fetched rows are searched. | ||
2165 | <h2 id=selectedbranches>1.3 Selected branches</h2> | ||
2166 | 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. | ||
2168 | <h3>1.3.1 Time ranges</h3> | ||
2169 | 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: | ||
2171 | <pre> | ||
2172 | 81073085947329-81073085958238 From 81073085947329 to 81073085958238 | ||
2173 | 100us-200us From 100us to 200us | ||
2174 | 10ms- From 10ms to the end | ||
2175 | -100ns The first 100ns | ||
2176 | -10ms- The last 10ms | ||
2177 | </pre> | ||
2178 | N.B. Due to the granularity of timestamps, there could be no branches in any given time range. | ||
2179 | <h1 id=tables>2. Tables</h1> | ||
2180 | 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 | ||
2182 | immediately. More records can be fetched using the Fetch bar provided. Columns can be sorted, | ||
2183 | but that can be slow for large tables. | ||
2184 | <p>There are also tables of database meta-information. | ||
2185 | For SQLite3 databases, the sqlite_master table is included. | ||
2186 | For PostgreSQL databases, information_schema.tables/views/columns are included. | ||
2187 | <h3>Find</h3> | ||
2188 | Ctrl-F displays a Find bar which finds substrings by either an exact match or a regular expression match. | ||
2189 | Refer to Python documentation for the regular expression syntax. | ||
2190 | All columns are searched, but only currently fetched rows are searched. | ||
2191 | <p>N.B. Results are found in id order, so if the table is re-ordered, find-next and find-previous | ||
2192 | will go to the next/previous result in id order, instead of display order. | ||
2193 | """ | ||
2194 | |||
2195 | # Help window | ||
2196 | |||
2197 | class HelpWindow(QMdiSubWindow): | ||
2198 | |||
2199 | def __init__(self, glb, parent=None): | ||
2200 | super(HelpWindow, self).__init__(parent) | ||
2201 | |||
2202 | self.text = QTextBrowser() | ||
2203 | self.text.setHtml(glb_help_text) | ||
2204 | self.text.setReadOnly(True) | ||
2205 | self.text.setOpenExternalLinks(True) | ||
2206 | |||
2207 | self.setWidget(self.text) | ||
2208 | |||
2209 | AddSubWindow(glb.mainwindow.mdi_area, self, "Exported SQL Viewer Help") | ||
2210 | |||
2211 | # Main window that only displays the help text | ||
2212 | |||
2213 | class HelpOnlyWindow(QMainWindow): | ||
2214 | |||
2215 | def __init__(self, parent=None): | ||
2216 | super(HelpOnlyWindow, self).__init__(parent) | ||
2217 | |||
2218 | self.setMinimumSize(200, 100) | ||
2219 | self.resize(800, 600) | ||
2220 | self.setWindowTitle("Exported SQL Viewer Help") | ||
2221 | self.setWindowIcon(self.style().standardIcon(QStyle.SP_MessageBoxInformation)) | ||
2222 | |||
2223 | self.text = QTextBrowser() | ||
2224 | self.text.setHtml(glb_help_text) | ||
2225 | self.text.setReadOnly(True) | ||
2226 | self.text.setOpenExternalLinks(True) | ||
2227 | |||
2228 | self.setCentralWidget(self.text) | ||
2229 | |||
1768 | # Font resize | 2230 | # Font resize |
1769 | 2231 | ||
1770 | def ResizeFont(widget, diff): | 2232 | def ResizeFont(widget, diff): |
@@ -1851,6 +2313,9 @@ class MainWindow(QMainWindow): | |||
1851 | 2313 | ||
1852 | self.window_menu = WindowMenu(self.mdi_area, menu) | 2314 | self.window_menu = WindowMenu(self.mdi_area, menu) |
1853 | 2315 | ||
2316 | help_menu = menu.addMenu("&Help") | ||
2317 | help_menu.addAction(CreateAction("&Exported SQL Viewer Help", "Helpful information", self.Help, self, QKeySequence.HelpContents)) | ||
2318 | |||
1854 | def Find(self): | 2319 | def Find(self): |
1855 | win = self.mdi_area.activeSubWindow() | 2320 | win = self.mdi_area.activeSubWindow() |
1856 | if win: | 2321 | if win: |
@@ -1888,6 +2353,8 @@ class MainWindow(QMainWindow): | |||
1888 | if event == "branches": | 2353 | if event == "branches": |
1889 | label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")" | 2354 | label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")" |
1890 | reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self)) | 2355 | reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self)) |
2356 | label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")" | ||
2357 | reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self)) | ||
1891 | 2358 | ||
1892 | def TableMenu(self, tables, menu): | 2359 | def TableMenu(self, tables, menu): |
1893 | table_menu = menu.addMenu("&Tables") | 2360 | table_menu = menu.addMenu("&Tables") |
@@ -1900,9 +2367,18 @@ class MainWindow(QMainWindow): | |||
1900 | def NewBranchView(self, event_id): | 2367 | def NewBranchView(self, event_id): |
1901 | BranchWindow(self.glb, event_id, "", "", self) | 2368 | BranchWindow(self.glb, event_id, "", "", self) |
1902 | 2369 | ||
2370 | def NewSelectedBranchView(self, event_id): | ||
2371 | dialog = SelectedBranchDialog(self.glb, self) | ||
2372 | ret = dialog.exec_() | ||
2373 | if ret: | ||
2374 | BranchWindow(self.glb, event_id, dialog.name, dialog.where_clause, self) | ||
2375 | |||
1903 | def NewTableView(self, table_name): | 2376 | def NewTableView(self, table_name): |
1904 | TableWindow(self.glb, table_name, self) | 2377 | TableWindow(self.glb, table_name, self) |
1905 | 2378 | ||
2379 | def Help(self): | ||
2380 | HelpWindow(self.glb, self) | ||
2381 | |||
1906 | # XED Disassembler | 2382 | # XED Disassembler |
1907 | 2383 | ||
1908 | class xed_state_t(Structure): | 2384 | class xed_state_t(Structure): |
@@ -1929,7 +2405,12 @@ class XEDInstruction(): | |||
1929 | class LibXED(): | 2405 | class LibXED(): |
1930 | 2406 | ||
1931 | def __init__(self): | 2407 | def __init__(self): |
1932 | self.libxed = CDLL("libxed.so") | 2408 | try: |
2409 | self.libxed = CDLL("libxed.so") | ||
2410 | except: | ||
2411 | self.libxed = None | ||
2412 | if not self.libxed: | ||
2413 | self.libxed = CDLL("/usr/local/lib/libxed.so") | ||
1933 | 2414 | ||
1934 | self.xed_tables_init = self.libxed.xed_tables_init | 2415 | self.xed_tables_init = self.libxed.xed_tables_init |
1935 | self.xed_tables_init.restype = None | 2416 | self.xed_tables_init.restype = None |
@@ -2097,10 +2578,16 @@ class DBRef(): | |||
2097 | 2578 | ||
2098 | def Main(): | 2579 | def Main(): |
2099 | if (len(sys.argv) < 2): | 2580 | if (len(sys.argv) < 2): |
2100 | print >> sys.stderr, "Usage is: exported-sql-viewer.py <database name>" | 2581 | print >> sys.stderr, "Usage is: exported-sql-viewer.py {<database name> | --help-only}" |
2101 | raise Exception("Too few arguments") | 2582 | raise Exception("Too few arguments") |
2102 | 2583 | ||
2103 | dbname = sys.argv[1] | 2584 | dbname = sys.argv[1] |
2585 | if dbname == "--help-only": | ||
2586 | app = QApplication(sys.argv) | ||
2587 | mainwindow = HelpOnlyWindow() | ||
2588 | mainwindow.show() | ||
2589 | err = app.exec_() | ||
2590 | sys.exit(err) | ||
2104 | 2591 | ||
2105 | is_sqlite3 = False | 2592 | is_sqlite3 = False |
2106 | try: | 2593 | try: |