github.com/grafana/pyroscope@v1.18.0/pkg/og/convert/pprof/profile_test.go (about)

     1  package pprof
     2  
     3  import (
     4  	"testing"
     5  
     6  	profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
     7  	"github.com/grafana/pyroscope/api/model/labelset"
     8  	phlaremodel "github.com/grafana/pyroscope/pkg/model"
     9  	"github.com/grafana/pyroscope/pkg/og/convert/pprof/bench"
    10  	"github.com/grafana/pyroscope/pkg/og/ingestion"
    11  	"github.com/grafana/pyroscope/pkg/og/storage/tree"
    12  	"github.com/grafana/pyroscope/pkg/pprof"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestEmptyPPROF(t *testing.T) {
    19  
    20  	p := RawProfile{
    21  		FormDataContentType: "multipart/form-data; boundary=ae798a53dec9077a712cf18e2ebf54842f5c792cfed6a31b6f469cfd2684",
    22  		RawData: []byte("--ae798a53dec9077a712cf18e2ebf54842f5c792cfed6a31b6f469cfd2684\r\n" +
    23  			"Content-Disposition: form-data; name=\"profile\"; filename=\"profile.pprof\"\r\n" +
    24  			"Content-Type: application/octet-stream\r\n" +
    25  			"\r\n" +
    26  			"\r\n" +
    27  			"--ae798a53dec9077a712cf18e2ebf54842f5c792cfed6a31b6f469cfd2684--\r\n"),
    28  	}
    29  	profile, err := p.ParseToPprof(nil, ingestion.Metadata{}, nil)
    30  	require.NoError(t, err)
    31  	require.Equal(t, 0, len(profile.Series))
    32  }
    33  
    34  func TestFixFunctionNamesForScriptingLanguages(t *testing.T) {
    35  	profile := pprof.RawFromProto(&profilev1.Profile{
    36  		StringTable: []string{"", "main", "func1", "func2", "qwe.py"},
    37  		Function: []*profilev1.Function{
    38  			{Id: 1, Name: 1, Filename: 4, SystemName: 1, StartLine: 239},
    39  			{Id: 2, Name: 2, Filename: 4, SystemName: 2, StartLine: 42},
    40  			{Id: 3, Name: 3, Filename: 4, SystemName: 3, StartLine: 7},
    41  		},
    42  		Location: []*profilev1.Location{
    43  			{Id: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 242}}},
    44  			{Id: 2, Line: []*profilev1.Line{{FunctionId: 2, Line: 50}}},
    45  			{Id: 3, Line: []*profilev1.Line{{FunctionId: 3, Line: 8}}},
    46  		},
    47  		Sample: []*profilev1.Sample{
    48  			{LocationId: []uint64{2, 1}, Value: []int64{10, 1000}},
    49  			{LocationId: []uint64{3, 1}, Value: []int64{13, 1300}},
    50  		},
    51  	})
    52  	functionNameFromLocation := func(locID uint64) string {
    53  		for _, loc := range profile.Location {
    54  			if loc.Id == locID {
    55  				for _, fun := range profile.Function {
    56  					if fun.Id == loc.Line[0].FunctionId {
    57  						return profile.StringTable[fun.Name]
    58  					}
    59  				}
    60  			}
    61  		}
    62  		return ""
    63  	}
    64  
    65  	md := ingestion.Metadata{
    66  		SpyName: "scripting",
    67  	}
    68  
    69  	FixFunctionNamesForScriptingLanguages(profile, md)
    70  
    71  	assert.Len(t, profile.Function, 6) // we do not remove unreferenced functions for now, let the distributor do it
    72  	assert.Len(t, profile.Location, 3)
    73  	assert.Len(t, profile.Sample, 2)
    74  
    75  	collapsed := bench.StackCollapseProto(profile.Profile, 0, 1)
    76  	expected := []string{
    77  		"qwe.py main;qwe.py func1 10",
    78  		"qwe.py main;qwe.py func2 13",
    79  	}
    80  	assert.Equal(t, expected, collapsed)
    81  
    82  	assert.Equal(t, "qwe.py main", functionNameFromLocation(profile.Location[0].Id))
    83  	assert.Equal(t, "qwe.py func1", functionNameFromLocation(profile.Location[1].Id))
    84  	assert.Equal(t, "qwe.py func2", functionNameFromLocation(profile.Location[2].Id))
    85  }
    86  
    87  func TestFixFunctionNamesForPyspyRelativePaths(t *testing.T) {
    88  	// pyspy with relative paths should have function names rewritten
    89  	profile := pprof.RawFromProto(&profilev1.Profile{
    90  		StringTable: []string{"", "main", "func1", "qwe.py"},
    91  		Function: []*profilev1.Function{
    92  			{Id: 1, Name: 1, Filename: 3, SystemName: 1, StartLine: 1},
    93  			{Id: 2, Name: 2, Filename: 3, SystemName: 2, StartLine: 10},
    94  		},
    95  		Location: []*profilev1.Location{
    96  			{Id: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}}},
    97  			{Id: 2, Line: []*profilev1.Line{{FunctionId: 2, Line: 10}}},
    98  		},
    99  		Sample: []*profilev1.Sample{
   100  			{LocationId: []uint64{2, 1}, Value: []int64{10, 1000}},
   101  		},
   102  	})
   103  
   104  	md := ingestion.Metadata{SpyName: "pyspy"}
   105  	FixFunctionNamesForScriptingLanguages(profile, md)
   106  
   107  	collapsed := bench.StackCollapseProto(profile.Profile, 0, 1)
   108  	expected := []string{"qwe.py main;qwe.py func1 10"}
   109  	assert.Equal(t, expected, collapsed)
   110  }
   111  
   112  func TestFixFunctionNamesForPyspyAbsolutePaths(t *testing.T) {
   113  	// pyspy with absolute paths should NOT have function names rewritten
   114  	profile := pprof.RawFromProto(&profilev1.Profile{
   115  		StringTable: []string{"", "main", "func1", "/home/user/project/qwe.py"},
   116  		Function: []*profilev1.Function{
   117  			{Id: 1, Name: 1, Filename: 3, SystemName: 1, StartLine: 1},
   118  			{Id: 2, Name: 2, Filename: 3, SystemName: 2, StartLine: 10},
   119  		},
   120  		Location: []*profilev1.Location{
   121  			{Id: 1, Line: []*profilev1.Line{{FunctionId: 1, Line: 1}}},
   122  			{Id: 2, Line: []*profilev1.Line{{FunctionId: 2, Line: 10}}},
   123  		},
   124  		Sample: []*profilev1.Sample{
   125  			{LocationId: []uint64{2, 1}, Value: []int64{10, 1000}},
   126  		},
   127  	})
   128  
   129  	md := ingestion.Metadata{SpyName: "pyspy"}
   130  	FixFunctionNamesForScriptingLanguages(profile, md)
   131  
   132  	collapsed := bench.StackCollapseProto(profile.Profile, 0, 1)
   133  	// Function names should remain unchanged (not prefixed with filename)
   134  	expected := []string{"main;func1 10"}
   135  	assert.Equal(t, expected, collapsed)
   136  }
   137  
   138  func TestCreateLabels(t *testing.T) {
   139  	testCases := []struct {
   140  		name                string
   141  		labelMap            map[string]string
   142  		expectedServiceName string
   143  	}{
   144  		{
   145  			name: "with existing service_name",
   146  			labelMap: map[string]string{
   147  				"service_name": "existing-service",
   148  				"region":       "us-west",
   149  			},
   150  			expectedServiceName: "existing-service",
   151  		},
   152  		{
   153  			name: "without service_name uses __name__ value",
   154  			labelMap: map[string]string{
   155  				"region":   "us-west",
   156  				"__name__": "test-service",
   157  			},
   158  			expectedServiceName: "test-service",
   159  		},
   160  	}
   161  
   162  	for _, tc := range testCases {
   163  		t.Run(tc.name, func(t *testing.T) {
   164  			p := RawProfile{
   165  				SampleTypeConfig: map[string]*tree.SampleTypeConfig{
   166  					"samples": {
   167  						DisplayName: "samples",
   168  						Units:       "count",
   169  					},
   170  				},
   171  			}
   172  
   173  			// Create a proper pprof.Profile with sample types
   174  			profile := &pprof.Profile{
   175  				Profile: &profilev1.Profile{
   176  					SampleType: []*profilev1.ValueType{
   177  						{
   178  							Type: 1,
   179  							Unit: 2,
   180  						},
   181  					},
   182  					StringTable: []string{"", "samples", "count"},
   183  				},
   184  			}
   185  
   186  			// Create metadata with LabelSet
   187  			md := ingestion.Metadata{
   188  				LabelSet: labelset.New(tc.labelMap),
   189  				SpyName:  "test-spy",
   190  			}
   191  
   192  			// Call createLabels
   193  			labels := p.createLabels(profile, md)
   194  
   195  			// Convert labels to a map for easier checking
   196  			labelMap := make(map[string]string)
   197  			for _, label := range labels {
   198  				labelMap[label.Name] = label.Value
   199  			}
   200  
   201  			// Check that service_name has the expected value
   202  			assert.Equal(t, tc.expectedServiceName, labelMap["service_name"], "service_name should have the expected value")
   203  
   204  			// Check that required labels are present
   205  			assert.Contains(t, labelMap, "__name__", "Should contain __name__ label")
   206  			assert.Contains(t, labelMap, phlaremodel.LabelNameDelta, "Should contain delta label")
   207  			assert.Contains(t, labelMap, phlaremodel.LabelNamePyroscopeSpy, "Should contain spy label")
   208  		})
   209  	}
   210  }