| // Copyright 2014 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package symbolizer |
| |
| import ( |
| "fmt" |
| "regexp" |
| "sort" |
| "strings" |
| "testing" |
| |
| "github.com/google/pprof/internal/plugin" |
| "github.com/google/pprof/internal/proftest" |
| "github.com/google/pprof/profile" |
| ) |
| |
| var testM = []*profile.Mapping{ |
| { |
| ID: 1, |
| Start: 0x1000, |
| Limit: 0x5000, |
| File: "mapping", |
| }, |
| } |
| |
| var testL = []*profile.Location{ |
| { |
| ID: 1, |
| Mapping: testM[0], |
| Address: 1000, |
| }, |
| { |
| ID: 2, |
| Mapping: testM[0], |
| Address: 2000, |
| }, |
| { |
| ID: 3, |
| Mapping: testM[0], |
| Address: 3000, |
| }, |
| { |
| ID: 4, |
| Mapping: testM[0], |
| Address: 4000, |
| }, |
| { |
| ID: 5, |
| Mapping: testM[0], |
| Address: 5000, |
| }, |
| } |
| |
| var testProfile = profile.Profile{ |
| DurationNanos: 10e9, |
| SampleType: []*profile.ValueType{ |
| {Type: "cpu", Unit: "cycles"}, |
| }, |
| Sample: []*profile.Sample{ |
| { |
| Location: []*profile.Location{testL[0]}, |
| Value: []int64{1}, |
| }, |
| { |
| Location: []*profile.Location{testL[1], testL[0]}, |
| Value: []int64{10}, |
| }, |
| { |
| Location: []*profile.Location{testL[2], testL[0]}, |
| Value: []int64{100}, |
| }, |
| { |
| Location: []*profile.Location{testL[3], testL[0]}, |
| Value: []int64{1}, |
| }, |
| { |
| Location: []*profile.Location{testL[4], testL[3], testL[0]}, |
| Value: []int64{10000}, |
| }, |
| }, |
| Location: testL, |
| Mapping: testM, |
| PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"}, |
| Period: 10, |
| } |
| |
| func TestSymbolization(t *testing.T) { |
| sSym := symbolzSymbolize |
| lSym := localSymbolize |
| defer func() { |
| symbolzSymbolize = sSym |
| localSymbolize = lSym |
| demangleFunction = Demangle |
| }() |
| symbolzSymbolize = symbolzMock |
| localSymbolize = localMock |
| demangleFunction = demangleMock |
| |
| type testcase struct { |
| mode string |
| wantComment string |
| } |
| |
| s := Symbolizer{ |
| Obj: mockObjTool{}, |
| UI: &proftest.TestUI{T: t}, |
| } |
| for i, tc := range []testcase{ |
| { |
| "local", |
| "local=[]", |
| }, |
| { |
| "fastlocal", |
| "local=[fast]", |
| }, |
| { |
| "remote", |
| "symbolz=[]", |
| }, |
| { |
| "", |
| "local=[]:symbolz=[]", |
| }, |
| { |
| "demangle=none", |
| "demangle=[none]:force:local=[force]:symbolz=[force]", |
| }, |
| { |
| "remote:demangle=full", |
| "demangle=[full]:force:symbolz=[force]", |
| }, |
| { |
| "local:demangle=templates", |
| "demangle=[templates]:force:local=[force]", |
| }, |
| { |
| "force:remote", |
| "force:symbolz=[force]", |
| }, |
| } { |
| prof := testProfile.Copy() |
| if err := s.Symbolize(tc.mode, nil, prof); err != nil { |
| t.Errorf("symbolize #%d: %v", i, err) |
| continue |
| } |
| sort.Strings(prof.Comments) |
| if got, want := strings.Join(prof.Comments, ":"), tc.wantComment; got != want { |
| t.Errorf("%q: got %s, want %s", tc.mode, got, want) |
| continue |
| } |
| } |
| } |
| |
| func symbolzMock(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error { |
| var args []string |
| if force { |
| args = append(args, "force") |
| } |
| p.Comments = append(p.Comments, "symbolz=["+strings.Join(args, ",")+"]") |
| return nil |
| } |
| |
| func localMock(p *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error { |
| var args []string |
| if fast { |
| args = append(args, "fast") |
| } |
| if force { |
| args = append(args, "force") |
| } |
| p.Comments = append(p.Comments, "local=["+strings.Join(args, ",")+"]") |
| return nil |
| } |
| |
| func demangleMock(p *profile.Profile, force bool, mode string) { |
| if force { |
| p.Comments = append(p.Comments, "force") |
| } |
| if mode != "" { |
| p.Comments = append(p.Comments, "demangle=["+mode+"]") |
| } |
| } |
| |
| func TestLocalSymbolization(t *testing.T) { |
| prof := testProfile.Copy() |
| |
| if prof.HasFunctions() { |
| t.Error("unexpected function names") |
| } |
| if prof.HasFileLines() { |
| t.Error("unexpected filenames or line numbers") |
| } |
| |
| b := mockObjTool{} |
| if err := localSymbolize(prof, false, false, b, &proftest.TestUI{T: t}); err != nil { |
| t.Fatalf("localSymbolize(): %v", err) |
| } |
| |
| for _, loc := range prof.Location { |
| if err := checkSymbolizedLocation(loc.Address, loc.Line); err != nil { |
| t.Errorf("location %d: %v", loc.Address, err) |
| } |
| } |
| if !prof.HasFunctions() { |
| t.Error("missing function names") |
| } |
| if !prof.HasFileLines() { |
| t.Error("missing filenames or line numbers") |
| } |
| } |
| |
| func checkSymbolizedLocation(a uint64, got []profile.Line) error { |
| want, ok := mockAddresses[a] |
| if !ok { |
| return fmt.Errorf("unexpected address") |
| } |
| if len(want) != len(got) { |
| return fmt.Errorf("want len %d, got %d", len(want), len(got)) |
| } |
| |
| for i, w := range want { |
| g := got[i] |
| if g.Function.Name != w.Func { |
| return fmt.Errorf("want function: %q, got %q", w.Func, g.Function.Name) |
| } |
| if g.Function.Filename != w.File { |
| return fmt.Errorf("want filename: %q, got %q", w.File, g.Function.Filename) |
| } |
| if g.Line != int64(w.Line) { |
| return fmt.Errorf("want lineno: %d, got %d", w.Line, g.Line) |
| } |
| } |
| return nil |
| } |
| |
| var mockAddresses = map[uint64][]plugin.Frame{ |
| 1000: {frame("fun11", "file11.src", 10)}, |
| 2000: {frame("fun21", "file21.src", 20), frame("fun22", "file22.src", 20)}, |
| 3000: {frame("fun31", "file31.src", 30), frame("fun32", "file32.src", 30), frame("fun33", "file33.src", 30)}, |
| 4000: {frame("fun41", "file41.src", 40), frame("fun42", "file42.src", 40), frame("fun43", "file43.src", 40), frame("fun44", "file44.src", 40)}, |
| 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)}, |
| } |
| |
| func frame(fname, file string, line int) plugin.Frame { |
| return plugin.Frame{ |
| Func: fname, |
| File: file, |
| Line: line} |
| } |
| |
| func TestDemangleSingleFunction(t *testing.T) { |
| // All tests with default mode. |
| demanglerMode := "" |
| options := demanglerModeToOptions(demanglerMode) |
| |
| cases := []struct { |
| symbol string |
| want string |
| }{ |
| { |
| // Trivial C symbol. |
| symbol: "printf", |
| want: "printf", |
| }, |
| { |
| // foo::bar(int) |
| symbol: "_ZN3foo3barEi", |
| want: "foo::bar", |
| }, |
| { |
| // Already demangled. |
| symbol: "foo::bar(int)", |
| want: "foo::bar", |
| }, |
| { |
| // int foo::baz<double>(double) |
| symbol: "_ZN3foo3bazIdEEiT", |
| want: "foo::baz", |
| }, |
| { |
| // Already demangled. |
| // |
| // TODO: The demangled form of this is actually |
| // 'int foo::baz<double>(double)', but our heuristic |
| // can't strip the return type. Should it be able to? |
| symbol: "foo::baz<double>(double)", |
| want: "foo::baz", |
| }, |
| { |
| // operator delete[](void*) |
| symbol: "_ZdaPv", |
| want: "operator delete[]", |
| }, |
| { |
| // Already demangled. |
| symbol: "operator delete[](void*)", |
| want: "operator delete[]", |
| }, |
| { |
| // bar(int (*) [5]) |
| symbol: "_Z3barPA5_i", |
| want: "bar", |
| }, |
| { |
| // Already demangled. |
| symbol: "bar(int (*) [5])", |
| want: "bar", |
| }, |
| // Java symbols, do not demangle. |
| { |
| symbol: "java.lang.Float.parseFloat", |
| want: "java.lang.Float.parseFloat", |
| }, |
| { |
| symbol: "java.lang.Float.<init>", |
| want: "java.lang.Float.<init>", |
| }, |
| // Go symbols, do not demangle. |
| { |
| symbol: "example.com/foo.Bar", |
| want: "example.com/foo.Bar", |
| }, |
| { |
| symbol: "example.com/foo.(*Bar).Bat", |
| want: "example.com/foo.(*Bar).Bat", |
| }, |
| { |
| // Method on type with type parameters, as reported by |
| // Go pprof profiles (simplified symbol name). |
| symbol: "example.com/foo.(*Bar[...]).Bat", |
| want: "example.com/foo.(*Bar[...]).Bat", |
| }, |
| { |
| // Method on type with type parameters, as reported by |
| // perf profiles (actual symbol name). |
| symbol: "example.com/foo.(*Bar[go.shape.string_0,go.shape.int_1]).Bat", |
| want: "example.com/foo.(*Bar[go.shape.string_0,go.shape.int_1]).Bat", |
| }, |
| { |
| // Function with type parameters, as reported by Go |
| // pprof profiles (simplified symbol name). |
| symbol: "example.com/foo.Bar[...]", |
| want: "example.com/foo.Bar[...]", |
| }, |
| { |
| // Function with type parameters, as reported by perf |
| // profiles (actual symbol name). |
| symbol: "example.com/foo.Bar[go.shape.string_0,go.shape.int_1]", |
| want: "example.com/foo.Bar[go.shape.string_0,go.shape.int_1]", |
| }, |
| } |
| for _, tc := range cases { |
| fn := &profile.Function{ |
| SystemName: tc.symbol, |
| } |
| demangleSingleFunction(fn, options) |
| if fn.Name != tc.want { |
| t.Errorf("demangleSingleFunction(%s) got %s want %s", tc.symbol, fn.Name, tc.want) |
| } |
| } |
| } |
| |
| type mockObjTool struct{} |
| |
| func (mockObjTool) Open(file string, start, limit, offset uint64, relocationSymbol string) (plugin.ObjFile, error) { |
| return mockObjFile{frames: mockAddresses}, nil |
| } |
| |
| func (mockObjTool) Disasm(file string, start, end uint64, intelSyntax bool) ([]plugin.Inst, error) { |
| return nil, fmt.Errorf("disassembly not supported") |
| } |
| |
| type mockObjFile struct { |
| frames map[uint64][]plugin.Frame |
| } |
| |
| func (mockObjFile) Name() string { |
| return "" |
| } |
| |
| func (mockObjFile) ObjAddr(addr uint64) (uint64, error) { |
| return addr, nil |
| } |
| |
| func (mockObjFile) BuildID() string { |
| return "" |
| } |
| |
| func (mf mockObjFile) SourceLine(addr uint64) ([]plugin.Frame, error) { |
| return mf.frames[addr], nil |
| } |
| |
| func (mockObjFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { |
| return []*plugin.Sym{}, nil |
| } |
| |
| func (mockObjFile) Close() error { |
| return nil |
| } |