[EventAggregator] Encode PerDeviceCountObservations

Add a method to encode PerDeviceCountObservations. Also
adds a method to encode ReportParticipationObservations,
which will be used by the PerDeviceCount report generator to
compute the number of unique devices contributing to a
given PerDeviceCount report over a given aggregation window.

Change-Id: I1d1e34425bd732efd6cf6bbe8cc205fc6299d26a
diff --git a/logger/encoder.cc b/logger/encoder.cc
index 12ae75b..ec43c6e 100644
--- a/logger/encoder.cc
+++ b/logger/encoder.cc
@@ -272,6 +272,28 @@
   return result;
 }
 
+Encoder::Result Encoder::EncodePerDeviceCountObservation(
+    MetricRef metric, const ReportDefinition* report, uint32_t day_index,
+    const std::string component, uint32_t event_code, int64_t count,
+    uint32_t window_size) const {
+  auto result = EncodeIntegerEventObservation(metric, report, day_index,
+                                              event_code, component, count);
+  auto* integer_event_observation = result.observation->release_numeric_event();
+  auto* count_observation = result.observation->mutable_per_device_count();
+  count_observation->set_allocated_integer_event_obs(integer_event_observation);
+  count_observation->set_window_size(window_size);
+  return result;
+}
+
+Encoder::Result Encoder::EncodeReportParticipationObservation(
+    MetricRef metric, const ReportDefinition* report,
+    uint32_t day_index) const {
+  auto result = MakeObservation(metric, report, day_index);
+  auto* observation = result.observation.get();
+  observation->mutable_report_participation();
+  return result;
+}
+
 Encoder::Result Encoder::EncodeNullBasicRapporObservation(
     MetricRef metric, const ReportDefinition* report, uint32_t day_index,
     uint32_t num_categories) const {
diff --git a/logger/encoder.h b/logger/encoder.h
index 02de824..bc9538e 100644
--- a/logger/encoder.h
+++ b/logger/encoder.h
@@ -280,6 +280,49 @@
                                         bool was_active,
                                         uint32_t window_size) const;
 
+  // Encodes an Observation of type PerDeviceCountObservation.
+  //
+  // 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.
+  // This is the last day (inclusive) of the rolling window associated with this
+  // Observation.
+  //
+  // component: The component associated with this Observation. The hash of this
+  // value will populate the Observation's |component_name_hash| field.
+  //
+  // event_code: The event code of the Event associated with this Observation.
+  //
+  // count: This will populate the |value| field of the the
+  // IntegerEventObservation wrapped by the PerDeviceCountObservation.
+  //
+  // window_size: The number of days in the window associated with the
+  // Observation. This should be one of the window sizes specified in |report|,
+  // but it is the caller's responsibility to ensure this.
+  Result EncodePerDeviceCountObservation(MetricRef metric,
+                                         const ReportDefinition* report,
+                                         uint32_t day_index,
+                                         const std::string component,
+                                         uint32_t event_code, int64_t count,
+                                         uint32_t window_size) const;
+
+  // Encodes an Observation of type ReportParticipationObservation.
+  //
+  // 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.
+  Result EncodeReportParticipationObservation(MetricRef metric,
+                                              const ReportDefinition* report,
+                                              uint32_t day_index) const;
+
  private:
   // Encodes a BasicRapporObservation for a given |metric|, |report|, and
   // |day_index| in which the data field is a Basic RAPPOR encoding of a vector
diff --git a/logger/encoder_test.cc b/logger/encoder_test.cc
index 99d1f85..e9ecb27 100644
--- a/logger/encoder_test.cc
+++ b/logger/encoder_test.cc
@@ -143,6 +143,23 @@
   }
 }
 
+metric {
+  metric_name: "ConnectionFailures"
+  metric_type: EVENT_COUNT
+  customer_id: 1
+  project_id: 1
+  id: 10
+  reports: {
+    report_name: "ConnectionFailures_PerDeviceCount"
+    id: 101
+    report_type: PER_DEVICE_COUNT_STATS
+    window_size: 7
+    window_size: 30
+    system_profile_field: OS
+    system_profile_field: ARCH
+  }
+}
+
 )";
 
 bool PopulateMetricDefinitions(MetricDefinitions* metric_definitions) {
@@ -449,5 +466,50 @@
                     .size());
 }
 
