Skip to content

Commit

Permalink
Add cvd cache
Browse files Browse the repository at this point in the history
New command used to manage the local fetch cache.  Will eventually be
automatically invoked during every `cvd fetch` call to keep the cache
from growing without end.

Bug: 384803921
Test: cvd help # see summary help
Test: cvd help cache
Test: cvd cache
Test: cvd cache where
Test: cvd cache size
Test: cvd cache empty
Test: cvd cache cleanup
Test: cvd cache cleanup --allowed_size_GB=<`cvd cache size` - 1>
Test: cvd cache cleanup --allowed_size_GB=-1
  • Loading branch information
cjreynol committed Dec 21, 2024
1 parent 7c84add commit c40ba5b
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 2 deletions.
2 changes: 2 additions & 0 deletions base/cvd/cuttlefish/host/commands/cvd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ cc_library(
"cli/commands/acloud_mixsuperimage.cpp",
"cli/commands/acloud_translator.cpp",
"cli/commands/bugreport.cpp",
"cli/commands/cache.cpp",
"cli/commands/clear.cpp",
"cli/commands/cmd_list.cpp",
"cli/commands/create.cpp",
Expand Down Expand Up @@ -154,6 +155,7 @@ cc_library(
"cli/commands/acloud_mixsuperimage.h",
"cli/commands/acloud_translator.h",
"cli/commands/bugreport.h",
"cli/commands/cache.h",
"cli/commands/clear.h",
"cli/commands/cmd_list.h",
"cli/commands/create.h",
Expand Down
228 changes: 228 additions & 0 deletions base/cvd/cuttlefish/host/commands/cvd/cli/commands/cache.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "host/commands/cvd/cli/commands/cache.h"

#include <algorithm>
#include <cstddef>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#include <android-base/logging.h>
#include <fmt/format.h>

#include "common/libs/utils/files.h"
#include "common/libs/utils/flag_parser.h"
#include "common/libs/utils/result.h"
#include "common/libs/utils/tee_logging.h"
#include "host/commands/cvd/cli/commands/server_handler.h"
#include "host/commands/cvd/cli/types.h"
#include "host/commands/cvd/utils/common.h"

namespace cuttlefish {

namespace {
constexpr int kDefaultCacheSizeGigabytes = 25;

constexpr char kDetailedHelpText[] =
R"(usage: cvd cache <subcommand>
Example usage:
cvd cache where - display the filepath of the cache
cvd cache size - display the approximate size of the cache
cvd cache cleanup - caps the cache at the default size
cvd cache cleanup --allowed_size_GB=<n> - caps the cache at the given size
cvd cache empty - wipes out all files in the cache directory
**Notes**:
- size and cleanup round the cache size up to the nearest gigabyte for calculating
- cleanup uses last modification time to remove oldest files first
)";

constexpr char kSummaryHelpText[] = "Used to manage the files cached by cvd";

enum class CacheSubcommand {
Cleanup,
Empty,
Size,
Where,
};

struct CacheArguments {
CacheSubcommand subcommand = CacheSubcommand::Where;
std::size_t allowed_size_GB = kDefaultCacheSizeGigabytes;
};

std::string GetCacheDirectory() {
const std::string cache_base_path = PerUserDir() + "/cache";
return cache_base_path;
}

Result<CacheSubcommand> ToCacheSubcommand(const std::string& key) {
const std::unordered_map<std::string, CacheSubcommand> kString_mapping{
{"cleanup", CacheSubcommand::Cleanup},
{"empty", CacheSubcommand::Empty},
{"size", CacheSubcommand::Size},
{"where", CacheSubcommand::Where},
};
auto lookup = kString_mapping.find(key);
CF_EXPECT(lookup != kString_mapping.end(), "Unable to find subcommand");
return lookup->second;
}

Result<CacheArguments> ProcessArguments(
const std::vector<std::string>& subcommand_arguments) {
CF_EXPECT(subcommand_arguments.empty() == false,
"cvd cache requires at least a subcommand argument. Run `cvd help "
"cache` for details.");
std::vector<std::string> cache_arguments = subcommand_arguments;
std::string subcommand = cache_arguments.front();
cache_arguments.erase(cache_arguments.begin());
CacheArguments result{
.subcommand =
CF_EXPECTF(ToCacheSubcommand(subcommand),
"Provided \"{}\" is not a valid cache subcommand. (Is "
"there a non-selector flag before the subcommand?)",
subcommand),
};

std::vector<Flag> flags;
flags.emplace_back(GflagsCompatFlag("allowed_size_GB", result.allowed_size_GB)
.Help("Allowed size of the cache during cleanup "
"operation, in gigabytes."));
flags.emplace_back(UnexpectedArgumentGuard());
CF_EXPECT(ConsumeFlags(flags, cache_arguments));
CF_EXPECTF(ConsumeFlags(flags, cache_arguments),
"Failure processing arguments and flags: cvd {} {}", subcommand,
fmt::join(cache_arguments, " "));

return result;
}

// TODO CJR: this could live generically in files.h/cpp
Result<std::vector<std::string>> GetFilesByModTimeDescending(
const std::string& cache_directory) {
std::vector<std::string> cache_files = CF_EXPECTF(
DirectoryContents(cache_directory),
"Failure retrieving cache file list at \"{}\"", cache_directory);
std::vector<std::string> filtered;
std::copy_if(
cache_files.begin(), cache_files.end(), std::back_inserter(filtered),
[](std::string filename) { return filename != "." && filename != ".."; });
std::sort(filtered.begin(), filtered.end(), [](std::string a, std::string b) {
return FileModificationTime(a) > FileModificationTime(b);
});
return filtered;
}

Result<void> RunCleanup(const std::string& cache_directory,
const std::size_t allowed_size_GB) {
std::size_t cache_size = CF_EXPECT(GetDiskUsageGigabytes(cache_directory));
// Descending because elements are removed from the back
std::vector<std::string> cache_files =
CF_EXPECT(GetFilesByModTimeDescending(cache_directory));
std::string next;
while (cache_size > allowed_size_GB) {
// assuming all files in the cache are directories
// TODO CJR: test what happens if that is untrue
CF_EXPECT(cache_files.empty() == false,
"Error removing files for cleanup, no more files found.");
next = cache_files.back();
cache_files.pop_back();
LOG(DEBUG) << fmt::format("Deleting \"{}\" for cleanup", next);
CF_EXPECT(RecursivelyRemoveDirectory(
fmt::format("{}/{}", cache_directory, next)));
cache_size = CF_EXPECT(GetDiskUsageGigabytes(cache_directory));
}
LOG(INFO) << fmt::format("Cache at \"{}\": ~{}/{}GB", cache_directory,
cache_size, allowed_size_GB);
return {};
}

Result<void> RunEmpty(const std::string& cache_directory) {
CF_EXPECT(RecursivelyRemoveDirectory(cache_directory));
CF_EXPECT(EnsureDirectoryExists(cache_directory));
LOG(INFO) << fmt::format("Cache at \"{}\" has been emptied", cache_directory);
return {};
}

Result<void> RunSize(const std::string& cache_directory) {
std::size_t cache_size = CF_EXPECT(GetDiskUsageGigabytes(cache_directory));
LOG(INFO) << fmt::format("Cache at \"{}\": ~{}GB", cache_directory,
cache_size);
return {};
}

void RunWhere(std::string_view cache_directory) {
LOG(INFO) << fmt::format("Cache located at: {}", cache_directory);
}

class CvdCacheCommandHandler : public CvdServerHandler {
public:
Result<void> Handle(const CommandRequest& request) override;
cvd_common::Args CmdList() const override { return {"cache"}; }
Result<std::string> SummaryHelp() const override;
bool ShouldInterceptHelp() const override { return true; }
Result<std::string> DetailedHelp(std::vector<std::string>&) const override;
};

Result<void> CvdCacheCommandHandler::Handle(const CommandRequest& request) {
CF_EXPECT(CanHandle(request));
auto logger = ScopedTeeLogger(LogToStderr());

CacheArguments arguments =
CF_EXPECT(ProcessArguments(request.SubcommandArguments()));
std::string cache_directory = GetCacheDirectory();
switch (arguments.subcommand) {
case CacheSubcommand::Cleanup:
CF_EXPECTF(RunCleanup(cache_directory, arguments.allowed_size_GB),
"Unable to clean up cache at {}", cache_directory);
break;
case CacheSubcommand::Empty:
CF_EXPECTF(RunEmpty(cache_directory), "Unable to clean up cache at {}",
cache_directory);
break;
case CacheSubcommand::Size:
CF_EXPECTF(RunSize(cache_directory), "Unable to clean up cache at {}",
cache_directory);
break;
case CacheSubcommand::Where:
RunWhere(cache_directory);
break;
}

return {};
}

Result<std::string> CvdCacheCommandHandler::SummaryHelp() const {
return kSummaryHelpText;
}

Result<std::string> CvdCacheCommandHandler::DetailedHelp(
std::vector<std::string>&) const {
return kDetailedHelpText;
}

} // namespace

std::unique_ptr<CvdServerHandler> NewCvdCacheCommandHandler() {
return std::unique_ptr<CvdServerHandler>(new CvdCacheCommandHandler());
}

} // namespace cuttlefish
27 changes: 27 additions & 0 deletions base/cvd/cuttlefish/host/commands/cvd/cli/commands/cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <memory>

#include "host/commands/cvd/cli/commands/server_handler.h"

namespace cuttlefish {

std::unique_ptr<CvdServerHandler> NewCvdCacheCommandHandler();

} // namespace cuttlefish
6 changes: 4 additions & 2 deletions base/cvd/cuttlefish/host/commands/cvd/cli/request_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@
#include "common/libs/utils/result.h"
#include "host/commands/cvd/cli/command_request.h"
#include "host/commands/cvd/cli/command_sequence.h"
#include "host/commands/cvd/instances/instance_lock.h"
#include "host/commands/cvd/instances/instance_manager.h"
#include "host/commands/cvd/cli/commands/acloud_command.h"
#include "host/commands/cvd/cli/commands/acloud_mixsuperimage.h"
#include "host/commands/cvd/cli/commands/acloud_translator.h"
#include "host/commands/cvd/cli/commands/bugreport.h"
#include "host/commands/cvd/cli/commands/cache.h"
#include "host/commands/cvd/cli/commands/clear.h"
#include "host/commands/cvd/cli/commands/cmd_list.h"
#include "host/commands/cvd/cli/commands/create.h"
Expand All @@ -51,6 +50,8 @@
#include "host/commands/cvd/cli/commands/stop.h"
#include "host/commands/cvd/cli/commands/try_acloud.h"
#include "host/commands/cvd/cli/commands/version.h"
#include "host/commands/cvd/instances/instance_lock.h"
#include "host/commands/cvd/instances/instance_manager.h"

namespace cuttlefish {

Expand All @@ -63,6 +64,7 @@ RequestContext::RequestContext(
request_handlers_.emplace_back(NewAcloudCommand(command_sequence_executor_));
request_handlers_.emplace_back(NewAcloudMixSuperImageCommand());
request_handlers_.emplace_back(NewAcloudTranslatorCommand(instance_manager_));
request_handlers_.emplace_back(NewCvdCacheCommandHandler());
request_handlers_.emplace_back(
NewCvdCmdlistHandler(command_sequence_executor_));
request_handlers_.emplace_back(NewCvdCreateCommandHandler(
Expand Down

0 comments on commit c40ba5b

Please sign in to comment.