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