Add product name as a system profile field.

Change-Id: Id2e7fab040054d233050e25ce24ff094892dfefe
diff --git a/analyzer/report_master/report_serializer.cc b/analyzer/report_master/report_serializer.cc
index 4106f26..ecfe894 100644
--- a/analyzer/report_master/report_serializer.cc
+++ b/analyzer/report_master/report_serializer.cc
@@ -496,6 +496,9 @@
       case cobalt::SystemProfileField::BOARD_NAME:
         (*stream) << "Board_Name";
         break;
+      case cobalt::SystemProfileField::PRODUCT_NAME:
+        (*stream) << "Product_Name";
+        break;
     }
   }
   return grpc::Status::OK;
@@ -600,6 +603,9 @@
         case SystemProfileField::BOARD_NAME:
           (*stream) << ToCSVString(profile.board_name());
           break;
+        case SystemProfileField::PRODUCT_NAME:
+          (*stream) << ToCSVString(profile.product_name());
+          break;
       }
     }
   }
diff --git a/analyzer/report_master/report_serializer_test.cc b/analyzer/report_master/report_serializer_test.cc
index 58f742a..157b1b2 100644
--- a/analyzer/report_master/report_serializer_test.cc
+++ b/analyzer/report_master/report_serializer_test.cc
@@ -30,6 +30,7 @@
 const uint32_t kGroupedFruitHistogramReportConfigId = 6;
 const uint32_t kGroupedRawDumpReportConfigId = 7;
 const uint32_t kGroupedByBoardNameRawDumpReportConfigId = 8;
+const uint32_t kGroupedByProductNameRawDumpReportConfigId = 9;
 
 const char* kReportConfigText = R"(
 element {
@@ -171,6 +172,31 @@
   }
   system_profile_field: [BOARD_NAME]
 }
+
+element {
+  customer_id: 1
+  project_id: 1
+  id: 9
+  metric_id: 1
+  report_type: RAW_DUMP
+  variable {
+    metric_part: "City"
+  }
+  variable {
+    metric_part: "Fruit"
+  }
+  variable  {
+    metric_part: "Minutes"
+  }
+  variable  {
+    metric_part: "Rating"
+  }
+  export_configs {
+    csv {}
+  }
+  system_profile_field: [PRODUCT_NAME]
+}
+
 )";
 
 ReportMetadataLite BuildHistogramMetadata(uint32_t variable_index) {
@@ -201,6 +227,7 @@
 }
 
 void FillSystemProfile(SystemProfile* profile) {
+  profile->set_product_name("ReportSerializerTest");
   profile->set_board_name("ReportSerializerTest");
   profile->set_arch(SystemProfile::X86_64);
   profile->set_os(SystemProfile::FUCHSIA);
@@ -386,7 +413,7 @@
     std::string mime_type;
     std::string report;
 
-    // Test firt using the method SerializeReport().
+    // Test first using the method SerializeReport().
     auto status = SerializeRawDumpReport(report_config_id, variable_indices,
                                          report_rows, &report, &mime_type);
     EXPECT_TRUE(status.ok()) << status.error_message() << " ";
@@ -522,6 +549,25 @@
 }
 
 // Tests the function SerializeReport in the case that the
+// report is a raw dump report with several row added and system profile set.
+TEST_F(ReportSerializerTest, SerializeGroupedByProductNameRawDumpReportToCSV) {
+  std::vector<ReportRow> report_rows;
+  report_rows.push_back(BuildRawDumpReportRow("New York", "Apple", 42, 3.14));
+  report_rows.push_back(BuildRawDumpReportRow("Chicago", "Pear", -1, 2.718281));
+  report_rows.push_back(
+      BuildRawDumpReportRow("Miami", "Coconut", 9999999, 1.41421356237309504));
+  const char* kExpectedCSV =
+      R"(date,City,Fruit,Minutes,Rating,Product_Name
+2035-10-22,"New York","Apple",42,3.140,"ReportSerializerTest"
+2035-10-22,"Chicago","Pear",-1,2.718,"ReportSerializerTest"
+2035-10-22,"Miami","Coconut",9999999,1.414,"ReportSerializerTest"
+)";
+  DoSerializeRawDumpReportTest(kGroupedByProductNameRawDumpReportConfigId,
+                               {0, 1, 2, 3}, report_rows, "text/csv",
+                               kExpectedCSV);
+}
+
+// Tests the function SerializeReport in the case that the
 // report is a histogram report with rows added whose values are integers,
 // and the export is to csv.
 TEST_F(ReportSerializerTest, SerializeHistogramReportToCSVIntegerRows) {
diff --git a/analyzer/store/observation_store.cc b/analyzer/store/observation_store.cc
index 63b9f6c..3979817 100644
--- a/analyzer/store/observation_store.cc
+++ b/analyzer/store/observation_store.cc
@@ -234,6 +234,9 @@
         case SystemProfileField::BOARD_NAME:
           dst->set_allocated_board_name(src->release_board_name());
           break;
+        case SystemProfileField::PRODUCT_NAME:
+          dst->set_allocated_product_name(src->release_product_name());
+          break;
       }
     }
   }
diff --git a/config/metrics.proto b/config/metrics.proto
index 1609282..0ed7aed 100644
--- a/config/metrics.proto
+++ b/config/metrics.proto
@@ -25,6 +25,7 @@
   OS = 0;
   ARCH = 1;
   BOARD_NAME = 2;
+  PRODUCT_NAME = 3;
 }
 
 // ExponentialIntegerBuckets is used to define a partition of the integers into
diff --git a/encoder/encoder.cc b/encoder/encoder.cc
index 9204a2d..848b95c 100644
--- a/encoder/encoder.cc
+++ b/encoder/encoder.cc
@@ -414,6 +414,10 @@
           result.metadata->mutable_system_profile()->set_board_name(
               profile.board_name());
           break;
