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.
17 "runtime/pprof/internal/profile"
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
) {
28 b
:= newProfileBuilder(&buf
)
29 if err
:= b
.addCPUData(data
, nil); err
!= nil {
33 return profile
.Parse(&buf
)
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")
44 func TestConvertCPUProfileEmpty(t
*testing
.T
) {
45 // A test server with mock cpu profile data.
48 b
:= []uint64{3, 0, 500} // empty profile at 500 Hz (2ms sample period)
49 p
, err
:= translateCPUProfile(b
)
51 t
.Fatalf("translateCPUProfile: %v", err
)
53 if err
:= p
.Write(&buf
); err
!= nil {
54 t
.Fatalf("writing profile: %v", err
)
57 p
, err
= profile
.Parse(&buf
)
59 t
.Fatalf("profile.Parse: %v", err
)
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"},
69 checkProfile(t
, p
, 2000*1000, periodType
, sampleType
, nil, "")
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) }
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
) {
81 case "linux", "android", "netbsd":
82 // Figure out two addresses from /proc/self/maps.
83 mmap
, err
:= ioutil
.ReadFile("/proc/self/maps")
87 mprof
:= &profile
.Profile
{}
88 if err
= mprof
.ParseMemoryMap(bytes
.NewReader(mmap
)); err
!= nil {
89 t
.Fatalf("parsing /proc/self/maps: %v", err
)
91 if len(mprof
.Mapping
) < 2 {
92 // It is possible for a binary to only have 1 executable
94 t
.Skipf("need 2 or more mappings, got %v", len(mprof
.Mapping
))
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
)
103 addr1
= uint64(funcPC(f1
))
104 addr2
= uint64(funcPC(f2
))
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
116 func TestConvertCPUProfile(t
*testing
.T
) {
117 addr1
, addr2
, map1
, map2
:= testPCs(t
)
120 3, 0, 500, // hz = 500
121 5, 0, 10, uint64(addr1
), uint64(addr1
+ 2), // 10 samples in addr1
122 5, 0, 40, uint64(addr2
), uint64(addr2
+ 2), // 40 samples in addr2
123 5, 0, 10, uint64(addr1
), uint64(addr1
+ 2), // 10 samples in addr1
125 p
, err
:= translateCPUProfile(b
)
127 t
.Fatalf("translating profile: %v", err
)
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"},
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},
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},
145 checkProfile(t
, p
, period
, periodType
, sampleType
, samples
, "")
148 func checkProfile(t
*testing
.T
, p
*profile
.Profile
, period
int64, periodType
*profile
.ValueType
, sampleType
[]*profile
.ValueType
, samples
[]*profile
.Sample
, defaultSampleType
string) {
151 if p
.Period
!= period
{
152 t
.Errorf("p.Period = %d, want %d", p
.Period
, period
)
154 if !reflect
.DeepEqual(p
.PeriodType
, periodType
) {
155 t
.Errorf("p.PeriodType = %v\nwant = %v", fmtJSON(p
.PeriodType
), fmtJSON(periodType
))
157 if !reflect
.DeepEqual(p
.SampleType
, sampleType
) {
158 t
.Errorf("p.SampleType = %v\nwant = %v", fmtJSON(p
.SampleType
), fmtJSON(sampleType
))
160 if defaultSampleType
!= p
.DefaultSampleType
{
161 t
.Errorf("p.DefaultSampleType = %v\nwant = %v", p
.DefaultSampleType
, defaultSampleType
)
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
{
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
]))
181 t
.Fatalf("p.Sample = %v\nwant = %v", fmtJSON(p
.Sample
), fmtJSON(samples
))
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]
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]
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
218 07000000 07093000 06c00000 /path/to/gobench_server_main
221 func TestProcSelfMaps(t
*testing
.T
) {
222 for tx
, tt
:= range strings
.Split(profSelfMapsTests
, "\n\n") {
223 i
:= strings
.Index(tt
, "->\n")
225 t
.Fatal("malformed test case")
227 in
, out
:= tt
[:i
], tt
[i
+len("->\n"):]
228 if len(out
) > 0 && out
[len(out
)-1] != '\n' {
232 parseProcSelfMaps([]byte(in
), func(lo
, hi
, offset
uint64, file
, buildID
string) {
233 fmt
.Fprintf(&buf
, "%08x %08x %08x %s\n", lo
, hi
, offset
, file
)
235 if buf
.String() != out
{
236 t
.Errorf("#%d: have:\n%s\nwant:\n%s\n%q\n%q", tx
, buf
.String(), out
, buf
.String(), out
)
241 // TestMapping checkes the mapping section of CPU profiles
242 // has the HasFunctions field set correctly. If all PCs included
243 // in the samples are successfully symbolized, the corresponding
244 // mapping entry (in this test case, only one entry) should have
245 // its HasFunctions field set true.
246 // The test generates a CPU profile that includes PCs from C side
247 // that the runtime can't symbolize. See ./testdata/mappingtest.
248 func TestMapping(t
*testing
.T
) {
249 testenv
.MustHaveGoRun(t
)
250 testenv
.MustHaveCGO(t
)
252 prog
:= "./testdata/mappingtest/main.go"
254 // GoOnly includes only Go symbols that runtime will symbolize.
255 // Go+C includes C symbols that runtime will not symbolize.
256 for _
, traceback
:= range []string{"GoOnly", "Go+C"} {
257 t
.Run("traceback"+traceback
, func(t
*testing
.T
) {
258 cmd
:= exec
.Command(testenv
.GoToolPath(t
), "run", prog
)
259 if traceback
!= "GoOnly" {
260 cmd
.Env
= append(os
.Environ(), "SETCGOTRACEBACK=1")
262 cmd
.Stderr
= new(bytes
.Buffer
)
264 out
, err
:= cmd
.Output()
266 t
.Fatalf("failed to run the test program %q: %v\n%v", prog
, err
, cmd
.Stderr
)
269 prof
, err
:= profile
.Parse(bytes
.NewReader(out
))
271 t
.Fatalf("failed to parse the generated profile data: %v", err
)
273 t
.Logf("Profile: %s", prof
)
275 hit
:= make(map[*profile
.Mapping
]bool)
276 miss
:= make(map[*profile
.Mapping
]bool)
277 for _
, loc
:= range prof
.Location
{
279 hit
[loc
.Mapping
] = true
281 miss
[loc
.Mapping
] = true
285 t
.Log("no location with missing symbol info was sampled")
288 for _
, m
:= range prof
.Mapping
{
289 if miss
[m
] && m
.HasFunctions
{
290 t
.Errorf("mapping %+v has HasFunctions=true, but contains locations with failed symbolization", m
)
293 if !miss
[m
] && hit
[m
] && !m
.HasFunctions
{
294 t
.Errorf("mapping %+v has HasFunctions=false, but all referenced locations from this lapping were symbolized successfully", m
)
302 func symbolized(loc
*profile
.Location
) bool {
303 if len(loc
.Line
) == 0 {
308 if l
.Line
== 0 || f
== nil || f
.Name
== "" || f
.Filename
== "" {
314 // TestFakeMapping tests if at least one mapping exists
315 // (including a fake mapping), and their HasFunctions bits
316 // are set correctly.
317 func TestFakeMapping(t
*testing
.T
) {
319 if err
:= Lookup("heap").WriteTo(&buf
, 0); err
!= nil {
320 t
.Fatalf("failed to write heap profile: %v", err
)
322 prof
, err
:= profile
.Parse(&buf
)
324 t
.Fatalf("failed to parse the generated profile data: %v", err
)
326 t
.Logf("Profile: %s", prof
)
327 if len(prof
.Mapping
) == 0 {
328 t
.Fatal("want profile with at least one mapping entry, got 0 mapping")
331 hit
:= make(map[*profile
.Mapping
]bool)
332 miss
:= make(map[*profile
.Mapping
]bool)
333 for _
, loc
:= range prof
.Location
{
335 hit
[loc
.Mapping
] = true
337 miss
[loc
.Mapping
] = true
340 for _
, m
:= range prof
.Mapping
{
341 if miss
[m
] && m
.HasFunctions
{
342 t
.Errorf("mapping %+v has HasFunctions=true, but contains locations with failed symbolization", m
)
345 if !miss
[m
] && hit
[m
] && !m
.HasFunctions
{
346 t
.Errorf("mapping %+v has HasFunctions=false, but all referenced locations from this lapping were symbolized successfully", m
)