github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/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  	"internal/testenv"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/exec"
    15  	"reflect"
    16  	"runtime"
    17  	"runtime/pprof/internal/profile"
    18  	"strings"
    19  	"testing"
    20  )
    21  
    22  // translateCPUProfile parses binary CPU profiling stack trace data
    23  // generated by runtime.CPUProfile() into a profile struct.
    24  // This is only used for testing. Real conversions stream the
    25  // data into the profileBuilder as it becomes available.
    26  func translateCPUProfile(data []uint64) (*profile.Profile, error) {
    27  	var buf bytes.Buffer
    28  	b := newProfileBuilder(&buf)
    29  	if err := b.addCPUData(data, nil); err != nil {
    30  		return nil, err
    31  	}
    32  	b.build()
    33  	return profile.Parse(&buf)
    34  }
    35  
    36  // fmtJSON returns a pretty-printed JSON form for x.
    37  // It works reasonbly well for printing protocol-buffer
    38  // data structures like profile.Profile.
    39  func fmtJSON(x interface{}) string {
    40  	js, _ := json.MarshalIndent(x, "", "\t")
    41  	return string(js)
    42  }
    43  
    44  func TestConvertCPUProfileEmpty(t *testing.T) {
    45  	// A test server with mock cpu profile data.
    46  	var buf bytes.Buffer
    47  
    48  	b := []uint64{3, 0, 500} // empty profile at 500 Hz (2ms sample period)
    49  	p, err := translateCPUProfile(b)
    50  	if err != nil {
    51  		t.Fatalf("translateCPUProfile: %v", err)
    52  	}
    53  	if err := p.Write(&buf); err != nil {
    54  		t.Fatalf("writing profile: %v", err)
    55  	}
    56  
    57  	p, err = profile.Parse(&buf)
    58  	if err != nil {
    59  		t.Fatalf("profile.Parse: %v", err)
    60  	}
    61  
    62  	// Expected PeriodType and SampleType.
    63  	periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
    64  	sampleType := []*profile.ValueType{
    65  		{Type: "samples", Unit: "count"},
    66  		{Type: "cpu", Unit: "nanoseconds"},
    67  	}
    68  
    69  	checkProfile(t, p, 2000*1000, periodType, sampleType, nil, "")
    70  }
    71  
    72  // For gccgo make these functions different so that gccgo doesn't
    73  // merge them with each other and with lostProfileEvent.
    74  func f1(i int) { f1(i + 1) }
    75  func f2(i int) { f2(i + 2) }
    76  
    77  // testPCs returns two PCs and two corresponding memory mappings
    78  // to use in test profiles.
    79  func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) {
    80  	switch runtime.GOOS {
    81  	case "linux", "android", "netbsd":
    82  		// Figure out two addresses from /proc/self/maps.
    83  		mmap, err := ioutil.ReadFile("/proc/self/maps")
    84  		if err != nil {
    85  			t.Fatal(err)
    86  		}
    87  		mprof := &profile.Profile{}
    88  		if err = mprof.ParseMemoryMap(bytes.NewReader(mmap)); err != nil {
    89  			t.Fatalf("parsing /proc/self/maps: %v", err)
    90  		}
    91  		if len(mprof.Mapping) < 2 {
    92  			// It is possible for a binary to only have 1 executable
    93  			// region of memory.
    94  			t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping))
    95  		}
    96  		addr1 = mprof.Mapping[0].Start
    97  		map1 = mprof.Mapping[0]
    98  		map1.BuildID, _ = elfBuildID(map1.File)
    99  		addr2 = mprof.Mapping[1].Start
   100  		map2 = mprof.Mapping[1]
   101  		map2.BuildID, _ = elfBuildID(map2.File)
   102  	case "js":
   103  		addr1 = uint64(funcPC(f1))
   104  		addr2 = uint64(funcPC(f2))
   105  	default:
   106  		addr1 = uint64(funcPC(f1))
   107  		addr2 = uint64(funcPC(f2))
   108  		// Fake mapping - HasFunctions will be true because two PCs from Go
   109  		// will be fully symbolized.
   110  		fake := &profile.Mapping{ID: 1, HasFunctions: true}
   111  		map1, map2 = fake, fake
   112  	}
   113  	return
   114  }
   115  
   116  func TestConvertCPUProfile(t *testing.T) {
   117  	addr1, addr2, map1, map2 := testPCs(t)
   118  
   119  	b := []uint64{
   120  		3, 0, 500, // hz = 500
   121  		5, 0, 10, uint64(addr1 + 1), uint64(addr1 + 2), // 10 samples in addr1
   122  		5, 0, 40, uint64(addr2 + 1), uint64(addr2 + 2), // 40 samples in addr2
   123  		5, 0, 10, uint64(addr1 + 1), uint64(addr1 + 2), // 10 samples in addr1
   124  	}
   125  	p, err := translateCPUProfile(b)
   126  	if err != nil {
   127  		t.Fatalf("translating profile: %v", err)
   128  	}
   129  	period := int64(2000 * 1000)
   130  	periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
   131  	sampleType := []*profile.ValueType{
   132  		{Type: "samples", Unit: "count"},
   133  		{Type: "cpu", Unit: "nanoseconds"},
   134  	}
   135  	samples := []*profile.Sample{
   136  		{Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{
   137  			{ID: 1, Mapping: map1, Address: addr1},
   138  			{ID: 2, Mapping: map1, Address: addr1 + 1},
   139  		}},
   140  		{Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{
   141  			{ID: 3, Mapping: map2, Address: addr2},
   142  			{ID: 4, Mapping: map2, Address: addr2 + 1},
   143  		}},
   144  	}
   145  	checkProfile(t, p, period, periodType, sampleType, samples, "")
   146  }
   147  
   148  func checkProfile(t *testing.T, p *profile.Profile, period int64, periodType *profile.ValueType, sampleType []*profile.ValueType, samples []*profile.Sample, defaultSampleType string) {
   149  	t.Helper()
   150  
   151  	if p.Period != period {
   152  		t.Errorf("p.Period = %d, want %d", p.Period, period)
   153  	}
   154  	if !reflect.DeepEqual(p.PeriodType, periodType) {
   155  		t.Errorf("p.PeriodType = %v\nwant = %v", fmtJSON(p.PeriodType), fmtJSON(periodType))
   156  	}
   157  	if !reflect.DeepEqual(p.SampleType, sampleType) {
   158  		t.Errorf("p.SampleType = %v\nwant = %v", fmtJSON(p.SampleType), fmtJSON(sampleType))
   159  	}
   160  	if defaultSampleType != p.DefaultSampleType {
   161  		t.Errorf("p.DefaultSampleType = %v\nwant = %v", p.DefaultSampleType, defaultSampleType)
   162  	}
   163  	// Clear line info since it is not in the expected samples.
   164  	// If we used f1 and f2 above, then the samples will have line info.
   165  	for _, s := range p.Sample {
   166  		for _, l := range s.Location {
   167  			l.Line = nil
   168  		}
   169  	}
   170  	if fmtJSON(p.Sample) != fmtJSON(samples) { // ignore unexported fields
   171  		if len(p.Sample) == len(samples) {
   172  			for i := range p.Sample {
   173  				if !reflect.DeepEqual(p.Sample[i], samples[i]) {
   174  					t.Errorf("sample %d = %v\nwant = %v\n", i, fmtJSON(p.Sample[i]), fmtJSON(samples[i]))
   175  				}
   176  			}
   177  			if t.Failed() {
   178  				t.FailNow()
   179  			}
   180  		}
   181  		t.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p.Sample), fmtJSON(samples))
   182  	}
   183  }
   184  
   185  var profSelfMapsTests = `
   186  00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat
   187  0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat
   188  0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat
   189  014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap]
   190  7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive
   191  7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   192  7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   193  7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   194  7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   195  7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
   196  7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   197  7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
   198  7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
   199  7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   200  7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   201  7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
   202  7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack]
   203  7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso]
   204  ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0                  [vsyscall]
   205  ->
   206  00400000 0040b000 00000000 /bin/cat
   207  7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so
   208  7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so
   209  7ffc34343000 7ffc34345000 00000000 [vdso]
   210  ffffffffff600000 ffffffffff601000 00000090 [vsyscall]
   211  
   212  00400000-07000000 r-xp 00000000 00:00 0
   213  07000000-07093000 r-xp 06c00000 00:2e 536754                             /path/to/gobench_server_main
   214  07093000-0722d000 rw-p 06c92000 00:2e 536754                             /path/to/gobench_server_main
   215  0722d000-07b21000 rw-p 00000000 00:00 0
   216  c000000000-c000036000 rw-p 00000000 00:00 0
   217  ->
   218  07000000 07093000 06c00000 /path/to/gobench_server_main
   219  `
   220  
   221  var profSelfMapsTestsWithDeleted = `
   222  00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat (deleted)
   223  0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat (deleted)
   224  0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat (deleted)
   225  014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap]
   226  7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive
   227  7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   228  7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   229  7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   230  7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   231  7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
   232  7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   233  7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
   234  7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
   235  7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   236  7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   237  7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
   238  7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack]
   239  7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso]
   240  ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0                  [vsyscall]
   241  ->
   242  00400000 0040b000 00000000 /bin/cat
   243  7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so
   244  7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so
   245  7ffc34343000 7ffc34345000 00000000 [vdso]
   246  ffffffffff600000 ffffffffff601000 00000090 [vsyscall]
   247  
   248  00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat with space
   249  0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat with space
   250  0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat with space
   251  014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap]
   252  7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive
   253  7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   254  7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   255  7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   256  7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   257  7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
   258  7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   259  7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
   260  7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
   261  7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   262  7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   263  7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
   264  7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack]
   265  7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso]
   266  ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0                  [vsyscall]
   267  ->
   268  00400000 0040b000 00000000 /bin/cat with space
   269  7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so
   270  7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so
   271  7ffc34343000 7ffc34345000 00000000 [vdso]
   272  ffffffffff600000 ffffffffff601000 00000090 [vsyscall]
   273  `
   274  
   275  func TestProcSelfMaps(t *testing.T) {
   276  
   277  	f := func(t *testing.T, input string) {
   278  		for tx, tt := range strings.Split(input, "\n\n") {
   279  			i := strings.Index(tt, "->\n")
   280  			if i < 0 {
   281  				t.Fatal("malformed test case")
   282  			}
   283  			in, out := tt[:i], tt[i+len("->\n"):]
   284  			if len(out) > 0 && out[len(out)-1] != '\n' {
   285  				out += "\n"
   286  			}
   287  			var buf bytes.Buffer
   288  			parseProcSelfMaps([]byte(in), func(lo, hi, offset uint64, file, buildID string) {
   289  				fmt.Fprintf(&buf, "%08x %08x %08x %s\n", lo, hi, offset, file)
   290  			})
   291  			if buf.String() != out {
   292  				t.Errorf("#%d: have:\n%s\nwant:\n%s\n%q\n%q", tx, buf.String(), out, buf.String(), out)
   293  			}
   294  		}
   295  	}
   296  
   297  	t.Run("Normal", func(t *testing.T) {
   298  		f(t, profSelfMapsTests)
   299  	})
   300  
   301  	t.Run("WithDeletedFile", func(t *testing.T) {
   302  		f(t, profSelfMapsTestsWithDeleted)
   303  	})
   304  }
   305  
   306  // TestMapping checks the mapping section of CPU profiles
   307  // has the HasFunctions field set correctly. If all PCs included
   308  // in the samples are successfully symbolized, the corresponding
   309  // mapping entry (in this test case, only one entry) should have
   310  // its HasFunctions field set true.
   311  // The test generates a CPU profile that includes PCs from C side
   312  // that the runtime can't symbolize. See ./testdata/mappingtest.
   313  func TestMapping(t *testing.T) {
   314  	testenv.MustHaveGoRun(t)
   315  	testenv.MustHaveCGO(t)
   316  
   317  	prog := "./testdata/mappingtest/main.go"
   318  
   319  	// GoOnly includes only Go symbols that runtime will symbolize.
   320  	// Go+C includes C symbols that runtime will not symbolize.
   321  	for _, traceback := range []string{"GoOnly", "Go+C"} {
   322  		t.Run("traceback"+traceback, func(t *testing.T) {
   323  			cmd := exec.Command(testenv.GoToolPath(t), "run", prog)
   324  			if traceback != "GoOnly" {
   325  				cmd.Env = append(os.Environ(), "SETCGOTRACEBACK=1")
   326  			}
   327  			cmd.Stderr = new(bytes.Buffer)
   328  
   329  			out, err := cmd.Output()
   330  			if err != nil {
   331  				t.Fatalf("failed to run the test program %q: %v\n%v", prog, err, cmd.Stderr)
   332  			}
   333  
   334  			prof, err := profile.Parse(bytes.NewReader(out))
   335  			if err != nil {
   336  				t.Fatalf("failed to parse the generated profile data: %v", err)
   337  			}
   338  			t.Logf("Profile: %s", prof)
   339  
   340  			hit := make(map[*profile.Mapping]bool)
   341  			miss := make(map[*profile.Mapping]bool)
   342  			for _, loc := range prof.Location {
   343  				if symbolized(loc) {
   344  					hit[loc.Mapping] = true
   345  				} else {
   346  					miss[loc.Mapping] = true
   347  				}
   348  			}
   349  			if len(miss) == 0 {
   350  				t.Log("no location with missing symbol info was sampled")
   351  			}
   352  
   353  			for _, m := range prof.Mapping {
   354  				if miss[m] && m.HasFunctions {
   355  					t.Errorf("mapping %+v has HasFunctions=true, but contains locations with failed symbolization", m)
   356  					continue
   357  				}
   358  				if !miss[m] && hit[m] && !m.HasFunctions {
   359  					t.Errorf("mapping %+v has HasFunctions=false, but all referenced locations from this lapping were symbolized successfully", m)
   360  					continue
   361  				}
   362  			}
   363  
   364  			if traceback == "Go+C" {
   365  				// The test code was arranged to have PCs from C and
   366  				// they are not symbolized.
   367  				// Check no Location containing those unsymbolized PCs contains multiple lines.
   368  				for i, loc := range prof.Location {
   369  					if !symbolized(loc) && len(loc.Line) > 1 {
   370  						t.Errorf("Location[%d] contains unsymbolized PCs and multiple lines: %v", i, loc)
   371  					}
   372  				}
   373  			}
   374  		})
   375  	}
   376  }
   377  
   378  func symbolized(loc *profile.Location) bool {
   379  	if len(loc.Line) == 0 {
   380  		return false
   381  	}
   382  	l := loc.Line[0]
   383  	f := l.Function
   384  	if l.Line == 0 || f == nil || f.Name == "" || f.Filename == "" {
   385  		return false
   386  	}
   387  	return true
   388  }
   389  
   390  // TestFakeMapping tests if at least one mapping exists
   391  // (including a fake mapping), and their HasFunctions bits
   392  // are set correctly.
   393  func TestFakeMapping(t *testing.T) {
   394  	var buf bytes.Buffer
   395  	if err := Lookup("heap").WriteTo(&buf, 0); err != nil {
   396  		t.Fatalf("failed to write heap profile: %v", err)
   397  	}
   398  	prof, err := profile.Parse(&buf)
   399  	if err != nil {
   400  		t.Fatalf("failed to parse the generated profile data: %v", err)
   401  	}
   402  	t.Logf("Profile: %s", prof)
   403  	if len(prof.Mapping) == 0 {
   404  		t.Fatal("want profile with at least one mapping entry, got 0 mapping")
   405  	}
   406  
   407  	hit := make(map[*profile.Mapping]bool)
   408  	miss := make(map[*profile.Mapping]bool)
   409  	for _, loc := range prof.Location {
   410  		if symbolized(loc) {
   411  			hit[loc.Mapping] = true
   412  		} else {
   413  			miss[loc.Mapping] = true
   414  		}
   415  	}
   416  	for _, m := range prof.Mapping {
   417  		if miss[m] && m.HasFunctions {
   418  			t.Errorf("mapping %+v has HasFunctions=true, but contains locations with failed symbolization", m)
   419  			continue
   420  		}
   421  		if !miss[m] && hit[m] && !m.HasFunctions {
   422  			t.Errorf("mapping %+v has HasFunctions=false, but all referenced locations from this lapping were symbolized successfully", m)
   423  			continue
   424  		}
   425  	}
   426  }