-
Notifications
You must be signed in to change notification settings - Fork 615
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 a folded stacks output format #669
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,7 @@ const ( | |
Traces | ||
Tree | ||
WebList | ||
Folded | ||
) | ||
|
||
// Options are the formatting and filtering options used to generate a | ||
|
@@ -94,6 +95,8 @@ func Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error { | |
return printDOT(w, rpt) | ||
case Tree: | ||
return printTree(w, rpt) | ||
case Folded: | ||
return printFolded(w, rpt) | ||
case Text: | ||
return printText(w, rpt) | ||
case Traces: | ||
|
@@ -828,6 +831,55 @@ func printText(w io.Writer, rpt *Report) error { | |
return nil | ||
} | ||
|
||
// printFolded prints a profile in Brendan Gregg's Folded Stacks format. | ||
func printFolded(w io.Writer, rpt *Report) error { | ||
prof := rpt.prof | ||
o := rpt.options | ||
|
||
_, locations := graph.CreateNodes(prof, &graph.Options{}) | ||
for _, sample := range prof.Sample { | ||
var stack []*graph.NodeInfo | ||
for _, loc := range sample.Location { | ||
nodes := locations[loc.ID] | ||
for _, n := range nodes { | ||
stack = append(stack, &n.Info) | ||
} | ||
} | ||
|
||
if len(stack) == 0 { | ||
continue | ||
} | ||
|
||
var d, v int64 | ||
v = o.SampleValue(sample.Value) | ||
if o.SampleMeanDivisor != nil { | ||
d = o.SampleMeanDivisor(sample.Value) | ||
} | ||
if d != 0 { | ||
v = v / d | ||
} | ||
// Print call stack. | ||
for i := range stack { | ||
// Folded stack convention: start with root frame, end | ||
// with leaves. | ||
s := stack[len(stack)-i-1] | ||
if i > 0 { | ||
fmt.Fprint(w, ";") | ||
} | ||
// TODO: should we print more than just s.Name? | ||
// NodeInfo.PrintableName() has a lot more. | ||
|
||
// Remove semicolons and newlines. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a note on why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Err, didn't mean to click "Add single comment", I'll have more comments! |
||
name := strings.ReplaceAll(s.Name, ";", "") | ||
name = strings.ReplaceAll(name, "\n", "") | ||
fmt.Fprint(w, name) | ||
} | ||
// We just want a raw number, so don't use rpt.formatValue(). | ||
fmt.Fprintf(w, " %d\n", v) | ||
} | ||
return nil | ||
} | ||
|
||
// printTraces prints all traces from a profile. | ||
func printTraces(w io.Writer, rpt *Report) error { | ||
fmt.Fprintln(w, strings.Join(ProfileLabels(rpt), "\n")) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,9 +33,12 @@ type testcase struct { | |
want string | ||
} | ||
|
||
func TestSource(t *testing.T) { | ||
func TestTextReports(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we have a separate test for the folded report? I find such combined meta-tests somewhat complicated... If this is to share setup / teardown code then maybe we can share it by other means? |
||
const path = "testdata/" | ||
|
||
sampleValue0 := func(v []int64) int64 { | ||
return v[0] | ||
} | ||
sampleValue1 := func(v []int64) int64 { | ||
return v[1] | ||
} | ||
|
@@ -53,7 +56,7 @@ func TestSource(t *testing.T) { | |
SampleUnit: testProfile.SampleType[1].Unit, | ||
}, | ||
), | ||
want: path + "source.rpt", | ||
want: path + "report.source", | ||
}, | ||
{ | ||
rpt: New( | ||
|
@@ -68,7 +71,44 @@ func TestSource(t *testing.T) { | |
SampleUnit: testProfile.SampleType[1].Unit, | ||
}, | ||
), | ||
want: path + "source.dot", | ||
want: path + "report.dot", | ||
}, | ||
{ | ||
rpt: New( | ||
testProfile.Copy(), | ||
&Options{ | ||
OutputFormat: Folded, | ||
|
||
SampleValue: sampleValue1, | ||
SampleUnit: testProfile.SampleType[1].Unit, | ||
}, | ||
), | ||
want: path + "report.folded", | ||
}, | ||
{ | ||
rpt: New( | ||
testProfile.Copy(), | ||
&Options{ | ||
OutputFormat: Folded, | ||
|
||
SampleValue: sampleValue0, | ||
SampleUnit: testProfile.SampleType[0].Unit, | ||
}, | ||
), | ||
want: path + "report0.folded", | ||
}, | ||
{ | ||
rpt: New( | ||
testProfile.Copy(), | ||
&Options{ | ||
OutputFormat: Folded, | ||
|
||
SampleValue: sampleValue1, | ||
SampleUnit: testProfile.SampleType[1].Unit, | ||
SampleMeanDivisor: sampleValue0, | ||
}, | ||
), | ||
want: path + "report_mean.folded", | ||
}, | ||
} { | ||
var b bytes.Buffer | ||
|
@@ -169,7 +209,7 @@ var testF = []*profile.Function{ | |
}, | ||
{ | ||
ID: 3, | ||
Name: "bar", | ||
Name: "b;ar", | ||
Filename: "testdata/source1", | ||
}, | ||
{ | ||
|
@@ -259,7 +299,7 @@ var testProfile = &profile.Profile{ | |
}, | ||
{ | ||
Location: []*profile.Location{testL[4], testL[3], testL[0]}, | ||
Value: []int64{1, 10000}, | ||
Value: []int64{2, 10000}, | ||
}, | ||
}, | ||
Location: testL, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
main 1 | ||
main;foo;bar 10 | ||
main;bar;tee 100 | ||
main;tee 1000 | ||
main;tee;tee 10000 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
main 1 | ||
main;foo;bar 1 | ||
main;bar;tee 1 | ||
main;tee 1 | ||
main;tee;tee 2 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
main 1 | ||
main;foo;bar 10 | ||
main;bar;tee 100 | ||
main;tee 1000 | ||
main;tee;tee 5000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think this needs more discussion. There is an implicit interplay with the granularity. See below. I would expect that we utilize that - i.e. use PrintableName() or NameComponents() directly defining how they should join. This is in particular important because graph.CreateNodes() respects the granularity and so if we don't encode all components in the string, then something like "-folded -filefunctions" may produce duplicate elements.