1
0
mirror of https://github.com/golang/go synced 2024-11-06 00:36:14 -07:00
go/internal/lsp/cache/cache.go
Heschi Kreinick 8b7669898d internal/lsp/debug: save per-package stats in memory debug files
The per-package stats have proven pretty useful, and I don't want to
have to teach users how to save them. Create zip files and add them in.

Since some users may be sensitive about revealing any information about
the code, generate two variants: one with package names, and one
without.

Change-Id: Icc5631b4cebbbabfdd2fcea4a4cdf4f205dbcab9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/239037
Run-TryBot: Heschi Kreinick <heschi@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-06-19 18:00:37 +00:00

254 lines
5.9 KiB
Go

// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cache
import (
"context"
"crypto/sha1"
"fmt"
"go/ast"
"go/token"
"go/types"
"html/template"
"io/ioutil"
"os"
"reflect"
"sort"
"strconv"
"sync/atomic"
"time"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
func New(ctx context.Context, options func(*source.Options)) *Cache {
index := atomic.AddInt64(&cacheIndex, 1)
c := &Cache{
id: strconv.FormatInt(index, 10),
fset: token.NewFileSet(),
options: options,
}
return c
}
type Cache struct {
id string
fset *token.FileSet
options func(*source.Options)
store memoize.Store
}
type fileKey struct {
uri span.URI
modTime time.Time
}
type fileHandle struct {
uri span.URI
memoize.NoCopy
bytes []byte
hash string
err error
}
func (c *Cache) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
var modTime time.Time
if fi, err := os.Stat(uri.Filename()); err == nil {
modTime = fi.ModTime()
}
key := fileKey{
uri: uri,
modTime: modTime,
}
h := c.store.Bind(key, func(ctx context.Context) interface{} {
return readFile(ctx, uri, modTime)
})
v := h.Get(ctx)
if v == nil {
return nil, ctx.Err()
}
return v.(*fileHandle), nil
}
// ioLimit limits the number of parallel file reads per process.
var ioLimit = make(chan struct{}, 128)
func readFile(ctx context.Context, uri span.URI, origTime time.Time) *fileHandle {
ctx, done := event.Start(ctx, "cache.getFile", tag.File.Of(uri.Filename()))
_ = ctx
defer done()
ioLimit <- struct{}{}
defer func() { <-ioLimit }()
var modTime time.Time
if fi, err := os.Stat(uri.Filename()); err == nil {
modTime = fi.ModTime()
}
if modTime != origTime {
return &fileHandle{err: errors.Errorf("%s: file has been modified", uri.Filename())}
}
data, err := ioutil.ReadFile(uri.Filename())
if err != nil {
return &fileHandle{err: err}
}
return &fileHandle{
uri: uri,
bytes: data,
hash: hashContents(data),
}
}
func (c *Cache) NewSession(ctx context.Context) *Session {
index := atomic.AddInt64(&sessionIndex, 1)
s := &Session{
cache: c,
id: strconv.FormatInt(index, 10),
options: source.DefaultOptions(),
overlays: make(map[span.URI]*overlay),
}
event.Log(ctx, "New session", KeyCreateSession.Of(s))
return s
}
func (c *Cache) FileSet() *token.FileSet {
return c.fset
}
func (h *fileHandle) URI() span.URI {
return h.uri
}
func (h *fileHandle) Kind() source.FileKind {
return source.DetectLanguage("", h.uri.Filename())
}
func (h *fileHandle) Version() float64 {
return 0
}
func (h *fileHandle) Identity() source.FileIdentity {
return source.FileIdentity{
URI: h.uri,
Identifier: h.hash,
Kind: h.Kind(),
}
}
func (h *fileHandle) Read() ([]byte, error) {
return h.bytes, h.err
}
func hashContents(contents []byte) string {
// TODO: consider whether sha1 is the best choice here
// This hash is used for internal identity detection only
return fmt.Sprintf("%x", sha1.Sum(contents))
}
var cacheIndex, sessionIndex, viewIndex int64
func (c *Cache) ID() string { return c.id }
func (c *Cache) MemStats() map[reflect.Type]int { return c.store.Stats() }
type packageStat struct {
id packageID
mode source.ParseMode
file int64
ast int64
types int64
typesInfo int64
total int64
}
func (c *Cache) PackageStats(withNames bool) template.HTML {
var packageStats []packageStat
c.store.DebugOnlyIterate(func(k, v interface{}) {
switch k.(type) {
case packageHandleKey:
v := v.(*packageData)
stat := packageStat{
id: v.pkg.id,
mode: v.pkg.mode,
types: typesCost(v.pkg.types.Scope()),
typesInfo: typesInfoCost(v.pkg.typesInfo),
}
for _, f := range v.pkg.compiledGoFiles {
fvi := f.handle.Cached()
if fvi == nil {
continue
}
fv := fvi.(*parseGoData)
stat.file += int64(len(fv.src))
stat.ast += astCost(fv.ast)
}
stat.total = stat.file + stat.ast + stat.types + stat.typesInfo
packageStats = append(packageStats, stat)
}
})
var totalCost int64
for _, stat := range packageStats {
totalCost += stat.total
}
sort.Slice(packageStats, func(i, j int) bool {
return packageStats[i].total > packageStats[j].total
})
html := "<table><thead><td>Name</td><td>total = file + ast + types + types info</td></thead>\n"
human := func(n int64) string {
return fmt.Sprintf("%.2f", float64(n)/(1024*1024))
}
var printedCost int64
for _, stat := range packageStats {
name := stat.id
if !withNames {
name = "-"
}
html += fmt.Sprintf("<tr><td>%v (%v)</td><td>%v = %v + %v + %v + %v</td></tr>\n", name, stat.mode,
human(stat.total), human(stat.file), human(stat.ast), human(stat.types), human(stat.typesInfo))
printedCost += stat.total
if float64(printedCost) > float64(totalCost)*.9 {
break
}
}
html += "</table>\n"
return template.HTML(html)
}
func astCost(f *ast.File) int64 {
var count int64
ast.Inspect(f, func(n ast.Node) bool {
count += 32 // nodes are pretty small.
return true
})
return count
}
func typesCost(scope *types.Scope) int64 {
cost := 64 + int64(scope.Len())*128 // types.object looks pretty big
for i := 0; i < scope.NumChildren(); i++ {
cost += typesCost(scope.Child(i))
}
return cost
}
func typesInfoCost(info *types.Info) int64 {
// Most of these refer to existing objects, with the exception of InitOrder, Selections, and Types.
cost := 24*len(info.Defs) +
32*len(info.Implicits) +
256*len(info.InitOrder) + // these are big, but there aren't many of them.
32*len(info.Scopes) +
128*len(info.Selections) + // wild guess
128*len(info.Types) + // wild guess
32*len(info.Uses)
return int64(cost)
}