github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/appendstats/main.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "go/ast" 7 "go/types" 8 "os" 9 10 "golang.org/x/exp/maps" 11 "golang.org/x/exp/slices" 12 "golang.org/x/tools/go/packages" 13 ) 14 15 type Category struct { 16 Size int64 17 HasPointers bool 18 } 19 20 func main() { 21 top := flag.Int("top", 10, "only print top entries") 22 23 flag.Parse() 24 25 config := &packages.Config{ 26 Mode: packages.NeedName | packages.NeedFiles | packages.LoadImports | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedTypesSizes, 27 } 28 29 roots, err := packages.Load(config, flag.Args()...) 30 if err != nil { 31 fmt.Fprintln(os.Stderr, err) 32 os.Exit(1) 33 } 34 35 summary := map[Category]int64{} 36 summaryByType := map[types.Type]int64{} 37 38 for _, pkg := range All(roots) { 39 for _, file := range pkg.Syntax { 40 ast.Inspect(file, func(n ast.Node) bool { 41 call, ok := n.(*ast.CallExpr) 42 if !ok { 43 return true 44 } 45 46 if !isAppendName(call.Fun) { 47 return true 48 } 49 if call.Args[0] == nil { 50 return true 51 } 52 argType := pkg.TypesInfo.TypeOf(call.Args[0]) 53 if argType == nil { 54 return true 55 } 56 57 argType = argType.Underlying() 58 59 sliceType, ok := argType.(*types.Slice) 60 if !ok { 61 ast.Fprint(os.Stderr, pkg.Fset, call, ast.NotNilFilter) 62 fmt.Fprintf(os.Stderr, "%#v as argument\n", argType) 63 panic("invalid argument to append") 64 } 65 66 elemType := sliceType.Elem() 67 if _, isGeneric := elemType.(*types.TypeParam); isGeneric { 68 return true 69 } 70 71 size := pkg.TypesSizes.Sizeof(elemType) 72 cat := Category{ 73 Size: size, 74 HasPointers: HasPointers(elemType), 75 } 76 summary[cat]++ 77 summaryByType[elemType]++ 78 79 return true 80 }) 81 } 82 } 83 84 { 85 fmt.Println("TOP BY CATEGORY") 86 results := KeyValues(summary) 87 slices.SortFunc(results, func(a, b KV[Category, int64]) bool { 88 return a.Value > b.Value 89 }) 90 for _, r := range Top(results, *top) { 91 ptr := "" 92 if r.Key.HasPointers { 93 ptr = "ptr" 94 } 95 fmt.Printf(" %6v %4v %v bytes\n", r.Value, ptr, r.Key.Size) 96 } 97 } 98 { 99 fmt.Println("TOP BY TYPE") 100 var results []KV[types.Type, int64] 101 for k, v := range summaryByType { 102 results = append(results, KV[types.Type, int64]{Key: k, Value: v}) 103 } 104 slices.SortFunc(results, func(a, b KV[types.Type, int64]) bool { 105 return a.Value > b.Value 106 }) 107 for _, r := range Top(results, *top) { 108 ptr := "" 109 if HasPointers(r.Key) { 110 ptr = "ptr" 111 } 112 fmt.Printf(" %6v %4v %v\n", r.Value, ptr, r.Key) 113 } 114 } 115 } 116 117 func All(roots []*packages.Package) []*packages.Package { 118 all := map[*packages.Package]struct{}{} 119 120 var walk func(*packages.Package) 121 walk = func(p *packages.Package) { 122 if _, seen := all[p]; seen { 123 return 124 } 125 all[p] = struct{}{} 126 127 for _, c := range p.Imports { 128 walk(c) 129 } 130 } 131 for _, root := range roots { 132 walk(root) 133 } 134 135 pkgs := maps.Keys(all) 136 slices.SortFunc(pkgs, func(a, b *packages.Package) bool { 137 return a.ID < b.ID 138 }) 139 return pkgs 140 } 141 142 func Top[T any](xs []T, n int) []T { 143 if len(xs) <= n { 144 return xs 145 } 146 return xs[:n] 147 } 148 149 type KV[K, V any] struct { 150 Key K 151 Value V 152 } 153 154 func KeyValues[K comparable, V any](m map[K]V) []KV[K, V] { 155 var kvs []KV[K, V] 156 for k, v := range m { 157 kvs = append(kvs, KV[K, V]{Key: k, Value: v}) 158 } 159 return kvs 160 } 161 162 func isAppendName(x ast.Expr) bool { 163 switch x := x.(type) { 164 case *ast.SelectorExpr: 165 return false 166 case *ast.Ident: 167 return x.Name == "append" 168 default: 169 return false 170 } 171 } 172 173 func qualifiedName(x ast.Expr) string { 174 switch x := x.(type) { 175 case *ast.SelectorExpr: 176 pkg, ok := x.X.(*ast.Ident) 177 if !ok { 178 return "" 179 } 180 return pkg.Name + "." + x.Sel.Name 181 case *ast.Ident: 182 return x.Name 183 default: 184 return "" 185 } 186 } 187 188 var hasPointers = map[types.Type]bool{} 189 190 func HasPointers(T types.Type) (result bool) { 191 TU := T.Underlying() 192 if cached, ok := hasPointers[TU]; ok { 193 return cached 194 } 195 hasPointers[TU] = true 196 defer func() { hasPointers[TU] = result }() 197 198 switch t := TU.(type) { 199 case *types.Basic: 200 switch t.Kind() { 201 case types.String, types.UnsafePointer: 202 return true 203 } 204 return false 205 case *types.Chan, *types.Map, *types.Pointer, *types.Signature, *types.Slice: 206 return true 207 case *types.Interface: 208 return true 209 case *types.Array: 210 return t.Len() > 0 && HasPointers(t.Elem()) 211 case *types.Struct: 212 nf := t.NumFields() 213 for i := 0; i < nf; i++ { 214 if HasPointers(t.Field(i).Type()) { 215 return true 216 } 217 } 218 return false 219 } 220 221 panic("impossible") 222 }