-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmethodcalls.go
More file actions
118 lines (99 loc) · 3.49 KB
/
methodcalls.go
File metadata and controls
118 lines (99 loc) · 3.49 KB
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package reqflow
import (
"go/ast"
"golang.org/x/tools/go/packages"
)
// noiseFields are struct field names that are infrastructure, not request flow.
var noiseFields = map[string]bool{
"Logger": true, "logger": true, "log": true, "Log": true,
"Metrics": true, "metrics": true, "Metric": true,
"Tracer": true, "tracer": true,
"Context": true, "Ctx": true, "ctx": true,
"mu": true, "Mutex": true, "mutex": true,
"wg": true, "WaitGroup": true,
"once": true, "Once": true,
"timer": true, "Timer": true, "ticker": true,
}
// noiseMethods are method names that are infrastructure calls.
var noiseMethods = map[string]bool{
"Errorf": true, "Infof": true, "Warnf": true, "Debugf": true, "Printf": true,
"Error": true, "Info": true, "Warn": true, "Debug": true, "Print": true, "Println": true,
"Fatalf": true, "Fatal": true, "Panicf": true, "Panic": true,
"Log": true, "Logf": true,
"WithField": true, "WithFields": true, "WithError": true, "WithContext": true,
"Lock": true, "Unlock": true, "RLock": true, "RUnlock": true,
"Add": true, "Done": true, "Wait": true,
"IncrementCounter": true, "RecordHistogram": true, "SetGauge": true,
"Start": true, "End": true, "Span": true,
}
func isNoiseCall(fieldName, methodName string) bool {
return noiseFields[fieldName] || noiseMethods[methodName]
}
// MethodCall represents a call from one struct method to another struct's method
// via a struct field. For example, if Handler.GetMetrics() contains the call
// h.svc.GetMetricsByOrg(), the MethodCall would be {FieldName: "svc", TargetMethod: "GetMetricsByOrg"}.
type MethodCall struct {
FieldName string // e.g. "svc", "store"
TargetMethod string // e.g. "GetMetrics", "GetCostRecords"
}
// MethodCallIndex maps "pkg.Struct.Method" → list of outgoing method calls.
type MethodCallIndex map[string][]MethodCall
// buildMethodCallIndex walks all receiver methods and finds calls of the form:
//
// receiver.field.Method(...)
//
// This allows the trace to show only the specific methods called at each layer,
// not all methods on the struct.
func buildMethodCallIndex(pkgs []*packages.Package, graph *Graph) MethodCallIndex {
index := make(MethodCallIndex)
for _, pkg := range pkgs {
for _, file := range pkg.Syntax {
ast.Inspect(file, func(n ast.Node) bool {
fn, ok := n.(*ast.FuncDecl)
if !ok || fn.Recv == nil || fn.Body == nil {
return true
}
// Get receiver struct ID
recvType := fn.Recv.List[0].Type
if star, ok := recvType.(*ast.StarExpr); ok {
recvType = star.X
}
ident, ok := recvType.(*ast.Ident)
if !ok {
return true
}
structID := pkg.PkgPath + "." + ident.Name
methodKey := structID + "." + fn.Name.Name
// Walk the body looking for field.Method() calls
ast.Inspect(fn.Body, func(inner ast.Node) bool {
call, ok := inner.(*ast.CallExpr)
if !ok {
return true
}
// Pattern: h.svc.GetMetrics() → SelectorExpr(SelectorExpr(Ident, field), method)
outerSel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
methodName := outerSel.Sel.Name
innerSel, ok := outerSel.X.(*ast.SelectorExpr)
if !ok {
return true
}
fieldName := innerSel.Sel.Name
// Skip noise: logging, metrics, context helpers
if isNoiseCall(fieldName, methodName) {
return true
}
index[methodKey] = append(index[methodKey], MethodCall{
FieldName: fieldName,
TargetMethod: methodName,
})
return true
})
return true
})
}
}
return index
}