+TEST_F(EncoderTest, EncodePerDeviceCountObservation) {
+  const char kMetricName[] = "ConnectionFailures";
+  const char kReportName[] = "ConnectionFailures_PerDeviceCount";
+  const uint32_t kExpectedMetricId = 10;
+  const uint32_t kExpectedReportId = 101;
+  const uint32_t kDayIndex = 111;
+  const char kComponent[] = "Some Component";
+  const uint32_t kEventCode = 0;
+  const int64_t kCount = 1728;
+  const uint32_t kWindowSize = 7;
+  auto pair = GetMetricAndReport(kMetricName, kReportName);
+
+  auto result = encoder_->EncodePerDeviceCountObservation(
+      project_context_->RefMetric(pair.first), pair.second, kDayIndex,
+      kComponent, kEventCode, kCount, kWindowSize);
+  CheckResult(result, kExpectedMetricId, kExpectedReportId, kDayIndex);
+  // In the SystemProfile only the OS and ARCH should be set.
+  CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "",
+                     "");
+  ASSERT_TRUE(result.observation->has_per_device_count());
+  EXPECT_EQ(kWindowSize, result.observation->per_device_count().window_size());
+  ASSERT_TRUE(result.observation->per_device_count().has_integer_event_obs());
+  auto integer_obs = result.observation->per_device_count().integer_event_obs();
+  EXPECT_EQ(kEventCode, integer_obs.event_code());
+  EXPECT_EQ(32u, integer_obs.component_name_hash().size());
+  EXPECT_EQ(kCount, integer_obs.value());
+}
+
+TEST_F(EncoderTest, EncodeReportParticipationObservation) {
+  const char kMetricName[] = "ConnectionFailures";
+  const char kReportName[] = "ConnectionFailures_PerDeviceCount";
+  const uint32_t kExpectedMetricId = 10;
+  const uint32_t kExpectedReportId = 101;
+  const uint32_t kDayIndex = 111;
+  auto pair = GetMetricAndReport(kMetricName, kReportName);
+
+  auto result = encoder_->EncodeReportParticipationObservation(
+      project_context_->RefMetric(pair.first), pair.second, kDayIndex);
+  CheckResult(result, kExpectedMetricId, kExpectedReportId, kDayIndex);
+  // In the SystemProfile only the OS and ARCH should be set.
+  CheckSystemProfile(result, SystemProfile::FUCHSIA, SystemProfile::ARM_64, "",
+                     "");
+  ASSERT_TRUE(result.observation->has_report_participation());
+}
+
 }  // namespace logger
 }  // namespace cobalt
diff --git a/observation2.proto b/observation2.proto
index 720f431..479b546 100644
--- a/observation2.proto
+++ b/observation2.proto
@@ -47,7 +47,7 @@
     UniqueActivesObservation unique_actives = 6;
     PerDeviceCountObservation per_device_count = 7;
     CustomObservation custom = 1000;
-    ReportDeviceCountObservation report_device_count = 10000;
+    ReportParticipationObservation report_participation = 10000;
   }
 
   // A quasi-unique identifier for this observation. This is randomly generated
@@ -218,14 +218,20 @@
   map<string, CustomDimensionValue> values = 1;
 }
 
-// Observations of this type are produced by the EventAggregator and consumed
-// by the ReportGenerator for reports of type PER_DEVICE_COUNT_STATS. For each
-// report of that type, for each window size of the report, each device sends
-// one Observation of this type per day. The ReportGenerator uses the number
-// of such Observations to compute the number of devices in the fleet on which
-// a given pair (component, event code) did *not* occur during the aggregation
-// window.
-message ReportDeviceCountObservation {
-  // The size of the aggregation window.
-  uint32 window_size = 1;
-}
\ No newline at end of file
+// Observations of this type are used to signal that a given device was
+// collecting data for a given report, over some window of time. This
+// Observation type has no required fields, although the size of an
+// aggregation window may be provided. Observations are generated and
+// rate-limited by the EventAggregator.
+//
+// Current usage:
+// ReportParticipationObservations are produced by the EventAggregator and
+// consumed by the ReportGenerator for reports of type PER_DEVICE_COUNT_STATS.
+// For each report of that type, each device sends one Observation of this type
+// per day, along with a PerDeviceCountObservation for tuple (component, event
+// code, window size) for which at least one event with that code occurred
+// during the window of that size, with that component. The ReportGenerator
+// uses the number of each of the two kinds of Observations to compute the
+// number of devices in the fleet on which a given pair (component, event
+// code) did *not* occur during the aggregation window.
+message ReportParticipationObservation {}