1// Copyright 2014 Google Inc. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package symbolizer
16
17import (
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
29var testM = []*profile.Mapping{
30	{
31		ID:    1,
32		Start: 0x1000,
33		Limit: 0x5000,
34		File:  "mapping",
35	},
36}
37
38var testL = []*profile.Location{
39	{
40		ID:      1,
41		Mapping: testM[0],
42		Address: 1000,
43	},
44	{
45		ID:      2,
46		Mapping: testM[0],
47		Address: 2000,
48	},
49	{
50		ID:      3,
51		Mapping: testM[0],
52		Address: 3000,
53	},
54	{
55		ID:      4,
56		Mapping: testM[0],
57		Address: 4000,
58	},
59	{
60		ID:      5,
61		Mapping: testM[0],
62		Address: 5000,
63	},
64}
65
66var testProfile = profile.Profile{
67	DurationNanos: 10e9,
68	SampleType: []*profile.ValueType{
69		{Type: "cpu", Unit: "cycles"},
70	},
71	Sample: []*profile.Sample{
72		{
73			Location: []*profile.Location{testL[0]},
74			Value:    []int64{1},
75		},
76		{
77			Location: []*profile.Location{testL[1], testL[0]},
78			Value:    []int64{10},
79		},
80		{
81			Location: []*profile.Location{testL[2], testL[0]},
82			Value:    []int64{100},
83		},
84		{
85			Location: []*profile.Location{testL[3], testL[0]},
86			Value:    []int64{1},
87		},
88		{
89			Location: []*profile.Location{testL[4], testL[3], testL[0]},
90			Value:    []int64{10000},
91		},
92	},
93	Location:   testL,
94	Mapping:    testM,
95	PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
96	Period:     10,
97}
98
99func TestSymbolization(t *testing.T) {
100	sSym := symbolzSymbolize
101	lSym := localSymbolize
102	defer func() {
103		symbolzSymbolize = sSym
104		localSymbolize = lSym
105		demangleFunction = Demangle
106	}()
107	symbolzSymbolize = symbolzMock
108	localSymbolize = localMock
109	demangleFunction = demangleMock
110
111	type testcase struct {
112		mode        string
113		wantComment string
114	}
115
116	s := Symbolizer{
117		Obj: mockObjTool{},
118		UI:  &proftest.TestUI{T: t},
119	}
120	for i, tc := range []testcase{
121		{
122			"local",
123			"local=[]",
124		},
125		{
126			"fastlocal",
127			"local=[fast]",
128		},
129		{
130			"remote",
131			"symbolz=[]",
132		},
133		{
134			"",
135			"local=[]:symbolz=[]",
136		},
137		{
138			"demangle=none",
139			"demangle=[none]:force:local=[force]:symbolz=[force]",
140		},
141		{
142			"remote:demangle=full",
143			"demangle=[full]:force:symbolz=[force]",
144		},
145		{
146			"local:demangle=templates",
147			"demangle=[templates]:force:local=[force]",
148		},
149		{
150			"force:remote",
151			"force:symbolz=[force]",
152		},
153	} {
154		prof := testProfile.Copy()
155		if err := s.Symbolize(tc.mode, nil, prof); err != nil {
156			t.Errorf("symbolize #%d: %v", i, err)
157			continue
158		}
159		sort.Strings(prof.Comments)
160		if got, want := strings.Join(prof.Comments, ":"), tc.wantComment; got != want {
161			t.Errorf("%q: got %s, want %s", tc.mode, got, want)
162			continue
163		}
164	}
165}
166
167func symbolzMock(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error {
168	var args []string
169	if force {
170		args = append(args, "force")
171	}
172	p.Comments = append(p.Comments, "symbolz=["+strings.Join(args, ",")+"]")
173	return nil
174}
175
176func localMock(p *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error {
177	var args []string
178	if fast {
179		args = append(args, "fast")
180	}
181	if force {
182		args = append(args, "force")
183	}
184	p.Comments = append(p.Comments, "local=["+strings.Join(args, ",")+"]")
185	return nil
186}
187
188func demangleMock(p *profile.Profile, force bool, mode string) {
189	if force {
190		p.Comments = append(p.Comments, "force")
191	}
192	if mode != "" {
193		p.Comments = append(p.Comments, "demangle=["+mode+"]")
194	}
195}
196
197func TestLocalSymbolization(t *testing.T) {
198	prof := testProfile.Copy()
199
200	if prof.HasFunctions() {
201		t.Error("unexpected function names")
202	}
203	if prof.HasFileLines() {
204		t.Error("unexpected filenames or line numbers")
205	}
206
207	b := mockObjTool{}
208	if err := localSymbolize(prof, false, false, b, &proftest.TestUI{T: t}); err != nil {
209		t.Fatalf("localSymbolize(): %v", err)
210	}
211
212	for _, loc := range prof.Location {
213		if err := checkSymbolizedLocation(loc.Address, loc.Line); err != nil {
214			t.Errorf("location %d: %v", loc.Address, err)
215		}
216	}
217	if !prof.HasFunctions() {
218		t.Error("missing function names")
219	}
220	if !prof.HasFileLines() {
221		t.Error("missing filenames or line numbers")
222	}
223}
224
225func checkSymbolizedLocation(a uint64, got []profile.Line) error {
226	want, ok := mockAddresses[a]
227	if !ok {
228		return fmt.Errorf("unexpected address")
229	}
230	if len(want) != len(got) {
231		return fmt.Errorf("want len %d, got %d", len(want), len(got))
232	}
233
234	for i, w := range want {
235		g := got[i]
236		if g.Function.Name != w.Func {
237			return fmt.Errorf("want function: %q, got %q", w.Func, g.Function.Name)
238		}
239		if g.Function.Filename != w.File {
240			return fmt.Errorf("want filename: %q, got %q", w.File, g.Function.Filename)
241		}
242		if g.Line != int64(w.Line) {
243			return fmt.Errorf("want lineno: %d, got %d", w.Line, g.Line)
244		}
245	}
246	return nil
247}
248
249var mockAddresses = map[uint64][]plugin.Frame{
250	1000: {frame("fun11", "file11.src", 10)},
251	2000: {frame("fun21", "file21.src", 20), frame("fun22", "file22.src", 20)},
252	3000: {frame("fun31", "file31.src", 30), frame("fun32", "file32.src", 30), frame("fun33", "file33.src", 30)},
253	4000: {frame("fun41", "file41.src", 40), frame("fun42", "file42.src", 40), frame("fun43", "file43.src", 40), frame("fun44", "file44.src", 40)},
254	5000: {frame("fun51", "file51.src", 50), frame("fun52", "file52.src", 50), frame("fun53", "file53.src", 50), frame("fun54", "file54.src", 50), frame("fun55", "file55.src", 50)},
255}
256
257func frame(fname, file string, line int) plugin.Frame {
258	return plugin.Frame{
259		Func: fname,
260		File: file,
261		Line: line}
262}
263
264type mockObjTool struct{}
265
266func (mockObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
267	return mockObjFile{frames: mockAddresses}, nil
268}
269
270func (mockObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) {
271	return nil, fmt.Errorf("disassembly not supported")
272}
273
274type mockObjFile struct {
275	frames map[uint64][]plugin.Frame
276}
277
278func (mockObjFile) Name() string {
279	return ""
280}
281
282func (mockObjFile) Base() uint64 {
283	return 0
284}
285
286func (mockObjFile) BuildID() string {
287	return ""
288}
289
290func (mf mockObjFile) SourceLine(addr uint64) ([]plugin.Frame, error) {
291	return mf.frames[addr], nil
292}
293
294func (mockObjFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
295	return []*plugin.Sym{}, nil
296}
297
298func (mockObjFile) Close() error {
299	return nil
300}
301