Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add column numbers to profile.proto. #818

Merged
merged 7 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,10 @@ type ObjFile interface {

// A Frame describes a single line in a source file.
type Frame struct {
Func string // name of function
File string // source file name
Line int // line in file
Func string // name of function
File string // source file name
Line int // line in file
Column int // column in file
}

// A Sym describes a single symbol in an object file.
Expand Down
22 changes: 14 additions & 8 deletions internal/binutils/addr2liner_llvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) {
}

linenumber := 0
columnnumber := 0
// The llvm-symbolizer outputs the <file_name>:<line_number>:<column_number>.
// When it cannot identify the source code location, it outputs "??:0:0".
// Older versions output just the filename and line number, so we check for
Expand All @@ -137,22 +138,27 @@ func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) {
fileline = ""
} else {
switch split := strings.Split(fileline, ":"); len(split) {
case 1:
// filename
fileline = split[0]
case 2, 3:
// filename:line , or
// filename:line:disc , or
fileline = split[0]
case 3:
// filename:line:column
if col, err := strconv.Atoi(split[2]); err == nil {
columnnumber = col
}
fallthrough
case 2:
// filename:line
if line, err := strconv.Atoi(split[1]); err == nil {
linenumber = line
}
fallthrough
case 1:
// filename
fileline = split[0]
default:
// Unrecognized, ignore
}
}

return plugin.Frame{Func: funcname, File: fileline, Line: linenumber}, false
return plugin.Frame{Func: funcname, File: fileline, Line: linenumber, Column: columnnumber}, false
}

