@@ -1427,3 +1427,279 @@ func TestStackFilterAndLogicValidation(t *testing.T) {
14271427 }
14281428 }
14291429}
1430+
1431+ func TestStackFilterFunctionNameStartsWith (t * testing.T ) {
1432+ mem := memory .NewCheckedAllocator (memory .DefaultAllocator )
1433+ defer mem .AssertSize (t , 0 )
1434+
1435+ originalRecords , cleanup := createTestProfileData (mem )
1436+ defer cleanup ()
1437+
1438+ // Test stack filter: keep only stacks containing functions that start with "app."
1439+ // Expected: Only sample 1 from record 1 should remain (contains app.server.handleRequest)
1440+ recs , _ , err := FilterProfileData (
1441+ context .Background (),
1442+ noop .NewTracerProvider ().Tracer ("" ),
1443+ mem ,
1444+ originalRecords ,
1445+ []* pb.Filter {
1446+ {
1447+ Filter : & pb.Filter_StackFilter {
1448+ StackFilter : & pb.StackFilter {
1449+ Filter : & pb.StackFilter_Criteria {
1450+ Criteria : & pb.FilterCriteria {
1451+ FunctionName : & pb.StringCondition {
1452+ Condition : & pb.StringCondition_StartsWith {
1453+ StartsWith : "app." ,
1454+ },
1455+ },
1456+ },
1457+ },
1458+ },
1459+ },
1460+ },
1461+ },
1462+ )
1463+ require .NoError (t , err )
1464+ defer func () {
1465+ for _ , r := range recs {
1466+ r .Release ()
1467+ }
1468+ }()
1469+
1470+ // Should have 3 samples remaining (samples containing app.* functions)
1471+ totalSamples := int64 (0 )
1472+ for _ , rec := range recs {
1473+ totalSamples += rec .NumRows ()
1474+ }
1475+ require .Equal (t , int64 (3 ), totalSamples , "Should have 3 samples with functions starting with 'app.'" )
1476+
1477+ // Validate the remaining samples contain at least one function starting with "app."
1478+ for _ , rec := range recs {
1479+ r := profile .NewRecordReader (rec )
1480+ for i := 0 ; i < int (rec .NumRows ()); i ++ {
1481+ lOffsetStart , lOffsetEnd := r .Locations .ValueOffsets (i )
1482+ firstStart , _ := r .Lines .ValueOffsets (int (lOffsetStart ))
1483+ _ , lastEnd := r .Lines .ValueOffsets (int (lOffsetEnd - 1 ))
1484+
1485+ foundAppFunction := false
1486+ for k := int (firstStart ); k < int (lastEnd ); k ++ {
1487+ fnIndex := r .LineFunctionNameIndices .Value (k )
1488+ functionName := string (r .LineFunctionNameDict .Value (int (fnIndex )))
1489+
1490+ if strings .HasPrefix (strings .ToLower (functionName ), "app." ) {
1491+ foundAppFunction = true
1492+ break
1493+ }
1494+ }
1495+
1496+ require .True (t , foundAppFunction , "Sample should contain at least one function starting with 'app.'" )
1497+ }
1498+ }
1499+ }
1500+
1501+ func TestStackFilterFunctionNameNotStartsWith (t * testing.T ) {
1502+ mem := memory .NewCheckedAllocator (memory .DefaultAllocator )
1503+ defer mem .AssertSize (t , 0 )
1504+
1505+ originalRecords , cleanup := createTestProfileData (mem )
1506+ defer cleanup ()
1507+
1508+ // Test stack filter: keep only stacks NOT containing functions that start with "database."
1509+ // Expected: Samples NOT containing database.query should remain (4 out of 5)
1510+ recs , _ , err := FilterProfileData (
1511+ context .Background (),
1512+ noop .NewTracerProvider ().Tracer ("" ),
1513+ mem ,
1514+ originalRecords ,
1515+ []* pb.Filter {
1516+ {
1517+ Filter : & pb.Filter_StackFilter {
1518+ StackFilter : & pb.StackFilter {
1519+ Filter : & pb.StackFilter_Criteria {
1520+ Criteria : & pb.FilterCriteria {
1521+ FunctionName : & pb.StringCondition {
1522+ Condition : & pb.StringCondition_NotStartsWith {
1523+ NotStartsWith : "database." ,
1524+ },
1525+ },
1526+ },
1527+ },
1528+ },
1529+ },
1530+ },
1531+ },
1532+ )
1533+ require .NoError (t , err )
1534+ defer func () {
1535+ for _ , r := range recs {
1536+ r .Release ()
1537+ }
1538+ }()
1539+
1540+ // Should have 2 samples remaining (all except those with database.* functions)
1541+ totalSamples := int64 (0 )
1542+ for _ , rec := range recs {
1543+ totalSamples += rec .NumRows ()
1544+ }
1545+ require .Equal (t , int64 (2 ), totalSamples , "Should have 2 samples NOT containing functions starting with 'database.'" )
1546+
1547+ // Validate none of the remaining samples contain functions starting with "database."
1548+ for _ , rec := range recs {
1549+ r := profile .NewRecordReader (rec )
1550+ for i := 0 ; i < int (rec .NumRows ()); i ++ {
1551+ lOffsetStart , lOffsetEnd := r .Locations .ValueOffsets (i )
1552+ firstStart , _ := r .Lines .ValueOffsets (int (lOffsetStart ))
1553+ _ , lastEnd := r .Lines .ValueOffsets (int (lOffsetEnd - 1 ))
1554+
1555+ for k := int (firstStart ); k < int (lastEnd ); k ++ {
1556+ fnIndex := r .LineFunctionNameIndices .Value (k )
1557+ functionName := string (r .LineFunctionNameDict .Value (int (fnIndex )))
1558+
1559+ require .False (t , strings .HasPrefix (strings .ToLower (functionName ), "database." ),
1560+ "Sample should NOT contain functions starting with 'database.', found: %s" , functionName )
1561+ }
1562+ }
1563+ }
1564+ }
1565+
1566+ func TestFrameFilterFunctionNameStartsWith (t * testing.T ) {
1567+ mem := memory .NewCheckedAllocator (memory .DefaultAllocator )
1568+ defer mem .AssertSize (t , 0 )
1569+
1570+ originalRecords , cleanup := createTestProfileData (mem )
1571+ defer cleanup ()
1572+
1573+ // Test frame filter: keep only frames starting with "runtime."
1574+ // Expected: All 5 samples remain, but only runtime.* frames are kept
1575+ recs , _ , err := FilterProfileData (
1576+ context .Background (),
1577+ noop .NewTracerProvider ().Tracer ("" ),
1578+ mem ,
1579+ originalRecords ,
1580+ []* pb.Filter {
1581+ {
1582+ Filter : & pb.Filter_FrameFilter {
1583+ FrameFilter : & pb.FrameFilter {
1584+ Filter : & pb.FrameFilter_Criteria {
1585+ Criteria : & pb.FilterCriteria {
1586+ FunctionName : & pb.StringCondition {
1587+ Condition : & pb.StringCondition_StartsWith {
1588+ StartsWith : "runtime." ,
1589+ },
1590+ },
1591+ },
1592+ },
1593+ },
1594+ },
1595+ },
1596+ },
1597+ )
1598+ require .NoError (t , err )
1599+ defer func () {
1600+ for _ , r := range recs {
1601+ r .Release ()
1602+ }
1603+ }()
1604+
1605+ // Should have all 5 samples remaining (with only runtime frames kept)
1606+ totalSamples := int64 (0 )
1607+ for _ , rec := range recs {
1608+ totalSamples += rec .NumRows ()
1609+ }
1610+ require .Equal (t , int64 (5 ), totalSamples , "Should have 5 samples with only runtime.* frames" )
1611+
1612+ // Validate the remaining frames all start with "runtime."
1613+ for _ , rec := range recs {
1614+ r := profile .NewRecordReader (rec )
1615+ for i := 0 ; i < int (rec .NumRows ()); i ++ {
1616+ lOffsetStart , lOffsetEnd := r .Locations .ValueOffsets (i )
1617+ firstStart , _ := r .Lines .ValueOffsets (int (lOffsetStart ))
1618+ _ , lastEnd := r .Lines .ValueOffsets (int (lOffsetEnd - 1 ))
1619+
1620+ for k := int (firstStart ); k < int (lastEnd ); k ++ {
1621+ if r .Line .IsValid (k ) {
1622+ fnIndex := r .LineFunctionNameIndices .Value (k )
1623+ functionName := string (r .LineFunctionNameDict .Value (int (fnIndex )))
1624+
1625+ // All remaining frames must start with "runtime."
1626+ require .True (t , strings .HasPrefix (strings .ToLower (functionName ), "runtime." ),
1627+ "All remaining frames should start with 'runtime.', found: %s" , functionName )
1628+ }
1629+ }
1630+ }
1631+ }
1632+ }
1633+
1634+ func TestFrameFilterFunctionNameNotStartsWith (t * testing.T ) {
1635+ mem := memory .NewCheckedAllocator (memory .DefaultAllocator )
1636+ defer mem .AssertSize (t , 0 )
1637+
1638+ originalRecords , cleanup := createTestProfileData (mem )
1639+ defer cleanup ()
1640+
1641+ // Test frame filter: keep only frames NOT starting with "runtime."
1642+ // Expected: All 5 samples remain, but runtime.* frames are filtered out
1643+ recs , _ , err := FilterProfileData (
1644+ context .Background (),
1645+ noop .NewTracerProvider ().Tracer ("" ),
1646+ mem ,
1647+ originalRecords ,
1648+ []* pb.Filter {
1649+ {
1650+ Filter : & pb.Filter_FrameFilter {
1651+ FrameFilter : & pb.FrameFilter {
1652+ Filter : & pb.FrameFilter_Criteria {
1653+ Criteria : & pb.FilterCriteria {
1654+ FunctionName : & pb.StringCondition {
1655+ Condition : & pb.StringCondition_NotStartsWith {
1656+ NotStartsWith : "runtime." ,
1657+ },
1658+ },
1659+ },
1660+ },
1661+ },
1662+ },
1663+ },
1664+ },
1665+ )
1666+ require .NoError (t , err )
1667+ defer func () {
1668+ for _ , r := range recs {
1669+ r .Release ()
1670+ }
1671+ }()
1672+
1673+ // Should have all 5 samples remaining (with runtime frames filtered out)
1674+ totalSamples := int64 (0 )
1675+ for _ , rec := range recs {
1676+ totalSamples += rec .NumRows ()
1677+ }
1678+ require .Equal (t , int64 (5 ), totalSamples , "Should have 5 samples with runtime.* frames filtered out" )
1679+
1680+ // Validate none of the remaining frames start with "runtime."
1681+ for _ , rec := range recs {
1682+ r := profile .NewRecordReader (rec )
1683+ for i := 0 ; i < int (rec .NumRows ()); i ++ {
1684+ lOffsetStart , lOffsetEnd := r .Locations .ValueOffsets (i )
1685+ firstStart , _ := r .Lines .ValueOffsets (int (lOffsetStart ))
1686+ _ , lastEnd := r .Lines .ValueOffsets (int (lOffsetEnd - 1 ))
1687+
1688+ validFrameCount := 0
1689+ for k := int (firstStart ); k < int (lastEnd ); k ++ {
1690+ if r .Line .IsValid (k ) {
1691+ fnIndex := r .LineFunctionNameIndices .Value (k )
1692+ functionName := string (r .LineFunctionNameDict .Value (int (fnIndex )))
1693+
1694+ // All remaining frames must NOT start with "runtime."
1695+ require .False (t , strings .HasPrefix (strings .ToLower (functionName ), "runtime." ),
1696+ "All remaining frames should NOT start with 'runtime.', found: %s" , functionName )
1697+ validFrameCount ++
1698+ }
1699+ }
1700+
1701+ // Should have at least 1 frame remaining after filtering out runtime frames
1702+ require .Greater (t , validFrameCount , 0 , "Sample should have at least 1 non-runtime.* frame remaining" )
1703+ }
1704+ }
1705+ }
0 commit comments