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 }