github.com/grafana/pyroscope@v1.18.0/pkg/model/sampletype/relabel_test.go (about)

     1  package sampletype
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/require"
     7  
     8  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
     9  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
    10  
    11  	"github.com/prometheus/common/model"
    12  	"github.com/prometheus/prometheus/model/relabel"
    13  	"github.com/stretchr/testify/assert"
    14  
    15  	googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
    16  	"github.com/grafana/pyroscope/pkg/pprof"
    17  	"github.com/grafana/pyroscope/pkg/validation"
    18  )
    19  
    20  func TestRelabelProfile(t *testing.T) {
    21  	tests := []struct {
    22  		name           string
    23  		profile        *googlev1.Profile
    24  		rules          []*relabel.Config
    25  		expectedTypes  []string
    26  		expectedValues [][]int64
    27  	}{
    28  		{
    29  			name: "drop alloc_objects and alloc_space from memory profile",
    30  			profile: &googlev1.Profile{
    31  				StringTable: []string{"", "alloc_objects", "count", "alloc_space", "bytes", "inuse_objects", "inuse_space"},
    32  				SampleType: []*googlev1.ValueType{
    33  					{Type: 1, Unit: 2}, // alloc_objects, count
    34  					{Type: 3, Unit: 4}, // alloc_space, bytes
    35  					{Type: 5, Unit: 2}, // inuse_objects, count
    36  					{Type: 6, Unit: 4}, // inuse_space, bytes
    37  				},
    38  				Sample: []*googlev1.Sample{
    39  					{LocationId: []uint64{1}, Value: []int64{100, 2048, 50, 1024}},
    40  					{LocationId: []uint64{2}, Value: []int64{200, 4096, 150, 3072}},
    41  				},
    42  				Location: []*googlev1.Location{
    43  					{Id: 1, MappingId: 1, Address: 0xef},
    44  					{Id: 2, MappingId: 1, Address: 0xcafe000},
    45  				},
    46  				Mapping: []*googlev1.Mapping{{Id: 1}},
    47  			},
    48  			rules: []*relabel.Config{
    49  				{
    50  					SourceLabels: []model.LabelName{"__type__"},
    51  					Regex:        relabel.MustNewRegexp("alloc_.*"),
    52  					Action:       relabel.Drop,
    53  				},
    54  			},
    55  			expectedTypes: []string{"inuse_objects", "inuse_space"},
    56  			expectedValues: [][]int64{
    57  				{50, 1024},
    58  				{150, 3072},
    59  			},
    60  		},
    61  		{
    62  			name: "keep only inuse_space",
    63  			profile: &googlev1.Profile{
    64  				StringTable: []string{"", "alloc_objects", "count", "alloc_space", "bytes", "inuse_objects", "inuse_space"},
    65  				SampleType: []*googlev1.ValueType{
    66  					{Type: 1, Unit: 2}, // alloc_objects, count
    67  					{Type: 3, Unit: 4}, // alloc_space, bytes
    68  					{Type: 5, Unit: 2}, // inuse_objects, count
    69  					{Type: 6, Unit: 4}, // inuse_space, bytes
    70  				},
    71  				Sample: []*googlev1.Sample{
    72  					{LocationId: []uint64{1}, Value: []int64{100, 2048, 50, 1024}},
    73  					{LocationId: []uint64{2}, Value: []int64{200, 4096, 150, 3072}},
    74  				},
    75  				Location: []*googlev1.Location{
    76  					{Id: 1, MappingId: 1, Address: 0xef},
    77  					{Id: 2, MappingId: 1, Address: 0xcafe000},
    78  				},
    79  				Mapping: []*googlev1.Mapping{{Id: 1}},
    80  			},
    81  			rules: []*relabel.Config{
    82  				{
    83  					SourceLabels: []model.LabelName{"__type__"},
    84  					Regex:        relabel.MustNewRegexp("inuse_space"),
    85  					Action:       relabel.Keep,
    86  				},
    87  			},
    88  			expectedTypes: []string{"inuse_space"},
    89  			expectedValues: [][]int64{
    90  				{1024},
    91  				{3072},
    92  			},
    93  		},
    94  		{
    95  			name: "drop by unit - drop count types",
    96  			profile: &googlev1.Profile{
    97  				StringTable: []string{"", "alloc_objects", "count", "alloc_space", "bytes", "inuse_objects", "inuse_space"},
    98  				SampleType: []*googlev1.ValueType{
    99  					{Type: 1, Unit: 2}, // alloc_objects, count
   100  					{Type: 3, Unit: 4}, // alloc_space, bytes
   101  					{Type: 5, Unit: 2}, // inuse_objects, count
   102  					{Type: 6, Unit: 4}, // inuse_space, bytes
   103  				},
   104  				Sample: []*googlev1.Sample{
   105  					{LocationId: []uint64{1}, Value: []int64{100, 2048, 50, 1024}},
   106  				},
   107  				Location: []*googlev1.Location{
   108  					{Id: 1, MappingId: 1, Address: 0xef},
   109  				},
   110  				Mapping: []*googlev1.Mapping{{Id: 1}},
   111  			},
   112  			rules: []*relabel.Config{
   113  				{
   114  					SourceLabels: []model.LabelName{"__unit__"},
   115  					Regex:        relabel.MustNewRegexp("count"),
   116  					Action:       relabel.Drop,
   117  				},
   118  			},
   119  			expectedTypes: []string{"alloc_space", "inuse_space"},
   120  			expectedValues: [][]int64{
   121  				{2048, 1024},
   122  			},
   123  		},
   124  		{
   125  			name: "drop all sample types",
   126  			profile: &googlev1.Profile{
   127  				StringTable: []string{"", "cpu", "nanoseconds"},
   128  				SampleType: []*googlev1.ValueType{
   129  					{Type: 1, Unit: 2}, // cpu, nanoseconds
   130  				},
   131  				Sample: []*googlev1.Sample{
   132  					{LocationId: []uint64{1}, Value: []int64{1000}},
   133  				},
   134  				Location: []*googlev1.Location{
   135  					{Id: 1, MappingId: 1, Address: 0xef},
   136  				},
   137  				Mapping: []*googlev1.Mapping{{Id: 1}},
   138  			},
   139  			rules: []*relabel.Config{
   140  				{
   141  					SourceLabels: []model.LabelName{"__type__"},
   142  					Regex:        relabel.MustNewRegexp(".*"),
   143  					Action:       relabel.Drop,
   144  				},
   145  			},
   146  			expectedTypes:  []string{},
   147  			expectedValues: [][]int64{},
   148  		},
   149  		{
   150  			name: "no rules - no changes",
   151  			profile: &googlev1.Profile{
   152  				StringTable: []string{"", "cpu", "nanoseconds"},
   153  				SampleType: []*googlev1.ValueType{
   154  					{Type: 1, Unit: 2},
   155  				},
   156  				Sample: []*googlev1.Sample{
   157  					{LocationId: []uint64{1}, Value: []int64{1000}},
   158  				},
   159  				Location: []*googlev1.Location{
   160  					{Id: 1, MappingId: 1, Address: 0xef},
   161  				},
   162  				Mapping: []*googlev1.Mapping{{Id: 1}},
   163  			},
   164  			rules:         []*relabel.Config{},
   165  			expectedTypes: []string{"cpu"},
   166  			expectedValues: [][]int64{
   167  				{1000},
   168  			},
   169  		},
   170  		{
   171  			name: "complex relabeling with multiple rules",
   172  			profile: &googlev1.Profile{
   173  				StringTable: []string{"", "samples", "count", "cpu", "nanoseconds", "wall", "goroutines"},
   174  				SampleType: []*googlev1.ValueType{
   175  					{Type: 1, Unit: 2}, // samples, count
   176  					{Type: 3, Unit: 4}, // cpu, nanoseconds
   177  					{Type: 5, Unit: 4}, // wall, nanoseconds
   178  					{Type: 6, Unit: 2}, // goroutines, count
   179  				},
   180  				Sample: []*googlev1.Sample{
   181  					{LocationId: []uint64{1}, Value: []int64{10, 1000000, 2000000, 5}},
   182  					{LocationId: []uint64{2}, Value: []int64{20, 3000000, 4000000, 8}},
   183  				},
   184  				Location: []*googlev1.Location{
   185  					{Id: 1, MappingId: 1, Address: 0xef},
   186  					{Id: 2, MappingId: 1, Address: 0xcafe000},
   187  				},
   188  				Mapping: []*googlev1.Mapping{{Id: 1}},
   189  			},
   190  			rules: []*relabel.Config{
   191  				{
   192  					SourceLabels: []model.LabelName{"__type__"},
   193  					Regex:        relabel.MustNewRegexp("samples"),
   194  					Action:       relabel.Drop,
   195  				},
   196  				{
   197  					SourceLabels: []model.LabelName{"__type__", "__unit__"},
   198  					Separator:    "/",
   199  					Regex:        relabel.MustNewRegexp("goroutines/count"),
   200  					Action:       relabel.Drop,
   201  				},
   202  			},
   203  			expectedTypes: []string{"cpu", "wall"},
   204  			expectedValues: [][]int64{
   205  				{1000000, 2000000},
   206  				{3000000, 4000000},
   207  			},
   208  		},
   209  		{
   210  			name: "keep rule with no matches drops everything",
   211  			profile: &googlev1.Profile{
   212  				StringTable: []string{"", "cpu", "nanoseconds", "wall"},
   213  				SampleType: []*googlev1.ValueType{
   214  					{Type: 1, Unit: 2}, // cpu, nanoseconds
   215  					{Type: 3, Unit: 2}, // wall, nanoseconds
   216  				},
   217  				Sample: []*googlev1.Sample{
   218  					{LocationId: []uint64{1}, Value: []int64{1000, 2000}},
   219  				},
   220  				Location: []*googlev1.Location{
   221  					{Id: 1, MappingId: 1, Address: 0xef},
   222  				},
   223  				Mapping: []*googlev1.Mapping{{Id: 1}},
   224  			},
   225  			rules: []*relabel.Config{
   226  				{
   227  					SourceLabels: []model.LabelName{"__type__"},
   228  					Regex:        relabel.MustNewRegexp("memory"),
   229  					Action:       relabel.Keep,
   230  				},
   231  			},
   232  			expectedTypes:  []string{},
   233  			expectedValues: [][]int64{},
   234  		},
   235  	}
   236  
   237  	for _, tt := range tests {
   238  		t.Run(tt.name, func(t *testing.T) {
   239  			check := func(t testing.TB) {
   240  				assert.Equal(t, len(tt.expectedTypes), len(tt.profile.SampleType), "sample type count mismatch")
   241  				for i, expectedType := range tt.expectedTypes {
   242  					actualType := tt.profile.StringTable[tt.profile.SampleType[i].Type]
   243  					assert.Equal(t, expectedType, actualType, "sample type at index %d", i)
   244  				}
   245  				if tt.expectedValues != nil {
   246  					assert.Equal(t, len(tt.expectedValues), len(tt.profile.Sample), "sample count mismatch")
   247  					for i, sample := range tt.profile.Sample {
   248  						if i < len(tt.expectedValues) {
   249  							assert.Equal(t, tt.expectedValues[i], sample.Value, "sample values at index %d", i)
   250  						}
   251  					}
   252  				}
   253  			}
   254  
   255  			p := validation.ValidatedProfile{Profile: pprof.RawFromProto(tt.profile)}
   256  
   257  			Relabel(p, tt.rules, nil)
   258  
   259  			p.Normalize()
   260  			check(t)
   261  		})
   262  	}
   263  }
   264  
   265  func TestTestdata(t *testing.T) {
   266  	tests := []struct {
   267  		f                      string
   268  		rules                  []*relabel.Config
   269  		series                 phlaremodel.Labels
   270  		expectedSize           int
   271  		expectedNormalizedSize int
   272  	}{
   273  		{
   274  			f: "../../../pkg/pprof/testdata/heap",
   275  			rules: []*relabel.Config{
   276  				{
   277  					SourceLabels: []model.LabelName{"__type__", "service_name"},
   278  					Separator:    ";",
   279  					Regex:        relabel.MustNewRegexp("inuse_space;test_service_name"),
   280  					Action:       relabel.Keep,
   281  				},
   282  			},
   283  			series: []*typesv1.LabelPair{{
   284  				Name:  "service_name",
   285  				Value: "test_service_name",
   286  			}},
   287  			expectedSize:           847138,
   288  			expectedNormalizedSize: 46178,
   289  		},
   290  	}
   291  	for _, td := range tests {
   292  		t.Run(td.f, func(t *testing.T) {
   293  			f, err := pprof.OpenFile(td.f)
   294  			require.NoError(t, err)
   295  			require.Equal(t, td.expectedSize, f.SizeVT())
   296  			Relabel(validation.ValidatedProfile{Profile: f}, td.rules, td.series)
   297  			f.Normalize()
   298  			require.Equal(t, td.expectedNormalizedSize, f.SizeVT())
   299  		})
   300  	}
   301  }