Implement logging serialized protocol buffers to enable nested custom protos.

Change-Id: Ic3f9414670d8302c3b577290f3e03e7e988d5bb6
Reviewed-on: https://fuchsia-review.googlesource.com/c/cobalt/+/506359
Reviewed-by: Cameron Dale <camrdale@google.com>
Commit-Queue: Alexandre Zani <azani@google.com>
diff --git a/src/logger/encoder.cc b/src/logger/encoder.cc
index 912bf96..2bbb132 100644
--- a/src/logger/encoder.cc
+++ b/src/logger/encoder.cc
@@ -148,6 +148,16 @@
   return result;
 }
 
+Encoder::Result Encoder::EncodeSerializedCustomObservation(
+    MetricRef metric, const ReportDefinition* report, uint32_t day_index,
+    std::unique_ptr<std::string> serialized_proto) const {
+  auto result = NewObservationWithMetadata(metric, report, day_index);
+  auto* observation = result.observation.get();
+  auto* custom_observation = observation->mutable_custom();
+  custom_observation->mutable_serialized_proto()->swap(*serialized_proto);
+  return result;
+}
+
 Encoder::Result Encoder::EncodeUniqueActivesObservation(
     MetricRef metric, const ReportDefinition* report, uint32_t day_index, uint32_t event_code,
     bool was_active, const OnDeviceAggregationWindow& aggregation_window) const {
diff --git a/src/logger/encoder.h b/src/logger/encoder.h
index b27198c..5a32a5a 100644
--- a/src/logger/encoder.h
+++ b/src/logger/encoder.h
@@ -197,6 +197,22 @@
   Result EncodeCustomObservation(MetricRef metric, const ReportDefinition* report,
                                  uint32_t day_index, EventValuesPtr event_values) const;
 
+  // Encodes an Observation of type CustomObservation.
+  //
+  // metric: Provides access to the names and IDs of the customer, project and
+  // metric associated with the Observation being encoded.
+  //
+  // report: The definition of the Report associated with the Observation being
+  // encoded.
+  //
+  // day_index: The day index associated with the Observation being encoded.
+  //
+  // serialized_proto: This will be used to populate the Observation's |serialized_proto| field.
+  // This method does not validate |serialized_proto|. That is the caller's responsibility.
+  Result EncodeSerializedCustomObservation(MetricRef metric, const ReportDefinition* report,
+                                           uint32_t day_index,
+                                           std::unique_ptr<std::string> serialized_proto) const;
+
   // Encodes an Observation of type UniqueActivesObservation.
   //
   // metric: Provides access to the names and IDs of the customer, project and
diff --git a/src/logger/encoder_test.cc b/src/logger/encoder_test.cc
index ac3dcfe..a80815e 100644
--- a/src/logger/encoder_test.cc
+++ b/src/logger/encoder_test.cc
@@ -306,6 +306,27 @@
   }
 }
 
