bc998d034f
Reviewed-on: https://go-review.googlesource.com/63753 From-SVN: r252767
225 lines
7.9 KiB
Go
225 lines
7.9 KiB
Go
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package pprof
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"reflect"
|
|
"runtime"
|
|
"runtime/pprof/internal/profile"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// translateCPUProfile parses binary CPU profiling stack trace data
|
|
// generated by runtime.CPUProfile() into a profile struct.
|
|
// This is only used for testing. Real conversions stream the
|
|
// data into the profileBuilder as it becomes available.
|
|
func translateCPUProfile(data []uint64) (*profile.Profile, error) {
|
|
var buf bytes.Buffer
|
|
b := newProfileBuilder(&buf)
|
|
if err := b.addCPUData(data, nil); err != nil {
|
|
return nil, err
|
|
}
|
|
b.build()
|
|
return profile.Parse(&buf)
|
|
}
|
|
|
|
// fmtJSON returns a pretty-printed JSON form for x.
|
|
// It works reasonbly well for printing protocol-buffer
|
|
// data structures like profile.Profile.
|
|
func fmtJSON(x interface{}) string {
|
|
js, _ := json.MarshalIndent(x, "", "\t")
|
|
return string(js)
|
|
}
|
|
|
|
func TestConvertCPUProfileEmpty(t *testing.T) {
|
|
// A test server with mock cpu profile data.
|
|
var buf bytes.Buffer
|
|
|
|
b := []uint64{3, 0, 500} // empty profile at 500 Hz (2ms sample period)
|
|
p, err := translateCPUProfile(b)
|
|
if err != nil {
|
|
t.Fatalf("translateCPUProfile: %v", err)
|
|
}
|
|
if err := p.Write(&buf); err != nil {
|
|
t.Fatalf("writing profile: %v", err)
|
|
}
|
|
|
|
p, err = profile.Parse(&buf)
|
|
if err != nil {
|
|
t.Fatalf("profile.Parse: %v", err)
|
|
}
|
|
|
|
// Expected PeriodType and SampleType.
|
|
periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
|
|
sampleType := []*profile.ValueType{
|
|
{Type: "samples", Unit: "count"},
|
|
{Type: "cpu", Unit: "nanoseconds"},
|
|
}
|
|
|
|
checkProfile(t, p, 2000*1000, periodType, sampleType, nil)
|
|
}
|
|
|
|
// For gccgo make these functions different so that gccgo doesn't
|
|
// merge them with each other and with lostProfileEvent.
|
|
func f1(i int) { f1(i + 1) }
|
|
func f2(i int) { f2(i + 2) }
|
|
|
|
// testPCs returns two PCs and two corresponding memory mappings
|
|
// to use in test profiles.
|
|
func testPCs(t *testing.T) (addr1, addr2 uint64, map1, map2 *profile.Mapping) {
|
|
switch runtime.GOOS {
|
|
case "linux", "android", "netbsd":
|
|
// Figure out two addresses from /proc/self/maps.
|
|
mmap, err := ioutil.ReadFile("/proc/self/maps")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mprof := &profile.Profile{}
|
|
if err = mprof.ParseMemoryMap(bytes.NewReader(mmap)); err != nil {
|
|
t.Fatalf("parsing /proc/self/maps: %v", err)
|
|
}
|
|
if len(mprof.Mapping) < 2 {
|
|
// It is possible for a binary to only have 1 executable
|
|
// region of memory.
|
|
t.Skipf("need 2 or more mappings, got %v", len(mprof.Mapping))
|
|
}
|
|
addr1 = mprof.Mapping[0].Start
|
|
map1 = mprof.Mapping[0]
|
|
map1.BuildID, _ = elfBuildID(map1.File)
|
|
addr2 = mprof.Mapping[1].Start
|
|
map2 = mprof.Mapping[1]
|
|
map2.BuildID, _ = elfBuildID(map2.File)
|
|
default:
|
|
addr1 = uint64(funcPC(f1))
|
|
addr2 = uint64(funcPC(f2))
|
|
}
|
|
return
|
|
}
|
|
|
|
func TestConvertCPUProfile(t *testing.T) {
|
|
addr1, addr2, map1, map2 := testPCs(t)
|
|
|
|
b := []uint64{
|
|
3, 0, 500, // hz = 500
|
|
5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1
|
|
5, 0, 40, uint64(addr2), uint64(addr2 + 2), // 40 samples in addr2
|
|
5, 0, 10, uint64(addr1), uint64(addr1 + 2), // 10 samples in addr1
|
|
}
|
|
p, err := translateCPUProfile(b)
|
|
if err != nil {
|
|
t.Fatalf("translating profile: %v", err)
|
|
}
|
|
period := int64(2000 * 1000)
|
|
periodType := &profile.ValueType{Type: "cpu", Unit: "nanoseconds"}
|
|
sampleType := []*profile.ValueType{
|
|
{Type: "samples", Unit: "count"},
|
|
{Type: "cpu", Unit: "nanoseconds"},
|
|
}
|
|
samples := []*profile.Sample{
|
|
{Value: []int64{20, 20 * 2000 * 1000}, Location: []*profile.Location{
|
|
{ID: 1, Mapping: map1, Address: addr1},
|
|
{ID: 2, Mapping: map1, Address: addr1 + 1},
|
|
}},
|
|
{Value: []int64{40, 40 * 2000 * 1000}, Location: []*profile.Location{
|
|
{ID: 3, Mapping: map2, Address: addr2},
|
|
{ID: 4, Mapping: map2, Address: addr2 + 1},
|
|
}},
|
|
}
|
|
checkProfile(t, p, period, periodType, sampleType, samples)
|
|
}
|
|
|
|
func checkProfile(t *testing.T, p *profile.Profile, period int64, periodType *profile.ValueType, sampleType []*profile.ValueType, samples []*profile.Sample) {
|
|
if p.Period != period {
|
|
t.Fatalf("p.Period = %d, want %d", p.Period, period)
|
|
}
|
|
if !reflect.DeepEqual(p.PeriodType, periodType) {
|
|
t.Fatalf("p.PeriodType = %v\nwant = %v", fmtJSON(p.PeriodType), fmtJSON(periodType))
|
|
}
|
|
if !reflect.DeepEqual(p.SampleType, sampleType) {
|
|
t.Fatalf("p.SampleType = %v\nwant = %v", fmtJSON(p.SampleType), fmtJSON(sampleType))
|
|
}
|
|
// Clear line info since it is not in the expected samples.
|
|
// If we used f1 and f2 above, then the samples will have line info.
|
|
for _, s := range p.Sample {
|
|
for _, l := range s.Location {
|
|
l.Line = nil
|
|
}
|
|
}
|
|
if fmtJSON(p.Sample) != fmtJSON(samples) { // ignore unexported fields
|
|
if len(p.Sample) == len(samples) {
|
|
for i := range p.Sample {
|
|
if !reflect.DeepEqual(p.Sample[i], samples[i]) {
|
|
t.Errorf("sample %d = %v\nwant = %v\n", i, fmtJSON(p.Sample[i]), fmtJSON(samples[i]))
|
|
}
|
|
}
|
|
if t.Failed() {
|
|
t.FailNow()
|
|
}
|
|
}
|
|
t.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p.Sample), fmtJSON(samples))
|
|
}
|
|
}
|
|
|
|
var profSelfMapsTests = `
|
|
00400000-0040b000 r-xp 00000000 fc:01 787766 /bin/cat
|
|
0060a000-0060b000 r--p 0000a000 fc:01 787766 /bin/cat
|
|
0060b000-0060c000 rw-p 0000b000 fc:01 787766 /bin/cat
|
|
014ab000-014cc000 rw-p 00000000 00:00 0 [heap]
|
|
7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064 /usr/lib/locale/locale-archive
|
|
7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
|
|
7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
|
|
7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
|
|
7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
|
|
7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
|
|
7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
|
|
7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
|
|
7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
|
|
7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
|
|
7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
|
|
7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
|
|
7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0 [stack]
|
|
7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0 [vdso]
|
|
ffffffffff600000-ffffffffff601000 r-xp 00000090 00:00 0 [vsyscall]
|
|
->
|
|
00400000 0040b000 00000000 /bin/cat
|
|
7f7d7797c000 7f7d77b36000 00000000 /lib/x86_64-linux-gnu/libc-2.19.so
|
|
7f7d77d41000 7f7d77d64000 00000000 /lib/x86_64-linux-gnu/ld-2.19.so
|
|
7ffc34343000 7ffc34345000 00000000 [vdso]
|
|
ffffffffff600000 ffffffffff601000 00000090 [vsyscall]
|
|
|
|
00400000-07000000 r-xp 00000000 00:00 0
|
|
07000000-07093000 r-xp 06c00000 00:2e 536754 /path/to/gobench_server_main
|
|
07093000-0722d000 rw-p 06c92000 00:2e 536754 /path/to/gobench_server_main
|
|
0722d000-07b21000 rw-p 00000000 00:00 0
|
|
c000000000-c000036000 rw-p 00000000 00:00 0
|
|
->
|
|
07000000 07093000 06c00000 /path/to/gobench_server_main
|
|
`
|
|
|
|
func TestProcSelfMaps(t *testing.T) {
|
|
for tx, tt := range strings.Split(profSelfMapsTests, "\n\n") {
|
|
i := strings.Index(tt, "->\n")
|
|
if i < 0 {
|
|
t.Fatal("malformed test case")
|
|
}
|
|
in, out := tt[:i], tt[i+len("->\n"):]
|
|
if len(out) > 0 && out[len(out)-1] != '\n' {
|
|
out += "\n"
|
|
}
|
|
var buf bytes.Buffer
|
|
parseProcSelfMaps([]byte(in), func(lo, hi, offset uint64, file, buildID string) {
|
|
fmt.Fprintf(&buf, "%08x %08x %08x %s\n", lo, hi, offset, file)
|
|
})
|
|
if buf.String() != out {
|
|
t.Errorf("#%d: have:\n%s\nwant:\n%s\n%q\n%q", tx, buf.String(), out, buf.String(), out)
|
|
}
|
|
}
|
|
}
|