[forensics] Add Inspect budget Cobalt metric

* Enable logging event in cobalt with no dimensions.

Bug: 64072

Change-Id: I86f01a771693804ce6744459661bdd18578d862e
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/512146
Commit-Queue: Daniel Salazar Villarreal <villarreald@google.com>
Reviewed-by: Alex Pankhurst <pankhurst@google.com>
Reviewed-by: Francois Rousseau <frousseau@google.com>
diff --git a/src/developer/forensics/feedback_data/BUILD.gn b/src/developer/forensics/feedback_data/BUILD.gn
index 4e2e1d01..92d9560 100644
--- a/src/developer/forensics/feedback_data/BUILD.gn
+++ b/src/developer/forensics/feedback_data/BUILD.gn
@@ -260,6 +260,7 @@
     "//sdk/lib/sys/inspect/cpp",
     "//src/developer/forensics/utils:archive",
     "//src/developer/forensics/utils:inspect_node_manager",
+    "//src/developer/forensics/utils/cobalt",
   ]
 
   deps = [
diff --git a/src/developer/forensics/feedback_data/inspect_data_budget.cc b/src/developer/forensics/feedback_data/inspect_data_budget.cc
index f49f3c3..50dcca9 100644
--- a/src/developer/forensics/feedback_data/inspect_data_budget.cc
+++ b/src/developer/forensics/feedback_data/inspect_data_budget.cc
@@ -5,11 +5,13 @@
 #include "src/developer/forensics/feedback_data/inspect_data_budget.h"
 
 #include <lib/syslog/cpp/macros.h>
+#include <stdint.h>
 
 #include <algorithm>
 #include <filesystem>
 
 #include "src/developer/forensics/feedback_data/constants.h"
+#include "src/developer/forensics/utils/cobalt/metrics.h"
 
 namespace forensics::feedback_data {
 namespace {
@@ -22,8 +24,9 @@
 
 }  // namespace
 
-InspectDataBudget::InspectDataBudget(const char* limit_data_flag_path, InspectNodeManager* node)
-    : inspect_node_(node) {
+InspectDataBudget::InspectDataBudget(const char* limit_data_flag_path, InspectNodeManager* node,
+                                     cobalt::Logger* cobalt)
+    : inspect_node_(node), cobalt_(cobalt) {
   limit_data_flag_ = std::filesystem::exists(limit_data_flag_path);
   inspect_budget_enabled_ =
       inspect_node_->Get("/inspect_budget")
@@ -79,6 +82,8 @@
   if (inspect_last_ten_readings_.size() > 10) {
     inspect_last_ten_readings_.pop_front();
   }
+
+  cobalt_->LogEvent(cobalt::kInspectBudgetMetricId, data_budget_.value());
 }
 
 }  // namespace forensics::feedback_data
diff --git a/src/developer/forensics/feedback_data/inspect_data_budget.h b/src/developer/forensics/feedback_data/inspect_data_budget.h
index f849c02..4a6673d 100644
--- a/src/developer/forensics/feedback_data/inspect_data_budget.h
+++ b/src/developer/forensics/feedback_data/inspect_data_budget.h
@@ -12,6 +12,7 @@
 #include <optional>
 
 #include "src/developer/forensics/utils/archive.h"
+#include "src/developer/forensics/utils/cobalt/logger.h"
 #include "src/developer/forensics/utils/inspect_node_manager.h"
 
 namespace forensics {
@@ -21,7 +22,8 @@
 // 2.0 MB. If the file 'limit_data_flag_path' does not exist, prediction is disabled.
 class InspectDataBudget {
  public:
-  InspectDataBudget(const char* limit_data_flag_path, InspectNodeManager* node);
+  InspectDataBudget(const char* limit_data_flag_path, InspectNodeManager* node,
+                    cobalt::Logger* cobalt);
 
   void UpdateBudget(const std::map<std::string, ArchiveFileStats>& file_size_stats);
 
@@ -40,6 +42,8 @@
   inspect::UintProperty inspect_target_size_;
   std::list<inspect::UintArray> inspect_last_ten_readings_;
   size_t next_reading_idx_ = 0;
+
+  cobalt::Logger* cobalt_;
 };
 
 }  // namespace feedback_data
diff --git a/src/developer/forensics/feedback_data/main_service.cc b/src/developer/forensics/feedback_data/main_service.cc
index c42c84c..37e2642 100644
--- a/src/developer/forensics/feedback_data/main_service.cc
+++ b/src/developer/forensics/feedback_data/main_service.cc
@@ -105,7 +105,7 @@
       inspect_manager_(root_node),
       cobalt_(std::move(cobalt)),
       clock_(),
-      inspect_data_budget_(kUserBuildFlagPath, inspect_manager_.GetNodeManager()),
+      inspect_data_budget_(kUserBuildFlagPath, inspect_manager_.GetNodeManager(), cobalt_.get()),
       device_id_manager_(dispatcher_, kDeviceIdPath),
       datastore_(dispatcher_, services, cobalt_.get(), config.annotation_allowlist,
                  config.attachment_allowlist, boot_id_file, build_version_file,
diff --git a/src/developer/forensics/feedback_data/tests/data_provider_unittest.cc b/src/developer/forensics/feedback_data/tests/data_provider_unittest.cc
index de998e1..0bb1694 100644
--- a/src/developer/forensics/feedback_data/tests/data_provider_unittest.cc
+++ b/src/developer/forensics/feedback_data/tests/data_provider_unittest.cc
@@ -137,12 +137,6 @@
 // connecting through FIDL.
 class DataProviderTest : public UnitTestFixture {
  public:
-  DataProviderTest() {
-    inspect_node_manager_ = std::make_unique<InspectNodeManager>(&InspectRoot());
-    inspect_data_budget_ =
-        std::make_unique<InspectDataBudget>("non-existent_path", inspect_node_manager_.get());
-  }
-
   void SetUp() override {
     // |cobalt_| owns the test clock through a unique_ptr so we need to allocate |clock_| on the
     // heap and then give |cobalt_| ownership of it. This allows us to control the time perceived by
@@ -151,6 +145,10 @@
     cobalt_ = std::make_unique<cobalt::Logger>(dispatcher(), services(),
                                                std::unique_ptr<timekeeper::TestClock>(clock_));
     SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
+
+    inspect_node_manager_ = std::make_unique<InspectNodeManager>(&InspectRoot());
+    inspect_data_budget_ = std::make_unique<InspectDataBudget>(
+        "non-existent_path", inspect_node_manager_.get(), cobalt_.get());
   }
 
  protected:
diff --git a/src/developer/forensics/feedback_data/tests/datastore_unittest.cc b/src/developer/forensics/feedback_data/tests/datastore_unittest.cc
index 91049ba..6a26668 100644
--- a/src/developer/forensics/feedback_data/tests/datastore_unittest.cc
+++ b/src/developer/forensics/feedback_data/tests/datastore_unittest.cc
@@ -72,15 +72,15 @@
 
 class DatastoreTest : public UnitTestFixture {
  public:
-  DatastoreTest() : executor_(dispatcher()) {
-    inspect_node_manager_ = std::make_unique<InspectNodeManager>(&InspectRoot());
-    inspect_data_budget_ =
-        std::make_unique<InspectDataBudget>("non-existent_path", inspect_node_manager_.get());
-  }
+  DatastoreTest() : executor_(dispatcher()) {}
 
   void SetUp() override {
     SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
     cobalt_ = std::make_unique<cobalt::Logger>(dispatcher(), services());
+
+    inspect_node_manager_ = std::make_unique<InspectNodeManager>(&InspectRoot());
+    inspect_data_budget_ = std::make_unique<InspectDataBudget>(
+        "non-existent_path", inspect_node_manager_.get(), cobalt_.get());
   }
 
   void TearDown() override {
diff --git a/src/developer/forensics/feedback_data/tests/inspect_data_budget_unittest.cc b/src/developer/forensics/feedback_data/tests/inspect_data_budget_unittest.cc
index ef737f8..b4477a4 100644
--- a/src/developer/forensics/feedback_data/tests/inspect_data_budget_unittest.cc
+++ b/src/developer/forensics/feedback_data/tests/inspect_data_budget_unittest.cc
@@ -10,7 +10,10 @@
 #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"
@@ -37,10 +40,15 @@
 
 class InspectDataBudgetTest : public UnitTestFixture {
  public:
+  void SetUp() override {
+    SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
+    cobalt_ = std::make_unique<cobalt::Logger>(dispatcher(), services());
+  }
+
   void MakeUnlimitedBudget() {
     inspect_node_manager_ = std::make_unique<InspectNodeManager>(&InspectRoot());
-    inspect_data_budget_ =
-        std::make_unique<InspectDataBudget>("non-existent_path", inspect_node_manager_.get());
+    inspect_data_budget_ = std::make_unique<InspectDataBudget>(
+        "non-existent_path", inspect_node_manager_.get(), cobalt_.get());
   }
 
   void MakeLimitedBudget() {
@@ -48,8 +56,8 @@
 
     std::string limit_data_flag_path = files::JoinPath(tmp_dir_.path(), "limit_inspect_data");
     files::WriteFile(limit_data_flag_path, " ");
-    inspect_data_budget_ = std::make_unique<InspectDataBudget>(limit_data_flag_path.c_str(),
-                                                               inspect_node_manager_.get());
+    inspect_data_budget_ = std::make_unique<InspectDataBudget>(
+        limit_data_flag_path.c_str(), inspect_node_manager_.get(), cobalt_.get());
   }
 
   void CalcBudget(size_t zip_file_bytes) {
@@ -73,6 +81,8 @@
 
   files::ScopedTempDir tmp_dir_;
   std::unique_ptr<InspectDataBudget> inspect_data_budget_;
+
+  std::unique_ptr<cobalt::Logger> cobalt_;
 };
 
 TEST_F(InspectDataBudgetTest, TestUnlimitedBudget) {
@@ -231,6 +241,24 @@
   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::kMultidimensionalEvent,
+                                cobalt::kInspectBudgetMetricId, {}, 20971520),
+              }));
+}
+
 }  // namespace
 }  // namespace feedback_data
 }  // namespace forensics
