@@ -5,8 +5,13 @@ package compat
5
5
6
6
import (
7
7
"bytes"
8
+ "fmt"
9
+ "github.com/google/pprof/profile"
10
+ "io"
8
11
"runtime"
9
12
"runtime/pprof"
13
+ "slices"
14
+ "strings"
10
15
"sync"
11
16
"testing"
12
17
"time"
@@ -194,3 +199,153 @@ func TestMutex(t *testing.T) {
194
199
expectStackFrames (t , buffer , expectedRealShape , 19 )
195
200
})
196
201
}
202
+
203
+ func profileToStrings (p * profile.Profile ) []string {
204
+ var res []string
205
+ for _ , s := range p .Sample {
206
+ res = append (res , sampleToString (s ))
207
+ }
208
+ return res
209
+ }
210
+
211
+ func sampleToString (s * profile.Sample ) string {
212
+ var funcs []string
213
+ for i := len (s .Location ) - 1 ; i >= 0 ; i -- {
214
+ loc := s .Location [i ]
215
+ funcs = locationToStrings (loc , funcs )
216
+ }
217
+ return fmt .Sprintf ("%s %v" , strings .Join (funcs , ";" ), s .Value )
218
+ }
219
+
220
+ func locationToStrings (loc * profile.Location , funcs []string ) []string {
221
+ for j := range loc .Line {
222
+ line := loc .Line [len (loc .Line )- 1 - j ]
223
+ funcs = append (funcs , line .Function .Name )
224
+ }
225
+ return funcs
226
+ }
227
+
228
+ // This is a regression test for https://go.dev/issue/64528 .
229
+ func TestGenericsHashKeyInPprofBuilder (t * testing.T ) {
230
+ previousRate := runtime .MemProfileRate
231
+ runtime .MemProfileRate = 1
232
+ defer func () {
233
+ runtime .MemProfileRate = previousRate
234
+ }()
235
+ for _ , sz := range []int {128 , 256 } {
236
+ genericAllocFunc [uint32 ](sz / 4 )
237
+ }
238
+ for _ , sz := range []int {32 , 64 } {
239
+ genericAllocFunc [uint64 ](sz / 8 )
240
+ }
241
+
242
+ runtime .GC ()
243
+ buf := bytes .NewBuffer (nil )
244
+ if err := WriteHeapProfile (buf ); err != nil {
245
+ t .Fatalf ("writing profile: %v" , err )
246
+ }
247
+ p , err := profile .Parse (buf )
248
+ if err != nil {
249
+ t .Fatalf ("profile.Parse: %v" , err )
250
+ }
251
+
252
+ actual := profileToStrings (p )
253
+ expected := []string {
254
+ "testing.tRunner;github.com/grafana/pyroscope-go/godeltaprof/compat.TestGenericsHashKeyInPprofBuilder;github.com/grafana/pyroscope-go/godeltaprof/compat.genericAllocFunc[go.shape.uint32] [1 128 0 0]" ,
255
+ "testing.tRunner;github.com/grafana/pyroscope-go/godeltaprof/compat.TestGenericsHashKeyInPprofBuilder;github.com/grafana/pyroscope-go/godeltaprof/compat.genericAllocFunc[go.shape.uint32] [1 256 0 0]" ,
256
+ "testing.tRunner;github.com/grafana/pyroscope-go/godeltaprof/compat.TestGenericsHashKeyInPprofBuilder;github.com/grafana/pyroscope-go/godeltaprof/compat.genericAllocFunc[go.shape.uint64] [1 32 0 0]" ,
257
+ "testing.tRunner;github.com/grafana/pyroscope-go/godeltaprof/compat.TestGenericsHashKeyInPprofBuilder;github.com/grafana/pyroscope-go/godeltaprof/compat.genericAllocFunc[go.shape.uint64] [1 64 0 0]" ,
258
+ }
259
+
260
+ for _ , l := range expected {
261
+ if ! slices .Contains (actual , l ) {
262
+ t .Errorf ("profile = %v\n want = %v" , strings .Join (actual , "\n " ), l )
263
+ }
264
+ }
265
+ }
266
+
267
+ type opAlloc struct {
268
+ buf [128 ]byte
269
+ }
270
+
271
+ type opCall struct {
272
+ }
273
+
274
+ var sink []byte
275
+
276
+ func storeAlloc () {
277
+ sink = make ([]byte , 16 )
278
+ }
279
+
280
+ func nonRecursiveGenericAllocFunction [CurrentOp any , OtherOp any ](alloc bool ) {
281
+ if alloc {
282
+ storeAlloc ()
283
+ } else {
284
+ nonRecursiveGenericAllocFunction [OtherOp , CurrentOp ](true )
285
+ }
286
+ }
287
+
288
+ func TestGenericsInlineLocations (t * testing.T ) {
289
+ if OptimizationOff () {
290
+ t .Skip ("skipping test with optimizations disabled" )
291
+ }
292
+
293
+ previousRate := runtime .MemProfileRate
294
+ runtime .MemProfileRate = 1
295
+ defer func () {
296
+ runtime .MemProfileRate = previousRate
297
+ sink = nil
298
+ }()
299
+
300
+ nonRecursiveGenericAllocFunction [opAlloc , opCall ](true )
301
+ nonRecursiveGenericAllocFunction [opCall , opAlloc ](false )
302
+
303
+ runtime .GC ()
304
+
305
+ buf := bytes .NewBuffer (nil )
306
+ if err := WriteHeapProfile (buf ); err != nil {
307
+ t .Fatalf ("writing profile: %v" , err )
308
+ }
309
+ p , err := profile .Parse (buf )
310
+ if err != nil {
311
+ t .Fatalf ("profile.Parse: %v" , err )
312
+ }
313
+
314
+ const expectedSample = "testing.tRunner;github.com/grafana/pyroscope-go/godeltaprof/compat.TestGenericsInlineLocations;github.com/grafana/pyroscope-go/godeltaprof/compat.nonRecursiveGenericAllocFunction[go.shape.struct {},go.shape.struct { github.com/grafana/pyroscope-go/godeltaprof/compat.buf [128]uint8 }];github.com/grafana/pyroscope-go/godeltaprof/compat.nonRecursiveGenericAllocFunction[go.shape.struct { github.com/grafana/pyroscope-go/godeltaprof/compat.buf [128]uint8 },go.shape.struct {}];github.com/grafana/pyroscope-go/godeltaprof/compat.storeAlloc [1 16 1 16]"
315
+ const expectedLocation = "github.com/grafana/pyroscope-go/godeltaprof/compat.nonRecursiveGenericAllocFunction[go.shape.struct {},go.shape.struct { github.com/grafana/pyroscope-go/godeltaprof/compat.buf [128]uint8 }];github.com/grafana/pyroscope-go/godeltaprof/compat.nonRecursiveGenericAllocFunction[go.shape.struct { github.com/grafana/pyroscope-go/godeltaprof/compat.buf [128]uint8 },go.shape.struct {}];github.com/grafana/pyroscope-go/godeltaprof/compat.storeAlloc"
316
+ const expectedLocationNewInliner = "github.com/grafana/pyroscope-go/godeltaprof/compat.TestGenericsInlineLocations;" + expectedLocation
317
+ var s * profile.Sample
318
+ for _ , sample := range p .Sample {
319
+ if sampleToString (sample ) == expectedSample {
320
+ s = sample
321
+ break
322
+ }
323
+ }
324
+ if s == nil {
325
+ t .Fatalf ("expected \n %s\n got\n %s" , expectedSample , strings .Join (profileToStrings (p ), "\n " ))
326
+ }
327
+ loc := s .Location [0 ]
328
+ actual := strings .Join (locationToStrings (loc , nil ), ";" )
329
+ if expectedLocation != actual && expectedLocationNewInliner != actual {
330
+ t .Errorf ("expected a location with at least 3 functions\n %s\n got\n %s\n " , expectedLocation , actual )
331
+ }
332
+ }
333
+
334
+ func OptimizationOff () bool {
335
+ optimizationMarker := func () uintptr {
336
+ pc , _ , _ , _ := runtime .Caller (0 )
337
+ return pc
338
+ }
339
+ pc := optimizationMarker ()
340
+ f := runtime .FuncForPC (runtime .FuncForPC (pc ).Entry ())
341
+ return f .Name () == "github.com/grafana/pyroscope-go/godeltaprof/compat.OptimizationOff.func1"
342
+ }
343
+
344
+ func WriteHeapProfile (w io.Writer ) error {
345
+ runtime .GC ()
346
+ dh := godeltaprof .NewHeapProfilerWithOptions (godeltaprof.ProfileOptions {
347
+ GenericsFrames : true ,
348
+ LazyMappings : true ,
349
+ })
350
+ return dh .Profile (w )
351
+ }
0 commit comments