blob: 59c1080e7b205748ae06765d8d8cff47dc1492ec [file] [log] [blame]
// Copyright 2016 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 pprof
import (
"bytes"
"encoding/json"
"io/ioutil"
"reflect"
"runtime"
"runtime/pprof/internal/profile"
"testing"
)
// translateCPUProfile parses binary CPU profiling stack trace data
// generated by runtime.CPUProfile() into a profile struct.
// This is only used for testing. Real conversions stream the
// data into the profileBuilder as it becomes available.
func translateCPUProfile(data []uint64) (*profile.Profile, error) {
var buf bytes.Buffer
b := newProfileBuilder(&buf)
if err := b.addCPUData(data, nil); err != nil {
return nil, err
}
b.build()
return profile.Parse(&buf)
}
// fmtJSON returns a pretty-printed JSON form for x.
// It works reasonbly well for printing protocol-buffer
// data structures like profile.Profile.
func fmtJSON(x interface{}) string {
js, _ := json.MarshalIndent(x, "", "\t")
return string(js)
}
func TestConvertCPUProfileEmpty(t *testing.T) {
// A test server with mock cpu profile data.
var buf bytes.Buffer
b := []uint64{3, 0, 500} // empty profile at 500 Hz (2ms sample period)
p, err := translateCPUProfile(b)
if err != nil {
t.Fatalf("translateCPUProfile: %v", err)
}
if err := p.Write(&buf); err != nil {
t.Fatalf("writing profile: %v", err)
}
p, err = profile.Parse(&buf)
if err != nil {
t.Fatalf("profile.Parse: %v", err)
}
// Expected PeriodType and SampleType.
periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
sampleType := []*profile.ValueType{
{Type: "samples", Unit: "count"},
{Type: "cpu", Unit: "nanoseconds"},
}
checkProfile(t, p, 2000*1000, periodType, sampleType, nil)
}
func f1() { f1() }
func f2() { f2() }
// testPCs returns two PCs and two corresponding memory mappings
// to use in test profiles.
func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) {
switch runtime.GOOS {
case "linux", "android", "netbsd":
// Figure out two addresses from /proc/self/maps.
mmap, err := ioutil.ReadFile("/proc/self/maps")
if err != nil {
t.Fatal(err)
}
mprof := &profile.Profile{}
if err = mprof.ParseMemoryMap(bytes.NewReader(mmap)); err != nil {
t.Fatalf("parsing /proc/self/maps: %v", err)
}
if len(mprof.Mapping) < 2 {
// It is possible for a binary to only have 1 executable
// region of memory.
t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping))
}
addr1 = mprof.Mapping[0].Start
map1 = mprof.Mapping[0]
map1.BuildID, _ = elfBuildID(map1.File)
addr2 = mprof.Mapping[1].Start
map2 = mprof.Mapping[1]
map2.BuildID, _ = elfBuildID(map2.File)
default:
addr1 = uint64(funcPC(f1))
addr2 = uint64(funcPC(f2))
}
return
}
func TestConvertCPUProfile(t *testing.T) {
addr1, addr2, map1, map2 := testPCs(t)
b := []uint64{
3, 0, 500, // hz = 500
5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1
5, 0, 40, uint64(addr2), uint64(addr2 + 2), // 40 samples in addr2
5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1
}
p, err := translateCPUProfile(b)
if err != nil {
t.Fatalf("translating profile: %v", err)
}
period := int64(2000 * 1000)
periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
sampleType := []*profile.ValueType{
{Type: "samples", Unit: "count"},
{Type: "cpu", Unit: "nanoseconds"},
}
samples := []*profile.Sample{
{Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{
{ID: 1, Mapping: map1, Address: addr1},
{ID: 2, Mapping: map1, Address: addr1 + 1},
}},
{Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{
{ID: 3, Mapping: map2, Address: addr2},
{ID: 4, Mapping: map2, Address: addr2 + 1},
}},
}
checkProfile(t, p, period, periodType, sampleType, samples)
}
func checkProfile(t *testing.T, p *profile.Profile, period int64, periodType *profile.ValueType, sampleType []*profile.ValueType, samples []*profile.Sample) {
if p.Period != period {
t.Fatalf("p.Period = %d, want %d", p.Period, period)
}
if !reflect.DeepEqual(p.PeriodType, periodType) {
t.Fatalf("p.PeriodType = %v\nwant = %v", fmtJSON(p.PeriodType), fmtJSON(periodType))
}
if !reflect.DeepEqual(p.SampleType, sampleType) {
t.Fatalf("p.SampleType = %v\nwant = %v", fmtJSON(p.SampleType), fmtJSON(sampleType))
}
// Clear line info since it is not in the expected samples.
// If we used f1 and f2 above, then the samples will have line info.
for _, s := range p.Sample {
for _, l := range s.Location {
l.Line = nil
}
}
if fmtJSON(p.Sample) != fmtJSON(samples) { // ignore unexported fields
if len(p.Sample) == len(samples) {
for i := range p.Sample {
if !reflect.DeepEqual(p.Sample[i], samples[i]) {
t.Errorf("sample %d = %v\nwant = %v\n", i, fmtJSON(p.Sample[i]), fmtJSON(samples[i]))
}
}
if t.Failed() {
t.FailNow()
}
}
t.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p.Sample), fmtJSON(samples))
}
}
type fakeFunc struct {
name string
file string
lineno int
}
func (f *fakeFunc) Name() string {
return f.name
}
func (f *fakeFunc) FileLine(uintptr) (string, int) {
return f.file, f.lineno
}
/*
// TestRuntimeFunctionTrimming tests if symbolize trims runtime functions as intended.
func TestRuntimeRunctionTrimming(t *testing.T) {
fakeFuncMap := map[uintptr]*fakeFunc{
0x10: &fakeFunc{"runtime.goexit", "runtime.go", 10},
0x20: &fakeFunc{"runtime.other", "runtime.go", 20},
0x30: &fakeFunc{"foo", "foo.go", 30},
0x40: &fakeFunc{"bar", "bar.go", 40},
}
backupFuncForPC := funcForPC
funcForPC = func(pc uintptr) function {
return fakeFuncMap[pc]
}
defer func() {
funcForPC = backupFuncForPC
}()
testLoc := []*profile.Location{
{ID: 1, Address: 0x10},
{ID: 2, Address: 0x20},
{ID: 3, Address: 0x30},
{ID: 4, Address: 0x40},
}
testProfile := &profile.Profile{
Sample: []*profile.Sample{
{Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[2]}},
{Location: []*profile.Location{testLoc[1], testLoc[3], testLoc[2]}},
{Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[1]}},
{Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[0]}},
{Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[0]}},
},
Location: testLoc,
}
testProfiles := make([]*profile.Profile, 2)
testProfiles[0] = testProfile.Copy()
testProfiles[1] = testProfile.Copy()
// Test case for profilez.
testProfiles[0].PeriodType = &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
// Test case for heapz.
testProfiles[1].PeriodType = &profile.ValueType{Type: "space", Unit: "bytes"}
wantFunc := []*profile.Function{
{ID: 1, Name: "runtime.goexit", SystemName: "runtime.goexit", Filename: "runtime.go"},
{ID: 2, Name: "runtime.other", SystemName: "runtime.other", Filename: "runtime.go"},
{ID: 3, Name: "foo", SystemName: "foo", Filename: "foo.go"},
{ID: 4, Name: "bar", SystemName: "bar", Filename: "bar.go"},
}
wantLoc := []*profile.Location{
{ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}},
{ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}},
{ID: 3, Address: 0x30, Line: []profile.Line{{Function: wantFunc[2], Line: 30}}},
{ID: 4, Address: 0x40, Line: []profile.Line{{Function: wantFunc[3], Line: 40}}},
}
wantProfiles := []*profile.Profile{
{
PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"},
Sample: []*profile.Sample{
{Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}},
{Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}},
{Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}},
{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
{Location: []*profile.Location{wantLoc[1], wantLoc[3]}},
},
Location: wantLoc,
Function: wantFunc,
},
{
PeriodType: &profile.ValueType{Type: "space", Unit: "bytes"},
Sample: []*profile.Sample{
{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
{Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}},
{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
{Location: []*profile.Location{wantLoc[3]}},
},
Location: wantLoc,
Function: wantFunc,
},
}
for i := 0; i < 2; i++ {
symbolize(testProfiles[i])
if !reflect.DeepEqual(testProfiles[i], wantProfiles[i]) {
t.Errorf("incorrect trimming (testcase = %d): got {%v}, want {%v}", i, testProfiles[i], wantProfiles[i])
}
}
}
*/