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.
24 "golang.org/x/tools/internal/event"
25 "golang.org/x/tools/internal/gocommand"
26 "golang.org/x/tools/internal/lsp/debug/tag"
27 "golang.org/x/tools/internal/lsp/source"
28 "golang.org/x/tools/internal/memoize"
29 "golang.org/x/tools/internal/span"
32 func New(ctx context.Context, options func(*source.Options)) *Cache {
33 index := atomic.AddInt64(&cacheIndex, 1)
35 id: strconv.FormatInt(index, 10),
36 fset: token.NewFileSet(),
38 fileContent: map[span.URI]*fileHandle{},
46 options func(*source.Options)
51 fileContent map[span.URI]*fileHandle
54 type fileHandle struct {
61 // size is the file length as reported by Stat, for the purpose of
62 // invalidation. Probably we could just use len(bytes), but this is done
63 // defensively in case the definition of file size in the file system
68 func (h *fileHandle) Saved() bool {
72 func (c *Cache) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
73 return c.getFile(ctx, uri)
76 func (c *Cache) getFile(ctx context.Context, uri span.URI) (*fileHandle, error) {
77 fi, statErr := os.Stat(uri.Filename())
86 fh, ok := c.fileContent[uri]
89 // Check mtime and file size to infer whether the file has changed. This is
90 // an imperfect heuristic. Notably on some real systems (such as WSL) the
91 // filesystem clock resolution can be large -- 1/64s was observed. Therefore
92 // it's quite possible for multiple file modifications to occur within a
93 // single logical 'tick'. This can leave the cache in an incorrect state, but
94 // unfortunately we can't afford to pay the price of reading the actual file
95 // content here. Or to be more precise, reading would be a risky change and
96 // we don't know if we can afford it.
98 // We check file size in an attempt to reduce the probability of false cache
100 if ok && fh.modTime.Equal(fi.ModTime()) && fh.size == fi.Size() {
104 fh, err := readFile(ctx, uri, fi)
109 c.fileContent[uri] = fh
114 // ioLimit limits the number of parallel file reads per process.
115 var ioLimit = make(chan struct{}, 128)
117 func readFile(ctx context.Context, uri span.URI, fi os.FileInfo) (*fileHandle, error) {
119 case ioLimit <- struct{}{}:
121 return nil, ctx.Err()
123 defer func() { <-ioLimit }()
125 ctx, done := event.Start(ctx, "cache.readFile", tag.File.Of(uri.Filename()))
129 data, err := ioutil.ReadFile(uri.Filename())
132 modTime: fi.ModTime(),
138 modTime: fi.ModTime(),
142 hash: hashContents(data),
146 func (c *Cache) NewSession(ctx context.Context) *Session {
147 index := atomic.AddInt64(&sessionIndex, 1)
148 options := source.DefaultOptions().Clone()
149 if c.options != nil {
154 id: strconv.FormatInt(index, 10),
156 overlays: make(map[span.URI]*overlay),
157 gocmdRunner: &gocommand.Runner{},
159 event.Log(ctx, "New session", KeyCreateSession.Of(s))
163 func (c *Cache) FileSet() *token.FileSet {
167 func (h *fileHandle) URI() span.URI {
171 func (h *fileHandle) Kind() source.FileKind {
172 return source.DetectLanguage("", h.uri.Filename())
175 func (h *fileHandle) Hash() string {
179 func (h *fileHandle) FileIdentity() source.FileIdentity {
180 return source.FileIdentity{
187 func (h *fileHandle) Read() ([]byte, error) {
188 return h.bytes, h.err
191 func hashContents(contents []byte) string {
192 return fmt.Sprintf("%x", sha256.Sum256(contents))
195 var cacheIndex, sessionIndex, viewIndex int64
197 func (c *Cache) ID() string { return c.id }
198 func (c *Cache) MemStats() map[reflect.Type]int { return c.store.Stats() }
200 type packageStat struct {
202 mode source.ParseMode
210 func (c *Cache) PackageStats(withNames bool) template.HTML {
211 var packageStats []packageStat
212 c.store.DebugOnlyIterate(func(k, v interface{}) {
214 case packageHandleKey:
215 v := v.(*packageData)
219 var typsCost, typInfoCost int64
220 if v.pkg.types != nil {
221 typsCost = typesCost(v.pkg.types.Scope())
223 if v.pkg.typesInfo != nil {
224 typInfoCost = typesInfoCost(v.pkg.typesInfo)
230 typesInfo: typInfoCost,
232 for _, f := range v.pkg.compiledGoFiles {
233 stat.file += int64(len(f.Src))
234 stat.ast += astCost(f.File)
236 stat.total = stat.file + stat.ast + stat.types + stat.typesInfo
237 packageStats = append(packageStats, stat)
241 for _, stat := range packageStats {
242 totalCost += stat.total
244 sort.Slice(packageStats, func(i, j int) bool {
245 return packageStats[i].total > packageStats[j].total
247 html := "<table><thead><td>Name</td><td>total = file + ast + types + types info</td></thead>\n"
248 human := func(n int64) string {
249 return fmt.Sprintf("%.2f", float64(n)/(1024*1024))
251 var printedCost int64
252 for _, stat := range packageStats {
257 html += fmt.Sprintf("<tr><td>%v (%v)</td><td>%v = %v + %v + %v + %v</td></tr>\n", name, stat.mode,
258 human(stat.total), human(stat.file), human(stat.ast), human(stat.types), human(stat.typesInfo))
259 printedCost += stat.total
260 if float64(printedCost) > float64(totalCost)*.9 {
265 return template.HTML(html)
268 func astCost(f *ast.File) int64 {
273 ast.Inspect(f, func(n ast.Node) bool {
274 count += 32 // nodes are pretty small.
280 func typesCost(scope *types.Scope) int64 {
281 cost := 64 + int64(scope.Len())*128 // types.object looks pretty big
282 for i := 0; i < scope.NumChildren(); i++ {
283 cost += typesCost(scope.Child(i))
288 func typesInfoCost(info *types.Info) int64 {
289 // Most of these refer to existing objects, with the exception of InitOrder, Selections, and Types.
290 cost := 24*len(info.Defs) +
291 32*len(info.Implicits) +
292 256*len(info.InitOrder) + // these are big, but there aren't many of them.
293 32*len(info.Scopes) +
294 128*len(info.Selections) + // wild guess
295 128*len(info.Types) + // wild guess