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  }