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