github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/symdb/partition_memory_test.go (about)

     1  package symdb
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
    12  	schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1"
    13  	"github.com/grafana/pyroscope/pkg/pprof"
    14  )
    15  
    16  func Test_Stacktrace_append_empty(t *testing.T) {
    17  	db := NewSymDB(new(Config))
    18  	w := db.PartitionWriter(0)
    19  
    20  	sids := make([]uint32, 2)
    21  	w.AppendStacktraces(sids, nil)
    22  	assert.Equal(t, []uint32{0, 0}, sids)
    23  
    24  	w.AppendStacktraces(sids, []*schemav1.Stacktrace{})
    25  	assert.Equal(t, []uint32{0, 0}, sids)
    26  
    27  	w.AppendStacktraces(sids, []*schemav1.Stacktrace{{}})
    28  	assert.Equal(t, []uint32{0, 0}, sids)
    29  }
    30  
    31  func Test_Stacktrace_append_existing(t *testing.T) {
    32  	db := NewSymDB(new(Config))
    33  	w := db.PartitionWriter(0)
    34  	sids := make([]uint32, 2)
    35  	w.AppendStacktraces(sids, []*schemav1.Stacktrace{
    36  		{LocationIDs: []uint64{5, 4, 3, 2, 1}},
    37  		{LocationIDs: []uint64{5, 4, 3, 2, 1}},
    38  	})
    39  	assert.Equal(t, []uint32{5, 5}, sids)
    40  
    41  	w.AppendStacktraces(sids, []*schemav1.Stacktrace{
    42  		{LocationIDs: []uint64{5, 4, 3, 2, 1}},
    43  		{LocationIDs: []uint64{6, 5, 4, 3, 2, 1}},
    44  	})
    45  	assert.Equal(t, []uint32{5, 6}, sids)
    46  }
    47  
    48  func Test_Stacktraces_memory_resolve_pprof(t *testing.T) {
    49  	p, err := pprof.OpenFile("testdata/profile.pb.gz")
    50  	require.NoError(t, err)
    51  	stacktraces := pprofSampleToStacktrace(p.Sample)
    52  	sids := make([]uint32, len(stacktraces))
    53  
    54  	db := NewSymDB(new(Config))
    55  	w := db.PartitionWriter(0)
    56  	w.AppendStacktraces(sids, stacktraces)
    57  
    58  	r, ok := db.lookupPartition(0)
    59  	require.True(t, ok)
    60  
    61  	si := newStacktracesMapInserter()
    62  	err = r.ResolveStacktraceLocations(context.Background(), si, sids)
    63  	require.NoError(t, err)
    64  
    65  	si.assertValid(t, sids, stacktraces)
    66  }
    67  
    68  func Test_Stacktraces_memory_resolve_chunked(t *testing.T) {
    69  	p, err := pprof.OpenFile("testdata/profile.pb.gz")
    70  	require.NoError(t, err)
    71  	stacktraces := pprofSampleToStacktrace(p.Sample)
    72  	sids := make([]uint32, len(stacktraces))
    73  
    74  	db := NewSymDB(new(Config))
    75  	w := db.PartitionWriter(0)
    76  	w.AppendStacktraces(sids, stacktraces)
    77  
    78  	r, ok := db.lookupPartition(0)
    79  	require.True(t, ok)
    80  
    81  	// ResolveStacktraceLocations modifies sids in-place,
    82  	// if stacktraces are chunked.
    83  	sidsCopy := make([]uint32, len(sids))
    84  	copy(sidsCopy, sids)
    85  
    86  	si := newStacktracesMapInserter()
    87  	err = r.ResolveStacktraceLocations(context.Background(), si, sids)
    88  	require.NoError(t, err)
    89  
    90  	si.assertValid(t, sidsCopy, stacktraces)
    91  }
    92  
    93  // Test_Stacktraces_memory_resolve_concurrency validates if concurrent
    94  // append and resolve do not cause race conditions.
    95  func Test_Stacktraces_memory_resolve_concurrency(t *testing.T) {
    96  	p, err := pprof.OpenFile("testdata/profile.pb.gz")
    97  	require.NoError(t, err)
    98  	stacktraces := pprofSampleToStacktrace(p.Sample)
    99  
   100  	// Allocate stacktrace IDs.
   101  	sids := make([]uint32, len(stacktraces))
   102  	db := NewSymDB(new(Config))
   103  	w := db.PartitionWriter(0)
   104  	w.AppendStacktraces(sids, stacktraces)
   105  
   106  	const (
   107  		iterations = 10
   108  		resolvers  = 100
   109  		appenders  = 5
   110  		appends    = 100
   111  	)
   112  
   113  	runTest := func(t *testing.T) {
   114  		t.Helper()
   115  		db := NewSymDB(new(Config))
   116  
   117  		var wg sync.WaitGroup
   118  		wg.Add(appenders)
   119  		for i := 0; i < appenders; i++ {
   120  			go func() {
   121  				defer wg.Done()
   122  				w := db.PartitionWriter(0)
   123  				for j := 0; j < appends; j++ {
   124  					w.AppendStacktraces(make([]uint32, len(stacktraces)), stacktraces)
   125  				}
   126  			}()
   127  		}
   128  
   129  		wg.Add(resolvers)
   130  		for i := 0; i < resolvers; i++ {
   131  			go func() {
   132  				defer wg.Done()
   133  
   134  				r, ok := db.lookupPartition(0)
   135  				if !ok {
   136  					return
   137  				}
   138  
   139  				// ResolveStacktraceLocations modifies sids in-place,
   140  				// if stacktraces are chunked.
   141  				sidsCopy := make([]uint32, len(sids))
   142  				copy(sidsCopy, sids)
   143  
   144  				// It's expected that only fraction of stack traces may not
   145  				// be appended by the time of querying, therefore validation
   146  				// of the result is omitted (covered separately).
   147  				si := newStacktracesMapInserter()
   148  				_ = r.ResolveStacktraceLocations(context.Background(), si, sidsCopy)
   149  			}()
   150  		}
   151  
   152  		wg.Wait()
   153  	}
   154  
   155  	for i := 0; i < iterations; i++ {
   156  		runTest(t)
   157  	}
   158  }
   159  
   160  type stacktracesMapInserter struct {
   161  	m map[uint32][]int32 // Stacktrace ID => resolved locations
   162  
   163  	unresolved int
   164  }
   165  
   166  func newStacktracesMapInserter() *stacktracesMapInserter {
   167  	return &stacktracesMapInserter{m: make(map[uint32][]int32)}
   168  }
   169  
   170  func (m *stacktracesMapInserter) InsertStacktrace(sid uint32, locations []int32) {
   171  	if len(locations) == 0 {
   172  		m.unresolved++
   173  		return
   174  	}
   175  	s := make([]int32, len(locations)) // InsertStacktrace must not retain input locations.
   176  	copy(s, locations)
   177  	m.m[sid] = s
   178  }
   179  
   180  func (m *stacktracesMapInserter) assertValid(t *testing.T, sids []uint32, stacktraces []*schemav1.Stacktrace) {
   181  	assert.LessOrEqual(t, len(m.m), len(sids))
   182  	require.Equal(t, len(sids), len(stacktraces))
   183  	require.Zero(t, m.unresolved)
   184  	for s, sid := range sids {
   185  		locations := stacktraces[s].LocationIDs
   186  		resolved := m.m[sid]
   187  		require.Equal(t, len(locations), len(resolved))
   188  		for i := range locations {
   189  			require.Equal(t, int32(locations[i]), resolved[i])
   190  		}
   191  	}
   192  }
   193  
   194  func pprofSampleToStacktrace(samples []*googlev1.Sample) []*schemav1.Stacktrace {
   195  	s := make([]*schemav1.Stacktrace, len(samples))
   196  	for i := range samples {
   197  		s[i] = &schemav1.Stacktrace{LocationIDs: samples[i].LocationId}
   198  	}
   199  	return s
   200  }