github.com/jd-ly/tools@v0.5.7/internal/lsp/cache/cache.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cache
     6  
     7  import (
     8  	"context"
     9  	"crypto/sha256"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/token"
    13  	"go/types"
    14  	"html/template"
    15  	"io/ioutil"
    16  	"os"
    17  	"reflect"
    18  	"sort"
    19  	"strconv"
    20  	"sync"
    21  	"sync/atomic"
    22  	"time"
    23  
    24  	"github.com/jd-ly/tools/internal/event"
    25  	"github.com/jd-ly/tools/internal/gocommand"
    26  	"github.com/jd-ly/tools/internal/lsp/debug/tag"
    27  	"github.com/jd-ly/tools/internal/lsp/source"
    28  	"github.com/jd-ly/tools/internal/memoize"
    29  	"github.com/jd-ly/tools/internal/span"
    30  )
    31  
    32  func New(ctx context.Context, options func(*source.Options)) *Cache {
    33  	index := atomic.AddInt64(&cacheIndex, 1)
    34  	c := &Cache{
    35  		id:          strconv.FormatInt(index, 10),
    36  		fset:        token.NewFileSet(),
    37  		options:     options,
    38  		fileContent: map[span.URI]*fileHandle{},
    39  	}
    40  	return c
    41  }
    42  
    43  type Cache struct {
    44  	id      string
    45  	fset    *token.FileSet
    46  	options func(*source.Options)
    47  
    48  	store memoize.Store
    49  
    50  	fileMu      sync.Mutex
    51  	fileContent map[span.URI]*fileHandle
    52  }
    53  
    54  type fileHandle struct {
    55  	modTime time.Time
    56  	uri     span.URI
    57  	bytes   []byte
    58  	hash    string
    59  	err     error
    60  }
    61  
    62  func (h *fileHandle) Saved() bool {
    63  	return true
    64  }
    65  
    66  func (c *Cache) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
    67  	return c.getFile(ctx, uri)
    68  }
    69  
    70  func (c *Cache) getFile(ctx context.Context, uri span.URI) (*fileHandle, error) {
    71  	fi, statErr := os.Stat(uri.Filename())
    72  	if statErr != nil {
    73  		return &fileHandle{
    74  			err: statErr,
    75  			uri: uri,
    76  		}, nil
    77  	}
    78  
    79  	c.fileMu.Lock()
    80  	fh, ok := c.fileContent[uri]
    81  	c.fileMu.Unlock()
    82  	if ok && fh.modTime.Equal(fi.ModTime()) {
    83  		return fh, nil
    84  	}
    85  
    86  	fh, err := readFile(ctx, uri, fi.ModTime())
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	c.fileMu.Lock()
    91  	c.fileContent[uri] = fh
    92  	c.fileMu.Unlock()
    93  	return fh, nil
    94  }
    95  
    96  // ioLimit limits the number of parallel file reads per process.
    97  var ioLimit = make(chan struct{}, 128)
    98  
    99  func readFile(ctx context.Context, uri span.URI, modTime time.Time) (*fileHandle, error) {
   100  	select {
   101  	case ioLimit <- struct{}{}:
   102  	case <-ctx.Done():
   103  		return nil, ctx.Err()
   104  	}
   105  	defer func() { <-ioLimit }()
   106  
   107  	ctx, done := event.Start(ctx, "cache.readFile", tag.File.Of(uri.Filename()))
   108  	_ = ctx
   109  	defer done()
   110  
   111  	data, err := ioutil.ReadFile(uri.Filename())
   112  	if err != nil {
   113  		return &fileHandle{
   114  			modTime: modTime,
   115  			err:     err,
   116  		}, nil
   117  	}
   118  	return &fileHandle{
   119  		modTime: modTime,
   120  		uri:     uri,
   121  		bytes:   data,
   122  		hash:    hashContents(data),
   123  	}, nil
   124  }
   125  
   126  func (c *Cache) NewSession(ctx context.Context) *Session {
   127  	index := atomic.AddInt64(&sessionIndex, 1)
   128  	options := source.DefaultOptions().Clone()
   129  	if c.options != nil {
   130  		c.options(options)
   131  	}
   132  	s := &Session{
   133  		cache:       c,
   134  		id:          strconv.FormatInt(index, 10),
   135  		options:     options,
   136  		overlays:    make(map[span.URI]*overlay),
   137  		gocmdRunner: &gocommand.Runner{},
   138  	}
   139  	event.Log(ctx, "New session", KeyCreateSession.Of(s))
   140  	return s
   141  }
   142  
   143  func (c *Cache) FileSet() *token.FileSet {
   144  	return c.fset
   145  }
   146  
   147  func (h *fileHandle) URI() span.URI {
   148  	return h.uri
   149  }
   150  
   151  func (h *fileHandle) Kind() source.FileKind {
   152  	return source.DetectLanguage("", h.uri.Filename())
   153  }
   154  
   155  func (h *fileHandle) Hash() string {
   156  	return h.hash
   157  }
   158  
   159  func (h *fileHandle) FileIdentity() source.FileIdentity {
   160  	return source.FileIdentity{
   161  		URI:  h.uri,
   162  		Hash: h.hash,
   163  		Kind: h.Kind(),
   164  	}
   165  }
   166  
   167  func (h *fileHandle) Read() ([]byte, error) {
   168  	return h.bytes, h.err
   169  }
   170  
   171  func hashContents(contents []byte) string {
   172  	return fmt.Sprintf("%x", sha256.Sum256(contents))
   173  }
   174  
   175  var cacheIndex, sessionIndex, viewIndex int64
   176  
   177  func (c *Cache) ID() string                     { return c.id }
   178  func (c *Cache) MemStats() map[reflect.Type]int { return c.store.Stats() }
   179  
   180  type packageStat struct {
   181  	id        packageID
   182  	mode      source.ParseMode
   183  	file      int64
   184  	ast       int64
   185  	types     int64
   186  	typesInfo int64
   187  	total     int64
   188  }
   189  
   190  func (c *Cache) PackageStats(withNames bool) template.HTML {
   191  	var packageStats []packageStat
   192  	c.store.DebugOnlyIterate(func(k, v interface{}) {
   193  		switch k.(type) {
   194  		case packageHandleKey:
   195  			v := v.(*packageData)
   196  			if v.pkg == nil {
   197  				break
   198  			}
   199  			var typsCost, typInfoCost int64
   200  			if v.pkg.types != nil {
   201  				typsCost = typesCost(v.pkg.types.Scope())
   202  			}
   203  			if v.pkg.typesInfo != nil {
   204  				typInfoCost = typesInfoCost(v.pkg.typesInfo)
   205  			}
   206  			stat := packageStat{
   207  				id:        v.pkg.m.id,
   208  				mode:      v.pkg.mode,
   209  				types:     typsCost,
   210  				typesInfo: typInfoCost,
   211  			}
   212  			for _, f := range v.pkg.compiledGoFiles {
   213  				stat.file += int64(len(f.Src))
   214  				stat.ast += astCost(f.File)
   215  			}
   216  			stat.total = stat.file + stat.ast + stat.types + stat.typesInfo
   217  			packageStats = append(packageStats, stat)
   218  		}
   219  	})
   220  	var totalCost int64
   221  	for _, stat := range packageStats {
   222  		totalCost += stat.total
   223  	}
   224  	sort.Slice(packageStats, func(i, j int) bool {
   225  		return packageStats[i].total > packageStats[j].total
   226  	})
   227  	html := "<table><thead><td>Name</td><td>total = file + ast + types + types info</td></thead>\n"
   228  	human := func(n int64) string {
   229  		return fmt.Sprintf("%.2f", float64(n)/(1024*1024))
   230  	}
   231  	var printedCost int64
   232  	for _, stat := range packageStats {
   233  		name := stat.id
   234  		if !withNames {
   235  			name = "-"
   236  		}
   237  		html += fmt.Sprintf("<tr><td>%v (%v)</td><td>%v = %v + %v + %v + %v</td></tr>\n", name, stat.mode,
   238  			human(stat.total), human(stat.file), human(stat.ast), human(stat.types), human(stat.typesInfo))
   239  		printedCost += stat.total
   240  		if float64(printedCost) > float64(totalCost)*.9 {
   241  			break
   242  		}
   243  	}
   244  	html += "</table>\n"
   245  	return template.HTML(html)
   246  }
   247  
   248  func astCost(f *ast.File) int64 {
   249  	if f == nil {
   250  		return 0
   251  	}
   252  	var count int64
   253  	ast.Inspect(f, func(n ast.Node) bool {
   254  		count += 32 // nodes are pretty small.
   255  		return true
   256  	})
   257  	return count
   258  }
   259  
   260  func typesCost(scope *types.Scope) int64 {
   261  	cost := 64 + int64(scope.Len())*128 // types.object looks pretty big
   262  	for i := 0; i < scope.NumChildren(); i++ {
   263  		cost += typesCost(scope.Child(i))
   264  	}
   265  	return cost
   266  }
   267  
   268  func typesInfoCost(info *types.Info) int64 {
   269  	// Most of these refer to existing objects, with the exception of InitOrder, Selections, and Types.
   270  	cost := 24*len(info.Defs) +
   271  		32*len(info.Implicits) +
   272  		256*len(info.InitOrder) + // these are big, but there aren't many of them.
   273  		32*len(info.Scopes) +
   274  		128*len(info.Selections) + // wild guess
   275  		128*len(info.Types) + // wild guess
   276  		32*len(info.Uses)
   277  	return int64(cost)
   278  }