github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/src/runtime/pprof/proto_test.go (about)

     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  	"encoding/json"
    10  	"io/ioutil"
    11  	"reflect"
    12  	"runtime"
    13  	"runtime/pprof/internal/profile"
    14  	"testing"
    15  )
    16  
    17  // translateCPUProfile parses binary CPU profiling stack trace data
    18  // generated by runtime.CPUProfile() into a profile struct.
    19  // This is only used for testing. Real conversions stream the
    20  // data into the profileBuilder as it becomes available.
    21  func translateCPUProfile(data []uint64) (*profile.Profile, error) {
    22  	var buf bytes.Buffer
    23  	b := newProfileBuilder(&buf)
    24  	if err := b.addCPUData(data, nil); err != nil {
    25  		return nil, err
    26  	}
    27  	b.build()
    28  	return profile.Parse(&buf)
    29  }
    30  
    31  // fmtJSON returns a pretty-printed JSON form for x.
    32  // It works reasonbly well for printing protocol-buffer
    33  // data structures like profile.Profile.
    34  func fmtJSON(x interface{}) string {
    35  	js, _ := json.MarshalIndent(x, "", "\t")
    36  	return string(js)
    37  }
    38  
    39  func TestConvertCPUProfileEmpty(t *testing.T) {
    40  	// A test server with mock cpu profile data.
    41  	var buf bytes.Buffer
    42  
    43  	b := []uint64{3, 0, 2000} // empty profile with 2ms sample period
    44  	p, err := translateCPUProfile(b)
    45  	if err != nil {
    46  		t.Fatalf("translateCPUProfile: %v", err)
    47  	}
    48  	if err := p.Write(&buf); err != nil {
    49  		t.Fatalf("writing profile: %v", err)
    50  	}
    51  
    52  	p, err = profile.Parse(&buf)
    53  	if err != nil {
    54  		t.Fatalf("profile.Parse: %v", err)
    55  	}
    56  
    57  	// Expected PeriodType and SampleType.
    58  	periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
    59  	sampleType := []*profile.ValueType{
    60  		{Type: "samples", Unit: "count"},
    61  		{Type: "cpu", Unit: "nanoseconds"},
    62  	}
    63  
    64  	checkProfile(t, p, 2000*1000, periodType, sampleType, nil)
    65  }
    66  
    67  func f1() { f1() }
    68  func f2() { f2() }
    69  
    70  // testPCs returns two PCs and two corresponding memory mappings
    71  // to use in test profiles.
    72  func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) {
    73  	switch runtime.GOOS {
    74  	case "linux", "android", "netbsd":
    75  		// Figure out two addresses from /proc/self/maps.
    76  		mmap, err := ioutil.ReadFile("/proc/self/maps")
    77  		if err != nil {
    78  			t.Fatal(err)
    79  		}
    80  		mprof := &profile.Profile{}
    81  		if err = mprof.ParseMemoryMap(bytes.NewReader(mmap)); err != nil {
    82  			t.Fatalf("parsing /proc/self/maps: %v", err)
    83  		}
    84  		if len(mprof.Mapping) < 2 {
    85  			// It is possible for a binary to only have 1 executable
    86  			// region of memory.
    87  			t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping))
    88  		}
    89  		addr1 = mprof.Mapping[0].Start
    90  		map1 = mprof.Mapping[0]
    91  		map1.BuildID, _ = elfBuildID(map1.File)
    92  		addr2 = mprof.Mapping[1].Start
    93  		map2 = mprof.Mapping[1]
    94  		map2.BuildID, _ = elfBuildID(map2.File)
    95  	default:
    96  		addr1 = uint64(funcPC(f1))
    97  		addr2 = uint64(funcPC(f2))
    98  	}
    99  	return
   100  }
   101  
   102  func TestConvertCPUProfile(t *testing.T) {
   103  	addr1, addr2, map1, map2 := testPCs(t)
   104  
   105  	b := []uint64{
   106  		3, 0, 2000, // periodMs = 2000
   107  		5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1
   108  		5, 0, 40, uint64(addr2), uint64(addr2 + 2), // 40 samples in addr2
   109  		5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1
   110  	}
   111  	p, err := translateCPUProfile(b)
   112  	if err != nil {
   113  		t.Fatalf("translating profile: %v", err)
   114  	}
   115  	period := int64(2000 * 1000)
   116  	periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
   117  	sampleType := []*profile.ValueType{
   118  		{Type: "samples", Unit: "count"},
   119  		{Type: "cpu", Unit: "nanoseconds"},
   120  	}
   121  	samples := []*profile.Sample{
   122  		{Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{
   123  			{ID: 1, Mapping: map1, Address: addr1},
   124  			{ID: 2, Mapping: map1, Address: addr1 + 1},
   125  		}},
   126  		{Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{
   127  			{ID: 3, Mapping: map2, Address: addr2},
   128  			{ID: 4, Mapping: map2, Address: addr2 + 1},
   129  		}},
   130  	}
   131  	checkProfile(t, p, period, periodType, sampleType, samples)
   132  }
   133  
   134  func checkProfile(t *testing.T, p *profile.Profile, period int64, periodType *profile.ValueType, sampleType []*profile.ValueType, samples []*profile.Sample) {
   135  	if p.Period != period {
   136  		t.Fatalf("p.Period = %d, want %d", p.Period, period)
   137  	}
   138  	if !reflect.DeepEqual(p.PeriodType, periodType) {
   139  		t.Fatalf("p.PeriodType = %v\nwant = %v", fmtJSON(p.PeriodType), fmtJSON(periodType))
   140  	}
   141  	if !reflect.DeepEqual(p.SampleType, sampleType) {
   142  		t.Fatalf("p.SampleType = %v\nwant = %v", fmtJSON(p.SampleType), fmtJSON(sampleType))
   143  	}
   144  	// Clear line info since it is not in the expected samples.
   145  	// If we used f1 and f2 above, then the samples will have line info.
   146  	for _, s := range p.Sample {
   147  		for _, l := range s.Location {
   148  			l.Line = nil
   149  		}
   150  	}
   151  	if fmtJSON(p.Sample) != fmtJSON(samples) { // ignore unexported fields
   152  		if len(p.Sample) == len(samples) {
   153  			for i := range p.Sample {
   154  				if !reflect.DeepEqual(p.Sample[i], samples[i]) {
   155  					t.Errorf("sample %d = %v\nwant = %v\n", i, fmtJSON(p.Sample[i]), fmtJSON(samples[i]))
   156  				}
   157  			}
   158  			if t.Failed() {
   159  				t.FailNow()
   160  			}
   161  		}
   162  		t.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p.Sample), fmtJSON(samples))
   163  	}
   164  }
   165  
   166  type fakeFunc struct {
   167  	name   string
   168  	file   string
   169  	lineno int
   170  }
   171  
   172  func (f *fakeFunc) Name() string {
   173  	return f.name
   174  }
   175  func (f *fakeFunc) FileLine(uintptr) (string, int) {
   176  	return f.file, f.lineno
   177  }
   178  
   179  /*
   180  // TestRuntimeFunctionTrimming tests if symbolize trims runtime functions as intended.
   181  func TestRuntimeRunctionTrimming(t *testing.T) {
   182  	fakeFuncMap := map[uintptr]*fakeFunc{
   183  		0x10: &fakeFunc{"runtime.goexit", "runtime.go", 10},
   184  		0x20: &fakeFunc{"runtime.other", "runtime.go", 20},
   185  		0x30: &fakeFunc{"foo", "foo.go", 30},
   186  		0x40: &fakeFunc{"bar", "bar.go", 40},
   187  	}
   188  	backupFuncForPC := funcForPC
   189  	funcForPC = func(pc uintptr) function {
   190  		return fakeFuncMap[pc]
   191  	}
   192  	defer func() {
   193  		funcForPC = backupFuncForPC
   194  	}()
   195  	testLoc := []*profile.Location{
   196  		{ID: 1, Address: 0x10},
   197  		{ID: 2, Address: 0x20},
   198  		{ID: 3, Address: 0x30},
   199  		{ID: 4, Address: 0x40},
   200  	}
   201  	testProfile := &profile.Profile{
   202  		Sample: []*profile.Sample{
   203  			{Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[2]}},
   204  			{Location: []*profile.Location{testLoc[1], testLoc[3], testLoc[2]}},
   205  			{Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[1]}},
   206  			{Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[0]}},
   207  			{Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[0]}},
   208  		},
   209  		Location: testLoc,
   210  	}
   211  	testProfiles := make([]*profile.Profile, 2)
   212  	testProfiles[0] = testProfile.Copy()
   213  	testProfiles[1] = testProfile.Copy()
   214  	// Test case for profilez.
   215  	testProfiles[0].PeriodType = &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
   216  	// Test case for heapz.
   217  	testProfiles[1].PeriodType = &profile.ValueType{Type: "space", Unit: "bytes"}
   218  	wantFunc := []*profile.Function{
   219  		{ID: 1, Name: "runtime.goexit", SystemName: "runtime.goexit", Filename: "runtime.go"},
   220  		{ID: 2, Name: "runtime.other", SystemName: "runtime.other", Filename: "runtime.go"},
   221  		{ID: 3, Name: "foo", SystemName: "foo", Filename: "foo.go"},
   222  		{ID: 4, Name: "bar", SystemName: "bar", Filename: "bar.go"},
   223  	}
   224  	wantLoc := []*profile.Location{
   225  		{ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}},
   226  		{ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}},
   227  		{ID: 3, Address: 0x30, Line: []profile.Line{{Function: wantFunc[2], Line: 30}}},
   228  		{ID: 4, Address: 0x40, Line: []profile.Line{{Function: wantFunc[3], Line: 40}}},
   229  	}
   230  	wantProfiles := []*profile.Profile{
   231  		{
   232  			PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"},
   233  			Sample: []*profile.Sample{
   234  				{Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}},
   235  				{Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}},
   236  				{Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}},
   237  				{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
   238  				{Location: []*profile.Location{wantLoc[1], wantLoc[3]}},
   239  			},
   240  			Location: wantLoc,
   241  			Function: wantFunc,
   242  		},
   243  		{
   244  			PeriodType: &profile.ValueType{Type: "space", Unit: "bytes"},
   245  			Sample: []*profile.Sample{
   246  				{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
   247  				{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
   248  				{Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}},
   249  				{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
   250  				{Location: []*profile.Location{wantLoc[3]}},
   251  			},
   252  			Location: wantLoc,
   253  			Function: wantFunc,
   254  		},
   255  	}
   256  	for i := 0; i < 2; i++ {
   257  		symbolize(testProfiles[i])
   258  		if !reflect.DeepEqual(testProfiles[i], wantProfiles[i]) {
   259  			t.Errorf("incorrect trimming (testcase = %d): got {%v}, want {%v}", i, testProfiles[i], wantProfiles[i])
   260  		}
   261  	}
   262  }
   263  */