+        case SystemProfileField::PRODUCT_NAME:
+          result.metadata->mutable_system_profile()->set_product_name(
+              profile.product_name());
+          break;
       }
     }
   }
diff --git a/encoder/encoder_test.cc b/encoder/encoder_test.cc
index f94b1eb..6d8677b 100644
--- a/encoder/encoder_test.cc
+++ b/encoder/encoder_test.cc
@@ -57,6 +57,7 @@
     system_profile_.set_os(SystemProfile::FUCHSIA);
     system_profile_.set_arch(SystemProfile::ARM_64);
     system_profile_.set_board_name("Testing Board");
+    system_profile_.set_product_name("Testing Product");
   }
 
   const SystemProfile& system_profile() const override {
@@ -164,6 +165,14 @@
   } else {
     EXPECT_EQ("", result.metadata->system_profile().board_name());
   }
+
+  if (std::find(fields.begin(), fields.end(),
+                SystemProfileField::PRODUCT_NAME) != fields.end()) {
+    EXPECT_EQ("Testing Product",
+              result.metadata->system_profile().product_name());
+  } else {
+    EXPECT_EQ("", result.metadata->system_profile().product_name());
+  }
 }
 
 // Tests the EncodeString() method using the given |value| and the given
diff --git a/encoder/encoder_test_config.yaml b/encoder/encoder_test_config.yaml
index 06e88d8..ede9d0d 100644
--- a/encoder/encoder_test_config.yaml
+++ b/encoder/encoder_test_config.yaml
@@ -99,6 +99,17 @@
     - ARCH
     - OS
 
+# Metric 12 has a single string part and 4 system_profile_field selectors.
+- id: 12
+  time_zone_policy: LOCAL
+  parts:
+    "Part1":
+  system_profile_field:
+    - BOARD_NAME
+    - ARCH
+    - OS
+    - PRODUCT_NAME
+
 ################################################################################
 #  EncodingConfigs
 ################################################################################
diff --git a/encoder/system_data.cc b/encoder/system_data.cc
index 764e2d0..c02f142 100644
--- a/encoder/system_data.cc
+++ b/encoder/system_data.cc
@@ -83,7 +83,10 @@
 
 }  // namespace
 
-SystemData::SystemData() { PopulateSystemProfile(); }
+SystemData::SystemData(const std::string& product_name) {
+  system_profile_.set_product_name(product_name);
+  PopulateSystemProfile();
+}
 
 void SystemData::OverrideSystemProfile(const SystemProfile& profile) {
   system_profile_ = profile;
diff --git a/encoder/system_data.h b/encoder/system_data.h
index b948be0..0284f36 100644
--- a/encoder/system_data.h
+++ b/encoder/system_data.h
@@ -5,6 +5,8 @@
 #ifndef COBALT_ENCODER_SYSTEM_DATA_H_
 #define COBALT_ENCODER_SYSTEM_DATA_H_
 
+#include <string>
+
 #include "./observation.pb.h"
 
 namespace cobalt {
@@ -25,8 +27,8 @@
 class SystemData : public SystemDataInterface {
  public:
   // Constructor: Populuates system_profile_ with the real SystemProfile
-  // of the actual running system.
-  SystemData();
+  // of the actual running system and the specified product name.
+  explicit SystemData(const std::string& product_name);
 
   virtual ~SystemData() = default;
 
diff --git a/encoder/system_data_test.cc b/encoder/system_data_test.cc
index 119be46..80eeb19 100644
--- a/encoder/system_data_test.cc
+++ b/encoder/system_data_test.cc
@@ -16,10 +16,11 @@
 namespace encoder {
 
 TEST(SystemDataTest, BasicTest) {
-  SystemData system_data;
+  SystemData system_data("test_product");
   EXPECT_NE(SystemProfile::UNKNOWN_OS, system_data.system_profile().os());
   EXPECT_NE(SystemProfile::UNKNOWN_ARCH, system_data.system_profile().arch());
   EXPECT_NE(system_data.system_profile().board_name(), "");
+  EXPECT_EQ(system_data.system_profile().product_name(), "test_product");
 
   // Board names we expect to see.
   std::set<std::string> expected_board_names = {"Eve", "Generic ARM"};
diff --git a/observation.proto b/observation.proto
index 963e90c..af2f47f 100644
--- a/observation.proto
+++ b/observation.proto
@@ -140,6 +140,13 @@
   // This is a string representing the board name of the device. If a board name
   // cannot be determined, then this field will be 'unknown:<cpu signature>'.
   string board_name = 4;
+
+  // This is a string representing the source of the observation.
+  // For now, this is going to refer to layers of the Fuchsia cake such as
+  // "garnet", "zircon", "topaz", etc... In the future, we will use something
+  // related to what sort of device we are running on, such as
+  // "Google Lightbulb X" or "Android Laptop III".
+  string product_name = 5;
 }
 
 // An Observation consists of one or more ObservationParts.
diff --git a/tools/test_app/test_app.cc b/tools/test_app/test_app.cc
index 6466abc..470de79 100644
--- a/tools/test_app/test_app.cc
+++ b/tools/test_app/test_app.cc
@@ -510,7 +510,7 @@
     shuffler_encryption_scheme = EncryptedMessage::HYBRID_ECDH_V1;
   }
 
-  std::unique_ptr<SystemData> system_data(new SystemData());
+  std::unique_ptr<SystemData> system_data(new SystemData("test_app"));
   if (!FLAGS_override_board_name.empty()) {
     SystemProfile profile;
     profile.set_os(SystemProfile::FUCHSIA);