Create log event from key value maps.

BUG: b/112816333
Test: statsd test.
Change-Id: Ib66f06186abfacd77807436379e1e142a5b87c99
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index d7a926f..6065bbf 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -127,6 +127,7 @@
         WTFOccurred wtf_occurred = 80;
         LowMemReported low_mem_reported = 81;
         GenericAtom generic_atom = 82;
+        KeyValuePairsAtom key_value_pairs_atom = 83;
     }
 
     // Pulled events will start at field 10000.
@@ -177,6 +178,20 @@
     optional string tag = 2;
 }
 
+message KeyValuePair {
+    optional int32 key = 1;
+    oneof value {
+        int64 value_int = 2;
+        string value_str = 3;
+        float value_float = 4;
+    }
+}
+
+message KeyValuePairsAtom {
+    optional int32 uid = 1;
+    repeated KeyValuePair pairs = 2;
+}
+
 /*
  * *****************************************************************************
  * Below are all of the individual atoms that are logged by Android via statsd.
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 04d34f3..73e6572 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -18,6 +18,7 @@
 #include "logd/LogEvent.h"
 
 #include "stats_log_util.h"
+#include "statslog.h"
 
 namespace android {
 namespace os {
@@ -51,6 +52,52 @@
     }
 }
 
+LogEvent::LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                   int32_t uid,
+                   const std::map<int32_t, int64_t>& int_map,
+                   const std::map<int32_t, std::string>& string_map,
+                   const std::map<int32_t, float>& float_map) {
+    mLogdTimestampNs = wallClockTimestampNs;
+    mElapsedTimestampNs = elapsedTimestampNs;
+    mTagId = android::util::KEY_VALUE_PAIRS_ATOM;
+    mLogUid = uid;
+
+    int pos[] = {1, 1, 1};
+
+    mValues.push_back(FieldValue(Field(mTagId, pos, 0 /* depth */), Value(uid)));
+    pos[0]++;
+    for (const auto&itr : int_map) {
+        pos[2] = 1;
+        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
+        pos[2] = 2;
+        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
+        mValues.back().mField.decorateLastPos(2);
+        pos[1]++;
+    }
+
+    for (const auto&itr : string_map) {
+        pos[2] = 1;
+        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
+        pos[2] = 3;
+        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
+        mValues.back().mField.decorateLastPos(2);
+        pos[1]++;
+    }
+
+    for (const auto&itr : float_map) {
+        pos[2] = 1;
+        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.first)));
+        pos[2] = 4;
+        mValues.push_back(FieldValue(Field(mTagId, pos, 2 /* depth */), Value(itr.second)));
+        mValues.back().mField.decorateLastPos(2);
+        pos[1]++;
+    }
+    if (!mValues.empty()) {
+        mValues.back().mField.decorateLastPos(1);
+        mValues.at(mValues.size() - 2).mField.decorateLastPos(1);
+    }
+}
+
 LogEvent::LogEvent(int32_t tagId, int64_t timestampNs) {
     mLogdTimestampNs = timestampNs;
     mTagId = tagId;
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 24d624d..9ed09dd 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -69,6 +69,15 @@
     // For testing. The timestamp is used as both elapsed real time and logd timestamp.
     explicit LogEvent(int32_t tagId, int64_t timestampNs);
 
+    /**
+     * Constructs a KeyValuePairsAtom LogEvent from value maps.
+     */
+    explicit LogEvent(int32_t tagId, int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
+                      int32_t uid,
+                      const std::map<int32_t, int64_t>& int_map,
+                      const std::map<int32_t, std::string>& string_map,
+                      const std::map<int32_t, float>& float_map);
+
     ~LogEvent();
 
     /**
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 2fcde29..acfa151 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -158,6 +158,96 @@
     EXPECT_EQ((float)1.1, item7.mValue.float_value);
 }
 
+TEST(LogEventTest, TestKeyValuePairsAtomParsing) {
+    std::map<int32_t, int64_t> int_map;
+    std::map<int32_t, std::string> string_map;
+    std::map<int32_t, float> float_map;
+
+    int_map[11] = 123L;
+    int_map[22] = 345L;
+
+    string_map[1] = "test2";
+    string_map[2] = "test1";
+
+    float_map[111] = 2.2f;
+    float_map[222] = 1.1f;
+
+    LogEvent event1(83, 2000, 2001, 10001, int_map, string_map, float_map);
+    event1.init();
+
+    EXPECT_EQ(83, event1.GetTagId());
+    EXPECT_EQ((int64_t)2000, event1.GetLogdTimestampNs());
+    EXPECT_EQ((int64_t)2001, event1.GetElapsedTimestampNs());
+
+    const auto& items = event1.getValues();
+    EXPECT_EQ((size_t)13, items.size());
+
+    const FieldValue& item0 = event1.getValues()[0];
+    EXPECT_EQ(0x00010000, item0.mField.getField());
+    EXPECT_EQ(Type::INT, item0.mValue.getType());
+    EXPECT_EQ(10001, item0.mValue.int_value);
+
+    const FieldValue& item1 = event1.getValues()[1];
+    EXPECT_EQ(0x2020101, item1.mField.getField());
+    EXPECT_EQ(Type::INT, item1.mValue.getType());
+    EXPECT_EQ(11, item1.mValue.int_value);
+
+    const FieldValue& item2 = event1.getValues()[2];
+    EXPECT_EQ(0x2020182, item2.mField.getField());
+    EXPECT_EQ(Type::LONG, item2.mValue.getType());
+    EXPECT_EQ(123L, item2.mValue.long_value);
+
+    const FieldValue& item3 = event1.getValues()[3];
+    EXPECT_EQ(0x2020201, item3.mField.getField());
+    EXPECT_EQ(Type::INT, item3.mValue.getType());
+    EXPECT_EQ(22, item3.mValue.int_value);
+
+    const FieldValue& item4 = event1.getValues()[4];
+    EXPECT_EQ(0x2020282, item4.mField.getField());
+    EXPECT_EQ(Type::LONG, item4.mValue.getType());
+    EXPECT_EQ(345L, item4.mValue.long_value);
+
+    const FieldValue& item5 = event1.getValues()[5];
+    EXPECT_EQ(0x2020301, item5.mField.getField());
+    EXPECT_EQ(Type::INT, item5.mValue.getType());
+    EXPECT_EQ(1, item5.mValue.int_value);
+
+    const FieldValue& item6 = event1.getValues()[6];
+    EXPECT_EQ(0x2020383, item6.mField.getField());
+    EXPECT_EQ(Type::STRING, item6.mValue.getType());
+    EXPECT_EQ("test2", item6.mValue.str_value);
+
+    const FieldValue& item7 = event1.getValues()[7];
+    EXPECT_EQ(0x2020401, item7.mField.getField());
+    EXPECT_EQ(Type::INT, item7.mValue.getType());
+    EXPECT_EQ(2, item7.mValue.int_value);
+
+    const FieldValue& item8 = event1.getValues()[8];
+    EXPECT_EQ(0x2020483, item8.mField.getField());
+    EXPECT_EQ(Type::STRING, item8.mValue.getType());
+    EXPECT_EQ("test1", item8.mValue.str_value);
+
+    const FieldValue& item9 = event1.getValues()[9];
+    EXPECT_EQ(0x2020501, item9.mField.getField());
+    EXPECT_EQ(Type::INT, item9.mValue.getType());
+    EXPECT_EQ(111, item9.mValue.int_value);
+
+    const FieldValue& item10 = event1.getValues()[10];
+    EXPECT_EQ(0x2020584, item10.mField.getField());
+    EXPECT_EQ(Type::FLOAT, item10.mValue.getType());
+    EXPECT_EQ(2.2f, item10.mValue.float_value);
+
+    const FieldValue& item11 = event1.getValues()[11];
+    EXPECT_EQ(0x2028601, item11.mField.getField());
+    EXPECT_EQ(Type::INT, item11.mValue.getType());
+    EXPECT_EQ(222, item11.mValue.int_value);
+
+    const FieldValue& item12 = event1.getValues()[12];
+    EXPECT_EQ(0x2028684, item12.mField.getField());
+    EXPECT_EQ(Type::FLOAT, item12.mValue.getType());
+    EXPECT_EQ(1.1f, item12.mValue.float_value);
+}
+
 
 }  // namespace statsd
 }  // namespace os
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index ebdcdfd..40ee490 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -116,6 +116,9 @@
             if (field->message_type()->full_name() ==
                 "android.os.statsd.AttributionNode") {
               return JAVA_TYPE_ATTRIBUTION_CHAIN;
+            } else if (field->message_type()->full_name() ==
+                       "android.os.statsd.KeyValuePair") {
+              return JAVA_TYPE_KEY_VALUE_PAIR;
             } else {
                 return JAVA_TYPE_OBJECT;
             }
@@ -181,6 +184,16 @@
     expectedNumber++;
   }
 
+  // Skips the key value pair atom.
+  for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
+       it != fields.end(); it++) {
+    const FieldDescriptor *field = it->second;
+    java_type_t javaType = java_type(field);
+    if (javaType == JAVA_TYPE_KEY_VALUE_PAIR) {
+      return 0;
+    }
+  }
+
   // Check that only allowed types are present. Remove any invalid ones.
   for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
        it != fields.end(); it++) {
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 5d2c302..ccdd145 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -48,6 +48,7 @@
   JAVA_TYPE_DOUBLE = 6,
   JAVA_TYPE_STRING = 7,
   JAVA_TYPE_ENUM = 8,
+  JAVA_TYPE_KEY_VALUE_PAIR = 9,
 
   JAVA_TYPE_OBJECT = -1,
   JAVA_TYPE_BYTE_ARRAY = -2,