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

     1  package symdb
     2  
     3  import (
     4  	"bytes"
     5  	"math/rand"
     6  	"strconv"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/grafana/pyroscope/pkg/pprof"
    13  )
    14  
    15  func Test_stacktrace_tree_encoding(t *testing.T) {
    16  	stacks := [][]uint64{
    17  		{5, 4, 3, 2, 1},
    18  		{6, 4, 3, 2, 1},
    19  		{4, 3, 2, 1},
    20  		{3, 2, 1},
    21  		{4, 2, 1},
    22  		{7, 2, 1},
    23  		{2, 1},
    24  		{1},
    25  	}
    26  
    27  	x := newStacktraceTree(10)
    28  	var b bytes.Buffer
    29  
    30  	for i := range stacks {
    31  		x.insert(stacks[i])
    32  
    33  		b.Reset()
    34  		_, err := x.WriteTo(&b)
    35  		require.NoError(t, err)
    36  
    37  		ppt := newParentPointerTree(x.len())
    38  		_, err = ppt.ReadFrom(bytes.NewBuffer(b.Bytes()))
    39  		require.NoError(t, err)
    40  
    41  		for j := range x.nodes {
    42  			n, p := x.nodes[j], ppt.nodes[j]
    43  			if n.p != p.p || n.r != p.r {
    44  				t.Fatalf("tree mismatch on %v: n:%#v, p:%#v", stacks[i], n, p)
    45  			}
    46  		}
    47  	}
    48  }
    49  
    50  func Test_stacktrace_tree_encoding_group(t *testing.T) {
    51  	stacks := [][]uint64{
    52  		{5, 4, 3, 2, 1},
    53  		{6, 4, 3, 2, 1},
    54  		{4, 3, 2, 1},
    55  		{3, 2, 1},
    56  		{4, 2, 1},
    57  		{7, 2, 1},
    58  		{2, 1},
    59  		{1},
    60  	}
    61  
    62  	x := newStacktraceTree(10)
    63  	var b bytes.Buffer
    64  
    65  	for i := range stacks {
    66  		x.insert(stacks[i])
    67  
    68  		b.Reset()
    69  		e := treeEncoder{writeSize: 30}
    70  		err := e.marshal(x, &b)
    71  		require.NoError(t, err)
    72  
    73  		ppt := newParentPointerTree(x.len())
    74  		d := treeDecoder{
    75  			bufSize:     64,
    76  			peekSize:    20,
    77  			groupBuffer: 12,
    78  		}
    79  		err = d.unmarshal(ppt, bytes.NewBuffer(b.Bytes()))
    80  		require.NoError(t, err)
    81  
    82  		for j := range x.nodes {
    83  			n, p := x.nodes[j], ppt.nodes[j]
    84  			if n.p != p.p || n.r != p.r {
    85  				t.Fatalf("tree mismatch on %v: n:%#v, p:%#v", stacks[i], n, p)
    86  			}
    87  		}
    88  	}
    89  }
    90  
    91  func Test_stacktrace_tree_encoding_rand(t *testing.T) {
    92  	nodes := make([]node, 1<<20)
    93  	for i := range nodes {
    94  		nodes[i] = node{
    95  			fc: 2,
    96  			ns: 3,
    97  			p:  int32(rand.Intn(10 << 10)),
    98  			r:  int32(rand.Intn(10 << 10)),
    99  		}
   100  	}
   101  
   102  	x := &stacktraceTree{nodes: nodes}
   103  	var b bytes.Buffer
   104  	_, err := x.WriteTo(&b)
   105  	require.NoError(t, err)
   106  
   107  	ppt := newParentPointerTree(x.len())
   108  	_, err = ppt.ReadFrom(bytes.NewBuffer(b.Bytes()))
   109  	require.NoError(t, err)
   110  
   111  	for j := range x.nodes {
   112  		n, p := x.nodes[j], ppt.nodes[j]
   113  		if n.p != p.p || n.r != p.r {
   114  			t.Fatalf("tree mismatch at %d: n:%#v. p:%#v", j, n, p)
   115  		}
   116  	}
   117  }
   118  
   119  func Test_stacktrace_tree_pprof_locations_(t *testing.T) {
   120  	x := newStacktraceTree(0)
   121  	assert.Len(t, x.resolve([]int32{0, 1, 2, 3}, 42), 0)
   122  	assert.Len(t, x.resolveUint64([]uint64{0, 1, 2, 3}, 42), 0)
   123  
   124  	p := newParentPointerTree(0)
   125  	assert.Len(t, p.resolve([]int32{0, 1, 2, 3}, 42), 0)
   126  	assert.Len(t, p.resolveUint64([]uint64{0, 1, 2, 3}, 42), 0)
   127  }
   128  
   129  func Test_stacktrace_tree_pprof_locations(t *testing.T) {
   130  	p, err := pprof.OpenFile("testdata/profile.pb.gz")
   131  	require.NoError(t, err)
   132  
   133  	x := newStacktraceTree(defaultStacktraceTreeSize)
   134  	m := make(map[uint32]int)
   135  	for i := range p.Sample {
   136  		m[x.insert(p.Sample[i].LocationId)] = i
   137  	}
   138  
   139  	tmp := stacktraceLocations.get()
   140  	defer stacktraceLocations.put(tmp)
   141  	for sid, i := range m {
   142  		tmp = x.resolve(tmp, sid)
   143  		locs := p.Sample[i].LocationId
   144  		for j := range locs {
   145  			if tmp[j] != int32(locs[j]) {
   146  				t.Log("resolved:", tmp)
   147  				t.Log("locations:", locs)
   148  				t.Fatalf("ST: tmp[j] != locs[j]")
   149  			}
   150  		}
   151  	}
   152  
   153  	var b bytes.Buffer
   154  	n, err := x.WriteTo(&b)
   155  	require.NoError(t, err)
   156  	assert.Equal(t, b.Len(), int(n))
   157  
   158  	ppt := newParentPointerTree(x.len())
   159  	n, err = ppt.ReadFrom(bytes.NewReader(b.Bytes()))
   160  	require.NoError(t, err)
   161  	assert.Equal(t, b.Len(), int(n))
   162  
   163  	tmp = stacktraceLocations.get()
   164  	defer stacktraceLocations.put(tmp)
   165  	for sid, i := range m {
   166  		tmp = ppt.resolve(tmp, sid)
   167  		locs := p.Sample[i].LocationId
   168  		for j := range locs {
   169  			if tmp[j] != int32(locs[j]) {
   170  				t.Log("resolved:", tmp)
   171  				t.Log("locations:", locs)
   172  				t.Fatalf("PPT: tmp[j] != locs[j]")
   173  			}
   174  		}
   175  	}
   176  }
   177  
   178  // The test is helpful for debugging.
   179  func Test_parentPointerTree_toStacktraceTree(t *testing.T) {
   180  	x := newStacktraceTree(10)
   181  	for _, stack := range [][]uint64{
   182  		{5, 4, 3, 2, 1},
   183  		{6, 4, 3, 2, 1},
   184  		{4, 3, 2, 1},
   185  		{3, 2, 1},
   186  		{4, 2, 1},
   187  		{7, 2, 1},
   188  		{2, 1},
   189  		{1},
   190  	} {
   191  		x.insert(stack)
   192  	}
   193  	assertRestoredStacktraceTree(t, x)
   194  }
   195  
   196  func Test_parentPointerTree_toStacktraceTree_profile(t *testing.T) {
   197  	p, err := pprof.OpenFile("testdata/profile.pb.gz")
   198  	require.NoError(t, err)
   199  	x := newStacktraceTree(defaultStacktraceTreeSize)
   200  	for _, s := range p.Sample {
   201  		x.insert(s.LocationId)
   202  	}
   203  	assertRestoredStacktraceTree(t, x)
   204  }
   205  
   206  func assertRestoredStacktraceTree(t *testing.T, x *stacktraceTree) {
   207  	var b bytes.Buffer
   208  	_, _ = x.WriteTo(&b)
   209  	ppt := newParentPointerTree(x.len())
   210  	_, err := ppt.ReadFrom(bytes.NewBuffer(b.Bytes()))
   211  	require.NoError(t, err)
   212  	restored := ppt.toStacktraceTree()
   213  	assert.Equal(t, x.nodes, restored.nodes)
   214  }
   215  
   216  func Benchmark_stacktrace_tree_insert(b *testing.B) {
   217  	p, err := pprof.OpenFile("testdata/profile.pb.gz")
   218  	require.NoError(b, err)
   219  
   220  	b.ResetTimer()
   221  	b.ReportAllocs()
   222  
   223  	for i := 0; i < b.N; i++ {
   224  		x := newStacktraceTree(defaultStacktraceTreeSize)
   225  		for j := range p.Sample {
   226  			x.insert(p.Sample[j].LocationId)
   227  		}
   228  	}
   229  }
   230  
   231  func Benchmark_stacktrace_tree_insert_default_sizes(b *testing.B) {
   232  	p, err := pprof.OpenFile("testdata/profile.pb.gz")
   233  	require.NoError(b, err)
   234  
   235  	b.ResetTimer()
   236  
   237  	for _, size := range []int{0, 10, 1024, 2048, 4096, 8192} {
   238  		b.Run("size="+strconv.Itoa(size), func(b *testing.B) {
   239  			b.ReportAllocs()
   240  
   241  			for i := 0; i < b.N; i++ {
   242  				x := newStacktraceTree(size)
   243  				for j := range p.Sample {
   244  					x.insert(p.Sample[j].LocationId)
   245  				}
   246  
   247  				if testing.Verbose() {
   248  					c := float64(cap(x.nodes))
   249  					b.ReportMetric(c, "cap")
   250  					b.ReportMetric(c*float64(stacktraceTreeNodeSize), "size")
   251  					b.ReportMetric(float64(x.len())/float64(c)*100, "fill")
   252  				}
   253  			}
   254  		})
   255  	}
   256  }