diff options
Diffstat (limited to 'src/runtime/pprof/protomem_test.go')
-rw-r--r-- | src/runtime/pprof/protomem_test.go | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/src/runtime/pprof/protomem_test.go b/src/runtime/pprof/protomem_test.go new file mode 100644 index 0000000..5fb67c5 --- /dev/null +++ b/src/runtime/pprof/protomem_test.go @@ -0,0 +1,223 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pprof + +import ( + "bytes" + "fmt" + "internal/profile" + "internal/testenv" + "runtime" + "slices" + "strings" + "testing" +) + +func TestConvertMemProfile(t *testing.T) { + addr1, addr2, map1, map2 := testPCs(t) + + // MemProfileRecord stacks are return PCs, so add one to the + // addresses recorded in the "profile". The proto profile + // locations are call PCs, so conversion will subtract one + // from these and get back to addr1 and addr2. + a1, a2 := uintptr(addr1)+1, uintptr(addr2)+1 + rate := int64(512 * 1024) + rec := []runtime.MemProfileRecord{ + {AllocBytes: 4096, FreeBytes: 1024, AllocObjects: 4, FreeObjects: 1, Stack0: [32]uintptr{a1, a2}}, + {AllocBytes: 512 * 1024, FreeBytes: 0, AllocObjects: 1, FreeObjects: 0, Stack0: [32]uintptr{a2 + 1, a2 + 2}}, + {AllocBytes: 512 * 1024, FreeBytes: 512 * 1024, AllocObjects: 1, FreeObjects: 1, Stack0: [32]uintptr{a1 + 1, a1 + 2, a2 + 3}}, + } + + periodType := &profile.ValueType{Type: "space", Unit: "bytes"} + sampleType := []*profile.ValueType{ + {Type: "alloc_objects", Unit: "count"}, + {Type: "alloc_space", Unit: "bytes"}, + {Type: "inuse_objects", Unit: "count"}, + {Type: "inuse_space", Unit: "bytes"}, + } + samples := []*profile.Sample{ + { + Value: []int64{2050, 2099200, 1537, 1574400}, + Location: []*profile.Location{ + {ID: 1, Mapping: map1, Address: addr1}, + {ID: 2, Mapping: map2, Address: addr2}, + }, + NumLabel: map[string][]int64{"bytes": {1024}}, + }, + { + Value: []int64{1, 829411, 1, 829411}, + Location: []*profile.Location{ + {ID: 3, Mapping: map2, Address: addr2 + 1}, + {ID: 4, Mapping: map2, Address: addr2 + 2}, + }, + NumLabel: map[string][]int64{"bytes": {512 * 1024}}, + }, + { + Value: []int64{1, 829411, 0, 0}, + Location: []*profile.Location{ + {ID: 5, Mapping: map1, Address: addr1 + 1}, + {ID: 6, Mapping: map1, Address: addr1 + 2}, + {ID: 7, Mapping: map2, Address: addr2 + 3}, + }, + NumLabel: map[string][]int64{"bytes": {512 * 1024}}, + }, + } + for _, tc := range []struct { + name string + defaultSampleType string + }{ + {"heap", ""}, + {"allocs", "alloc_space"}, + } { + t.Run(tc.name, func(t *testing.T) { + var buf bytes.Buffer + if err := writeHeapProto(&buf, rec, rate, tc.defaultSampleType); err != nil { + t.Fatalf("writing profile: %v", err) + } + + p, err := profile.Parse(&buf) + if err != nil { + t.Fatalf("profile.Parse: %v", err) + } + + checkProfile(t, p, rate, periodType, sampleType, samples, tc.defaultSampleType) + }) + } +} + +func genericAllocFunc[T interface{ uint32 | uint64 }](n int) []T { + return make([]T, n) +} + +func profileToStrings(p *profile.Profile) []string { + var res []string + for _, s := range p.Sample { + res = append(res, sampleToString(s)) + } + return res +} + +func sampleToString(s *profile.Sample) string { + var funcs []string + for i := len(s.Location) - 1; i >= 0; i-- { + loc := s.Location[i] + funcs = locationToStrings(loc, funcs) + } + return fmt.Sprintf("%s %v", strings.Join(funcs, ";"), s.Value) +} + +func locationToStrings(loc *profile.Location, funcs []string) []string { + for j := range loc.Line { + line := loc.Line[len(loc.Line)-1-j] + funcs = append(funcs, line.Function.Name) + } + return funcs +} + +// This is a regression test for https://go.dev/issue/64528 . +func TestGenericsHashKeyInPprofBuilder(t *testing.T) { + previousRate := runtime.MemProfileRate + runtime.MemProfileRate = 1 + defer func() { + runtime.MemProfileRate = previousRate + }() + for _, sz := range []int{128, 256} { + genericAllocFunc[uint32](sz / 4) + } + for _, sz := range []int{32, 64} { + genericAllocFunc[uint64](sz / 8) + } + + runtime.GC() + buf := bytes.NewBuffer(nil) + if err := WriteHeapProfile(buf); err != nil { + t.Fatalf("writing profile: %v", err) + } + p, err := profile.Parse(buf) + if err != nil { + t.Fatalf("profile.Parse: %v", err) + } + + actual := profileToStrings(p) + expected := []string{ + "testing.tRunner;runtime/pprof.TestGenericsHashKeyInPprofBuilder;runtime/pprof.genericAllocFunc[go.shape.uint32] [1 128 0 0]", + "testing.tRunner;runtime/pprof.TestGenericsHashKeyInPprofBuilder;runtime/pprof.genericAllocFunc[go.shape.uint32] [1 256 0 0]", + "testing.tRunner;runtime/pprof.TestGenericsHashKeyInPprofBuilder;runtime/pprof.genericAllocFunc[go.shape.uint64] [1 32 0 0]", + "testing.tRunner;runtime/pprof.TestGenericsHashKeyInPprofBuilder;runtime/pprof.genericAllocFunc[go.shape.uint64] [1 64 0 0]", + } + + for _, l := range expected { + if !slices.Contains(actual, l) { + t.Errorf("profile = %v\nwant = %v", strings.Join(actual, "\n"), l) + } + } +} + +type opAlloc struct { + buf [128]byte +} + +type opCall struct { +} + +var sink []byte + +func storeAlloc() { + sink = make([]byte, 16) +} + +func nonRecursiveGenericAllocFunction[CurrentOp any, OtherOp any](alloc bool) { + if alloc { + storeAlloc() + } else { + nonRecursiveGenericAllocFunction[OtherOp, CurrentOp](true) + } +} + +func TestGenericsInlineLocations(t *testing.T) { + if testenv.OptimizationOff() { + t.Skip("skipping test with optimizations disabled") + } + + previousRate := runtime.MemProfileRate + runtime.MemProfileRate = 1 + defer func() { + runtime.MemProfileRate = previousRate + sink = nil + }() + + nonRecursiveGenericAllocFunction[opAlloc, opCall](true) + nonRecursiveGenericAllocFunction[opCall, opAlloc](false) + + runtime.GC() + + buf := bytes.NewBuffer(nil) + if err := WriteHeapProfile(buf); err != nil { + t.Fatalf("writing profile: %v", err) + } + p, err := profile.Parse(buf) + if err != nil { + t.Fatalf("profile.Parse: %v", err) + } + + const expectedSample = "testing.tRunner;runtime/pprof.TestGenericsInlineLocations;runtime/pprof.nonRecursiveGenericAllocFunction[go.shape.struct {},go.shape.struct { runtime/pprof.buf [128]uint8 }];runtime/pprof.nonRecursiveGenericAllocFunction[go.shape.struct { runtime/pprof.buf [128]uint8 },go.shape.struct {}];runtime/pprof.storeAlloc [1 16 1 16]" + const expectedLocation = "runtime/pprof.nonRecursiveGenericAllocFunction[go.shape.struct {},go.shape.struct { runtime/pprof.buf [128]uint8 }];runtime/pprof.nonRecursiveGenericAllocFunction[go.shape.struct { runtime/pprof.buf [128]uint8 },go.shape.struct {}];runtime/pprof.storeAlloc" + const expectedLocationNewInliner = "runtime/pprof.TestGenericsInlineLocations;" + expectedLocation + var s *profile.Sample + for _, sample := range p.Sample { + if sampleToString(sample) == expectedSample { + s = sample + break + } + } + if s == nil { + t.Fatalf("expected \n%s\ngot\n%s", expectedSample, strings.Join(profileToStrings(p), "\n")) + } + loc := s.Location[0] + actual := strings.Join(locationToStrings(loc, nil), ";") + if expectedLocation != actual && expectedLocationNewInliner != actual { + t.Errorf("expected a location with at least 3 functions\n%s\ngot\n%s\n", expectedLocation, actual) + } +} |