CachePruning.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. //===-CachePruning.cpp - LLVM Cache Directory Pruning ---------------------===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. //
  9. // This file implements the pruning of a directory based on least recently used.
  10. //
  11. //===----------------------------------------------------------------------===//
  12. #include "llvm/Support/CachePruning.h"
  13. #include "llvm/Support/Debug.h"
  14. #include "llvm/Support/Errc.h"
  15. #include "llvm/Support/Error.h"
  16. #include "llvm/Support/FileSystem.h"
  17. #include "llvm/Support/Path.h"
  18. #include "llvm/Support/raw_ostream.h"
  19. #define DEBUG_TYPE "cache-pruning"
  20. #include <set>
  21. #include <system_error>
  22. using namespace llvm;
  23. namespace {
  24. struct FileInfo {
  25. sys::TimePoint<> Time;
  26. uint64_t Size;
  27. std::string Path;
  28. /// Used to determine which files to prune first. Also used to determine
  29. /// set membership, so must take into account all fields.
  30. bool operator<(const FileInfo &Other) const {
  31. return std::tie(Time, Other.Size, Path) <
  32. std::tie(Other.Time, Size, Other.Path);
  33. }
  34. };
  35. } // anonymous namespace
  36. /// Write a new timestamp file with the given path. This is used for the pruning
  37. /// interval option.
  38. static void writeTimestampFile(StringRef TimestampFile) {
  39. std::error_code EC;
  40. raw_fd_ostream Out(TimestampFile.str(), EC, sys::fs::OF_None);
  41. }
  42. static Expected<std::chrono::seconds> parseDuration(StringRef Duration) {
  43. if (Duration.empty())
  44. return make_error<StringError>("Duration must not be empty",
  45. inconvertibleErrorCode());
  46. StringRef NumStr = Duration.slice(0, Duration.size()-1);
  47. uint64_t Num;
  48. if (NumStr.getAsInteger(0, Num))
  49. return make_error<StringError>("'" + NumStr + "' not an integer",
  50. inconvertibleErrorCode());
  51. switch (Duration.back()) {
  52. case 's':
  53. return std::chrono::seconds(Num);
  54. case 'm':
  55. return std::chrono::minutes(Num);
  56. case 'h':
  57. return std::chrono::hours(Num);
  58. default:
  59. return make_error<StringError>("'" + Duration +
  60. "' must end with one of 's', 'm' or 'h'",
  61. inconvertibleErrorCode());
  62. }
  63. }
  64. Expected<CachePruningPolicy>
  65. llvm::parseCachePruningPolicy(StringRef PolicyStr) {
  66. CachePruningPolicy Policy;
  67. std::pair<StringRef, StringRef> P = {"", PolicyStr};
  68. while (!P.second.empty()) {
  69. P = P.second.split(':');
  70. StringRef Key, Value;
  71. std::tie(Key, Value) = P.first.split('=');
  72. if (Key == "prune_interval") {
  73. auto DurationOrErr = parseDuration(Value);
  74. if (!DurationOrErr)
  75. return DurationOrErr.takeError();
  76. Policy.Interval = *DurationOrErr;
  77. } else if (Key == "prune_after") {
  78. auto DurationOrErr = parseDuration(Value);
  79. if (!DurationOrErr)
  80. return DurationOrErr.takeError();
  81. Policy.Expiration = *DurationOrErr;
  82. } else if (Key == "cache_size") {
  83. if (Value.back() != '%')
  84. return make_error<StringError>("'" + Value + "' must be a percentage",
  85. inconvertibleErrorCode());
  86. StringRef SizeStr = Value.drop_back();
  87. uint64_t Size;
  88. if (SizeStr.getAsInteger(0, Size))
  89. return make_error<StringError>("'" + SizeStr + "' not an integer",
  90. inconvertibleErrorCode());
  91. if (Size > 100)
  92. return make_error<StringError>("'" + SizeStr +
  93. "' must be between 0 and 100",
  94. inconvertibleErrorCode());
  95. Policy.MaxSizePercentageOfAvailableSpace = Size;
  96. } else if (Key == "cache_size_bytes") {
  97. uint64_t Mult = 1;
  98. switch (tolower(Value.back())) {
  99. case 'k':
  100. Mult = 1024;
  101. Value = Value.drop_back();
  102. break;
  103. case 'm':
  104. Mult = 1024 * 1024;
  105. Value = Value.drop_back();
  106. break;
  107. case 'g':
  108. Mult = 1024 * 1024 * 1024;
  109. Value = Value.drop_back();
  110. break;
  111. }
  112. uint64_t Size;
  113. if (Value.getAsInteger(0, Size))
  114. return make_error<StringError>("'" + Value + "' not an integer",
  115. inconvertibleErrorCode());
  116. Policy.MaxSizeBytes = Size * Mult;
  117. } else if (Key == "cache_size_files") {
  118. if (Value.getAsInteger(0, Policy.MaxSizeFiles))
  119. return make_error<StringError>("'" + Value + "' not an integer",
  120. inconvertibleErrorCode());
  121. } else {
  122. return make_error<StringError>("Unknown key: '" + Key + "'",
  123. inconvertibleErrorCode());
  124. }
  125. }
  126. return Policy;
  127. }
  128. /// Prune the cache of files that haven't been accessed in a long time.
  129. bool llvm::pruneCache(StringRef Path, CachePruningPolicy Policy) {
  130. using namespace std::chrono;
  131. if (Path.empty())
  132. return false;
  133. bool isPathDir;
  134. if (sys::fs::is_directory(Path, isPathDir))
  135. return false;
  136. if (!isPathDir)
  137. return false;
  138. Policy.MaxSizePercentageOfAvailableSpace =
  139. std::min(Policy.MaxSizePercentageOfAvailableSpace, 100u);
  140. if (Policy.Expiration == seconds(0) &&
  141. Policy.MaxSizePercentageOfAvailableSpace == 0 &&
  142. Policy.MaxSizeBytes == 0 && Policy.MaxSizeFiles == 0) {
  143. LLVM_DEBUG(dbgs() << "No pruning settings set, exit early\n");
  144. // Nothing will be pruned, early exit
  145. return false;
  146. }
  147. // Try to stat() the timestamp file.
  148. SmallString<128> TimestampFile(Path);
  149. sys::path::append(TimestampFile, "llvmcache.timestamp");
  150. sys::fs::file_status FileStatus;
  151. const auto CurrentTime = system_clock::now();
  152. if (auto EC = sys::fs::status(TimestampFile, FileStatus)) {
  153. if (EC == errc::no_such_file_or_directory) {
  154. // If the timestamp file wasn't there, create one now.
  155. writeTimestampFile(TimestampFile);
  156. } else {
  157. // Unknown error?
  158. return false;
  159. }
  160. } else {
  161. if (!Policy.Interval)
  162. return false;
  163. if (Policy.Interval != seconds(0)) {
  164. // Check whether the time stamp is older than our pruning interval.
  165. // If not, do nothing.
  166. const auto TimeStampModTime = FileStatus.getLastModificationTime();
  167. auto TimeStampAge = CurrentTime - TimeStampModTime;
  168. if (TimeStampAge <= *Policy.Interval) {
  169. LLVM_DEBUG(dbgs() << "Timestamp file too recent ("
  170. << duration_cast<seconds>(TimeStampAge).count()
  171. << "s old), do not prune.\n");
  172. return false;
  173. }
  174. }
  175. // Write a new timestamp file so that nobody else attempts to prune.
  176. // There is a benign race condition here, if two processes happen to
  177. // notice at the same time that the timestamp is out-of-date.
  178. writeTimestampFile(TimestampFile);
  179. }
  180. // Keep track of files to delete to get below the size limit.
  181. // Order by time of last use so that recently used files are preserved.
  182. std::set<FileInfo> FileInfos;
  183. uint64_t TotalSize = 0;
  184. // Walk the entire directory cache, looking for unused files.
  185. std::error_code EC;
  186. SmallString<128> CachePathNative;
  187. sys::path::native(Path, CachePathNative);
  188. // Walk all of the files within this directory.
  189. for (sys::fs::directory_iterator File(CachePathNative, EC), FileEnd;
  190. File != FileEnd && !EC; File.increment(EC)) {
  191. // Ignore any files not beginning with the string "llvmcache-". This
  192. // includes the timestamp file as well as any files created by the user.
  193. // This acts as a safeguard against data loss if the user specifies the
  194. // wrong directory as their cache directory.
  195. if (!sys::path::filename(File->path()).startswith("llvmcache-"))
  196. continue;
  197. // Look at this file. If we can't stat it, there's nothing interesting
  198. // there.
  199. ErrorOr<sys::fs::basic_file_status> StatusOrErr = File->status();
  200. if (!StatusOrErr) {
  201. LLVM_DEBUG(dbgs() << "Ignore " << File->path() << " (can't stat)\n");
  202. continue;
  203. }
  204. // If the file hasn't been used recently enough, delete it
  205. const auto FileAccessTime = StatusOrErr->getLastAccessedTime();
  206. auto FileAge = CurrentTime - FileAccessTime;
  207. if (Policy.Expiration != seconds(0) && FileAge > Policy.Expiration) {
  208. LLVM_DEBUG(dbgs() << "Remove " << File->path() << " ("
  209. << duration_cast<seconds>(FileAge).count()
  210. << "s old)\n");
  211. sys::fs::remove(File->path());
  212. continue;
  213. }
  214. // Leave it here for now, but add it to the list of size-based pruning.
  215. TotalSize += StatusOrErr->getSize();
  216. FileInfos.insert({FileAccessTime, StatusOrErr->getSize(), File->path()});
  217. }
  218. auto FileInfo = FileInfos.begin();
  219. size_t NumFiles = FileInfos.size();
  220. auto RemoveCacheFile = [&]() {
  221. // Remove the file.
  222. sys::fs::remove(FileInfo->Path);
  223. // Update size
  224. TotalSize -= FileInfo->Size;
  225. NumFiles--;
  226. LLVM_DEBUG(dbgs() << " - Remove " << FileInfo->Path << " (size "
  227. << FileInfo->Size << "), new occupancy is " << TotalSize
  228. << "%\n");
  229. ++FileInfo;
  230. };
  231. // Prune for number of files.
  232. if (Policy.MaxSizeFiles)
  233. while (NumFiles > Policy.MaxSizeFiles)
  234. RemoveCacheFile();
  235. // Prune for size now if needed
  236. if (Policy.MaxSizePercentageOfAvailableSpace > 0 || Policy.MaxSizeBytes > 0) {
  237. auto ErrOrSpaceInfo = sys::fs::disk_space(Path);
  238. if (!ErrOrSpaceInfo) {
  239. report_fatal_error("Can't get available size");
  240. }
  241. sys::fs::space_info SpaceInfo = ErrOrSpaceInfo.get();
  242. auto AvailableSpace = TotalSize + SpaceInfo.free;
  243. if (Policy.MaxSizePercentageOfAvailableSpace == 0)
  244. Policy.MaxSizePercentageOfAvailableSpace = 100;
  245. if (Policy.MaxSizeBytes == 0)
  246. Policy.MaxSizeBytes = AvailableSpace;
  247. auto TotalSizeTarget = std::min<uint64_t>(
  248. AvailableSpace * Policy.MaxSizePercentageOfAvailableSpace / 100ull,
  249. Policy.MaxSizeBytes);
  250. LLVM_DEBUG(dbgs() << "Occupancy: " << ((100 * TotalSize) / AvailableSpace)
  251. << "% target is: "
  252. << Policy.MaxSizePercentageOfAvailableSpace << "%, "
  253. << Policy.MaxSizeBytes << " bytes\n");
  254. // Remove the oldest accessed files first, till we get below the threshold.
  255. while (TotalSize > TotalSizeTarget && FileInfo != FileInfos.end())
  256. RemoveCacheFile();
  257. }
  258. return true;
  259. }