Source file src/cmd/internal/pgo/pprof_test.go

     1  // Copyright 2026 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 pgo
     6  
     7  import (
     8  	"bytes"
     9  	"internal/profile"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  func testProfile(sampleTypes []*profile.ValueType, defaultSampleType string, values []int64) *profile.Profile {
    15  	main := &profile.Function{ID: 1, Name: "main.main", StartLine: 10}
    16  	foo := &profile.Function{ID: 2, Name: "main.foo", StartLine: 20}
    17  	mainLoc := &profile.Location{ID: 1, Line: []profile.Line{{Function: main, Line: 12}}}
    18  	fooLoc := &profile.Location{ID: 2, Line: []profile.Line{{Function: foo, Line: 22}}}
    19  	return &profile.Profile{
    20  		SampleType:        sampleTypes,
    21  		DefaultSampleType: defaultSampleType,
    22  		Function:          []*profile.Function{main, foo},
    23  		Location:          []*profile.Location{mainLoc, fooLoc},
    24  		Sample: []*profile.Sample{
    25  			// Stacks are leaf-first: foo called from main.
    26  			{Location: []*profile.Location{fooLoc, mainLoc}, Value: values},
    27  		},
    28  	}
    29  }
    30  
    31  func serializeProfile(t *testing.T, p *profile.Profile) *bytes.Reader {
    32  	t.Helper()
    33  	var buf bytes.Buffer
    34  	if err := p.Write(&buf); err != nil {
    35  		t.Fatalf("writing profile: %v", err)
    36  	}
    37  	return bytes.NewReader(buf.Bytes())
    38  }
    39  
    40  func TestFromPProfSampleType(t *testing.T) {
    41  	perf := []*profile.ValueType{
    42  		{Type: "cycles_sample", Unit: "count"},
    43  		{Type: "cycles_event", Unit: "count"},
    44  	}
    45  
    46  	tests := []struct {
    47  		name            string
    48  		sampleTypes     []*profile.ValueType
    49  		defaultType     string
    50  		values          []int64
    51  		wantErr         string // substring of the expected error, if any
    52  		wantTotalWeight int64
    53  	}{
    54  		{
    55  			name:            "native samples/count",
    56  			sampleTypes:     []*profile.ValueType{{Type: "samples", Unit: "count"}, {Type: "cpu", Unit: "nanoseconds"}},
    57  			values:          []int64{5, 500},
    58  			wantTotalWeight: 5,
    59  		},
    60  		{
    61  			name:            "native cpu/nanoseconds",
    62  			sampleTypes:     []*profile.ValueType{{Type: "cpu", Unit: "nanoseconds"}},
    63  			values:          []int64{500},
    64  			wantTotalWeight: 500,
    65  		},
    66  		{
    67  			name:            "perf via default sample type",
    68  			sampleTypes:     perf,
    69  			defaultType:     "cycles_event",
    70  			values:          []int64{5, 500},
    71  			wantTotalWeight: 500,
    72  		},
    73  		{
    74  			name:            "perf without default uses last value type",
    75  			sampleTypes:     perf,
    76  			values:          []int64{5, 500},
    77  			wantTotalWeight: 500,
    78  		},
    79  		{
    80  			name:        "heap profile rejected",
    81  			sampleTypes: []*profile.ValueType{{Type: "alloc_objects", Unit: "count"}, {Type: "inuse_space", Unit: "bytes"}},
    82  			defaultType: "inuse_space",
    83  			values:      []int64{1, 2},
    84  			wantErr:     "Go heap profile",
    85  		},
    86  		{
    87  			name:        "mutex profile rejected",
    88  			sampleTypes: []*profile.ValueType{{Type: "contentions", Unit: "count"}, {Type: "delay", Unit: "nanoseconds"}},
    89  			values:      []int64{1, 2},
    90  			wantErr:     "Go mutex or block profile",
    91  		},
    92  		{
    93  			name:        "goroutine profile rejected",
    94  			sampleTypes: []*profile.ValueType{{Type: "goroutine", Unit: "count"}},
    95  			values:      []int64{1},
    96  			wantErr:     "Go goroutine profile",
    97  		},
    98  	}
    99  
   100  	for _, tc := range tests {
   101  		t.Run(tc.name, func(t *testing.T) {
   102  			got, err := FromPProf(serializeProfile(t, testProfile(tc.sampleTypes, tc.defaultType, tc.values)))
   103  			if tc.wantErr != "" {
   104  				if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
   105  					t.Fatalf("FromPProf got err %v, want error containing %q", err, tc.wantErr)
   106  				}
   107  				return
   108  			}
   109  			if err != nil {
   110  				t.Fatalf("FromPProf: %v", err)
   111  			}
   112  			if len(got.NamedEdgeMap.ByWeight) == 0 {
   113  				t.Error("empty edge map, want a call edge")
   114  			}
   115  			if got.TotalWeight != tc.wantTotalWeight {
   116  				t.Errorf("TotalWeight = %d, want %d", got.TotalWeight, tc.wantTotalWeight)
   117  			}
   118  		})
   119  	}
   120  }
   121  
   122  func TestSampleValueIndexNoSampleTypes(t *testing.T) {
   123  	if _, err := sampleValueIndex(&profile.Profile{}); err == nil {
   124  		t.Error("sampleValueIndex succeeded on a profile with no sample types, want error")
   125  	}
   126  }
   127  

View as plain text