github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/convert/pprof/pprof_test.go (about)

     1  package pprof
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  	"time"
     7  
     8  	. "github.com/onsi/ginkgo/v2"
     9  	. "github.com/onsi/gomega"
    10  
    11  	"github.com/pyroscope-io/pyroscope/pkg/storage"
    12  	"github.com/pyroscope-io/pyroscope/pkg/storage/tree"
    13  )
    14  
    15  type mockIngester struct{ actual []*storage.PutInput }
    16  
    17  func (m *mockIngester) Put(_ context.Context, p *storage.PutInput) error {
    18  	m.actual = append(m.actual, p)
    19  	return nil
    20  }
    21  
    22  var _ = Describe("pprof parsing", func() {
    23  	Context("Go", func() {
    24  		It("can parse CPU profiles", func() {
    25  			p, err := readPprofFixture("testdata/cpu.pb.gz")
    26  			Expect(err).ToNot(HaveOccurred())
    27  
    28  			ingester := new(mockIngester)
    29  			spyName := "spy-name"
    30  			now := time.Now()
    31  			start := now
    32  			end := now.Add(10 * time.Second)
    33  			labels := map[string]string{
    34  				"__name__": "app",
    35  				"foo":      "bar",
    36  			}
    37  
    38  			w := NewParser(ParserConfig{
    39  				Putter:      ingester,
    40  				SampleTypes: tree.DefaultSampleTypeMapping,
    41  				Labels:      labels,
    42  				SpyName:     spyName,
    43  			})
    44  
    45  			err = w.Convert(context.Background(), start, end, p, false)
    46  			Expect(err).ToNot(HaveOccurred())
    47  
    48  			Expect(ingester.actual).To(HaveLen(1))
    49  			input := ingester.actual[0]
    50  			Expect(input.SpyName).To(Equal(spyName))
    51  			Expect(input.StartTime).To(Equal(start))
    52  			Expect(input.EndTime).To(Equal(end))
    53  			Expect(input.SampleRate).To(Equal(uint32(100)))
    54  			Expect(input.Val.Samples()).To(Equal(uint64(47)))
    55  			Expect(input.Key.Normalized()).To(Equal("app.cpu{foo=bar}"))
    56  			Expect(input.Val.String()).To(ContainSubstring("runtime.main;main.main;main.slowFunction;main.work 1"))
    57  		})
    58  	})
    59  
    60  	Context("JS", func() {
    61  		It("can parse CPU profile", func() {
    62  			p, err := readPprofFixture("testdata/nodejs-wall.pb.gz")
    63  			Expect(err).ToNot(HaveOccurred())
    64  
    65  			ingester := new(mockIngester)
    66  			spyName := "nodespy"
    67  			now := time.Now()
    68  			start := now
    69  			end := now.Add(10 * time.Second)
    70  			labels := map[string]string{
    71  				"__name__": "app",
    72  				"foo":      "bar",
    73  			}
    74  
    75  			w := NewParser(ParserConfig{
    76  				Putter:      ingester,
    77  				SampleTypes: tree.DefaultSampleTypeMapping,
    78  				Labels:      labels,
    79  				SpyName:     spyName,
    80  			})
    81  
    82  			err = w.Convert(context.Background(), start, end, p, false)
    83  			Expect(err).ToNot(HaveOccurred())
    84  
    85  			Expect(ingester.actual).To(HaveLen(1))
    86  			input := ingester.actual[0]
    87  			Expect(input.SpyName).To(Equal(spyName))
    88  			Expect(input.StartTime).To(Equal(start))
    89  			Expect(input.EndTime).To(Equal(end))
    90  			Expect(input.SampleRate).To(Equal(uint32(100)))
    91  			Expect(input.Val.Samples()).To(Equal(uint64(898)))
    92  			Expect(input.Key.Normalized()).To(Equal("app.cpu{foo=bar}"))
    93  			Expect(input.Val.String()).To(ContainSubstring("node:_http_server:resOnFinish:819;node:_http_server:detachSocket:252 1"))
    94  		})
    95  
    96  		It("can parse heap profiles", func() {
    97  			p, err := readPprofFixture("testdata/nodejs-heap.pb.gz")
    98  			Expect(err).ToNot(HaveOccurred())
    99  
   100  			ingester := new(mockIngester)
   101  			spyName := "nodespy"
   102  			now := time.Now()
   103  			start := now
   104  			end := now.Add(10 * time.Second)
   105  			labels := map[string]string{
   106  				"__name__": "app",
   107  				"foo":      "bar",
   108  			}
   109  
   110  			Expect(tree.DefaultSampleTypeMapping["inuse_objects"].Cumulative).To(BeFalse())
   111  			Expect(tree.DefaultSampleTypeMapping["inuse_space"].Cumulative).To(BeFalse())
   112  			tree.DefaultSampleTypeMapping["inuse_objects"].Cumulative = false
   113  			tree.DefaultSampleTypeMapping["inuse_space"].Cumulative = false
   114  
   115  			w := NewParser(ParserConfig{
   116  				Putter:      ingester,
   117  				SampleTypes: tree.DefaultSampleTypeMapping,
   118  				Labels:      labels,
   119  				SpyName:     spyName,
   120  			})
   121  
   122  			err = w.Convert(context.Background(), start, end, p, false)
   123  			Expect(err).ToNot(HaveOccurred())
   124  			Expect(ingester.actual).To(HaveLen(2))
   125  			sort.Slice(ingester.actual, func(i, j int) bool {
   126  				return ingester.actual[i].Key.Normalized() < ingester.actual[j].Key.Normalized()
   127  			})
   128  
   129  			input := ingester.actual[0]
   130  			Expect(input.SpyName).To(Equal(spyName))
   131  			Expect(input.StartTime).To(Equal(start))
   132  			Expect(input.EndTime).To(Equal(end))
   133  			Expect(input.Val.Samples()).To(Equal(uint64(100498)))
   134  			Expect(input.Key.Normalized()).To(Equal("app.inuse_objects{foo=bar}"))
   135  			Expect(input.Val.String()).To(ContainSubstring("node:internal/streams/readable:readableAddChunk:236 138"))
   136  
   137  			input = ingester.actual[1]
   138  			Expect(input.SpyName).To(Equal(spyName))
   139  			Expect(input.StartTime).To(Equal(start))
   140  			Expect(input.EndTime).To(Equal(end))
   141  			Expect(input.Val.Samples()).To(Equal(uint64(8357762)))
   142  			Expect(input.Key.Normalized()).To(Equal("app.inuse_space{foo=bar}"))
   143  			Expect(input.Val.String()).To(ContainSubstring("node:internal/net:isIPv6:35;:test:0 555360"))
   144  		})
   145  	})
   146  
   147  	Context("pprof", func() {
   148  		It("can parse uncompressed protobuf", func() {
   149  			_, err := readPprofFixture("testdata/heap.pb")
   150  			Expect(err).ToNot(HaveOccurred())
   151  		})
   152  	})
   153  })
   154  
   155  var _ = Describe("pprof parser", func() {
   156  	p, err := readPprofFixture("testdata/cpu-exemplars.pb.gz")
   157  	Expect(err).ToNot(HaveOccurred())
   158  
   159  	m := make(map[string]*storage.PutInput)
   160  	var skipExemplars bool
   161  
   162  	JustBeforeEach(func() {
   163  		putter := new(mockIngester)
   164  		now := time.Now()
   165  		start := now
   166  		end := now.Add(10 * time.Second)
   167  
   168  		w := NewParser(ParserConfig{
   169  			Putter:        putter,
   170  			Labels:        map[string]string{"__name__": "app"},
   171  			SampleTypes:   tree.DefaultSampleTypeMapping,
   172  			SkipExemplars: skipExemplars,
   173  		})
   174  
   175  		err = w.Convert(context.Background(), start, end, p, false)
   176  		Expect(err).ToNot(HaveOccurred())
   177  		m = make(map[string]*storage.PutInput)
   178  		for _, x := range putter.actual {
   179  			m[x.Key.Normalized()] = x
   180  		}
   181  	})
   182  
   183  	expectBaselineProfiles := func(m map[string]*storage.PutInput) {
   184  		baseline, ok := m["app.cpu{foo=bar}"]
   185  		Expect(ok).To(BeTrue())
   186  		Expect(baseline.Val.Samples()).To(Equal(uint64(49)))
   187  
   188  		baseline, ok = m["app.cpu{foo=bar,function=fast}"]
   189  		Expect(ok).To(BeTrue())
   190  		Expect(baseline.Val.Samples()).To(Equal(uint64(150)))
   191  
   192  		baseline, ok = m["app.cpu{foo=bar,function=slow}"]
   193  		Expect(ok).To(BeTrue())
   194  		Expect(baseline.Val.Samples()).To(Equal(uint64(674)))
   195  	}
   196  
   197  	expectExemplarProfiles := func(m map[string]*storage.PutInput) {
   198  		exemplar, ok := m["app.cpu{foo=bar,function=slow,profile_id=72bee0038027cfb1}"]
   199  		Expect(ok).To(BeTrue())
   200  		Expect(exemplar.Val.Samples()).To(Equal(uint64(3)))
   201  
   202  		exemplar, ok = m["app.cpu{foo=bar,function=fast,profile_id=ff4d0449f061174f}"]
   203  		Expect(ok).To(BeTrue())
   204  		Expect(exemplar.Val.Samples()).To(Equal(uint64(1)))
   205  	}
   206  
   207  	Context("by default exemplars are not skipped", func() {
   208  		It("can parse all exemplars", func() {
   209  			Expect(len(m)).To(Equal(435))
   210  		})
   211  
   212  		It("correctly handles labels and values", func() {
   213  			expectExemplarProfiles(m)
   214  		})
   215  
   216  		It("merges baseline profiles", func() {
   217  			expectBaselineProfiles(m)
   218  		})
   219  	})
   220  
   221  	Context("when configured to skip exemplars", func() {
   222  		BeforeEach(func() {
   223  			skipExemplars = true
   224  		})
   225  
   226  		It("skip exemplars", func() {
   227  			Expect(len(m)).To(Equal(3))
   228  		})
   229  
   230  		It("merges baseline profiles", func() {
   231  			expectBaselineProfiles(m)
   232  		})
   233  	})
   234  })
   235  
   236  var _ = Describe("custom pprof parsing", func() {
   237  	It("parses data correctly", func() {
   238  		p, err := readPprofFixture("testdata/heap-js.pprof")
   239  		Expect(err).ToNot(HaveOccurred())
   240  
   241  		ingester := new(mockIngester)
   242  		spyName := "spy-name"
   243  		now := time.Now()
   244  		start := now
   245  		end := now.Add(10 * time.Second)
   246  		labels := map[string]string{
   247  			"__name__": "app",
   248  			"foo":      "bar",
   249  		}
   250  
   251  		w := NewParser(ParserConfig{
   252  			Putter: ingester,
   253  			SampleTypes: map[string]*tree.SampleTypeConfig{
   254  				"objects": {
   255  					Units:       "objects",
   256  					Aggregation: "average",
   257  				},
   258  				"space": {
   259  					Units:       "bytes",
   260  					Aggregation: "average",
   261  				},
   262  			},
   263  			Labels:  labels,
   264  			SpyName: spyName,
   265  		})
   266  
   267  		err = w.Convert(context.TODO(), start, end, p, false)
   268  		Expect(err).ToNot(HaveOccurred())
   269  		Expect(ingester.actual).To(HaveLen(2))
   270  		sort.Slice(ingester.actual, func(i, j int) bool {
   271  			return ingester.actual[i].Key.Normalized() < ingester.actual[j].Key.Normalized()
   272  		})
   273  
   274  		input := ingester.actual[0]
   275  		Expect(input.SpyName).To(Equal(spyName))
   276  		Expect(input.StartTime).To(Equal(start))
   277  		Expect(input.EndTime).To(Equal(end))
   278  		Expect(input.Val.Samples()).To(Equal(uint64(66148)))
   279  		Expect(input.Key.Normalized()).To(Equal("app.objects{foo=bar}"))
   280  		Expect(input.Val.String()).To(ContainSubstring("parserOnHeadersComplete;parserOnIncoming 2428"))
   281  
   282  		input = ingester.actual[1]
   283  		Expect(input.SpyName).To(Equal(spyName))
   284  		Expect(input.StartTime).To(Equal(start))
   285  		Expect(input.EndTime).To(Equal(end))
   286  		Expect(input.Val.Samples()).To(Equal(uint64(6388384)))
   287  		Expect(input.Key.Normalized()).To(Equal("app.space{foo=bar}"))
   288  		Expect(input.Val.String()).To(ContainSubstring("parserOnHeadersComplete;parserOnIncoming 524448"))
   289  	})
   290  })