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 4 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."),
"columns": helpText(
"Use column numbers when aggregating at the source code line level."),
}

func helpText(s ...string) string {
Expand Down
4 changes: 3 additions & 1 deletion 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"`
Columns bool `json:"columns,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe ShowColumns? See another comment.

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.


// Output granularity
Granularity string `json:"granularity,omitempty"`
Expand Down Expand Up @@ -124,7 +125,7 @@ func init() {
// take on one of a bounded set of values.
choices := map[string][]string{
"sort": {"cum", "flat"},
"granularity": {"functions", "filefunctions", "files", "lines", "addresses"},
"granularity": {"functions", "filefunctions", "files", "lines", "columns", "addresses"},
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it make sense to instead make this an option like columns similar to noinlines? But I guess pprof -lines -columns would look weird.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I did make this an option in internal/driver/commands.go. Can you take a look?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We should remove it from the granularity list here then?

}

// urlparam holds the mapping from a config field name to the URL
Expand Down Expand Up @@ -157,6 +158,7 @@ func init() {
"sort": "sort",
"granularity": "g",
"noinlines": "noinlines",
"columns": "columns",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe name it "showcolumns" so that -lines -showcolumns looks a bit better?

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.

}

def := defaultConfig()
Expand Down
9 changes: 7 additions & 2 deletions internal/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func dropEmptyStrings(in []string) (out []string) {
}

func aggregate(prof *profile.Profile, cfg config) error {
var function, filename, linenumber, address bool
var function, filename, linenumber, columnnumber, address bool
inlines := !cfg.NoInlines
switch cfg.Granularity {
case "addresses":
Expand All @@ -246,6 +246,11 @@ func aggregate(prof *profile.Profile, cfg config) error {
function = true
filename = true
linenumber = true
case "columns":
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this shouldn't be in the granularity switch anymore and should be handled as option instead?

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.

function = true
filename = true
linenumber = true
columnnumber = true
case "files":
filename = true
case "functions":
Expand All @@ -256,7 +261,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, columnnumber, 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,
Columns: true,
}
url, changed := cfg.makeURL(url.URL{})
if !changed {
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/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