Home | History | Annotate | Download | only in protopprof
      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 protopprof
      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