github.com/grafana/pyroscope@v1.18.0/pkg/test/integration/symbolization_test.go (about) 1 package integration 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "path/filepath" 8 "runtime" 9 "testing" 10 "time" 11 12 "connectrpc.com/connect" 13 "github.com/google/pprof/profile" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 17 pushv1 "github.com/grafana/pyroscope/api/gen/proto/go/push/v1" 18 querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" 19 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 20 "github.com/grafana/pyroscope/pkg/pprof" 21 "github.com/grafana/pyroscope/pkg/tenant" 22 "github.com/grafana/pyroscope/pkg/test/integration/cluster" 23 ) 24 25 const testBuildID = "2fa2055ef20fabc972d5751147e093275514b142" 26 27 func TestMicroServicesIntegrationV2Symbolization(t *testing.T) { 28 debuginfodServer, err := NewTestDebuginfodServer() 29 require.NoError(t, err) 30 31 _, currentFile, _, _ := runtime.Caller(0) 32 testDataDir := filepath.Join(filepath.Dir(currentFile), "..", "..", "symbolizer", "testdata") 33 debugFilePath := filepath.Join(testDataDir, "symbols.debug") 34 35 debuginfodServer.AddDebugFile(testBuildID, debugFilePath) 36 37 require.NoError(t, debuginfodServer.Start()) 38 defer func() { 39 _ = debuginfodServer.Stop() 40 }() 41 42 c := cluster.NewMicroServiceCluster( 43 cluster.WithV2(), 44 cluster.WithSymbolizer(debuginfodServer.URL()), 45 ) 46 47 ctx := context.Background() 48 49 require.NoError(t, c.Prepare(ctx)) 50 for _, comp := range c.Components { 51 t.Log(comp.String()) 52 } 53 54 require.NoError(t, c.Start(ctx)) 55 t.Log("Cluster ready") 56 defer func() { 57 waitStopped := c.Stop() 58 require.NoError(t, waitStopped(ctx)) 59 }() 60 61 t.Run("SymbolizationFlow", func(t *testing.T) { 62 testSymbolizationFlow(t, ctx, c) 63 }) 64 } 65 66 func testSymbolizationFlow(t *testing.T, ctx context.Context, c *cluster.Cluster) { 67 tests := []struct { 68 name string 69 profile func(now time.Time) *profile.Profile 70 expected string 71 skip bool 72 }{ 73 { 74 name: "fully unsymbolized", 75 profile: func(now time.Time) *profile.Profile { 76 p := &profile.Profile{ 77 DurationNanos: int64(10 * time.Second), 78 Period: 1000000000, 79 SampleType: []*profile.ValueType{ 80 {Type: "cpu", Unit: "nanoseconds"}, 81 }, 82 PeriodType: &profile.ValueType{ 83 Type: "cpu", 84 Unit: "nanoseconds", 85 }, 86 } 87 88 m := &profile.Mapping{ 89 ID: 1, 90 Start: 0, 91 Limit: 0x1000000, 92 Offset: 0, 93 File: "libfoo.so", 94 BuildID: testBuildID, 95 HasFunctions: false, 96 } 97 p.Mapping = []*profile.Mapping{m} 98 99 loc1 := &profile.Location{ 100 ID: 1, 101 Mapping: m, 102 Address: 0x1500, 103 } 104 loc2 := &profile.Location{ 105 ID: 2, 106 Mapping: m, 107 Address: 0x3c5a, 108 } 109 p.Location = []*profile.Location{loc1, loc2} 110 111 p.Sample = []*profile.Sample{ 112 { 113 Location: []*profile.Location{loc1}, 114 Value: []int64{100}, 115 }, 116 { 117 Location: []*profile.Location{loc2}, 118 Value: []int64{200}, 119 }, 120 { 121 Location: []*profile.Location{loc1, loc2}, 122 Value: []int64{3}, 123 }, 124 } 125 126 return p 127 }, 128 expected: `PeriodType: cpu nanoseconds 129 Period: 1000000000 130 Samples: 131 cpu/nanoseconds[dflt] 132 200: 2 133 3: 1 2 134 100: 1 135 Locations 136 1: 0x1500 M=1 main :0:0 s=0() 137 2: 0x3c5a M=1 atoll_b :0:0 s=0() 138 Mappings 139 1: 0x0/0x1000000/0x0 libfoo.so 2fa2055ef20fabc972d5751147e093275514b142 [FN] 140 `, 141 }, 142 { 143 name: "partially symbolized", 144 profile: func(now time.Time) *profile.Profile { 145 p := &profile.Profile{ 146 DurationNanos: int64(10 * time.Second), 147 Period: 1000000000, 148 SampleType: []*profile.ValueType{ 149 {Type: "cpu", Unit: "nanoseconds"}, 150 }, 151 PeriodType: &profile.ValueType{ 152 Type: "cpu", 153 Unit: "nanoseconds", 154 }, 155 } 156 157 m := &profile.Mapping{ 158 ID: 1, 159 Start: 0, 160 Limit: 0x1000000, 161 Offset: 0, 162 File: "libfoo.so", 163 BuildID: testBuildID, 164 HasFunctions: true, 165 } 166 p.Mapping = []*profile.Mapping{m} 167 f1 := &profile.Function{ 168 ID: 1, 169 Name: "symbolized_func", 170 Filename: "src.c", 171 } 172 loc1 := &profile.Location{ 173 ID: 1, 174 Mapping: m, 175 Address: 0x1500, 176 Line: []profile.Line{{Function: f1, Line: 239}}, 177 } 178 loc2 := &profile.Location{ 179 ID: 2, 180 Mapping: m, 181 Address: 0x3c5a, 182 } 183 p.Location = []*profile.Location{loc1, loc2} 184 185 p.Sample = []*profile.Sample{ 186 { 187 Location: []*profile.Location{loc1}, 188 Value: []int64{100}, 189 }, 190 { 191 Location: []*profile.Location{loc2}, 192 Value: []int64{200}, 193 }, 194 { 195 Location: []*profile.Location{loc1, loc2}, 196 Value: []int64{3}, 197 }, 198 } 199 p.Function = []*profile.Function{ 200 f1, 201 } 202 203 return p 204 }, 205 expected: `PeriodType: cpu nanoseconds 206 Period: 1000000000 207 Samples: 208 cpu/nanoseconds[dflt] 209 200: 2 210 3: 1 2 211 100: 1 212 Locations 213 1: 0x0 M=1 symbolized_func src.c:239:0 s=0() 214 2: 0x0 M=1 atoll_b :0:0 s=0() 215 Mappings 216 1: 0x0/0x0/0x0 libfoo.so 2fa2055ef20fabc972d5751147e093275514b142 [FN] 217 `, 218 skip: true, // TODO fix the testdata or symbolization 219 }, 220 } 221 pusher := c.PushClient() 222 querier := c.QueryClient() 223 224 now := time.Now().Truncate(time.Second) 225 tenantID := "test-tenant" 226 227 for _, test := range tests { 228 t.Run(test.name, func(t *testing.T) { 229 if test.skip { 230 t.Skip() 231 } 232 serviceName := "test-symbolization-service-" + test.name 233 src := test.profile(now) 234 235 var buf bytes.Buffer 236 err := src.Write(&buf) 237 require.NoError(t, err) 238 rawProfile := buf.Bytes() 239 240 ctx = tenant.InjectTenantID(ctx, tenantID) 241 _, err = pusher.Push(ctx, connect.NewRequest(&pushv1.PushRequest{ 242 Series: []*pushv1.RawProfileSeries{{ 243 Labels: []*typesv1.LabelPair{ 244 {Name: "service_name", Value: serviceName}, 245 {Name: "__name__", Value: "process_cpu"}, 246 }, 247 Samples: []*pushv1.RawSample{{RawProfile: rawProfile}}, 248 }}, 249 })) 250 require.NoError(t, err) 251 252 q := connect.NewRequest(&querierv1.SelectMergeProfileRequest{ 253 ProfileTypeID: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", 254 Start: now.Add(-time.Hour).UnixMilli(), 255 End: now.Add(time.Hour).UnixMilli(), 256 LabelSelector: `{service_name="` + serviceName + `"}`, 257 }) 258 require.Eventually(t, func() bool { 259 resp, err := querier.SelectMergeProfile(ctx, q) 260 if err != nil { 261 t.Logf("Error querying profile: %v", err) 262 return false 263 } 264 rp := pprof.RawFromProto(resp.Msg) 265 rp.TimeNanos = 0 266 actual := rp.DebugString() 267 268 fmt.Println(actual) 269 270 if len(resp.Msg.Sample) == 0 { 271 return false 272 } 273 274 if actual != test.expected { 275 assert.Equal(t, test.expected, actual) 276 //fmt.Println(src.String()) 277 return false 278 } 279 return true 280 }, 5*time.Second, 100*time.Millisecond) 281 }) 282 } 283 284 }