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 namedEdgeMap, totalWeight, err := createNamedEdgeMap(g)
54 if err != nil {
55 return nil, err
56 }
57
58 if totalWeight == 0 {
59 return emptyProfile(), nil
60 }
61
62 return &Profile{
63 TotalWeight: totalWeight,
64 NamedEdgeMap: namedEdgeMap,
65 }, nil
66 }
67
68
69
70
71
72 func createNamedEdgeMap(g *profile.Graph) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
73 seenStartLine := false
74
75
76
77 weight := make(map[NamedCallEdge]int64)
78 for _, n := range g.Nodes {
79 seenStartLine = seenStartLine || n.Info.StartLine != 0
80
81 canonicalName := n.Info.Name
82
83 namedEdge := NamedCallEdge{
84 CallerName: canonicalName,
85 CallSiteOffset: n.Info.Lineno - n.Info.StartLine,
86 }
87
88 for _, e := range n.Out {
89 totalWeight += e.WeightValue()
90 namedEdge.CalleeName = e.Dest.Info.Name
91
92 weight[namedEdge] += e.WeightValue()
93 }
94 }
95
96 if !seenStartLine {
97
98
99
100 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)")
101 }
102 return postProcessNamedEdgeMap(weight, totalWeight)
103 }
104
105 func sortByWeight(edges []NamedCallEdge, weight map[NamedCallEdge]int64) {
106 sort.Slice(edges, func(i, j int) bool {
107 ei, ej := edges[i], edges[j]
108 if wi, wj := weight[ei], weight[ej]; wi != wj {
109 return wi > wj
110 }
111
112 if ei.CallerName != ej.CallerName {
113 return ei.CallerName < ej.CallerName
114 }
115 if ei.CalleeName != ej.CalleeName {
116 return ei.CalleeName < ej.CalleeName
117 }
118 return ei.CallSiteOffset < ej.CallSiteOffset
119 })
120 }
121
122 func postProcessNamedEdgeMap(weight map[NamedCallEdge]int64, weightVal int64) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
123 if weightVal == 0 {
124 return NamedEdgeMap{}, 0, nil
125 }
126 byWeight := make([]NamedCallEdge, 0, len(weight))
127 for namedEdge := range weight {
128 byWeight = append(byWeight, namedEdge)
129 }
130 sortByWeight(byWeight, weight)
131
132 edgeMap = NamedEdgeMap{
133 Weight: weight,
134 ByWeight: byWeight,
135 }
136
137 totalWeight = weightVal
138
139 return edgeMap, totalWeight, nil
140 }
141
View as plain text