blob: 27ff34fe240e2774a5db2a3f1a8c70f46db96ec7 [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pgo
import (
"bytes"
"internal/profile"
"strings"
"testing"
)
func testProfile(sampleTypes []*profile.ValueType, defaultSampleType string, values []int64) *profile.Profile {
main := &profile.Function{ID: 1, Name: "main.main", StartLine: 10}
foo := &profile.Function{ID: 2, Name: "main.foo", StartLine: 20}
mainLoc := &profile.Location{ID: 1, Line: []profile.Line{{Function: main, Line: 12}}}
fooLoc := &profile.Location{ID: 2, Line: []profile.Line{{Function: foo, Line: 22}}}
return &profile.Profile{
SampleType: sampleTypes,
DefaultSampleType: defaultSampleType,
Function: []*profile.Function{main, foo},
Location: []*profile.Location{mainLoc, fooLoc},
Sample: []*profile.Sample{
// Stacks are leaf-first: foo called from main.
{Location: []*profile.Location{fooLoc, mainLoc}, Value: values},
},
}
}
func serializeProfile(t *testing.T, p *profile.Profile) *bytes.Reader {
t.Helper()
var buf bytes.Buffer
if err := p.Write(&buf); err != nil {
t.Fatalf("writing profile: %v", err)
}
return bytes.NewReader(buf.Bytes())
}
func TestFromPProfSampleType(t *testing.T) {
perf := []*profile.ValueType{
{Type: "cycles_sample", Unit: "count"},
{Type: "cycles_event", Unit: "count"},
}
tests := []struct {
name string
sampleTypes []*profile.ValueType
defaultType string
values []int64
wantErr string // substring of the expected error, if any
wantTotalWeight int64
}{
{
name: "native samples/count",
sampleTypes: []*profile.ValueType{{Type: "samples", Unit: "count"}, {Type: "cpu", Unit: "nanoseconds"}},
values: []int64{5, 500},
wantTotalWeight: 5,
},
{
name: "native cpu/nanoseconds",
sampleTypes: []*profile.ValueType{{Type: "cpu", Unit: "nanoseconds"}},
values: []int64{500},
wantTotalWeight: 500,
},
{
name: "perf via default sample type",
sampleTypes: perf,
defaultType: "cycles_event",
values: []int64{5, 500},
wantTotalWeight: 500,
},
{
name: "perf without default uses last value type",
sampleTypes: perf,
values: []int64{5, 500},
wantTotalWeight: 500,
},
{
name: "heap profile rejected",
sampleTypes: []*profile.ValueType{{Type: "alloc_objects", Unit: "count"}, {Type: "inuse_space", Unit: "bytes"}},
defaultType: "inuse_space",
values: []int64{1, 2},
wantErr: "Go heap profile",
},
{
name: "mutex profile rejected",
sampleTypes: []*profile.ValueType{{Type: "contentions", Unit: "count"}, {Type: "delay", Unit: "nanoseconds"}},
values: []int64{1, 2},
wantErr: "Go mutex or block profile",
},
{
name: "goroutine profile rejected",
sampleTypes: []*profile.ValueType{{Type: "goroutine", Unit: "count"}},
values: []int64{1},
wantErr: "Go goroutine profile",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := FromPProf(serializeProfile(t, testProfile(tc.sampleTypes, tc.defaultType, tc.values)))
if tc.wantErr != "" {
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
t.Fatalf("FromPProf got err %v, want error containing %q", err, tc.wantErr)
}
return
}
if err != nil {
t.Fatalf("FromPProf: %v", err)
}
if len(got.NamedEdgeMap.ByWeight) == 0 {
t.Error("empty edge map, want a call edge")
}
if got.TotalWeight != tc.wantTotalWeight {
t.Errorf("TotalWeight = %d, want %d", got.TotalWeight, tc.wantTotalWeight)
}
})
}
}
func TestSampleValueIndexNoSampleTypes(t *testing.T) {
if _, err := sampleValueIndex(&profile.Profile{}); err == nil {
t.Error("sampleValueIndex succeeded on a profile with no sample types, want error")
}
}