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