diff --git a/src/developer/forensics/testing/stubs/cobalt_logger.cc b/src/developer/forensics/testing/stubs/cobalt_logger.cc
index 17b342e..1605d8f 100644
--- a/src/developer/forensics/testing/stubs/cobalt_logger.cc
+++ b/src/developer/forensics/testing/stubs/cobalt_logger.cc
@@ -41,7 +41,7 @@
 void CobaltLoggerBase::SetLastEvent(uint32_t metric_id, std::vector<uint32_t> event_codes,
                                     uint64_t count) {
   events_.push_back(
-      cobalt::Event(cobalt::EventType::kMultidimensionalOccurrence, metric_id, event_codes, count));
+      cobalt::Event(cobalt::EventType::kMultidimensionalEvent, metric_id, event_codes, count));
 }
 
 void CobaltLogger::LogEvent(uint32_t metric_id, uint32_t event_code, LogEventCallback callback) {
diff --git a/src/developer/forensics/utils/cobalt/event.cc b/src/developer/forensics/utils/cobalt/event.cc
index c8c5b7d..444d925 100644
--- a/src/developer/forensics/utils/cobalt/event.cc
+++ b/src/developer/forensics/utils/cobalt/event.cc
@@ -20,8 +20,7 @@
     return false;
   }
 
