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  }