blob: 922ad36f777c4b3aa049f10f96db1c3fa1a36301 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/forensics/feedback_data/inspect_data_budget.h"
#include <algorithm>
#include <optional>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/developer/forensics/testing/stubs/cobalt_logger_factory.h"
#include "src/developer/forensics/testing/unit_test_fixture.h"
#include "src/developer/forensics/utils/cobalt/logger.h"
#include "src/developer/forensics/utils/cobalt/metrics.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/timekeeper/test_clock.h"
namespace forensics {
namespace feedback_data {
namespace {
constexpr size_t kKilobytes = 1024u;
constexpr size_t kMegabytes = 1048576u;
constexpr size_t kGigabytes = 1073741824u;
using inspect::testing::ChildrenMatch;
using inspect::testing::NameMatches;
using inspect::testing::NodeMatches;
using inspect::testing::PropertyList;
using inspect::testing::StringIs;
using inspect::testing::UintArrayIs;
using inspect::testing::UintIs;
using testing::AllOf;
using testing::IsEmpty;
using testing::UnorderedElementsAre;
using testing::UnorderedElementsAreArray;
class InspectDataBudgetTest : public UnitTestFixture {
public:
void SetUp() override {
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
cobalt_ = std::make_unique<cobalt::Logger>(dispatcher(), services(), &clock_);
}
void MakeUnlimitedBudget() {
inspect_node_manager_ = std::make_unique<InspectNodeManager>(&InspectRoot());
inspect_data_budget_ =
std::make_unique<InspectDataBudget>(false, inspect_node_manager_.get(), cobalt_.get());
}
void MakeLimitedBudget() {
inspect_node_manager_ = std::make_unique<InspectNodeManager>(&InspectRoot());
inspect_data_budget_ =
std::make_unique<InspectDataBudget>(true, inspect_node_manager_.get(), cobalt_.get());
}
void CalcBudget(size_t zip_file_bytes) {
std::map<std::string, ArchiveFileStats> file_size_stats;
// The Inspect file must exists or else the inspect budget is disabled.
file_size_stats["inspect.json"] = {0, 0};
file_size_stats["other"] = {0, zip_file_bytes};
inspect_data_budget_->UpdateBudget(file_size_stats);
}
void CalcBudget(const std::map<std::string, ArchiveFileStats>& file_size_stats) {
inspect_data_budget_->UpdateBudget(file_size_stats);
}
std::optional<size_t> GetSizeInBytes() { return inspect_data_budget_->SizeInBytes(); }
private:
timekeeper::TestClock clock_;
std::unique_ptr<InspectNodeManager> inspect_node_manager_;
std::unique_ptr<InspectDataBudget> inspect_data_budget_;
std::unique_ptr<cobalt::Logger> cobalt_;
};
TEST_F(InspectDataBudgetTest, TestUnlimitedBudget) {
MakeUnlimitedBudget();
ASSERT_FALSE(GetSizeInBytes());
// setting a budget should not do anything.
CalcBudget(1 * kMegabytes);
ASSERT_FALSE(GetSizeInBytes());
}
TEST_F(InspectDataBudgetTest, TestLimitedBudget) {
MakeLimitedBudget();
ASSERT_TRUE(GetSizeInBytes());
}
TEST_F(InspectDataBudgetTest, TestForCrash_MissingSizeStats) {
MakeLimitedBudget();
std::map<std::string, ArchiveFileStats> file_size_stats;
CalcBudget(file_size_stats);
}
TEST_F(InspectDataBudgetTest, TestSizeBudget_Maintain) {
MakeLimitedBudget();
ASSERT_TRUE(GetSizeInBytes());
size_t initial_budget = GetSizeInBytes().value();
CalcBudget(2 * kMegabytes);
ASSERT_TRUE(GetSizeInBytes());
ASSERT_EQ(GetSizeInBytes().value(), initial_budget);
}
TEST_F(InspectDataBudgetTest, TestSizeBudget_UpperLimit) {
MakeLimitedBudget();
ASSERT_TRUE(GetSizeInBytes());
size_t initial_budget = GetSizeInBytes().value();
CalcBudget(724 * kKilobytes);
ASSERT_TRUE(GetSizeInBytes());
ASSERT_EQ(GetSizeInBytes().value(), initial_budget);
}
TEST_F(InspectDataBudgetTest, TestSizeBudget_LowerLimit) {
// Arrive at the lower limit by making the zip size 2 GB twice (this should reduce the initial
// budget at most by 2^16 times).
MakeLimitedBudget();
CalcBudget(2 * kGigabytes);
CalcBudget(2 * kGigabytes);
ASSERT_TRUE(GetSizeInBytes());
size_t lower_limit = GetSizeInBytes().value();
CalcBudget(kGigabytes);
ASSERT_TRUE(GetSizeInBytes());
size_t new_budget = GetSizeInBytes().value();
ASSERT_TRUE(GetSizeInBytes());
ASSERT_EQ(lower_limit, new_budget);
}
TEST_F(InspectDataBudgetTest, TestSizeBudget_Reduce_Increase) {
MakeLimitedBudget();
ASSERT_TRUE(GetSizeInBytes());
size_t initial_budget = GetSizeInBytes().value();
size_t budget = (initial_budget * 1024) / 1500;
CalcBudget(3000 * kKilobytes);
ASSERT_TRUE(GetSizeInBytes());
ASSERT_EQ(GetSizeInBytes().value(), budget);
// Note: Make sure that the geometric mean of the last zip size and the new zip size > 2MB.
// Otherwise the resulting budget might be lower than our calculated value due to upper limit
// restrictions.
budget = (budget * 1024) / 800;
CalcBudget(1600 * kKilobytes);
ASSERT_TRUE(GetSizeInBytes());
ASSERT_EQ(GetSizeInBytes().value(), budget);
}
TEST_F(InspectDataBudgetTest, TestInspectBudget_BudgetDisabled) {
MakeUnlimitedBudget();
const auto node = AllOf(
NodeMatches(AllOf(NameMatches("inspect_budget"), PropertyList(UnorderedElementsAreArray({
StringIs("is_budget_enabled", "false"),
})))),
ChildrenMatch(IsEmpty()));
EXPECT_THAT(InspectTree(), ChildrenMatch(UnorderedElementsAre(AllOf(node))));
}
TEST_F(InspectDataBudgetTest, TestInspectBudget_BudgetEnabled) {
MakeLimitedBudget();
ASSERT_TRUE(GetSizeInBytes());
size_t initial_budget = GetSizeInBytes().value();
CalcBudget(1 * kMegabytes);
ASSERT_TRUE(GetSizeInBytes());
ASSERT_EQ(GetSizeInBytes().value(), initial_budget);
const auto budget =
AllOf(NodeMatches(AllOf(NameMatches("last_ten_input_budget_previous_snapshot_size_bytes"),
PropertyList(UnorderedElementsAreArray({
UintArrayIs("0", std::vector<uint64_t>{20971520u, 1048576u}),
})))),
ChildrenMatch(IsEmpty()));
const auto node = AllOf(NodeMatches(AllOf(NameMatches("inspect_budget"),
PropertyList(UnorderedElementsAreArray({
UintIs("min_input_budget_bytes", 4194304u),
UintIs("max_input_budget_bytes", 20971520u),
UintIs("target_snapshot_size_bytes", 2097152u),
StringIs("is_budget_enabled", "true"),
})))),
ChildrenMatch(UnorderedElementsAre(budget)));
EXPECT_THAT(InspectTree(), ChildrenMatch(UnorderedElementsAre(AllOf(node))));
}
TEST_F(InspectDataBudgetTest, TestInspectBudget_MaxEntries) {
MakeLimitedBudget();
// zip_size = min(floor(budget * 0.2), 3MB) + 512KB
for (int i = 0; i < 12; i++) {
const size_t budget = GetSizeInBytes().value();
const float compression_ratio = 0.2;
const size_t zip_size =
std::min<size_t>(std::floor(budget * compression_ratio), 3 * kMegabytes) + 512 * kKilobytes;
CalcBudget(zip_size);
}
const auto budget =
AllOf(NodeMatches(AllOf(NameMatches("last_ten_input_budget_previous_snapshot_size_bytes"),
PropertyList(UnorderedElementsAreArray({
UintArrayIs("2", std::vector<uint64_t>{8036989u, 2245028u}),
UintArrayIs("3", std::vector<uint64_t>{7906790u, 2131685u}),
UintArrayIs("4", std::vector<uint64_t>{7874894u, 2105646u}),
UintArrayIs("5", std::vector<uint64_t>{7866963u, 2099266u}),
UintArrayIs("6", std::vector<uint64_t>{7864982u, 2097680u}),
UintArrayIs("7", std::vector<uint64_t>{7864486u, 2097284u}),
UintArrayIs("8", std::vector<uint64_t>{7864362u, 2097185u}),
UintArrayIs("9", std::vector<uint64_t>{7864331u, 2097160u}),
UintArrayIs("10", std::vector<uint64_t>{7864323u, 2097154u}),
UintArrayIs("11", std::vector<uint64_t>{7864323u, 2097152u}),
})))),
ChildrenMatch(IsEmpty()));
const auto node = AllOf(NodeMatches(AllOf(NameMatches("inspect_budget"),
PropertyList(UnorderedElementsAreArray({
UintIs("min_input_budget_bytes", 4194304u),
UintIs("max_input_budget_bytes", 20971520u),
UintIs("target_snapshot_size_bytes", 2097152u),
StringIs("is_budget_enabled", "true"),
})))),
ChildrenMatch(UnorderedElementsAre(budget)));
EXPECT_THAT(InspectTree(), ChildrenMatch(UnorderedElementsAre(AllOf(node))));
}
TEST_F(InspectDataBudgetTest, TestCobalt_BudgetEnabled) {
MakeLimitedBudget();
ASSERT_TRUE(GetSizeInBytes());
size_t initial_budget = GetSizeInBytes().value();
CalcBudget(1 * kMegabytes);
ASSERT_TRUE(GetSizeInBytes());
ASSERT_EQ(GetSizeInBytes().value(), initial_budget);
RunLoopUntilIdle();
EXPECT_THAT(
ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::EventType::kInteger, cobalt::kInspectBudgetMetricId, {}, 20971520),
}));
}
} // namespace
} // namespace feedback_data
} // namespace forensics