diff options
| author | Adrian Hunter <adrian.hunter@intel.com> | 2018-11-04 10:12:36 -0500 |
|---|---|---|
| committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2018-11-05 12:51:55 -0500 |
| commit | 210cf1f96185f0c6383df8b6030e3d2945e1b41a (patch) | |
| tree | 2d1195af1923db541e211186f7766ad9f38731dd /tools/perf/scripts/python/exported-sql-viewer.py | |
| parent | 5ed4419d47f8ba6bbccd8e3203276b3c39a792b7 (diff) | |
perf scripts python: exported-sql-viewer.py: Add Selected branches report
Fetching data from the database can be slow. Add a report that provides
the ability to select a subset of branches.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Link: http://lkml.kernel.org/r/20181104151238.15947-3-adrian.hunter@intel.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/scripts/python/exported-sql-viewer.py')
| -rwxr-xr-x | tools/perf/scripts/python/exported-sql-viewer.py | 327 |
1 files changed, 327 insertions, 0 deletions
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py index 20cc8e7879b9..a9d2b3170141 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): |
| @@ -1888,6 +2207,8 @@ class MainWindow(QMainWindow): | |||
| 1888 | if event == "branches": | 2207 | if event == "branches": |
| 1889 | label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")" | 2208 | 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)) | 2209 | reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewBranchView(x), self)) |
| 2210 | label = "Selected branches" if branches_events == 1 else "Selected branches " + "(id=" + dbid + ")" | ||
| 2211 | reports_menu.addAction(CreateAction(label, "Create a new window displaying branch events", lambda x=dbid: self.NewSelectedBranchView(x), self)) | ||
| 1891 | 2212 | ||
| 1892 | def TableMenu(self, tables, menu): | 2213 | def TableMenu(self, tables, menu): |
| 1893 | table_menu = menu.addMenu("&Tables") | 2214 | table_menu = menu.addMenu("&Tables") |
| @@ -1900,6 +2221,12 @@ class MainWindow(QMainWindow): | |||
| 1900 | def NewBranchView(self, event_id): | 2221 | def NewBranchView(self, event_id): |
| 1901 | BranchWindow(self.glb, event_id, "", "", self) | 2222 | BranchWindow(self.glb, event_id, "", "", self) |
| 1902 | 2223 | ||
| 2224 | def NewSelectedBranchView(self, event_id): | ||
| 2225 | dialog = SelectedBranchDialog(self.glb, self) | ||
| 2226 | ret = dialog.exec_() | ||
| 2227 | if ret: | ||
| 2228 | BranchWindow(self.glb, event_id, dialog.name, dialog.where_clause, self) | ||
| 2229 | |||
| 1903 | def NewTableView(self, table_name): | 2230 | def NewTableView(self, table_name): |
| 1904 | TableWindow(self.glb, table_name, self) | 2231 | TableWindow(self.glb, table_name, self) |
| 1905 | 2232 | ||