-  if (lhs.type != EventType::kMultidimensionalOccurrence &&
-      lhs.dimensions[0] != rhs.dimensions[0]) {
+  if (lhs.type != EventType::kMultidimensionalEvent && lhs.dimensions[0] != rhs.dimensions[0]) {
     return false;
   }
 
@@ -33,7 +32,7 @@
       return lhs.count == rhs.count;
     case EventType::kTimeElapsed:
       return lhs.usecs_elapsed == rhs.usecs_elapsed;
-    case EventType::kMultidimensionalOccurrence:
+    case EventType::kMultidimensionalEvent:
       std::vector lhs_events = lhs.dimensions;
       std::vector rhs_events = rhs.dimensions;
 
@@ -58,7 +57,7 @@
       return fxl::StringPrintf(
           "{type: time elapsed, metric_id: %u, dimension: %u, usecs elapsed: %lu}", metric_id,
           dimensions[0], usecs_elapsed);
-    case EventType::kMultidimensionalOccurrence:
+    case EventType::kMultidimensionalEvent:
       std::string dimensions_str;
       for (const auto& dimension : dimensions) {
         dimensions_str += fxl::StringPrintf("%u, ", dimension);
diff --git a/src/developer/forensics/utils/cobalt/event.h b/src/developer/forensics/utils/cobalt/event.h
index 505c7eb..1b62cb9 100644
--- a/src/developer/forensics/utils/cobalt/event.h
+++ b/src/developer/forensics/utils/cobalt/event.h
@@ -41,7 +41,7 @@
   template <typename DimensionTypesH, typename... DimensionTypesT,
             typename = std::enable_if_t<(std::is_enum_v<DimensionTypesT> && ...)>>
   explicit Event(DimensionTypesH dimensions_h, DimensionTypesT... dimensions_t)
-      : type(EventType::kMultidimensionalOccurrence),
+      : type(EventType::kMultidimensionalEvent),
         metric_id(MetricIDForEventCode(dimensions_h, dimensions_t...)),
         dimensions(std::vector<uint32_t>({
             static_cast<uint32_t>(dimensions_h),
diff --git a/src/developer/forensics/utils/cobalt/logger.cc b/src/developer/forensics/utils/cobalt/logger.cc
index 5f01ee6..f55a432 100644
--- a/src/developer/forensics/utils/cobalt/logger.cc
+++ b/src/developer/forensics/utils/cobalt/logger.cc
@@ -160,7 +160,7 @@
       logger_->LogElapsedTime(event.metric_id, event.dimensions[0], /*component=*/"",
                               /*elapsed_micros=*/event.usecs_elapsed, std::move(cb));
       break;
-    case EventType::kMultidimensionalOccurrence:
+    case EventType::kMultidimensionalEvent:
       fuchsia::cobalt::CobaltEvent cobalt_event;
       cobalt_event.metric_id = event.metric_id;
       cobalt_event.event_codes = event.dimensions;
diff --git a/src/developer/forensics/utils/cobalt/logger.h b/src/developer/forensics/utils/cobalt/logger.h
index fc05a58..4c0226b 100644
--- a/src/developer/forensics/utils/cobalt/logger.h
+++ b/src/developer/forensics/utils/cobalt/logger.h
@@ -31,6 +31,11 @@
   Logger(async_dispatcher_t* dispatcher, std::shared_ptr<sys::ServiceDirectory> services,
          std::unique_ptr<timekeeper::Clock> clock = std::make_unique<timekeeper::SystemClock>());
 
+  // Log event with no dimensions.
+  void LogEvent(uint32_t metric_id, uint64_t count) {
+    LogEvent(Event(cobalt::EventType::kMultidimensionalEvent, metric_id, {}, count));
+  }
+
   // Log an occurrence event with fuchsia.cobalt.Logger with the provided parameters. If the service
   // is not accessible, keep the parameters to try again later.
   template <typename... DimensionTypes>
diff --git a/src/developer/forensics/utils/cobalt/metrics.h b/src/developer/forensics/utils/cobalt/metrics.h
index 66e6184..48849b48 100644
--- a/src/developer/forensics/utils/cobalt/metrics.h
+++ b/src/developer/forensics/utils/cobalt/metrics.h
@@ -11,6 +11,7 @@
 namespace cobalt {
 
 constexpr auto kProjectId = cobalt_registry::kProjectId;
+constexpr auto kInspectBudgetMetricId = cobalt_registry::kMaxInputInspectBudgetMetricId;
 
 enum class CrashState {
   kUnknown = cobalt_registry::CrashMetricDimensionState::Unknown,
@@ -165,7 +166,7 @@
   kOccurrence,
   kCount,
   kTimeElapsed,
-  kMultidimensionalOccurrence,
+  kMultidimensionalEvent,
 };
 
 inline constexpr EventType EventTypeForEventCode(const SnapshotVersion version) {