github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/symdb/block_reader_test.go (about) 1 package symdb 2 3 import ( 4 "bytes" 5 "context" 6 "os" 7 "testing" 8 9 "github.com/stretchr/testify/mock" 10 "github.com/stretchr/testify/require" 11 "github.com/thanos-io/objstore" 12 13 pystore "github.com/grafana/pyroscope/pkg/objstore" 14 "github.com/grafana/pyroscope/pkg/objstore/providers/filesystem" 15 "github.com/grafana/pyroscope/pkg/phlaredb/block" 16 schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" 17 ) 18 19 var ( 20 testBlockMeta = &block.Meta{ 21 Files: []block.File{ 22 {RelPath: DefaultFileName}, 23 }, 24 } 25 26 testBlockMetaV1 = &block.Meta{ 27 Files: []block.File{ 28 {RelPath: IndexFileName}, 29 {RelPath: StacktracesFileName}, 30 }, 31 } 32 33 testBlockMetaV2 = &block.Meta{ 34 Files: []block.File{ 35 {RelPath: IndexFileName}, 36 {RelPath: StacktracesFileName}, 37 {RelPath: "locations.parquet"}, 38 {RelPath: "mappings.parquet"}, 39 {RelPath: "functions.parquet"}, 40 {RelPath: "strings.parquet"}, 41 }, 42 } 43 ) 44 45 func Test_write_block_fixture(t *testing.T) { 46 t.Skip() 47 b := newBlockSuite(t, [][]string{ 48 {"testdata/profile.pb.gz"}, 49 {"testdata/profile.pb.gz"}, 50 }) 51 const fixtureDir = "testdata/symbols/v3" 52 require.NoError(t, os.RemoveAll(fixtureDir)) 53 require.NoError(t, os.Rename(b.config.Dir, fixtureDir)) 54 } 55 56 func Test_Reader_Open_v3(t *testing.T) { 57 // The block contains two partitions (0 and 1), each partition 58 // stores symbols of the testdata/profile.pb.gz profile 59 b, err := filesystem.NewBucket("testdata/symbols/v3") 60 require.NoError(t, err) 61 x, err := Open(context.Background(), b, testBlockMeta) 62 require.NoError(t, err) 63 64 r := NewResolver(context.Background(), x) 65 defer r.Release() 66 r.AddSamples(0, schemav1.Samples{ 67 StacktraceIDs: []uint32{1, 2, 3, 4, 5}, 68 Values: []uint64{1, 1, 1, 1, 1}, 69 }) 70 r.AddSamples(1, schemav1.Samples{ 71 StacktraceIDs: []uint32{1, 2, 3, 4, 5}, 72 Values: []uint64{1, 1, 1, 1, 1}, 73 }) 74 75 resolved, err := r.Tree() 76 require.NoError(t, err) 77 expected := `. 78 ├── github.com/pyroscope-io/pyroscope/pkg/agent.(*ProfileSession).takeSnapshots: self 2 total 8 79 │ └── github.com/pyroscope-io/pyroscope/pkg/agent/gospy.(*GoSpy).Snapshot: self 2 total 6 80 │ └── github.com/pyroscope-io/pyroscope/pkg/convert.ParsePprof: self 0 total 4 81 │ └── io/ioutil.ReadAll: self 2 total 4 82 │ └── io.ReadAll: self 2 total 2 83 └── net/http.(*conn).serve: self 2 total 2 84 ` 85 86 require.Equal(t, expected, resolved.String()) 87 } 88 89 func Test_Reader_Open_v3_fuzz(t *testing.T) { 90 // Make sure the test is valid. 91 corpus, err := os.ReadFile("testdata/symbols/v3/symbols.symdb") 92 require.NoError(t, err) 93 ctx := context.Background() 94 95 bucket := pystore.NewBucket(objstore.NewInMemBucket()) 96 require.NoError(t, bucket.Upload(ctx, DefaultFileName, bytes.NewReader(corpus))) 97 b, err := Open(ctx, bucket, testBlockMeta) 98 require.NoError(t, err) 99 100 r := NewResolver(context.Background(), b) 101 defer r.Release() 102 r.AddSamples(0, schemav1.Samples{}) 103 r.AddSamples(1, schemav1.Samples{}) 104 _, err = r.Pprof() 105 require.NoError(t, err) 106 } 107 108 func Fuzz_Reader_Open_v3(f *testing.F) { 109 corpus, err := os.ReadFile("testdata/symbols/v3/symbols.symdb") 110 require.NoError(f, err) 111 ctx := context.Background() 112 113 f.Add(corpus) 114 f.Fuzz(func(t *testing.T, data []byte) { 115 bucket := pystore.NewBucket(objstore.NewInMemBucket()) 116 require.NoError(t, bucket.Upload(ctx, DefaultFileName, bytes.NewReader(data))) 117 118 b, err := Open(context.Background(), bucket, testBlockMeta) 119 if err != nil { 120 return 121 } 122 123 r := NewResolver(context.Background(), b) 124 defer r.Release() 125 r.AddSamples(0, schemav1.Samples{}) 126 r.AddSamples(1, schemav1.Samples{}) 127 _, _ = r.Pprof() 128 }) 129 } 130 131 func Test_Reader_Open_v2(t *testing.T) { 132 // The block contains two partitions (0 and 1), each partition 133 // stores symbols of the testdata/profile.pb.gz profile 134 b, err := filesystem.NewBucket("testdata/symbols/v2") 135 require.NoError(t, err) 136 x, err := Open(context.Background(), b, testBlockMetaV2) 137 require.NoError(t, err) 138 139 r := NewResolver(context.Background(), x) 140 defer r.Release() 141 r.AddSamples(0, schemav1.Samples{ 142 StacktraceIDs: []uint32{1, 2, 3, 4, 5}, 143 Values: []uint64{1, 1, 1, 1, 1}, 144 }) 145 r.AddSamples(1, schemav1.Samples{ 146 StacktraceIDs: []uint32{1, 2, 3, 4, 5}, 147 Values: []uint64{1, 1, 1, 1, 1}, 148 }) 149 150 resolved, err := r.Tree() 151 require.NoError(t, err) 152 expected := `. 153 └── github.com/pyroscope-io/pyroscope/pkg/scrape.(*scrapeLoop).run: self 2 total 10 154 └── github.com/pyroscope-io/pyroscope/pkg/scrape.(*Target).report: self 2 total 8 155 └── github.com/pyroscope-io/pyroscope/pkg/scrape.(*scrapeLoop).scrape: self 2 total 6 156 └── github.com/pyroscope-io/pyroscope/pkg/scrape.(*pprofWriter).writeProfile: self 2 total 4 157 └── google.golang.org/protobuf/proto.Unmarshal: self 2 total 2 158 ` 159 160 require.Equal(t, expected, resolved.String()) 161 } 162 163 func Test_Reader_Open_v1(t *testing.T) { 164 b, err := filesystem.NewBucket("testdata/symbols/v1") 165 require.NoError(t, err) 166 x, err := Open(context.Background(), b, testBlockMetaV1) 167 require.NoError(t, err) 168 r, err := x.partition(context.Background(), 1) 169 require.NoError(t, err) 170 171 dst := new(mockStacktraceInserter) 172 dst.On("InsertStacktrace", uint32(2), []int32{2, 1}) 173 dst.On("InsertStacktrace", uint32(3), []int32{3, 2, 1}) 174 dst.On("InsertStacktrace", uint32(11), []int32{4, 3, 2, 1}) 175 dst.On("InsertStacktrace", uint32(16), []int32{3, 1}) 176 dst.On("InsertStacktrace", uint32(18), []int32{5, 2, 1}) 177 178 err = r.ResolveStacktraceLocations(context.Background(), dst, []uint32{3, 2, 11, 16, 18}) 179 require.NoError(t, err) 180 } 181 182 func Fuzz_ReadIndexFile_v12(f *testing.F) { 183 files := []string{ 184 "testdata/symbols/v2/index.symdb", 185 "testdata/symbols/v1/index.symdb", 186 } 187 for _, path := range files { 188 data, err := os.ReadFile(path) 189 require.NoError(f, err) 190 f.Add(data) 191 } 192 f.Fuzz(func(_ *testing.T, b []byte) { 193 _, _ = OpenIndex(b) 194 }) 195 } 196 197 type mockStacktraceInserter struct{ mock.Mock } 198 199 func (m *mockStacktraceInserter) InsertStacktrace(stacktraceID uint32, locations []int32) { 200 m.Called(stacktraceID, locations) 201 } 202 203 func Benchmark_Reader_ResolvePprof(b *testing.B) { 204 ctx := context.Background() 205 s := memSuite{t: b, files: [][]string{{"testdata/big-profile.pb.gz"}}} 206 s.config = DefaultConfig().WithDirectory(b.TempDir()) 207 s.init() 208 bs := blockSuite{memSuite: &s} 209 bs.flush() 210 211 b.ReportAllocs() 212 b.ResetTimer() 213 for i := 0; i < b.N; i++ { 214 r := NewResolver(ctx, bs.reader) 215 r.AddSamples(0, schemav1.Samples{}) 216 _, err := r.Pprof() 217 require.NoError(b, err) 218 r.Release() 219 } 220 221 b.ReportMetric(float64(bs.getRangeCount.Load())/float64(b.N), "get_range_calls/op") 222 b.ReportMetric(float64(bs.getRangeSize.Load())/float64(b.N), "get_range_bytes/op") 223 }