1
2
3
4
5
6
7
8 package pgo
9
10 import (
11 "errors"
12 "fmt"
13 "internal/profile"
14 "io"
15 "sort"
16 )
17
18
19 func FromPProf(r io.Reader) (*Profile, error) {
20 p, err := profile.Parse(r)
21 if errors.Is(err, profile.ErrNoData) {
22
23
24 return emptyProfile(), nil
25 } else if err != nil {
26 return nil, fmt.Errorf("error parsing profile: %w", err)
27 }
28
29 if len(p.Sample) == 0 {
30
31 return emptyProfile(), nil
32 }
33
34 valueIndex := -1
35 for i, s := range p.SampleType {
36
37
38 if (s.Type == "samples" && s.Unit == "count") ||
39 (s.Type == "cpu" && s.Unit == "nanoseconds") {
40 valueIndex = i
41 break
42 }
43 }
44
45 if valueIndex == -1 {
46 return nil, fmt.Errorf(`profile does not contain a sample index with value/type "samples/count" or cpu/nanoseconds"`)
47 }
48
49 g := profile.NewGraph(p, &profile.Options{
50 SampleValue: func(v []int64) int64 { return v[valueIndex] },
51 })
52
53 if len(g.Nodes) == 0 {
54
55
56 return emptyProfile(), nil
57 }
58
59 namedEdgeMap, totalWeight, err := createNamedEdgeMap(g)
60 if err != nil {
61 return nil, err
62 }
63
64 if totalWeight == 0 {
65 return emptyProfile(), nil
66 }
67
68 return &Profile{
69 TotalWeight: totalWeight,
70 NamedEdgeMap: namedEdgeMap,
71 }, nil
72 }
73
74
75
76
77
78 func createNamedEdgeMap(g *profile.Graph) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
79 seenStartLine := false
80
81
82
83 weight := make(map[NamedCallEdge]int64)
84 for _, n := range g.Nodes {
85 seenStartLine = seenStartLine || n.Info.StartLine != 0
86
87 canonicalName := n.Info.Name
88
89 namedEdge := NamedCallEdge{
90 CallerName: canonicalName,
91 CallSiteOffset: n.Info.Lineno - n.Info.StartLine,
92 }
93
94 for _, e := range n.Out {
95 totalWeight += e.WeightValue()
96 namedEdge.CalleeName = e.Dest.Info.Name
97
98 weight[namedEdge] += e.WeightValue()
99 }
100 }
101
102 if !seenStartLine {
103
104
105
106 return NamedEdgeMap{}, 0, fmt.Errorf("profile missing Function.start_line data (Go version of profiled application too old? Go 1.20+ automatically adds this to profiles)")
107 }
108 return postProcessNamedEdgeMap(weight, totalWeight)
109 }
110
111 func sortByWeight(edges []NamedCallEdge, weight map[NamedCallEdge]int64) {
112 sort.Slice(edges, func(i, j int) bool {
113 ei, ej := edges[i], edges[j]
114 if wi, wj := weight[ei], weight[ej]; wi != wj {
115 return wi > wj
116 }
117
118 if ei.CallerName != ej.CallerName {
119 return ei.CallerName < ej.CallerName
120 }
121 if ei.CalleeName != ej.CalleeName {
122 return ei.CalleeName < ej.CalleeName
123 }
124 return ei.CallSiteOffset < ej.CallSiteOffset
125 })
126 }
127
128 func postProcessNamedEdgeMap(weight map[NamedCallEdge]int64, weightVal int64) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
129 if weightVal == 0 {
130 return NamedEdgeMap{}, 0, nil
131 }
132 byWeight := make([]NamedCallEdge, 0, len(weight))
133 for namedEdge := range weight {
134 byWeight = append(byWeight, namedEdge)
135 }
136 sortByWeight(byWeight, weight)
137
138 edgeMap = NamedEdgeMap{
139 Weight: weight,
140 ByWeight: byWeight,
141 }
142
143 totalWeight = weightVal
144
145 return edgeMap, totalWeight, nil
146 }
147
View as plain text