...

Source file src/runtime/pprof/proto_test.go

Documentation: runtime/pprof

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

View as plain text