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/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  }
   224  

View as plain text