From d7d9d85053cfe065b577ac1e06502bde09651bd1 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Thu, 16 Mar 2017 03:42:00 +0000 Subject: [PATCH] Support: Add a cache pruning policy parser. The idea is that the policy string fully specifies the policy and is portable between clients. Differential Revision: https://reviews.llvm.org/D31020 llvm-svn: 297927 --- .../llvm/LTO/legacy/ThinLTOCodeGenerator.h | 6 -- include/llvm/Support/CachePruning.h | 17 ++++- lib/Support/CachePruning.cpp | 68 ++++++++++++++++++ unittests/Support/CMakeLists.txt | 1 + unittests/Support/CachePruningTest.cpp | 71 +++++++++++++++++++ 5 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 unittests/Support/CachePruningTest.cpp diff --git a/include/llvm/LTO/legacy/ThinLTOCodeGenerator.h b/include/llvm/LTO/legacy/ThinLTOCodeGenerator.h index 8fdfd3dd714..28e61cb382b 100644 --- a/include/llvm/LTO/legacy/ThinLTOCodeGenerator.h +++ b/include/llvm/LTO/legacy/ThinLTOCodeGenerator.h @@ -142,12 +142,6 @@ public: struct CachingOptions { std::string Path; // Path to the cache, empty to disable. CachePruningPolicy Policy; - - CachingOptions() { - Policy.Interval = std::chrono::seconds(1200); - Policy.Expiration = std::chrono::hours(7 * 24); // 1w - Policy.PercentageOfAvailableSpace = 75; - }; }; /// Provide a path to a directory where to store the cached files for diff --git a/include/llvm/Support/CachePruning.h b/include/llvm/Support/CachePruning.h index 282ecd0aacd..46bab386a21 100644 --- a/include/llvm/Support/CachePruning.h +++ b/include/llvm/Support/CachePruning.h @@ -20,25 +20,36 @@ namespace llvm { +template class Expected; + +/// Policy for the pruneCache() function. A default constructed +/// CachePruningPolicy provides a reasonable default policy. struct CachePruningPolicy { /// The pruning interval. This is intended to be used to avoid scanning the /// directory too often. It does not impact the decision of which file to /// prune. A value of 0 forces the scan to occur. - std::chrono::seconds Interval = std::chrono::seconds::zero(); + std::chrono::seconds Interval = std::chrono::seconds(1200); /// The expiration for a file. When a file hasn't been accessed for Expiration /// seconds, it is removed from the cache. A value of 0 disables the /// expiration-based pruning. - std::chrono::seconds Expiration = std::chrono::seconds::zero(); + std::chrono::seconds Expiration = std::chrono::hours(7 * 24); // 1w /// The maximum size for the cache directory, in terms of percentage of the /// available space on the the disk. Set to 100 to indicate no limit, 50 to /// indicate that the cache size will not be left over half the available disk /// space. A value over 100 will be reduced to 100. A value of 0 disables the /// size-based pruning. - unsigned PercentageOfAvailableSpace = 0; + unsigned PercentageOfAvailableSpace = 75; }; +/// Parse the given string as a cache pruning policy. Defaults are taken from a +/// default constructed CachePruningPolicy object. +/// For example: "prune_interval=30s:prune_after=24h:cache_size=50%" +/// which means a pruning interval of 30 seconds, expiration time of 24 hours +/// and maximum cache size of 50% of available disk space. +Expected parseCachePruningPolicy(StringRef PolicyStr); + /// Peform pruning using the supplied policy, returns true if pruning /// occured, i.e. if Policy.Interval was expired. bool pruneCache(StringRef Path, CachePruningPolicy Policy); diff --git a/lib/Support/CachePruning.cpp b/lib/Support/CachePruning.cpp index 27cffb7721a..73e55f24b21 100644 --- a/lib/Support/CachePruning.cpp +++ b/lib/Support/CachePruning.cpp @@ -15,6 +15,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" @@ -33,6 +34,73 @@ static void writeTimestampFile(StringRef TimestampFile) { raw_fd_ostream Out(TimestampFile.str(), EC, sys::fs::F_None); } +static Expected parseDuration(StringRef Duration) { + if (Duration.empty()) + return make_error("Duration must not be empty", + inconvertibleErrorCode()); + + StringRef NumStr = Duration.slice(0, Duration.size()-1); + uint64_t Num; + if (NumStr.getAsInteger(0, Num)) + return make_error("'" + NumStr + "' not an integer", + inconvertibleErrorCode()); + + switch (Duration.back()) { + case 's': + return std::chrono::seconds(Num); + case 'm': + return std::chrono::minutes(Num); + case 'h': + return std::chrono::hours(Num); + default: + return make_error("'" + Duration + + "' must end with one of 's', 'm' or 'h'", + inconvertibleErrorCode()); + } +} + +Expected +llvm::parseCachePruningPolicy(StringRef PolicyStr) { + CachePruningPolicy Policy; + std::pair P = {"", PolicyStr}; + while (!P.second.empty()) { + P = P.second.split(':'); + + StringRef Key, Value; + std::tie(Key, Value) = P.first.split('='); + if (Key == "prune_interval") { + auto DurationOrErr = parseDuration(Value); + if (!DurationOrErr) + return std::move(DurationOrErr.takeError()); + Policy.Interval = *DurationOrErr; + } else if (Key == "prune_after") { + auto DurationOrErr = parseDuration(Value); + if (!DurationOrErr) + return std::move(DurationOrErr.takeError()); + Policy.Expiration = *DurationOrErr; + } else if (Key == "cache_size") { + if (Value.back() != '%') + return make_error("'" + Value + "' must be a percentage", + inconvertibleErrorCode()); + StringRef SizeStr = Value.slice(0, Value.size() - 1); + uint64_t Size; + if (SizeStr.getAsInteger(0, Size)) + return make_error("'" + SizeStr + "' not an integer", + inconvertibleErrorCode()); + if (Size > 100) + return make_error("'" + SizeStr + + "' must be between 0 and 100", + inconvertibleErrorCode()); + Policy.PercentageOfAvailableSpace = Size; + } else { + return make_error("Unknown key: '" + Key + "'", + inconvertibleErrorCode()); + } + } + + return Policy; +} + /// Prune the cache of files that haven't been accessed in a long time. bool llvm::pruneCache(StringRef Path, CachePruningPolicy Policy) { using namespace std::chrono; diff --git a/unittests/Support/CMakeLists.txt b/unittests/Support/CMakeLists.txt index ed84ee9c2ef..a7be18b6a3c 100644 --- a/unittests/Support/CMakeLists.txt +++ b/unittests/Support/CMakeLists.txt @@ -10,6 +10,7 @@ add_llvm_unittest(SupportTests BinaryStreamTest.cpp BlockFrequencyTest.cpp BranchProbabilityTest.cpp + CachePruningTest.cpp Casting.cpp Chrono.cpp CommandLineTest.cpp diff --git a/unittests/Support/CachePruningTest.cpp b/unittests/Support/CachePruningTest.cpp new file mode 100644 index 00000000000..04ac0d09b49 --- /dev/null +++ b/unittests/Support/CachePruningTest.cpp @@ -0,0 +1,71 @@ +//===- CachePruningTest.cpp -----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/CachePruning.h" +#include "llvm/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; + +TEST(CachePruningPolicyParser, Empty) { + auto P = parseCachePruningPolicy(""); + ASSERT_TRUE(bool(P)); + EXPECT_EQ(std::chrono::seconds(1200), P->Interval); + EXPECT_EQ(std::chrono::hours(7 * 24), P->Expiration); + EXPECT_EQ(75u, P->PercentageOfAvailableSpace); +} + +TEST(CachePruningPolicyParser, Interval) { + auto P = parseCachePruningPolicy("prune_interval=1s"); + ASSERT_TRUE(bool(P)); + EXPECT_EQ(std::chrono::seconds(1), P->Interval); + P = parseCachePruningPolicy("prune_interval=2m"); + ASSERT_TRUE(bool(P)); + EXPECT_EQ(std::chrono::minutes(2), P->Interval); + P = parseCachePruningPolicy("prune_interval=3h"); + ASSERT_TRUE(bool(P)); + EXPECT_EQ(std::chrono::hours(3), P->Interval); +} + +TEST(CachePruningPolicyParser, Expiration) { + auto P = parseCachePruningPolicy("prune_after=1s"); + ASSERT_TRUE(bool(P)); + EXPECT_EQ(std::chrono::seconds(1), P->Expiration); +} + +TEST(CachePruningPolicyParser, PercentageOfAvailableSpace) { + auto P = parseCachePruningPolicy("cache_size=100%"); + ASSERT_TRUE(bool(P)); + EXPECT_EQ(100u, P->PercentageOfAvailableSpace); +} + +TEST(CachePruningPolicyParser, Multiple) { + auto P = parseCachePruningPolicy("prune_after=1s:cache_size=50%"); + ASSERT_TRUE(bool(P)); + EXPECT_EQ(std::chrono::seconds(1200), P->Interval); + EXPECT_EQ(std::chrono::seconds(1), P->Expiration); + EXPECT_EQ(50u, P->PercentageOfAvailableSpace); +} + +TEST(CachePruningPolicyParser, Errors) { + EXPECT_EQ("Duration must not be empty", + toString(parseCachePruningPolicy("prune_interval=").takeError())); + EXPECT_EQ("'foo' not an integer", + toString(parseCachePruningPolicy("prune_interval=foos").takeError())); + EXPECT_EQ("'24x' must end with one of 's', 'm' or 'h'", + toString(parseCachePruningPolicy("prune_interval=24x").takeError())); + EXPECT_EQ("'foo' must be a percentage", + toString(parseCachePruningPolicy("cache_size=foo").takeError())); + EXPECT_EQ("'foo' not an integer", + toString(parseCachePruningPolicy("cache_size=foo%").takeError())); + EXPECT_EQ("'101' must be between 0 and 100", + toString(parseCachePruningPolicy("cache_size=101%").takeError())); + EXPECT_EQ("Unknown key: 'foo'", + toString(parseCachePruningPolicy("foo=bar").takeError())); +}