1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 package pgo
42
43 import (
44 "cmd/compile/internal/base"
45 "cmd/compile/internal/ir"
46 "cmd/compile/internal/pgo/internal/graph"
47 "cmd/compile/internal/typecheck"
48 "cmd/compile/internal/types"
49 "errors"
50 "fmt"
51 "internal/profile"
52 "os"
53 "sort"
54 )
55
56
57
58
59
60
61
62
63
64
65
66 type IRGraph struct {
67
68
69 IRNodes map[string]*IRNode
70 }
71
72
73 type IRNode struct {
74
75 AST *ir.Func
76
77
78 LinkerSymbolName string
79
80
81
82 OutEdges map[NamedCallEdge]*IREdge
83 }
84
85
86 func (i *IRNode) Name() string {
87 if i.AST != nil {
88 return ir.LinkFuncName(i.AST)
89 }
90 return i.LinkerSymbolName
91 }
92
93
94
95 type IREdge struct {
96
97 Src, Dst *IRNode
98 Weight int64
99 CallSiteOffset int
100 }
101
102
103
104 type NamedCallEdge struct {
105 CallerName string
106 CalleeName string
107 CallSiteOffset int
108 }
109
110
111
112 type NamedEdgeMap struct {
113 Weight map[NamedCallEdge]int64
114
115
116 ByWeight []NamedCallEdge
117 }
118
119
120 type CallSiteInfo struct {
121 LineOffset int
122 Caller *ir.Func
123 Callee *ir.Func
124 }
125
126
127
128 type Profile struct {
129
130
131 TotalWeight int64
132
133
134
135 NamedEdgeMap NamedEdgeMap
136
137
138
139 WeightedCG *IRGraph
140 }
141
142
143 func New(profileFile string) (*Profile, error) {
144 f, err := os.Open(profileFile)
145 if err != nil {
146 return nil, fmt.Errorf("error opening profile: %w", err)
147 }
148 defer f.Close()
149 p, err := profile.Parse(f)
150 if errors.Is(err, profile.ErrNoData) {
151
152
153 return nil, nil
154 } else if err != nil {
155 return nil, fmt.Errorf("error parsing profile: %w", err)
156 }
157
158 if len(p.Sample) == 0 {
159
160 return nil, nil
161 }
162
163 valueIndex := -1
164 for i, s := range p.SampleType {
165
166
167 if (s.Type == "samples" && s.Unit == "count") ||
168 (s.Type == "cpu" && s.Unit == "nanoseconds") {
169 valueIndex = i
170 break
171 }
172 }
173
174 if valueIndex == -1 {
175 return nil, fmt.Errorf(`profile does not contain a sample index with value/type "samples/count" or cpu/nanoseconds"`)
176 }
177
178 g := graph.NewGraph(p, &graph.Options{
179 SampleValue: func(v []int64) int64 { return v[valueIndex] },
180 })
181
182 namedEdgeMap, totalWeight, err := createNamedEdgeMap(g)
183 if err != nil {
184 return nil, err
185 }
186
187 if totalWeight == 0 {
188 return nil, nil
189 }
190
191
192 wg := createIRGraph(namedEdgeMap)
193
194 return &Profile{
195 TotalWeight: totalWeight,
196 NamedEdgeMap: namedEdgeMap,
197 WeightedCG: wg,
198 }, nil
199 }
200
201
202
203
204
205 func createNamedEdgeMap(g *graph.Graph) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
206 seenStartLine := false
207
208
209
210 weight := make(map[NamedCallEdge]int64)
211 for _, n := range g.Nodes {
212 seenStartLine = seenStartLine || n.Info.StartLine != 0
213
214 canonicalName := n.Info.Name
215
216 namedEdge := NamedCallEdge{
217 CallerName: canonicalName,
218 CallSiteOffset: n.Info.Lineno - n.Info.StartLine,
219 }
220
221 for _, e := range n.Out {
222 totalWeight += e.WeightValue()
223 namedEdge.CalleeName = e.Dest.Info.Name
224
225 weight[namedEdge] += e.WeightValue()
226 }
227 }
228
229 if totalWeight == 0 {
230 return NamedEdgeMap{}, 0, nil
231 }
232
233 if !seenStartLine {
234
235
236
237 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)")
238 }
239
240 byWeight := make([]NamedCallEdge, 0, len(weight))
241 for namedEdge := range weight {
242 byWeight = append(byWeight, namedEdge)
243 }
244 sort.Slice(byWeight, func(i, j int) bool {
245 ei, ej := byWeight[i], byWeight[j]
246 if wi, wj := weight[ei], weight[ej]; wi != wj {
247 return wi > wj
248 }
249
250 if ei.CallerName != ej.CallerName {
251 return ei.CallerName < ej.CallerName
252 }
253 if ei.CalleeName != ej.CalleeName {
254 return ei.CalleeName < ej.CalleeName
255 }
256 return ei.CallSiteOffset < ej.CallSiteOffset
257 })
258
259 edgeMap = NamedEdgeMap{
260 Weight: weight,
261 ByWeight: byWeight,
262 }
263
264 return edgeMap, totalWeight, nil
265 }
266
267
268
269 func createIRGraph(namedEdgeMap NamedEdgeMap) *IRGraph {
270 g := &IRGraph{
271 IRNodes: make(map[string]*IRNode),
272 }
273
274
275 ir.VisitFuncsBottomUp(typecheck.Target.Funcs, func(list []*ir.Func, recursive bool) {
276 for _, fn := range list {
277 visitIR(fn, namedEdgeMap, g)
278 }
279 })
280
281
282
283
284
285
286
287
288
289
290
291 addIndirectEdges(g, namedEdgeMap)
292
293 return g
294 }
295
296
297
298 func visitIR(fn *ir.Func, namedEdgeMap NamedEdgeMap, g *IRGraph) {
299 name := ir.LinkFuncName(fn)
300 node, ok := g.IRNodes[name]
301 if !ok {
302 node = &IRNode{
303 AST: fn,
304 }
305 g.IRNodes[name] = node
306 }
307
308
309 createIRGraphEdge(fn, node, name, namedEdgeMap, g)
310 }
311
312
313
314
315 func createIRGraphEdge(fn *ir.Func, callernode *IRNode, name string, namedEdgeMap NamedEdgeMap, g *IRGraph) {
316 ir.VisitList(fn.Body, func(n ir.Node) {
317 switch n.Op() {
318 case ir.OCALLFUNC:
319 call := n.(*ir.CallExpr)
320
321 callee := DirectCallee(call.Fun)
322 if callee != nil {
323 addIREdge(callernode, name, n, callee, namedEdgeMap, g)
324 }
325 case ir.OCALLMETH:
326 call := n.(*ir.CallExpr)
327
328 callee := ir.MethodExprName(call.Fun).Func
329 addIREdge(callernode, name, n, callee, namedEdgeMap, g)
330 }
331 })
332 }
333
334
335 func NodeLineOffset(n ir.Node, fn *ir.Func) int {
336
337 line := int(base.Ctxt.InnermostPos(n.Pos()).RelLine())
338 startLine := int(base.Ctxt.InnermostPos(fn.Pos()).RelLine())
339 return line - startLine
340 }
341
342
343
344 func addIREdge(callerNode *IRNode, callerName string, call ir.Node, callee *ir.Func, namedEdgeMap NamedEdgeMap, g *IRGraph) {
345 calleeName := ir.LinkFuncName(callee)
346 calleeNode, ok := g.IRNodes[calleeName]
347 if !ok {
348 calleeNode = &IRNode{
349 AST: callee,
350 }
351 g.IRNodes[calleeName] = calleeNode
352 }
353
354 namedEdge := NamedCallEdge{
355 CallerName: callerName,
356 CalleeName: calleeName,
357 CallSiteOffset: NodeLineOffset(call, callerNode.AST),
358 }
359
360
361 edge := &IREdge{
362 Src: callerNode,
363 Dst: calleeNode,
364 Weight: namedEdgeMap.Weight[namedEdge],
365 CallSiteOffset: namedEdge.CallSiteOffset,
366 }
367
368 if callerNode.OutEdges == nil {
369 callerNode.OutEdges = make(map[NamedCallEdge]*IREdge)
370 }
371 callerNode.OutEdges[namedEdge] = edge
372 }
373
374
375
376 var LookupFunc = func(fullName string) (*ir.Func, error) {
377 base.Fatalf("pgo.LookupMethodFunc not overridden")
378 panic("unreachable")
379 }
380
381
382
383
384
385
386
387
388
389
390
391
392 func addIndirectEdges(g *IRGraph, namedEdgeMap NamedEdgeMap) {
393
394
395
396
397 localNodes := make(map[string]*IRNode, len(g.IRNodes))
398 for k, v := range g.IRNodes {
399 localNodes[k] = v
400 }
401
402
403
404
405
406
407
408
409 for _, key := range namedEdgeMap.ByWeight {
410 weight := namedEdgeMap.Weight[key]
411
412
413
414
415 callerNode, ok := localNodes[key.CallerName]
416 if !ok {
417 continue
418 }
419
420
421 if _, ok := callerNode.OutEdges[key]; ok {
422 continue
423 }
424
425 calleeNode, ok := g.IRNodes[key.CalleeName]
426 if !ok {
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443 fn, err := LookupFunc(key.CalleeName)
444 if err == nil {
445 if base.Debug.PGODebug >= 3 {
446 fmt.Printf("addIndirectEdges: %s found in export data\n", key.CalleeName)
447 }
448 calleeNode = &IRNode{AST: fn}
449
450
451
452
453
454
455
456
457
458
459
460
461
462 } else {
463
464
465
466
467
468
469
470 if base.Debug.PGODebug >= 3 {
471 fmt.Printf("addIndirectEdges: %s not found in export data: %v\n", key.CalleeName, err)
472 }
473 calleeNode = &IRNode{LinkerSymbolName: key.CalleeName}
474 }
475
476
477
478
479 g.IRNodes[key.CalleeName] = calleeNode
480 }
481 edge := &IREdge{
482 Src: callerNode,
483 Dst: calleeNode,
484 Weight: weight,
485 CallSiteOffset: key.CallSiteOffset,
486 }
487
488 if callerNode.OutEdges == nil {
489 callerNode.OutEdges = make(map[NamedCallEdge]*IREdge)
490 }
491 callerNode.OutEdges[key] = edge
492 }
493 }
494
495
496 func WeightInPercentage(value int64, total int64) float64 {
497 return (float64(value) / float64(total)) * 100
498 }
499
500
501 func (p *Profile) PrintWeightedCallGraphDOT(edgeThreshold float64) {
502 fmt.Printf("\ndigraph G {\n")
503 fmt.Printf("forcelabels=true;\n")
504
505
506 funcs := make(map[string]struct{})
507 ir.VisitFuncsBottomUp(typecheck.Target.Funcs, func(list []*ir.Func, recursive bool) {
508 for _, f := range list {
509 name := ir.LinkFuncName(f)
510 funcs[name] = struct{}{}
511 }
512 })
513
514
515
516
517
518 nodes := make(map[string]*ir.Func)
519 for name := range funcs {
520 if n, ok := p.WeightedCG.IRNodes[name]; ok {
521 for _, e := range n.OutEdges {
522 if _, ok := nodes[e.Src.Name()]; !ok {
523 nodes[e.Src.Name()] = e.Src.AST
524 }
525 if _, ok := nodes[e.Dst.Name()]; !ok {
526 nodes[e.Dst.Name()] = e.Dst.AST
527 }
528 }
529 if _, ok := nodes[n.Name()]; !ok {
530 nodes[n.Name()] = n.AST
531 }
532 }
533 }
534
535
536 for name, ast := range nodes {
537 if _, ok := p.WeightedCG.IRNodes[name]; ok {
538 style := "solid"
539 if ast == nil {
540 style = "dashed"
541 }
542
543 if ast != nil && ast.Inl != nil {
544 fmt.Printf("\"%v\" [color=black, style=%s, label=\"%v,inl_cost=%d\"];\n", name, style, name, ast.Inl.Cost)
545 } else {
546 fmt.Printf("\"%v\" [color=black, style=%s, label=\"%v\"];\n", name, style, name)
547 }
548 }
549 }
550
551 ir.VisitFuncsBottomUp(typecheck.Target.Funcs, func(list []*ir.Func, recursive bool) {
552 for _, f := range list {
553 name := ir.LinkFuncName(f)
554 if n, ok := p.WeightedCG.IRNodes[name]; ok {
555 for _, e := range n.OutEdges {
556 style := "solid"
557 if e.Dst.AST == nil {
558 style = "dashed"
559 }
560 color := "black"
561 edgepercent := WeightInPercentage(e.Weight, p.TotalWeight)
562 if edgepercent > edgeThreshold {
563 color = "red"
564 }
565
566 fmt.Printf("edge [color=%s, style=%s];\n", color, style)
567 fmt.Printf("\"%v\" -> \"%v\" [label=\"%.2f\"];\n", n.Name(), e.Dst.Name(), edgepercent)
568 }
569 }
570 }
571 })
572 fmt.Printf("}\n")
573 }
574
575
576
577
578
579 func DirectCallee(fn ir.Node) *ir.Func {
580 fn = ir.StaticValue(fn)
581 switch fn.Op() {
582 case ir.OMETHEXPR:
583 fn := fn.(*ir.SelectorExpr)
584 n := ir.MethodExprName(fn)
585
586
587
588 if n == nil || !types.Identical(n.Type().Recv().Type, fn.X.Type()) {
589 return nil
590 }
591 return n.Func
592 case ir.ONAME:
593 fn := fn.(*ir.Name)
594 if fn.Class == ir.PFUNC {
595 return fn.Func
596 }
597 case ir.OCLOSURE:
598 fn := fn.(*ir.ClosureExpr)
599 c := fn.Func
600 return c
601 }
602 return nil
603 }
604
View as plain text