1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package symbolizer
16
17 import (
18 "fmt"
19 "regexp"
20 "sort"
21 "strings"
22 "testing"
23
24 "github.com/google/pprof/internal/plugin"
25 "github.com/google/pprof/internal/proftest"
26 "github.com/google/pprof/profile"
27 )
28
29 const filePath = "mapping"
30 const buildID = "build-id"
31
32 var testM = []*profile.Mapping{
33 {
34 ID: 1,
35 Start: 0x1000,
36 Limit: 0x5000,
37 File: filePath,
38 BuildID: buildID,
39 },
40 }
41
42 var testL = []*profile.Location{
43 {
44 ID: 1,
45 Mapping: testM[0],
46 Address: 1000,
47 },
48 {
49 ID: 2,
50 Mapping: testM[0],
51 Address: 2000,
52 },
53 {
54 ID: 3,
55 Mapping: testM[0],
56 Address: 3000,
57 },
58 {
59 ID: 4,
60 Mapping: testM[0],
61 Address: 4000,
62 },
63 {
64 ID: 5,
65 Mapping: testM[0],
66 Address: 5000,
67 },
68 }
69
70 var testProfile = profile.Profile{
71 DurationNanos: 10e9,
72 SampleType: []*profile.ValueType{
73 {Type: "cpu", Unit: "cycles"},
74 },
75 Sample: []*profile.Sample{
76 {
77 Location: []*profile.Location{testL[0]},
78 Value: []int64{1},
79 },
80 {
81 Location: []*profile.Location{testL[1], testL[0]},
82 Value: []int64{10},
83 },
84 {
85 Location: []*profile.Location{testL[2], testL[0]},
86 Value: []int64{100},
87 },
88 {
89 Location: []*profile.Location{testL[3], testL[0]},
90 Value: []int64{1},
91 },
92 {
93 Location: []*profile.Location{testL[4], testL[3], testL[0]},
94 Value: []int64{10000},
95 },
96 },
97 Location: testL,
98 Mapping: testM,
99 PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
100 Period: 10,
101 }
102
103 func TestSymbolization(t *testing.T) {
104 sSym := symbolzSymbolize
105 lSym := localSymbolize
106 defer func() {
107 symbolzSymbolize = sSym
108 localSymbolize = lSym
109 demangleFunction = Demangle
110 }()
111 symbolzSymbolize = symbolzMock
112 localSymbolize = localMock
113 demangleFunction = demangleMock
114
115 type testcase struct {
116 mode string
117 wantComment string
118 }
119
120 s := Symbolizer{
121 Obj: mockObjTool{},
122 UI: &proftest.TestUI{T: t},
123 }
124 for i, tc := range []testcase{
125 {
126 "local",
127 "local=[]",
128 },
129 {
130 "fastlocal",
131 "local=[fast]",
132 },
133 {
134 "remote",
135 "symbolz=[]",
136 },
137 {
138 "",
139 "local=[]:symbolz=[]",
140 },
141 {
142 "demangle=none",
143 "demangle=[none]:force:local=[force]:symbolz=[force]",
144 },
145 {
146 "remote:demangle=full",
147 "demangle=[full]:force:symbolz=[force]",
148 },
149 {
150 "local:demangle=templates",
151 "demangle=[templates]:force:local=[force]",
152 },
153 {
154 "force:remote",
155 "force:symbolz=[force]",
156 },
157 } {
158 prof := testProfile.Copy()
159 if err := s.Symbolize(tc.mode, nil, prof); err != nil {
160 t.Errorf("symbolize #%d: %v", i, err)
161 continue
162 }
163 sort.Strings(prof.Comments)
164 if got, want := strings.Join(prof.Comments, ":"), tc.wantComment; got != want {
165 t.Errorf("%q: got %s, want %s", tc.mode, got, want)
166 continue
167 }
168 }
169 }
170
171 func symbolzMock(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error {
172 var args []string
173 if force {
174 args = append(args, "force")
175 }
176 p.Comments = append(p.Comments, "symbolz=["+strings.Join(args, ",")+"]")
177 return nil
178 }
179
180 func localMock(p *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error {
181 var args []string
182 if fast {
183 args = append(args, "fast")
184 }
185 if force {
186 args = append(args, "force")
187 }
188 p.Comments = append(p.Comments, "local=["+strings.Join(args, ",")+"]")
189 return nil
190 }
191
192 func demangleMock(p *profile.Profile, force bool, mode string) {
193 if force {
194 p.Comments = append(p.Comments, "force")
195 }
196 if mode != "" {
197 p.Comments = append(p.Comments, "demangle=["+mode+"]")
198 }
199 }
200
201 func TestLocalSymbolization(t *testing.T) {
202 prof := testProfile.Copy()
203
204 if prof.HasFunctions() {
205 t.Error("unexpected function names")
206 }
207 if prof.HasFileLines() {
208 t.Error("unexpected filenames or line numbers")
209 }
210
211 b := mockObjTool{}
212 if err := localSymbolize(prof, false, false, b, &proftest.TestUI{T: t}); err != nil {
213 t.Fatalf("localSymbolize(): %v", err)
214 }
215
216 for _, loc := range prof.Location {
217 if err := checkSymbolizedLocation(loc.Address, loc.Line); err != nil {
218 t.Errorf("location %d: %v", loc.Address, err)
219 }
220 }
221 if !prof.HasFunctions() {
222 t.Error("missing function names")
223 }
224 if !prof.HasFileLines() {
225 t.Error("missing filenames or line numbers")
226 }
227 }
228
229 func TestLocalSymbolizationHandlesSpecialCases(t *testing.T) {
230 for _, tc := range []struct {
231 desc, file, buildID, allowOutputRx string
232 wantNumOutputRegexMatches int
233 }{{
234 desc: "Unsymbolizable files are skipped",
235 file: "[some unsymbolizable file]",
236 buildID: "",
237 }, {
238 desc: "HTTP URL like paths are skipped",
239 file: "http://original-url-source-of-profile-fetch",
240 buildID: "",
241 }, {
242 desc: "Non-existent files are ignored",
243 file: "/does-not-exist",
244 buildID: buildID,
245 allowOutputRx: "(?s)unknown or non-existent file|Some binary filenames not available.*Try setting PPROF_BINARY_PATH",
246 wantNumOutputRegexMatches: 2,
247 }, {
248 desc: "Missing main binary is detected",
249 file: "",
250 buildID: buildID,
251 allowOutputRx: "Main binary filename not available",
252 wantNumOutputRegexMatches: 1,
253 }, {
254 desc: "Different build ID is detected",
255 file: filePath,
256 buildID: "unexpected-build-id",
257 allowOutputRx: "build ID mismatch",
258 wantNumOutputRegexMatches: 1,
259 },
260 } {
261 t.Run(tc.desc, func(t *testing.T) {
262 prof := testProfile.Copy()
263 prof.Mapping[0].File = tc.file
264 prof.Mapping[0].BuildID = tc.buildID
265 origProf := prof.Copy()
266
267 if prof.HasFunctions() {
268 t.Error("unexpected function names")
269 }
270 if prof.HasFileLines() {
271 t.Error("unexpected filenames or line numbers")
272 }
273
274 b := mockObjTool{}
275 ui := &proftest.TestUI{T: t, AllowRx: tc.allowOutputRx}
276 if err := localSymbolize(prof, false, false, b, ui); err != nil {
277 t.Fatalf("localSymbolize(): %v", err)
278 }
279 if ui.NumAllowRxMatches != tc.wantNumOutputRegexMatches {
280 t.Errorf("localSymbolize(): got %d matches for %q UI regexp, want %d", ui.NumAllowRxMatches, tc.allowOutputRx, tc.wantNumOutputRegexMatches)
281 }
282
283 if diff, err := proftest.Diff([]byte(origProf.String()), []byte(prof.String())); err != nil {
284 t.Fatalf("Failed to get diff: %v", err)
285 } else if string(diff) != "" {
286 t.Errorf("Profile changed unexpectedly, diff(want->got):\n%s", diff)
287 }
288 })
289 }
290 }
291
292 func checkSymbolizedLocation(a uint64, got []profile.Line) error {
293 want, ok := mockAddresses[a]
294 if !ok {
295 return fmt.Errorf("unexpected address")
296 }
297 if len(want) != len(got) {
298 return fmt.Errorf("want len %d, got %d", len(want), len(got))
299 }
300
301 for i, w := range want {
302 g := got[i]
303 if g.Function.Name != w.Func {
304 return fmt.Errorf("want function: %q, got %q", w.Func, g.Function.Name)
305 }
306 if g.Function.Filename != w.File {
307 return fmt.Errorf("want filename: %q, got %q", w.File, g.Function.Filename)
308 }
309 if g.Line != int64(w.Line) {
310 return fmt.Errorf("want lineno: %d, got %d", w.Line, g.Line)
311 }
312 if g.Column != int64(w.Column) {
313 return fmt.Errorf("want columnno: %d, got %d", w.Column, g.Column)
314 }
315 }
316 return nil
317 }
318
319 var mockAddresses = map[uint64][]plugin.Frame{
320 1000: {frame("fun11", "file11.src", 10, 1)},
321 2000: {frame("fun21", "file21.src", 20, 2), frame("fun22", "file22.src", 20, 2)},
322 3000: {frame("fun31", "file31.src", 30, 3), frame("fun32", "file32.src", 30, 3), frame("fun33", "file33.src", 30, 3)},
323 4000: {frame("fun41", "file41.src", 40, 4), frame("fun42", "file42.src", 40, 4), frame("fun43", "file43.src", 40, 4), frame("fun44", "file44.src", 40, 4)},
324 5000: {frame("fun51", "file51.src", 50, 5), frame("fun52", "file52.src", 50, 5), frame("fun53", "file53.src", 50, 5), frame("fun54", "file54.src", 50, 5), frame("fun55", "file55.src", 50, 5)},
325 }
326
327 func frame(fname, file string, line int, column int) plugin.Frame {
328 return plugin.Frame{
329 Func: fname,
330 File: file,
331 Line: line,
332 Column: column}
333 }
334
335 func TestDemangleSingleFunction(t *testing.T) {
336
337 demanglerMode := ""
338 options := demanglerModeToOptions(demanglerMode)
339
340 cases := []struct {
341 symbol string
342 want string
343 }{
344 {
345
346 symbol: "printf",
347 want: "printf",
348 },
349 {
350
351 symbol: "_ZN3foo3barEi",
352 want: "foo::bar",
353 },
354 {
355
356 symbol: "foo::bar(int)",
357 want: "foo::bar",
358 },
359 {
360
361 symbol: "_ZN3foo3bazIdEEiT",
362 want: "foo::baz",
363 },
364 {
365
366
367
368
369
370 symbol: "foo::baz<double>(double)",
371 want: "foo::baz",
372 },
373 {
374
375 symbol: "_ZdaPv",
376 want: "operator delete[]",
377 },
378 {
379
380 symbol: "operator delete[](void*)",
381 want: "operator delete[]",
382 },
383 {
384
385 symbol: "_Z3barPA5_i",
386 want: "bar",
387 },
388 {
389
390 symbol: "bar(int (*) [5])",
391 want: "bar",
392 },
393
394 {
395 symbol: "java.lang.Float.parseFloat",
396 want: "java.lang.Float.parseFloat",
397 },
398 {
399 symbol: "java.lang.Float.<init>",
400 want: "java.lang.Float.<init>",
401 },
402
403 {
404 symbol: "example.com/foo.Bar",
405 want: "example.com/foo.Bar",
406 },
407 {
408 symbol: "example.com/foo.(*Bar).Bat",
409 want: "example.com/foo.(*Bar).Bat",
410 },
411 {
412
413
414 symbol: "example.com/foo.(*Bar[...]).Bat",
415 want: "example.com/foo.(*Bar[...]).Bat",
416 },
417 {
418
419
420 symbol: "example.com/foo.(*Bar[go.shape.string_0,go.shape.int_1]).Bat",
421 want: "example.com/foo.(*Bar[go.shape.string_0,go.shape.int_1]).Bat",
422 },
423 {
424
425
426 symbol: "example.com/foo.Bar[...]",
427 want: "example.com/foo.Bar[...]",
428 },
429 {
430
431
432 symbol: "example.com/foo.Bar[go.shape.string_0,go.shape.int_1]",
433 want: "example.com/foo.Bar[go.shape.string_0,go.shape.int_1]",
434 },
435 }
436 for _, tc := range cases {
437 fn := &profile.Function{
438 SystemName: tc.symbol,
439 }
440 demangleSingleFunction(fn, options)
441 if fn.Name != tc.want {
442 t.Errorf("demangleSingleFunction(%s) got %s want %s", tc.symbol, fn.Name, tc.want)
443 }
444 }
445 }
446
447 type mockObjTool struct{}
448
449 func (mockObjTool) Open(file string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) {
450 if file != filePath {
451 return nil, fmt.Errorf("unknown or non-existent file %q", file)
452 }
453 return mockObjFile{frames: mockAddresses}, nil
454 }
455
456 func (mockObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {
457 if file != filePath {
458 return nil, fmt.Errorf("unknown or non-existent file %q", file)
459 }
460 return nil, fmt.Errorf("disassembly not supported")
461 }
462
463 type mockObjFile struct {
464 frames map[uint64][]plugin.Frame
465 }
466
467 func (mockObjFile) Name() string {
468 return filePath
469 }
470
471 func (mockObjFile) ObjAddr(addr uint64) (uint64, error) {
472 return addr, nil
473 }
474
475 func (mockObjFile) BuildID() string {
476 return buildID
477 }
478
479 func (mf mockObjFile) SourceLine(addr uint64) ([]plugin.Frame, error) {
480 return mf.frames[addr], nil
481 }
482
483 func (mockObjFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
484 return []*plugin.Sym{}, nil
485 }
486
487 func (mockObjFile) Close() error {
488 return nil
489 }
490
View as plain text