1// Copyright 2016 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package pprof 6 7import ( 8 "bytes" 9 "fmt" 10 "internal/profile" 11 "internal/profilerecord" 12 "internal/testenv" 13 "runtime" 14 "slices" 15 "strings" 16 "testing" 17) 18 19func TestConvertMemProfile(t *testing.T) { 20 addr1, addr2, map1, map2 := testPCs(t) 21 22 // MemProfileRecord stacks are return PCs, so add one to the 23 // addresses recorded in the "profile". The proto profile 24 // locations are call PCs, so conversion will subtract one 25 // from these and get back to addr1 and addr2. 26 a1, a2 := uintptr(addr1)+1, uintptr(addr2)+1 27 rate := int64(512 * 1024) 28 rec := []profilerecord.MemProfileRecord{ 29 {AllocBytes: 4096, FreeBytes: 1024, AllocObjects: 4, FreeObjects: 1, Stack: []uintptr{a1, a2}}, 30 {AllocBytes: 512 * 1024, FreeBytes: 0, AllocObjects: 1, FreeObjects: 0, Stack: []uintptr{a2 + 1, a2 + 2}}, 31 {AllocBytes: 512 * 1024, FreeBytes: 512 * 1024, AllocObjects: 1, FreeObjects: 1, Stack: []uintptr{a1 + 1, a1 + 2, a2 + 3}}, 32 } 33 34 periodType := &profile.ValueType{Type: "space", Unit: "bytes"} 35 sampleType := []*profile.ValueType{ 36 {Type: "alloc_objects", Unit: "count"}, 37 {Type: "alloc_space", Unit: "bytes"}, 38 {Type: "inuse_objects", Unit: "count"}, 39 {Type: "inuse_space", Unit: "bytes"}, 40 } 41 samples := []*profile.Sample{ 42 { 43 Value: []int64{2050, 2099200, 1537, 1574400}, 44 Location: []*profile.Location{ 45 {ID: 1, Mapping: map1, Address: addr1}, 46 {ID: 2, Mapping: map2, Address: addr2}, 47 }, 48 NumLabel: map[string][]int64{"bytes": {1024}}, 49 }, 50 { 51 Value: []int64{1, 829411, 1, 829411}, 52 Location: []*profile.Location{ 53 {ID: 3, Mapping: map2, Address: addr2 + 1}, 54 {ID: 4, Mapping: map2, Address: addr2 + 2}, 55 }, 56 NumLabel: map[string][]int64{"bytes": {512 * 1024}}, 57 }, 58 { 59 Value: []int64{1, 829411, 0, 0}, 60 Location: []*profile.Location{ 61 {ID: 5, Mapping: map1, Address: addr1 + 1}, 62 {ID: 6, Mapping: map1, Address: addr1 + 2}, 63 {ID: 7, Mapping: map2, Address: addr2 + 3}, 64 }, 65 NumLabel: map[string][]int64{"bytes": {512 * 1024}}, 66 }, 67 } 68 for _, tc := range []struct { 69 name string 70 defaultSampleType string 71 }{ 72 {"heap", ""}, 73 {"allocs", "alloc_space"}, 74 } { 75 t.Run(tc.name, func(t *testing.T) { 76 var buf bytes.Buffer 77 if err := writeHeapProto(&buf, rec, rate, tc.defaultSampleType); err != nil { 78 t.Fatalf("writing profile: %v", err) 79 } 80 81 p, err := profile.Parse(&buf) 82 if err != nil { 83 t.Fatalf("profile.Parse: %v", err) 84 } 85 86 checkProfile(t, p, rate, periodType, sampleType, samples, tc.defaultSampleType) 87 }) 88 } 89} 90 91func genericAllocFunc[T interface{ uint32 | uint64 }](n int) []T { 92 return make([]T, n) 93} 94 95func profileToStrings(p *profile.Profile) []string { 96 var res []string 97 for _, s := range p.Sample { 98 res = append(res, sampleToString(s)) 99 } 100 return res 101} 102 103func sampleToString(s *profile.Sample) string { 104 var funcs []string 105 for i := len(s.Location) - 1; i >= 0; i-- { 106 loc := s.Location[i] 107 funcs = locationToStrings(loc, funcs) 108 } 109 return fmt.Sprintf("%s %v", strings.Join(funcs, ";"), s.Value) 110} 111 112func locationToStrings(loc *profile.Location, funcs []string) []string { 113 for j := range loc.Line { 114 line := loc.Line[len(loc.Line)-1-j] 115 funcs = append(funcs, line.Function.Name) 116 } 117 return funcs 118} 119 120// This is a regression test for https://go.dev/issue/64528 . 121func TestGenericsHashKeyInPprofBuilder(t *testing.T) { 122 previousRate := runtime.MemProfileRate 123 runtime.MemProfileRate = 1 124 defer func() { 125 runtime.MemProfileRate = previousRate 126 }() 127 for _, sz := range []int{128, 256} { 128 genericAllocFunc[uint32](sz / 4) 129 } 130 for _, sz := range []int{32, 64} { 131 genericAllocFunc[uint64](sz / 8) 132 } 133 134 runtime.GC() 135 buf := bytes.NewBuffer(nil) 136 if err := WriteHeapProfile(buf); err != nil { 137 t.Fatalf("writing profile: %v", err) 138 } 139 p, err := profile.Parse(buf) 140 if err != nil { 141 t.Fatalf("profile.Parse: %v", err) 142 } 143 144 actual := profileToStrings(p) 145 expected := []string{ 146 "testing.tRunner;runtime/pprof.TestGenericsHashKeyInPprofBuilder;runtime/pprof.genericAllocFunc[go.shape.uint32] [1 128 0 0]", 147 "testing.tRunner;runtime/pprof.TestGenericsHashKeyInPprofBuilder;runtime/pprof.genericAllocFunc[go.shape.uint32] [1 256 0 0]", 148 "testing.tRunner;runtime/pprof.TestGenericsHashKeyInPprofBuilder;runtime/pprof.genericAllocFunc[go.shape.uint64] [1 32 0 0]", 149 "testing.tRunner;runtime/pprof.TestGenericsHashKeyInPprofBuilder;runtime/pprof.genericAllocFunc[go.shape.uint64] [1 64 0 0]", 150 } 151 152 for _, l := range expected { 153 if !slices.Contains(actual, l) { 154 t.Errorf("profile = %v\nwant = %v", strings.Join(actual, "\n"), l) 155 } 156 } 157} 158 159type opAlloc struct { 160 buf [128]byte 161} 162 163type opCall struct { 164} 165 166var sink []byte 167 168func storeAlloc() { 169 sink = make([]byte, 16) 170} 171 172func nonRecursiveGenericAllocFunction[CurrentOp any, OtherOp any](alloc bool) { 173 if alloc { 174 storeAlloc() 175 } else { 176 nonRecursiveGenericAllocFunction[OtherOp, CurrentOp](true) 177 } 178} 179 180func TestGenericsInlineLocations(t *testing.T) { 181 if testenv.OptimizationOff() { 182 t.Skip("skipping test with optimizations disabled") 183 } 184 185 previousRate := runtime.MemProfileRate 186 runtime.MemProfileRate = 1 187 defer func() { 188 runtime.MemProfileRate = previousRate 189 sink = nil 190 }() 191 192 nonRecursiveGenericAllocFunction[opAlloc, opCall](true) 193 nonRecursiveGenericAllocFunction[opCall, opAlloc](false) 194 195 runtime.GC() 196 197 buf := bytes.NewBuffer(nil) 198 if err := WriteHeapProfile(buf); err != nil { 199 t.Fatalf("writing profile: %v", err) 200 } 201 p, err := profile.Parse(buf) 202 if err != nil { 203 t.Fatalf("profile.Parse: %v", err) 204 } 205 206 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]" 207 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" 208 const expectedLocationNewInliner = "runtime/pprof.TestGenericsInlineLocations;" + expectedLocation 209 var s *profile.Sample 210 for _, sample := range p.Sample { 211 if sampleToString(sample) == expectedSample { 212 s = sample 213 break 214 } 215 } 216 if s == nil { 217 t.Fatalf("expected \n%s\ngot\n%s", expectedSample, strings.Join(profileToStrings(p), "\n")) 218 } 219 loc := s.Location[0] 220 actual := strings.Join(locationToStrings(loc, nil), ";") 221 if expectedLocation != actual && expectedLocationNewInliner != actual { 222 t.Errorf("expected a location with at least 3 functions\n%s\ngot\n%s\n", expectedLocation, actual) 223 } 224} 225