github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/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  	"fmt"
    10  	"internal/pprof/profile"
    11  	"io/ioutil"
    12  	"reflect"
    13  	"runtime"
    14  	"testing"
    15  	"time"
    16  	"unsafe"
    17  )
    18  
    19  // Helper function to initialize empty cpu profile with sampling period provided.
    20  func createEmptyProfileWithPeriod(t *testing.T, periodMs uint64) bytes.Buffer {
    21  	// Mock the sample header produced by cpu profiler. Write a sample
    22  	// period of 2000 microseconds, followed by no samples.
    23  	buf := new(bytes.Buffer)
    24  	// Profile header is as follows:
    25  	// The first, third and fifth words are 0. The second word is 3.
    26  	// The fourth word is the period.
    27  	// EOD marker:
    28  	// The sixth word -- count is initialized to 0 above.
    29  	// The code below sets the seventh word -- nstk to 1
    30  	// The eighth word -- addr is initialized to 0 above.
    31  	words := []int{0, 3, 0, int(periodMs), 0, 0, 1, 0}
    32  	n := int(unsafe.Sizeof(0)) * len(words)
    33  	data := ((*[1 << 29]byte)(unsafe.Pointer(&words[0])))[:n:n]
    34  	if _, err := buf.Write(data); err != nil {
    35  		t.Fatalf("createEmptyProfileWithPeriod failed: %v", err)
    36  	}
    37  	return *buf
    38  }
    39  
    40  // Helper function to initialize cpu profile with two sample values.
    41  func createProfileWithTwoSamples(t *testing.T, periodMs uintptr, count1 uintptr, count2 uintptr,
    42  	address1 uintptr, address2 uintptr) bytes.Buffer {
    43  	// Mock the sample header produced by cpu profiler. Write a sample
    44  	// period of 2000 microseconds, followed by no samples.
    45  	buf := new(bytes.Buffer)
    46  	words := []uintptr{0, 3, 0, uintptr(periodMs), 0, uintptr(count1), 2,
    47  		uintptr(address1), uintptr(address1 + 2),
    48  		uintptr(count2), 2, uintptr(address2), uintptr(address2 + 2),
    49  		0, 1, 0}
    50  	for _, n := range words {
    51  		var err error
    52  		switch unsafe.Sizeof(int(0)) {
    53  		case 8:
    54  			_, err = buf.Write((*[8]byte)(unsafe.Pointer(&n))[:8:8])
    55  		case 4:
    56  			_, err = buf.Write((*[4]byte)(unsafe.Pointer(&n))[:4:4])
    57  		}
    58  		if err != nil {
    59  			t.Fatalf("createProfileWithTwoSamples failed: %v", err)
    60  		}
    61  	}
    62  	return *buf
    63  }
    64  
    65  // Tests translateCPUProfile parses correct sampling period in an otherwise empty cpu profile.
    66  func TestTranlateCPUProfileSamplingPeriod(t *testing.T) {
    67  	// A test server with mock cpu profile data.
    68  	var buf bytes.Buffer
    69  
    70  	startTime := time.Now()
    71  	b := createEmptyProfileWithPeriod(t, 2000)
    72  	p, err := translateCPUProfile(b.Bytes(), startTime)
    73  	if err != nil {
    74  		t.Fatalf("translate failed: %v", err)
    75  	}
    76  	if err := p.Write(&buf); err != nil {
    77  		t.Fatalf("write failed: %v", err)
    78  	}
    79  
    80  	p, err = profile.Parse(&buf)
    81  	if err != nil {
    82  		t.Fatalf("Could not parse Profile profile: %v", err)
    83  	}
    84  
    85  	// Expected PeriodType and SampleType.
    86  	expectedPeriodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
    87  	expectedSampleType := []*profile.ValueType{
    88  		{Type: "samples", Unit: "count"},
    89  		{Type: "cpu", Unit: "nanoseconds"},
    90  	}
    91  	if p.Period != 2000*1000 || !reflect.DeepEqual(p.PeriodType, expectedPeriodType) ||
    92  		!reflect.DeepEqual(p.SampleType, expectedSampleType) || p.Sample != nil {
    93  		t.Fatalf("Unexpected Profile fields")
    94  	}
    95  }
    96  
    97  func getSampleAsString(sample []*profile.Sample) string {
    98  	var str string
    99  	for _, x := range sample {
   100  		for _, y := range x.Location {
   101  			if y.Mapping != nil {
   102  				str += fmt.Sprintf("Mapping:%v\n", *y.Mapping)
   103  			}
   104  			str += fmt.Sprintf("Location:%v\n", y)
   105  		}
   106  		str += fmt.Sprintf("Sample:%v\n", *x)
   107  	}
   108  	return str
   109  }
   110  
   111  // Tests translateCPUProfile parses a cpu profile with sample values present.
   112  func TestTranslateCPUProfileWithSamples(t *testing.T) {
   113  	if runtime.GOOS != "linux" {
   114  		t.Skip("test requires a system with /proc/self/maps")
   115  	}
   116  	// Figure out two addresses from /proc/self/maps.
   117  	mmap, err := ioutil.ReadFile("/proc/self/maps")
   118  	if err != nil {
   119  		t.Fatal("Cannot read /proc/self/maps")
   120  	}
   121  	rd := bytes.NewReader(mmap)
   122  	mprof := &profile.Profile{}
   123  	if err = mprof.ParseMemoryMap(rd); err != nil {
   124  		t.Fatalf("Cannot parse /proc/self/maps")
   125  	}
   126  	if len(mprof.Mapping) < 2 {
   127  		// It is possible for a binary to only have 1 executable
   128  		// region of memory.
   129  		t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping))
   130  	}
   131  	address1 := mprof.Mapping[0].Start
   132  	address2 := mprof.Mapping[1].Start
   133  	// A test server with mock cpu profile data.
   134  
   135  	startTime := time.Now()
   136  	b := createProfileWithTwoSamples(t, 2000, 20, 40, uintptr(address1), uintptr(address2))
   137  	p, err := translateCPUProfile(b.Bytes(), startTime)
   138  
   139  	if err != nil {
   140  		t.Fatalf("Could not parse Profile profile: %v", err)
   141  	}
   142  	// Expected PeriodType, SampleType and Sample.
   143  	expectedPeriodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
   144  	expectedSampleType := []*profile.ValueType{
   145  		{Type: "samples", Unit: "count"},
   146  		{Type: "cpu", Unit: "nanoseconds"},
   147  	}
   148  	expectedSample := []*profile.Sample{
   149  		{Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{
   150  			{ID: 1, Mapping: mprof.Mapping[0], Address: address1},
   151  			{ID: 2, Mapping: mprof.Mapping[0], Address: address1 + 1},
   152  		}},
   153  		{Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{
   154  			{ID: 3, Mapping: mprof.Mapping[1], Address: address2},
   155  			{ID: 4, Mapping: mprof.Mapping[1], Address: address2 + 1},
   156  		}},
   157  	}
   158  	if p.Period != 2000*1000 {
   159  		t.Fatalf("Sampling periods do not match")
   160  	}
   161  	if !reflect.DeepEqual(p.PeriodType, expectedPeriodType) {
   162  		t.Fatalf("Period types do not match")
   163  	}
   164  	if !reflect.DeepEqual(p.SampleType, expectedSampleType) {
   165  		t.Fatalf("Sample types do not match")
   166  	}
   167  	if !reflect.DeepEqual(p.Sample, expectedSample) {
   168  		t.Fatalf("Samples do not match: Expected: %v, Got:%v", getSampleAsString(expectedSample),
   169  			getSampleAsString(p.Sample))
   170  	}
   171  }
   172  
   173  type fakeFunc struct {
   174  	name   string
   175  	file   string
   176  	lineno int
   177  }
   178  
   179  func (f *fakeFunc) Name() string {
   180  	return f.name
   181  }
   182  func (f *fakeFunc) FileLine(_ uintptr) (string, int) {
   183  	return f.file, f.lineno
   184  }
   185  
   186  // TestRuntimeFunctionTrimming tests if symbolize trims runtime functions as intended.
   187  func TestRuntimeRunctionTrimming(t *testing.T) {
   188  	fakeFuncMap := map[uintptr]*fakeFunc{
   189  		0x10: &fakeFunc{"runtime.goexit", "runtime.go", 10},
   190  		0x20: &fakeFunc{"runtime.other", "runtime.go", 20},
   191  		0x30: &fakeFunc{"foo", "foo.go", 30},
   192  		0x40: &fakeFunc{"bar", "bar.go", 40},
   193  	}
   194  	backupFuncForPC := funcForPC
   195  	funcForPC = func(pc uintptr) function {
   196  		return fakeFuncMap[pc]
   197  	}
   198  	defer func() {
   199  		funcForPC = backupFuncForPC
   200  	}()
   201  	testLoc := []*profile.Location{
   202  		{ID: 1, Address: 0x10},
   203  		{ID: 2, Address: 0x20},
   204  		{ID: 3, Address: 0x30},
   205  		{ID: 4, Address: 0x40},
   206  	}
   207  	testProfile := &profile.Profile{
   208  		Sample: []*profile.Sample{
   209  			{Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[2]}},
   210  			{Location: []*profile.Location{testLoc[1], testLoc[3], testLoc[2]}},
   211  			{Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[1]}},
   212  			{Location: []*profile.Location{testLoc[3], testLoc[2], testLoc[0]}},
   213  			{Location: []*profile.Location{testLoc[0], testLoc[1], testLoc[3], testLoc[0]}},
   214  		},
   215  		Location: testLoc,
   216  	}
   217  	testProfiles := make([]*profile.Profile, 2)
   218  	testProfiles[0] = testProfile.Copy()
   219  	testProfiles[1] = testProfile.Copy()
   220  	// Test case for profilez.
   221  	testProfiles[0].PeriodType = &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
   222  	// Test case for heapz.
   223  	testProfiles[1].PeriodType = &profile.ValueType{Type: "space", Unit: "bytes"}
   224  	wantFunc := []*profile.Function{
   225  		{ID: 1, Name: "runtime.goexit", SystemName: "runtime.goexit", Filename: "runtime.go"},
   226  		{ID: 2, Name: "runtime.other", SystemName: "runtime.other", Filename: "runtime.go"},
   227  		{ID: 3, Name: "foo", SystemName: "foo", Filename: "foo.go"},
   228  		{ID: 4, Name: "bar", SystemName: "bar", Filename: "bar.go"},
   229  	}
   230  	wantLoc := []*profile.Location{
   231  		{ID: 1, Address: 0x10, Line: []profile.Line{{Function: wantFunc[0], Line: 10}}},
   232  		{ID: 2, Address: 0x20, Line: []profile.Line{{Function: wantFunc[1], Line: 20}}},
   233  		{ID: 3, Address: 0x30, Line: []profile.Line{{Function: wantFunc[2], Line: 30}}},
   234  		{ID: 4, Address: 0x40, Line: []profile.Line{{Function: wantFunc[3], Line: 40}}},
   235  	}
   236  	wantProfiles := []*profile.Profile{
   237  		{
   238  			PeriodType: &profile.ValueType{Type: "cpu", Unit: "nanoseconds"},
   239  			Sample: []*profile.Sample{
   240  				{Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}},
   241  				{Location: []*profile.Location{wantLoc[1], wantLoc[3], wantLoc[2]}},
   242  				{Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}},
   243  				{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
   244  				{Location: []*profile.Location{wantLoc[1], wantLoc[3]}},
   245  			},
   246  			Location: wantLoc,
   247  			Function: wantFunc,
   248  		},
   249  		{
   250  			PeriodType: &profile.ValueType{Type: "space", Unit: "bytes"},
   251  			Sample: []*profile.Sample{
   252  				{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
   253  				{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
   254  				{Location: []*profile.Location{wantLoc[3], wantLoc[2], wantLoc[1]}},
   255  				{Location: []*profile.Location{wantLoc[3], wantLoc[2]}},
   256  				{Location: []*profile.Location{wantLoc[3]}},
   257  			},
   258  			Location: wantLoc,
   259  			Function: wantFunc,
   260  		},
   261  	}
   262  	for i := 0; i < 2; i++ {
   263  		symbolize(testProfiles[i])
   264  		if !reflect.DeepEqual(testProfiles[i], wantProfiles[i]) {
   265  			t.Errorf("incorrect trimming (testcase = %d): got {%v}, want {%v}", i, testProfiles[i], wantProfiles[i])
   266  		}
   267  	}
   268  }