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/profilerecord"
    12  	"internal/testenv"
    13  	"runtime"
    14  	"slices"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  func 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  
    91  func genericAllocFunc[T interface{ uint32 | uint64 }](n int) []T {
    92  	return make([]T, n)
    93  }
    94  
    95  func 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  
   103  func 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  
   112  func 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 .
   121  func 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  
   159  type opAlloc struct {
   160  	buf [128]byte
   161  }
   162  
   163  type opCall struct {
   164  }
   165  
   166  var sink []byte
   167  
   168  func storeAlloc() {
   169  	sink = make([]byte, 16)
   170  }
   171  
   172  func nonRecursiveGenericAllocFunction[CurrentOp any, OtherOp any](alloc bool) {
   173  	if alloc {
   174  		storeAlloc()
   175  	} else {
   176  		nonRecursiveGenericAllocFunction[OtherOp, CurrentOp](true)
   177  	}
   178  }
   179  
   180  func 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  

View as plain text