+TEST_F(EncoderTest, EncodeSerializedCustomObservation) {
+  const char kMetricName[] = "ModuleInstalls";
+  const char kReportName[] = "ModuleInstalls_DetailedData";
+  const uint32_t kExpectedMetricId = 8;
+
+  std::string serialized_proto = "serialized proto";
+  auto serialized_proto_ptr = std::make_unique<std::string>(serialized_proto);
+  auto pair = GetMetricAndReport(kMetricName, kReportName);
+
+  auto result = encoder_->EncodeSerializedCustomObservation(project_context_->RefMetric(pair.first),
+                                                            pair.second, kDayIndex,
+                                                            std::move(serialized_proto_ptr));
+  CheckResult(result, kExpectedMetricId,
+              registry::kModuleInstallsModuleInstallsDetailedDataReportId, kDayIndex);
+  // In the SystemProfile only the OS and ARCH should be set.
+  CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "", "", "", "");
+  ASSERT_TRUE(result.observation->has_custom());
+  const CustomObservation& obs = result.observation->custom();
+  EXPECT_EQ(serialized_proto, obs.serialized_proto());
+}
+
 TEST_F(EncoderTest, EncodeUniqueActivesObservation) {
   const char kMetricName[] = "DeviceBoots";
   const char kReportName[] = "DeviceBoots_UniqueDevices";
diff --git a/src/logger/event_loggers.cc b/src/logger/event_loggers.cc
index 6925d9a..2f540db 100644
--- a/src/logger/event_loggers.cc
+++ b/src/logger/event_loggers.cc
@@ -1013,6 +1013,17 @@
     // Each report type has its own logic for generating immediate
     // observations.
     case ReportDefinition::CUSTOM_RAW_DUMP: {
+      if (!custom_event->serialized_proto().empty()) {
+        auto serialized_proto = std::make_unique<std::string>();
+        if (may_invalidate) {
+          serialized_proto->swap(*(custom_event->mutable_serialized_proto()));
+        } else {
+          *serialized_proto = custom_event->serialized_proto();
+        }
+        return encoder()->EncodeSerializedCustomObservation(
+            event_record->project_context()->RefMetric(&metric), &report, event.day_index(),
+            std::move(serialized_proto));
+      }
       EventValuesPtr event_values =
           std::make_unique<google::protobuf::Map<std::string, CustomDimensionValue>>();
       if (may_invalidate) {
diff --git a/src/logger/event_loggers_test.cc b/src/logger/event_loggers_test.cc
index db92a0f..f6612ee 100644
--- a/src/logger/event_loggers_test.cc
+++ b/src/logger/event_loggers_test.cc
@@ -251,6 +251,18 @@
     return logger_->Log(std::move(event_record), mock_clock_->now());
   }
 
+  Status LogSerializedCustomEvent(uint32_t metric_id, const std::string& serialized_proto) {
+    auto event_record = std::make_unique<EventRecord>(project_context_, metric_id);
+    auto* custom_event = event_record->event()->mutable_custom_event();
+    custom_event->set_serialized_proto(serialized_proto);
+    Status valid;
+    if (kOK != (valid = logger_->PrepareAndValidateEvent(metric_id, MetricDefinition::CUSTOM,
+                                                         event_record.get()))) {
+      return valid;
+    }
+    return logger_->Log(std::move(event_record), mock_clock_->now());
+  }
+
   std::unique_ptr<internal::EventLogger> logger_;
   std::unique_ptr<TestEventAggregatorManager> event_aggregator_mgr_;
   std::unique_ptr<LocalAggregation> local_aggregation_;
@@ -627,6 +639,19 @@
   }
 }
 
+TEST_F(CustomEventLoggerTest, LogSerializedCustomEvent) {
+  std::string serialized_proto = "serialized proto";
+  ASSERT_EQ(kOK, LogSerializedCustomEvent(testing::all_report_types::kModuleInstallsMetricId,
+                                          serialized_proto));
+  Observation observation;
+  uint32_t expected_report_id =
+      testing::all_report_types::kModuleInstallsModuleInstallsDetailedDataReportId;
+  ASSERT_TRUE(FetchSingleObservation(&observation, expected_report_id, observation_store_.get(),
+                                     update_recipient_.get()));
+  ASSERT_TRUE(observation.has_custom());
+  EXPECT_EQ(observation.custom().serialized_proto(), serialized_proto);
+}
+
 // Tests that UniqueActivesObservations with the expected values are generated
 // when no events have been logged.
 TEST_F(UniqueActivesLoggerTest, CheckUniqueActivesObsValuesNoEvents) {
diff --git a/src/logger/fake_logger.cc b/src/logger/fake_logger.cc
index 3c70d0c..6dd9a21 100644
--- a/src/logger/fake_logger.cc
+++ b/src/logger/fake_logger.cc
@@ -189,4 +189,16 @@
   return Status::kOK;
 }
 
+Status FakeLogger::LogSerializedCustomEvent(uint32_t metric_id,
+                                            std::unique_ptr<std::string> serialized_proto) {
+  call_count_ += 1;
+
+  Event event;
+  event.set_metric_id(metric_id);
+  event.mutable_custom_event()->mutable_serialized_proto()->swap(*serialized_proto);
+  last_event_logged_ = event;
+
+  return Status::kOK;
+}
+
 }  // namespace cobalt::logger::testing
