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 }