Skip to content

Commit 1970fec

Browse files
committed
[add] Added Flame Graph exporting support
1 parent 1d37944 commit 1970fec

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

cmd/export.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"github.com/google/pprof/driver"
9+
"github.com/google/pprof/profile"
910
"github.com/spf13/cobra"
1011
"io"
1112
"io/ioutil"
@@ -38,6 +39,16 @@ func exportLogic() func(cmd *cobra.Command, args []string) {
3839
log.Fatalf("Exactly one profile file is required")
3940
}
4041
if local {
42+
err, finalTree := generateFlameGraph(args[0])
43+
if err != nil {
44+
log.Fatalf("An Error Occured %v", err)
45+
}
46+
postBody, err := json.Marshal(finalTree)
47+
if err != nil {
48+
log.Fatalf("An Error Occured %v", err)
49+
}
50+
fmt.Println(string(postBody))
51+
4152
for _, granularity := range granularityOptions {
4253
err, report := generateTextReports(granularity, args[0])
4354
if err == nil {
@@ -59,12 +70,42 @@ func exportLogic() func(cmd *cobra.Command, args []string) {
5970
log.Fatal(err)
6071
}
6172
}
73+
err, finalTree := generateFlameGraph(args[0])
74+
if err != nil {
75+
log.Fatalf("An Error Occured %v", err)
76+
}
77+
remoteFlameGraphExport(finalTree)
6278
log.Printf("Successfully published profile data. Check it at: %s/gh/%s/%s/commit/%s/bench/%s/cpu", codeperfUrl, gitOrg, gitRepo, gitCommit, bench)
6379
}
6480

6581
}
6682
}
6783

84+
func remoteFlameGraphExport(tree treeNodeSlice) {
85+
postBody, err := json.Marshal(tree)
86+
if err != nil {
87+
log.Fatalf("An Error Occured %v", err)
88+
}
89+
responseBody := bytes.NewBuffer(postBody)
90+
endPoint := fmt.Sprintf("%s/v1/gh/%s/%s/commit/%s/bench/%s/cpu/flamegraph", codeperfApiUrl, gitOrg, gitRepo, gitCommit, bench)
91+
resp, err := http.Post(endPoint, "application/json", responseBody)
92+
//Handle Error
93+
if err != nil {
94+
log.Fatalf("An Error Occured %v", err)
95+
}
96+
defer resp.Body.Close()
97+
98+
//Read the response body
99+
reply, err := ioutil.ReadAll(resp.Body)
100+
if err != nil {
101+
log.Fatalln(err)
102+
}
103+
if resp.StatusCode != 200 {
104+
log.Fatalf("An error ocurred while phusing data to remote %s. Status code %d. Reply: %s", codeperfApiUrl, resp.StatusCode, string(reply))
105+
}
106+
107+
}
108+
68109
func remoteExportLogic(report TextReport, granularity string) {
69110
postBody, err := json.Marshal(report)
70111
if err != nil {
@@ -111,6 +152,48 @@ func localExportLogic(w io.Writer, report TextReport) {
111152
}
112153
}
113154

155+
func generateFlameGraph(input string) (err error, tree treeNodeSlice) {
156+
f := baseFlags()
157+
158+
// Read the profile from the encoded protobuf
159+
outputTempFile, err := ioutil.TempFile("", "profile_output")
160+
if err != nil {
161+
log.Fatalf("cannot create tempfile: %v", err)
162+
}
163+
log.Printf("Generating temp file %s", outputTempFile.Name())
164+
//defer os.Remove(outputTempFile.Name())
165+
defer outputTempFile.Close()
166+
f.strings["output"] = outputTempFile.Name()
167+
f.bools["proto"] = true
168+
f.bools["text"] = false
169+
f.args = []string{
170+
input,
171+
}
172+
reader := bufio.NewReader(os.Stdin)
173+
options := &driver.Options{
174+
Flagset: f,
175+
UI: &UI{r: reader},
176+
}
177+
178+
if err = driver.PProf(options); err != nil {
179+
log.Fatalf("cannot read pprof profile from %s. Error: %v", input, err)
180+
return
181+
}
182+
183+
file, err := os.Open(outputTempFile.Name())
184+
if err != nil {
185+
log.Fatal(err)
186+
}
187+
defer file.Close()
188+
r := bufio.NewReader(file)
189+
profile, err := profile.Parse(r)
190+
if err != nil {
191+
log.Fatal(err)
192+
}
193+
tree = profileToFolded(profile)
194+
return
195+
}
196+
114197
func generateTextReports(granularity string, input string) (err error, report TextReport) {
115198
f := baseFlags()
116199

cmd/folded.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"github.com/google/pprof/profile"
6+
"log"
7+
"sort"
8+
"strings"
9+
)
10+
11+
type treeNode struct {
12+
Name string `json:"n"`
13+
Cum int64 `json:"v"`
14+
Children map[string]*treeNode `json:"c"`
15+
}
16+
17+
type treeNodeSlice struct {
18+
Name string `json:"n"`
19+
Cum int64 `json:"v"`
20+
Children []treeNodeSlice `json:"c"`
21+
}
22+
23+
// Convert marshals the given protobuf profile into folded text format.
24+
func profileToFolded(protobuf *profile.Profile) treeNodeSlice {
25+
rootNode := treeNode{"root", 0, make(map[string]*treeNode, 0)}
26+
if err := protobuf.Aggregate(true, true, false, false, false); err != nil {
27+
log.Fatal(err)
28+
}
29+
protobuf = protobuf.Compact()
30+
sort.Slice(protobuf.Sample, func(i, j int) bool {
31+
return protobuf.Sample[i].Value[0] > protobuf.Sample[j].Value[0]
32+
})
33+
for _, sample := range protobuf.Sample {
34+
var frames []string
35+
var currentNode *treeNode
36+
var currentMap map[string]*treeNode = rootNode.Children
37+
for i := range sample.Location {
38+
var ok bool
39+
loc := sample.Location[len(sample.Location)-i-1]
40+
for j := range loc.Line {
41+
line := loc.Line[len(loc.Line)-j-1]
42+
fname := line.Function.Name
43+
currentNode, ok = currentMap[fname]
44+
if !ok {
45+
currentNode = &treeNode{fname, 0, make(map[string]*treeNode, 0)}
46+
currentMap[fname] = currentNode
47+
}
48+
currentMap = currentNode.Children
49+
frames = append(frames, fname)
50+
}
51+
}
52+
53+
var values []string
54+
for _, val := range sample.Value {
55+
values = append(values, fmt.Sprintf("%d", val))
56+
currentNode.Cum = currentNode.Cum + val
57+
break
58+
}
59+
fmt.Printf(
60+
"%s %s\n",
61+
strings.Join(frames, ";"),
62+
strings.Join(values, " "),
63+
)
64+
}
65+
finalTree := treeNodeSlice{rootNode.Name, rootNode.Cum, collapse(rootNode.Children)}
66+
return finalTree
67+
}
68+
69+
func collapse(children map[string]*treeNode) (tree []treeNodeSlice) {
70+
tree = make([]treeNodeSlice, 0)
71+
for _, k := range children {
72+
nS := treeNodeSlice{k.Name, k.Cum, collapse(k.Children)}
73+
tree = append(tree, nS)
74+
}
75+
return
76+
}

0 commit comments

Comments
 (0)