diff --git a/src/logger/fake_logger.h b/src/logger/fake_logger.h
index 7e30774..fb63c1f 100644
--- a/src/logger/fake_logger.h
+++ b/src/logger/fake_logger.h
@@ -62,6 +62,9 @@
 
   Status LogCustomEvent(uint32_t metric_id, EventValuesPtr event_values) override;
 
+  Status LogSerializedCustomEvent(uint32_t metric_id,
+                                  std::unique_ptr<std::string> serialized_proto) override;
+
   void RecordLoggerCall(PerProjectLoggerCallsMadeMetricDimensionLoggerMethod method) override {
     if (!internal_logging_paused_) {
       internal_logger_calls_[method]++;
diff --git a/src/logger/logger.cc b/src/logger/logger.cc
index 5865a5f..d300063 100644
--- a/src/logger/logger.cc
+++ b/src/logger/logger.cc
@@ -425,6 +425,16 @@
   return Log(metric_id, MetricDefinition::CUSTOM, std::move(event_record));
 }
 
+Status Logger::LogSerializedCustomEvent(uint32_t metric_id,
+                                        std::unique_ptr<std::string> serialized_proto) {
+  internal_metrics_->LoggerCalled(LoggerMethod::LogSerializedCustomEvent,
+                                  project_context_->project());
+  auto event_record = std::make_unique<EventRecord>(project_context_, metric_id);
+  auto* custom_event = event_record->event()->mutable_custom_event();
+  custom_event->mutable_serialized_proto()->swap(*serialized_proto);
+  return Log(metric_id, MetricDefinition::CUSTOM, std::move(event_record));
+}
+
 Status Logger::Log(uint32_t metric_id, MetricDefinition::MetricType metric_type,
                    std::unique_ptr<EventRecord> event_record) {
   const int kVerboseLoggingLevel = (is_internal_logger_ ? 9 : 7);
diff --git a/src/logger/logger.h b/src/logger/logger.h
index c6c126d..9617a75 100644
--- a/src/logger/logger.h
+++ b/src/logger/logger.h
@@ -173,6 +173,9 @@
 
   Status LogCustomEvent(uint32_t metric_id, EventValuesPtr event_values) override;
 
+  Status LogSerializedCustomEvent(uint32_t metric_id,
+                                  std::unique_ptr<std::string> serialized_proto) override;
+
   // LoggerCalled (cobalt_internal::metrics::logger_calls_made) and
   // (cobalt_internal::metrics::per_project_logger_calls_made) are logged for
   // every call to Logger along with which method was called and the project
diff --git a/src/logger/logger_interface.h b/src/logger/logger_interface.h
index fa5055a..942a8fc 100644
--- a/src/logger/logger_interface.h
+++ b/src/logger/logger_interface.h
@@ -247,6 +247,19 @@
   // contents match the proto defined.
   virtual Status LogCustomEvent(uint32_t metric_id, EventValuesPtr event_values) = 0;
 
+  // Logs a custom event. The structure of the event is defined in a proto file
+  // in the project's config folder.
+  //
+  // |metric_id| ID of the Metric the logged Event will belong to. It must
+  // be one of the Metrics from the ProjectContext passed to the constructor,
+  // and it must be of type CUSTOM.
+  //
+  // |serialized_proto| The serialized protocol buffer to be sent to the server. The serialized
+  // proto is parsed server-side, therefore it is the client's responsibility to make the sure the
+  // serialized proto matches the defined proto.
+  virtual Status LogSerializedCustomEvent(uint32_t metric_id,
+                                          std::unique_ptr<std::string> serialized_proto) = 0;
+
   // LoggerCalled (cobalt_internal::metrics::logger_calls_made) and
   // (cobalt_internal::metrics::per_project_logger_calls_made) are logged for
   // every call to Logger along with which method was called and the project
diff --git a/src/logger/logger_test.cc b/src/logger/logger_test.cc
index efea1f6..750d265 100644
--- a/src/logger/logger_test.cc
+++ b/src/logger/logger_test.cc
@@ -539,6 +539,22 @@
   }
 }
 
