Source file src/runtime/pprof/protomem_test.go

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

View as plain text