@@ -119,6 +119,14 @@ def dsoname(name):
119
119
return "[kernel]"
120
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
130
# Percent to one decimal place
123
131
124
132
def PercentToOneDP (n , d ):
@@ -1464,6 +1472,317 @@ def FindDone(self, row):
1464
1472
else :
1465
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
1786
# Event list
1468
1787
1469
1788
def GetEventList (db ):
@@ -1888,6 +2207,8 @@ def EventMenu(self, events, reports_menu):
1888
2207
if event == "branches" :
1889
2208
label = "All branches" if branches_events == 1 else "All branches " + "(id=" + dbid + ")"
1890
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
2213
def TableMenu (self , tables , menu ):
1893
2214
table_menu = menu .addMenu ("&Tables" )
@@ -1900,6 +2221,12 @@ def NewCallGraph(self):
1900
2221
def NewBranchView (self , event_id ):
1901
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
2230
def NewTableView (self , table_name ):
1904
2231
TableWindow (self .glb , table_name , self )
1905
2232
0 commit comments