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 })