+// Tests the method LogSerializedCustomEvent().
+TEST_F(LoggerTest, LogSerializedCustomEvent) {
+  std::string serialized_proto = "serialized proto";
+  auto serialized_proto_ptr = std::make_unique<std::string>(serialized_proto);
+  ASSERT_EQ(kOK,
+            logger_->LogSerializedCustomEvent(testing::all_report_types::kModuleInstallsMetricId,
+                                              std::move(serialized_proto_ptr)));
+  Observation observation;
+  uint32_t expected_report_id =
+      testing::all_report_types::kModuleInstallsModuleInstallsDetailedDataReportId;
+  ASSERT_TRUE(FetchSingleObservation(&observation, expected_report_id, observation_store_.get(),
+                                     update_recipient_.get()));
+  ASSERT_TRUE(observation.has_custom());
+  EXPECT_EQ(observation.custom().serialized_proto(), serialized_proto);
+}
+
 TEST_F(LoggerTest, ReplaceEventOccurredWithOccurrence) {
   SetUpFromMetrics(testing::cobalt_1_1_migration::kCobaltRegistryBase64,
                    testing::all_report_types::kExpectedAggregationParams);
diff --git a/src/pb/event.proto b/src/pb/event.proto
index bd46164..41bdb9a 100644
--- a/src/pb/event.proto
+++ b/src/pb/event.proto
@@ -153,11 +153,17 @@
   repeated HistogramBucket buckets = 3;
 }
 
-// Allows users of Cobalt to define custom Metrics with custom semantics. Each
-// Custom event is a collection of CustomEventValues, each of which has a
-// dimension name and a value associated with the event.
+// Allows users of Cobalt to define custom Metrics with custom semantics.
+// Each Custom event is either
+//
+// 1. a collection of CustomEventValues, each of which has a dimension name and a value associated
+// with the event.
+// 2. a serialized protocol buffer.
 message CustomEvent {
   // The keys are the names of the metric dimensions to which each ValuePart
   // is associated.
   map<string, CustomDimensionValue> values = 1;
+
+  // The serialized protocol buffer.
+  bytes serialized_proto = 2;
 }
diff --git a/src/pb/observation.proto b/src/pb/observation.proto
index 6ad177d..1985401 100644
--- a/src/pb/observation.proto
+++ b/src/pb/observation.proto
@@ -369,14 +369,18 @@
 // A MetricDefinition of type CUSTOM and Report of type
 // CUSTOM_RAW_DUMP. In this case the Observation is *immediate* meaning it is
 // generated directly from a single CUSTOM Event as soon as the Event is
-// logged. The Event contains a list of (name, value) pairs where the names
-// are the names of the CUSTOM parts and the values are of the appropriate
-// type defined in the CUSTOM. A CustomObservation directly stores those
-// pairs.
+// logged. The Event represents the contents of a protocol buffer either as a
+// list of (name, value) pairs corresponding to the fields in the protocol
+// buffer, or the serialized protocol buffer itself.
 message CustomObservation {
+  // Only one of values or serialized_proto must be set.
+
   // The keys are the names of the metric dimensions to which each
   // CustomDimensionValue is associated.
   map<string, CustomDimensionValue> values = 1;
+
+  // The serialized protocol buffer.
+  bytes serialized_proto = 2;
 }
 
 // Observations of this type are used to signal that a given device was
diff --git a/src/registry/metric_definition.proto b/src/registry/metric_definition.proto
index 5e6e78b..aaa8b93 100644
--- a/src/registry/metric_definition.proto
+++ b/src/registry/metric_definition.proto
@@ -266,7 +266,7 @@
     // the data logged for this metric. The semantics of the Metric should be
     // specified in the .proto file. Each event logged for this Metric must have
     // dimensions with names and types that correspond to the fields of the
-    // associated proto.
+    // associated proto or the serialized proto itself.
     //
     // Event fields:
     //   vector<CustomEventValue> event_values # Named, typed values