github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/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  	"fmt"
    11  	"io/ioutil"
    12  	"reflect"
    13  	"runtime"
    14  	"runtime/pprof/internal/profile"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  // translateCPUProfile parses binary CPU profiling stack trace data
    20  // generated by runtime.CPUProfile() into a profile struct.
    21  // This is only used for testing. Real conversions stream the
    22  // data into the profileBuilder as it becomes available.
    23  func translateCPUProfile(data []uint64) (*profile.Profile, error) {
    24  	var buf bytes.Buffer
    25  	b := newProfileBuilder(&buf)
    26  	if err := b.addCPUData(data, nil); err != nil {
    27  		return nil, err
    28  	}
    29  	b.build()
    30  	return profile.Parse(&buf)
    31  }
    32  
    33  // fmtJSON returns a pretty-printed JSON form for x.
    34  // It works reasonbly well for printing protocol-buffer
    35  // data structures like profile.Profile.
    36  func fmtJSON(x interface{}) string {
    37  	js, _ := json.MarshalIndent(x, "", "\t")
    38  	return string(js)
    39  }
    40  
    41  func TestConvertCPUProfileEmpty(t *testing.T) {
    42  	// A test server with mock cpu profile data.
    43  	var buf bytes.Buffer
    44  
    45  	b := []uint64{3, 0, 500} // empty profile at 500 Hz (2ms sample period)
    46  	p, err := translateCPUProfile(b)
    47  	if err != nil {
    48  		t.Fatalf("translateCPUProfile: %v", err)
    49  	}
    50  	if err := p.Write(&buf); err != nil {
    51  		t.Fatalf("writing profile: %v", err)
    52  	}
    53  
    54  	p, err = profile.Parse(&buf)
    55  	if err != nil {
    56  		t.Fatalf("profile.Parse: %v", err)
    57  	}
    58  
    59  	// Expected PeriodType and SampleType.
    60  	periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
    61  	sampleType := []*profile.ValueType{
    62  		{Type: "samples", Unit: "count"},
    63  		{Type: "cpu", Unit: "nanoseconds"},
    64  	}
    65  
    66  	checkProfile(t, p, 2000*1000, periodType, sampleType, nil)
    67  }
    68  
    69  func f1() { f1() }
    70  func f2() { f2() }
    71  
    72  // testPCs returns two PCs and two corresponding memory mappings
    73  // to use in test profiles.
    74  func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) {
    75  	switch runtime.GOOS {
    76  	case "linux", "android", "netbsd":
    77  		// Figure out two addresses from /proc/self/maps.
    78  		mmap, err := ioutil.ReadFile("/proc/self/maps")
    79  		if err != nil {
    80  			t.Fatal(err)
    81  		}
    82  		mprof := &profile.Profile{}
    83  		if err = mprof.ParseMemoryMap(bytes.NewReader(mmap)); err != nil {
    84  			t.Fatalf("parsing /proc/self/maps: %v", err)
    85  		}
    86  		if len(mprof.Mapping) < 2 {
    87  			// It is possible for a binary to only have 1 executable
    88  			// region of memory.
    89  			t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping))
    90  		}
    91  		addr1 = mprof.Mapping[0].Start
    92  		map1 = mprof.Mapping[0]
    93  		map1.BuildID, _ = elfBuildID(map1.File)
    94  		addr2 = mprof.Mapping[1].Start
    95  		map2 = mprof.Mapping[1]
    96  		map2.BuildID, _ = elfBuildID(map2.File)
    97  	default:
    98  		addr1 = uint64(funcPC(f1))
    99  		addr2 = uint64(funcPC(f2))
   100  	}
   101  	return
   102  }
   103  
   104  func TestConvertCPUProfile(t *testing.T) {
   105  	addr1, addr2, map1, map2 := testPCs(t)
   106  
   107  	b := []uint64{
   108  		3, 0, 500, // hz = 500
   109  		5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1
   110  		5, 0, 40, uint64(addr2), uint64(addr2 + 2), // 40 samples in addr2
   111  		5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1
   112  	}
   113  	p, err := translateCPUProfile(b)
   114  	if err != nil {
   115  		t.Fatalf("translating profile: %v", err)
   116  	}
   117  	period := int64(2000 * 1000)
   118  	periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
   119  	sampleType := []*profile.ValueType{
   120  		{Type: "samples", Unit: "count"},
   121  		{Type: "cpu", Unit: "nanoseconds"},
   122  	}
   123  	samples := []*profile.Sample{
   124  		{Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{
   125  			{ID: 1, Mapping: map1, Address: addr1},
   126  			{ID: 2, Mapping: map1, Address: addr1 + 1},
   127  		}},
   128  		{Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{
   129  			{ID: 3, Mapping: map2, Address: addr2},
   130  			{ID: 4, Mapping: map2, Address: addr2 + 1},
   131  		}},
   132  	}
   133  	checkProfile(t, p, period, periodType, sampleType, samples)
   134  }
   135  
   136  func checkProfile(t *testing.T, p *profile.Profile, period int64, periodType *profile.ValueType, sampleType []*profile.ValueType, samples []*profile.Sample) {
   137  	if p.Period != period {
   138  		t.Fatalf("p.Period = %d, want %d", p.Period, period)
   139  	}
   140  	if !reflect.DeepEqual(p.PeriodType, periodType) {
   141  		t.Fatalf("p.PeriodType = %v\nwant = %v", fmtJSON(p.PeriodType), fmtJSON(periodType))
   142  	}
   143  	if !reflect.DeepEqual(p.SampleType, sampleType) {
   144  		t.Fatalf("p.SampleType = %v\nwant = %v", fmtJSON(p.SampleType), fmtJSON(sampleType))
   145  	}
   146  	// Clear line info since it is not in the expected samples.
   147  	// If we used f1 and f2 above, then the samples will have line info.
   148  	for _, s := range p.Sample {
   149  		for _, l := range s.Location {
   150  			l.Line = nil
   151  		}
   152  	}
   153  	if fmtJSON(p.Sample) != fmtJSON(samples) { // ignore unexported fields
   154  		if len(p.Sample) == len(samples) {
   155  			for i := range p.Sample {
   156  				if !reflect.DeepEqual(p.Sample[i], samples[i]) {
   157  					t.Errorf("sample %d = %v\nwant = %v\n", i, fmtJSON(p.Sample[i]), fmtJSON(samples[i]))
   158  				}
   159  			}
   160  			if t.Failed() {
   161  				t.FailNow()
   162  			}
   163  		}
   164  		t.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p.Sample), fmtJSON(samples))
   165  	}
   166  }
   167  
   168  var profSelfMapsTests = `
   169  00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat
   170  0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat
   171  0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat
   172  014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap]
   173  7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive
   174  7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   175  7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   176  7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   177  7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   178  7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
   179  7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   180  7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
   181  7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
   182  7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   183  7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   184  7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
   185  7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack]
   186  7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso]
   187  ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0                  [vsyscall]
   188  ->
   189  00400000 0040b000 00000000 /bin/cat
   190  7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so
   191  7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so
   192  7ffc34343000 7ffc34345000 00000000 [vdso]
   193  ffffffffff600000 ffffffffff601000 00000090 [vsyscall]
   194  
   195  00400000-07000000 r-xp 00000000 00:00 0 
   196  07000000-07093000 r-xp 06c00000 00:2e 536754                             /path/to/gobench_server_main
   197  07093000-0722d000 rw-p 06c92000 00:2e 536754                             /path/to/gobench_server_main
   198  0722d000-07b21000 rw-p 00000000 00:00 0 
   199  c000000000-c000036000 rw-p 00000000 00:00 0 
   200  ->
   201  07000000 07093000 06c00000 /path/to/gobench_server_main
   202  `
   203  
   204  func TestProcSelfMaps(t *testing.T) {
   205  	for tx, tt := range strings.Split(profSelfMapsTests, "\n\n") {
   206  		i := strings.Index(tt, "->\n")
   207  		if i < 0 {
   208  			t.Fatal("malformed test case")
   209  		}
   210  		in, out := tt[:i], tt[i+len("->\n"):]
   211  		if len(out) > 0 && out[len(out)-1] != '\n' {
   212  			out += "\n"
   213  		}
   214  		var buf bytes.Buffer
   215  		parseProcSelfMaps([]byte(in), func(lo, hi, offset uint64, file, buildID string) {
   216  			fmt.Fprintf(&buf, "%08x %08x %08x %s\n", lo, hi, offset, file)
   217  		})
   218  		if buf.String() != out {
   219  			t.Errorf("#%d: have:\n%s\nwant:\n%s\n%q\n%q", tx, buf.String(), out, buf.String(), out)
   220  		}
   221  	}
   222  }