// addrInfo returns the stack frame information for a specific program
Expand Down
6 changes: 3 additions & 3 deletions internal/binutils/binutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,8 @@ func TestLLVMSymbolizer(t *testing.T) {
frames []plugin.Frame
}{
{0x10, false, []plugin.Frame{
{Func: "Inlined_0x10", File: "foo.h", Line: 0},
{Func: "Func_0x10", File: "foo.c", Line: 2},
{Func: "Inlined_0x10", File: "foo.h", Line: 0, Column: 0},
{Func: "Func_0x10", File: "foo.c", Line: 2, Column: 1},
}},
{0x20, true, []plugin.Frame{
{Func: "foo_0x20", File: "0x20 8"},
Expand Down Expand Up @@ -532,7 +532,7 @@ func TestPEFile(t *testing.T) {
t.Fatalf("SourceLine: unexpected error %v", err)
}
wantFrames := []plugin.Frame{
{Func: "main", File: "hello.c", Line: 3},
{Func: "main", File: "hello.c", Line: 3, Column: 12},
}
if !reflect.DeepEqual(gotFrames, wantFrames) {
t.Fatalf("SourceLine for main: got %v; want %v\n", gotFrames, wantFrames)
Expand Down
2 changes: 2 additions & 0 deletions internal/driver/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ var configHelp = map[string]string{
"noinlines": helpText(
"Ignore inlines.",
"Attributes inlined functions to their first out-of-line caller."),
"showcolumns": helpText(
"Show column numbers at the source code line level."),
}

func helpText(s ...string) string {
Expand Down
2 changes: 2 additions & 0 deletions internal/driver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type config struct {
TagShow string `json:"tagshow,omitempty"`
TagHide string `json:"taghide,omitempty"`
NoInlines bool `json:"noinlines,omitempty"`
ShowColumns bool `json:"showcolumns,omitempty"`

// Output granularity
Granularity string `json:"granularity,omitempty"`
Expand Down Expand Up @@ -157,6 +158,7 @@ func init() {
"sort": "sort",
"granularity": "g",
"noinlines": "noinlines",
"showcolumns": "showcolumns",
}

def := defaultConfig()
Expand Down
2 changes: 1 addition & 1 deletion internal/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func aggregate(prof *profile.Profile, cfg config) error {
default:
return fmt.Errorf("unexpected granularity")
}
return prof.Aggregate(inlines, function, filename, linenumber, address)
return prof.Aggregate(inlines, function, filename, linenumber, cfg.ShowColumns, address)
}

func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
Expand Down
5 changes: 3 additions & 2 deletions internal/driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1680,8 +1680,9 @@ func (m *mockFile) BuildID() string {
func (*mockFile) SourceLine(addr uint64) ([]plugin.Frame, error) {
// Return enough data to support the SourceLine() calls needed for
// weblist on cpuProfile() contents.
frame := func(fn, file string, line int) plugin.Frame {
return plugin.Frame{Func: fn, File: file, Line: line}
frame := func(fn, file string, num int) plugin.Frame {
// Reuse the same num for line number and column number.
return plugin.Frame{Func: fn, File: file, Line: num, Column: num}
}
switch addr {
case 0x1000:
Expand Down
1 change: 1 addition & 0 deletions internal/driver/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func TestParseConfig(t *testing.T) {
Sort: "cum",
Granularity: "functions",
NoInlines: true,
ShowColumns: true,
}
url, changed := cfg.makeURL(url.URL{})
if !changed {
Expand Down
15 changes: 11 additions & 4 deletions internal/graph/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ type NodeInfo struct {
Address uint64
File string
StartLine, Lineno int
Columnno int
Objfile string
}

Expand All @@ -174,8 +175,12 @@ func (i *NodeInfo) NameComponents() []string {

switch {
case i.Lineno != 0:
s := fmt.Sprintf("%s:%d", i.File, i.Lineno)
if i.Columnno != 0 {
s += fmt.Sprintf(":%d", i.Columnno)
}
// User requested line numbers, provide what we have.
name = append(name, fmt.Sprintf("%s:%d", i.File, i.Lineno))
name = append(name, s)
case i.File != "":
// User requested file name, provide it.
name = append(name, i.File)
Expand Down Expand Up @@ -239,6 +244,7 @@ func (nm NodeMap) FindOrInsertNode(info NodeInfo, kept NodeSet) *Node {
// Find a node that represents the whole function.
info.Address = 0
info.Lineno = 0
info.Columnno = 0
n.Function = nm.FindOrInsertNode(info, nil)
return n
}
Expand Down Expand Up @@ -592,9 +598,10 @@ func nodeInfo(l *profile.Location, line profile.Line, objfile string, o *Options
return &NodeInfo{Address: l.Address, Objfile: objfile}
}
ni := &NodeInfo{
Address: l.Address,
Lineno: int(line.Line),
Name: line.Function.Name,
Address: l.Address,
Lineno: int(line.Line),
Columnno: int(line.Column),
Name: line.Function.Name,
}
if fname := line.Function.Filename; fname != "" {
ni.File = filepath.Clean(fname)
Expand Down
9 changes: 5 additions & 4 deletions internal/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,12 @@ type ObjFile interface {
Close() error
}

// A Frame describes a single line in a source file.
// A Frame describes a location in a single line in a source file.
type Frame struct {
Func string // name of function
File string // source file name
Line int // line in file
Func string // name of function
File string // source file name
Line int // line in file
Column int // column in line (if available)
}

// A Sym describes a single symbol in an object file.
Expand Down
1 change: 1 addition & 0 deletions internal/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ func printTopProto(w io.Writer, rpt *Report) error {
Line: []profile.Line{
{
Line: int64(n.Info.Lineno),
Column: int64(n.Info.Columnno),
Function: f,
},
},
Expand Down
2 changes: 2 additions & 0 deletions internal/report/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ var testL = []*profile.Location{
{
Function: testF[0],
Line: 2,
Column: 2,
},
},
},
Expand All @@ -201,6 +202,7 @@ var testL = []*profile.Location{
{
Function: testF[1],
Line: 4,
Column: 4,
},
},
},
Expand Down
12 changes: 6 additions & 6 deletions internal/report/testdata/source.dot
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ digraph "unnamed" {
node [style=filled fillcolor="#f8f8f8"]
subgraph cluster_L { "Duration: 10s, Total samples = 11111 " [shape=box fontsize=16 label="Duration: 10s, Total samples = 11111 \lShowing nodes accounting for 11111, 100% of 11111 total\l\lSee https://git.io/JfYMW for how to read the graph\l"] }
N1 [label="tee\nsource2:8\n10000 (90.00%)" id="node1" fontsize=24 shape=box tooltip="tee testdata/source2:8 (10000)" color="#b20500" fillcolor="#edd6d5"]
N2 [label="main\nsource1:2\n1 (0.009%)\nof 11111 (100%)" id="node2" fontsize=9 shape=box tooltip="main testdata/source1:2 (11111)" color="#b20000" fillcolor="#edd5d5"]
N2 [label="main\nsource1:2:2\n1 (0.009%)\nof 11111 (100%)" id="node2" fontsize=9 shape=box tooltip="main testdata/source1:2:2 (11111)" color="#b20000" fillcolor="#edd5d5"]
N3 [label="tee\nsource2:2\n1000 (9.00%)\nof 11000 (99.00%)" id="node3" fontsize=14 shape=box tooltip="tee testdata/source2:2 (11000)" color="#b20000" fillcolor="#edd5d5"]
N4 [label="tee\nsource2:8\n100 (0.9%)" id="node4" fontsize=10 shape=box tooltip="tee testdata/source2:8 (100)" color="#b2b0aa" fillcolor="#edecec"]
N5 [label="bar\nsource1:10\n10 (0.09%)" id="node5" fontsize=9 shape=box tooltip="bar testdata/source1:10 (10)" color="#b2b2b1" fillcolor="#ededed"]
N6 [label="bar\nsource1:10\n0 of 100 (0.9%)" id="node6" fontsize=8 shape=box tooltip="bar testdata/source1:10 (100)" color="#b2b0aa" fillcolor="#edecec"]
N7 [label="foo\nsource1:4\n0 of 10 (0.09%)" id="node7" fontsize=8 shape=box tooltip="foo testdata/source1:4 (10)" color="#b2b2b1" fillcolor="#ededed"]
N2 -> N3 [label=" 11000" weight=100 penwidth=5 color="#b20000" tooltip="main testdata/source1:2 -> tee testdata/source2:2 (11000)" labeltooltip="main testdata/source1:2 -> tee testdata/source2:2 (11000)"]
N7 [label="foo\nsource1:4:4\n0 of 10 (0.09%)" id="node7" fontsize=8 shape=box tooltip="foo testdata/source1:4:4 (10)" color="#b2b2b1" fillcolor="#ededed"]
N2 -> N3 [label=" 11000" weight=100 penwidth=5 color="#b20000" tooltip="main testdata/source1:2:2 -> tee testdata/source2:2 (11000)" labeltooltip="main testdata/source1:2:2 -> tee testdata/source2:2 (11000)"]
N3 -> N1 [label=" 10000" weight=91 penwidth=5 color="#b20500" tooltip="tee testdata/source2:2 -> tee testdata/source2:8 (10000)" labeltooltip="tee testdata/source2:2 -> tee testdata/source2:8 (10000)"]
N6 -> N4 [label=" 100" color="#b2b0aa" tooltip="bar testdata/source1:10 -> tee testdata/source2:8 (100)" labeltooltip="bar testdata/source1:10 -> tee testdata/source2:8 (100)"]
N2 -> N6 [label=" 100" color="#b2b0aa" tooltip="main testdata/source1:2 -> bar testdata/source1:10 (100)" labeltooltip="main testdata/source1:2 -> bar testdata/source1:10 (100)"]
N7 -> N5 [label=" 10" color="#b2b2b1" tooltip="foo testdata/source1:4 -> bar testdata/source1:10 (10)" labeltooltip="foo testdata/source1:4 -> bar testdata/source1:10 (10)"]
N2 -> N7 [label=" 10" color="#b2b2b1" tooltip="main testdata/source1:2 -> foo testdata/source1:4 (10)" labeltooltip="main testdata/source1:2 -> foo testdata/source1:4 (10)"]
N2 -> N6 [label=" 100" color="#b2b0aa" tooltip="main testdata/source1:2:2 -> bar testdata/source1:10 (100)" labeltooltip="main testdata/source1:2:2 -> bar testdata/source1:10 (100)"]
N7 -> N5 [label=" 10" color="#b2b2b1" tooltip="foo testdata/source1:4:4 -> bar testdata/source1:10 (10)" labeltooltip="foo testdata/source1:4:4 -> bar testdata/source1:10 (10)"]
N2 -> N7 [label=" 10" color="#b2b2b1" tooltip="main testdata/source1:2:2 -> foo testdata/source1:4:4 (10)" labeltooltip="main testdata/source1:2:2 -> foo testdata/source1:4:4 (10)"]
}
1 change: 1 addition & 0 deletions internal/symbolizer/symbolizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjToo
l.Line[i] = profile.Line{
Function: f,
Line: int64(frame.Line),
Column: int64(frame.Column),
}
}

Expand Down
22 changes: 13 additions & 9 deletions internal/symbolizer/symbolizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,23 +242,27 @@ func checkSymbolizedLocation(a uint64, got []profile.Line) error {
if g.Line != int64(w.Line) {
return fmt.Errorf("want lineno: %d, got %d", w.Line, g.Line)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: "line" here or "columnno" below?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
if g.Column != int64(w.Column) {
return fmt.Errorf("want column: %d, got %d", w.Column, g.Column)
}
}
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)},
1000: {frame("fun11", "file11.src", 10, 1)},
2000: {frame("fun21", "file21.src", 20, 2), frame("fun22", "file22.src", 20, 2)},
3000: {frame("fun31", "file31.src", 30, 3), frame("fun32", "file32.src", 30, 3), frame("fun33", "file33.src", 30, 3)},
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)},
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)},
}

func frame(fname, file string, line int) plugin.Frame {
func frame(fname, file string, line int, column int) plugin.Frame {
return plugin.Frame{
Func: fname,
File: file,
Line: line}
Func: fname,
File: file,
Line: line,
Column: column}
}

func TestDemangleSingleFunction(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions profile/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ func (p *Line) decoder() []decoder {
func (p *Line) encode(b *buffer) {
encodeUint64Opt(b, 1, p.functionIDX)
encodeInt64Opt(b, 2, p.Line)
encodeInt64Opt(b, 3, p.Column)
}

var lineDecoder = []decoder{
Expand All @@ -538,6 +539,8 @@ var lineDecoder = []decoder{
func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) },
// optional int64 line = 2
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) },
// optional int64 column = 3
func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Column) },
}

func (p *Function) decoder() []decoder {
Expand Down
4 changes: 2 additions & 2 deletions profile/legacy_java_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func javaCPUProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte
}

// Strip out addresses for better merge.
if err = p.Aggregate(true, true, true, true, false); err != nil {
if err = p.Aggregate(true, true, true, true, false, false); err != nil {
return nil, err
}

Expand Down Expand Up @@ -99,7 +99,7 @@ func parseJavaProfile(b []byte) (*Profile, error) {
}

// Strip out addresses for better merge.
if err = p.Aggregate(true, true, true, true, false); err != nil {
if err = p.Aggregate(true, true, true, true, false, false); err != nil {
return nil, err
}

Expand Down
4 changes: 3 additions & 1 deletion profile/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,12 +326,13 @@ func (l *Location) key() locationKey {
key.addr -= l.Mapping.Start
key.mappingID = l.Mapping.ID
}
lines := make([]string, len(l.Line)*2)
lines := make([]string, len(l.Line)*3)
for i, line := range l.Line {
if line.Function != nil {
lines[i*2] = strconv.FormatUint(line.Function.ID, 16)
}
lines[i*2+1] = strconv.FormatInt(line.Line, 16)
lines[i*2+2] = strconv.FormatInt(line.Column, 16)
}
key.lines = strings.Join(lines, "|")
return key
Expand Down Expand Up @@ -418,6 +419,7 @@ func (pm *profileMerger) mapLine(src Line) Line {
ln := Line{
Function: pm.mapFunction(src.Function),
Line: src.Line,
Column: src.Column,
}
return ln
}
Expand Down
14 changes: 11 additions & 3 deletions profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ type Location struct {
type Line struct {
Function *Function
Line int64
Column int64

functionIDX uint64
}
Expand Down Expand Up @@ -436,7 +437,7 @@ func (p *Profile) CheckValid() error {
// Aggregate merges the locations in the profile into equivalence
// classes preserving the request attributes. It also updates the
// samples to point to the merged locations.
func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, columnnumber, address bool) error {
for _, m := range p.Mapping {
m.HasInlineFrames = m.HasInlineFrames && inlineFrame
m.HasFunctions = m.HasFunctions && function
Expand All @@ -458,14 +459,20 @@ func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address
}

// Aggregate locations
if !inlineFrame || !address || !linenumber {
if !inlineFrame || !address || !linenumber || !columnnumber {
for _, l := range p.Location {
if !inlineFrame && len(l.Line) > 1 {
l.Line = l.Line[len(l.Line)-1:]
}
if !linenumber {
for i := range l.Line {
l.Line[i].Line = 0
l.Line[i].Column = 0
}
}
if !columnnumber {
for i := range l.Line {
l.Line[i].Column = 0
}
}
if !address {
Expand Down Expand Up @@ -627,10 +634,11 @@ func (l *Location) string() string {
for li := range l.Line {
lnStr := "??"
if fn := l.Line[li].Function; fn != nil {
lnStr = fmt.Sprintf("%s %s:%d s=%d",
lnStr = fmt.Sprintf("%s %s:%d:%d s=%d",
fn.Name,
fn.Filename,
l.Line[li].Line,
l.Line[li].Column,
fn.StartLine)
if fn.Name != fn.SystemName {
lnStr = lnStr + "(" + fn.SystemName + ")"
Expand Down
Loading
Loading