Skip to content

Commit 210cf1f

Browse files
ahunter6acmel
authored andcommitted
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>
1 parent 5ed4419 commit 210cf1f

File tree

1 file changed

+327
-0
lines changed

1 file changed

+327
-0
lines changed

tools/perf/scripts/python/exported-sql-viewer.py

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,14 @@ def dsoname(name):
119119
return "[kernel]"
120120
return name
121121

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+
122130
# Percent to one decimal place
123131

124132
def PercentToOneDP(n, d):
@@ -1464,6 +1472,317 @@ def FindDone(self, row):
14641472
else:
14651473
self.find_bar.NotFound()
14661474

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+
14671786
# Event list
14681787

14691788
def GetEventList(db):
@@ -1888,6 +2207,8 @@ def EventMenu(self, events, reports_menu):
18882207
if event == "branches":
18892208
label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
18902209
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))
18912212

18922213
def TableMenu(self, tables, menu):
18932214
table_menu = menu.addMenu("&Tables")
@@ -1900,6 +2221,12 @@ def NewCallGraph(self):
19002221
def NewBranchView(self, event_id):
19012222
BranchWindow(self.glb, event_id, "", "", self)
19022223

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+
19032230
def NewTableView(self, table_name):
19042231
TableWindow(self.glb, table_name, self)
19052232

0 commit comments

Comments
 (0)