|
| 1 | +package dotty.tools.dotc |
| 2 | + |
| 3 | +import java.util.concurrent.ConcurrentHashMap |
| 4 | +import java.util.concurrent.atomic.LongAdder |
| 5 | + |
| 6 | +import scala.jdk.CollectionConverters.* |
| 7 | + |
| 8 | +import dotty.tools.dotc.core.Contexts.Context |
| 9 | +import dotty.tools.io.{AbstractFile, FileExtension} |
| 10 | + |
| 11 | +trait GlobalCache: |
| 12 | + /** Get the content of a file, possibly caching it globally. |
| 13 | + * |
| 14 | + * Implementations must be thread-safe. |
| 15 | + */ |
| 16 | + def getFileContent(file: AbstractFile): Array[Byte] |
| 17 | + |
| 18 | +object GlobalCache: |
| 19 | + /** A global cache that keeps file contents in memory without any size limit. |
| 20 | + * |
| 21 | + * @param shouldCache |
| 22 | + * A predicate that determines whether an [[AbstracFile]] should be cached. |
| 23 | + */ |
| 24 | + class ConcurrentGlobalCache(shouldCache: AbstractFile => Boolean) extends GlobalCache: |
| 25 | + private val cache = ConcurrentHashMap[AbstractFile, Array[Byte]]() |
| 26 | + private val totalByExt = ConcurrentHashMap[FileExtension, LongAdder]() |
| 27 | + private val missesByExt = ConcurrentHashMap[FileExtension, LongAdder]() |
| 28 | + private val uncachedByExt = ConcurrentHashMap[FileExtension, LongAdder]() |
| 29 | + |
| 30 | + override def getFileContent(file: AbstractFile): Array[Byte] = |
| 31 | + totalByExt.computeIfAbsent(file.ext, _ => LongAdder()).increment() |
| 32 | + if shouldCache(file) then |
| 33 | + cache.computeIfAbsent(file, f => |
| 34 | + missesByExt.computeIfAbsent(file.ext, _ => LongAdder()).increment() |
| 35 | + //println(s"Caching file: ${file.canonicalPath}") |
| 36 | + f.toByteArray |
| 37 | + ) |
| 38 | + else |
| 39 | + uncachedByExt.computeIfAbsent(file.ext, _ => LongAdder()).increment() |
| 40 | + file.toByteArray |
| 41 | + |
| 42 | + final def printCacheStats(): Unit = |
| 43 | + println(this.getClass.getSimpleName + " statistics:") |
| 44 | + totalByExt.forEach: (ext, totalAdder) => |
| 45 | + val misses = missesByExt.computeIfAbsent(ext, _ => LongAdder()).longValue() |
| 46 | + val uncached = uncachedByExt.computeIfAbsent(ext, _ => LongAdder()).longValue() |
| 47 | + val total = totalAdder.longValue() |
| 48 | + val hits = total - misses - uncached |
| 49 | + val files = cache.asScala.filter(_._1.ext == ext) |
| 50 | + val sizeMB = files.map(_._2.length.toLong).sum.toDouble / (1024 * 1024) |
| 51 | + println(f"- *.$ext: hits: $hits, misses: $misses, uncached: $uncached, total: $total, cache size: $sizeMB%.2f MB") |
| 52 | + |
| 53 | + /** A global cache that does not cache anything. |
| 54 | + * |
| 55 | + * This is the default value for [[GlobalCache]]. |
| 56 | + */ |
| 57 | + object NoGlobalCache extends GlobalCache: |
| 58 | + override def getFileContent(file: AbstractFile): Array[Byte] = |
| 59 | + file.toByteArray |
0 commit comments