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 }