aboutsummaryrefslogtreecommitdiffstats
path: root/tools/perf/scripts/python/exported-sql-viewer.py
diff options
context:
space:
mode:
authorAdrian Hunter <adrian.hunter@intel.com>2018-11-04 10:12:36 -0500
committerArnaldo Carvalho de Melo <acme@redhat.com>2018-11-05 12:51:55 -0500
commit210cf1f96185f0c6383df8b6030e3d2945e1b41a (patch)
tree2d1195af1923db541e211186f7766ad9f38731dd /tools/perf/scripts/python/exported-sql-viewer.py
parent5ed4419d47f8ba6bbccd8e3203276b3c39a792b7 (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-xtools/perf/scripts/python/exported-sql-viewer.py327
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
122def 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
124def PercentToOneDP(n, d): 132def 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
1477class 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
1705class 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
1469def GetEventList(db): 1788def 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