CachePruning.cpp 12 KB

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