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  }