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 }