diff options
author | Xin Li <delphij@google.com> | 2022-08-15 22:05:04 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2022-08-15 22:05:04 -0700 |
commit | 3a0243f6fe0b68ab278b651b455d6c35eff075fb (patch) | |
tree | f9c85ab726b614ed574eb4f021bd68b8f28fbb4f | |
parent | cae0fc21641f82c85bda220b19d60a24bdf9035e (diff) | |
parent | 687a9d54628a01ecd92043127fe19a28fd4b504b (diff) | |
download | StatsD-3a0243f6fe0b68ab278b651b455d6c35eff075fb.tar.gz |
DO NOT MERGE - Merge Android 13
Bug: 242648940
Merged-In: Id1e6c4772f30df1072350be14f96e371ffbb497f
Change-Id: I9bd9833489d4f2d7e0a184bc8d1b1116106664c3
40 files changed, 3531 insertions, 302 deletions
diff --git a/framework/test/unittests/Android.bp b/framework/test/unittests/Android.bp index e3fdfb9e..243328ca 100644 --- a/framework/test/unittests/Android.bp +++ b/framework/test/unittests/Android.bp @@ -24,6 +24,7 @@ android_test { static_libs: [ "androidx.test.rules", "truth-prebuilt", + "modules-utils-build", ], libs: [ "android.test.runner.stubs", diff --git a/framework/test/unittests/src/android/util/StatsEventTest.java b/framework/test/unittests/src/android/util/StatsEventTest.java index ded21c38..6d7e92d9 100644 --- a/framework/test/unittests/src/android/util/StatsEventTest.java +++ b/framework/test/unittests/src/android/util/StatsEventTest.java @@ -18,22 +18,18 @@ package android.util; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; - import static java.nio.charset.StandardCharsets.UTF_8; import android.os.SystemClock; - import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; - +import com.android.modules.utils.build.SdkLevel; import com.google.common.collect.Range; - -import org.junit.Test; -import org.junit.runner.RunWith; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Random; +import org.junit.Test; +import org.junit.runner.RunWith; /** * Internal tests for {@link StatsEvent}. @@ -421,6 +417,11 @@ public class StatsEventTest { @Test public void testBoolArrayIntArrayLongArray() { + // Skip test if build version isn't T or greater. + if (!SdkLevel.isAtLeastT()) { + return; + } + final int expectedAtomId = 109; final boolean[] field1 = new boolean[] {true, false, false}; final int[] field1Converted = new int[] {1, 0, 0}; @@ -525,6 +526,11 @@ public class StatsEventTest { @Test public void testFloatArrayStringArray() { + // Skip test if build version isn't T or greater. + if (!SdkLevel.isAtLeastT()) { + return; + } + final int expectedAtomId = 109; final float[] field1 = new float[] {0.21f, 0.13f}; final String[] field2 = new String[] {"str1", "str2", "str3"}; @@ -690,6 +696,11 @@ public class StatsEventTest { @Test public void testArrayFieldAnnotations() { + // Skip test if build version isn't T or greater. + if (!SdkLevel.isAtLeastT()) { + return; + } + final int expectedAtomId = 109; final int[] field1 = new int[] {4, 11}; final byte boolAnnotationId = 45; diff --git a/lib/libstatssocket/include/stats_event.h b/lib/libstatssocket/include/stats_event.h index 3d3baf9c..23e1419c 100644 --- a/lib/libstatssocket/include/stats_event.h +++ b/lib/libstatssocket/include/stats_event.h @@ -141,6 +141,49 @@ void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids, const char* const* tags, uint8_t numNodes); /** + * Write a int32 array field to this StatsEvent. + * + * Max size of array is 127. If exceeded, array is not written and ERROR_LIST_TOO_LONG is appended + * to StatsEvent. + **/ +void AStatsEvent_writeInt32Array(AStatsEvent* event, const int32_t* elements, size_t numElements); + +/** + * Write a int64 array field to this StatsEvent. + * + * Max size of array is 127. If exceeded, array is not written and ERROR_LIST_TOO_LONG is appended + * to StatsEvent. + **/ +void AStatsEvent_writeInt64Array(AStatsEvent* event, const int64_t* elements, size_t numElements); + +/** + * Write a float array field to this StatsEvent. + * + * Max size of array is 127. If exceeded, array is not written and ERROR_LIST_TOO_LONG is appended + * to StatsEvent. + **/ +void AStatsEvent_writeFloatArray(AStatsEvent* event, const float* elements, size_t numElements); + +/** + * Write a bool array field to this StatsEvent. + * + * Max size of array is 127. If exceeded, array is not written and ERROR_LIST_TOO_LONG is appended + * to StatsEvent. + **/ +void AStatsEvent_writeBoolArray(AStatsEvent* event, const bool* elements, size_t numElements); + +/** + * Write a string array field to this StatsEvent. + * + * String array encoding is UTF8. + * + * Strings must be null terminated. Max size of array is 127. If exceeded, array is not written and + * ERROR_LIST_TOO_LONG is appended to StatsEvent. + **/ +void AStatsEvent_writeStringArray(AStatsEvent* event, const char* const* elements, + size_t numElements); + +/** * Write a bool annotation for the previous field written. **/ void AStatsEvent_addBoolAnnotation(AStatsEvent* event, uint8_t annotationId, bool value); diff --git a/lib/libstatssocket/libstatssocket.map.txt b/lib/libstatssocket/libstatssocket.map.txt index 5c139041..aa6eb305 100644 --- a/lib/libstatssocket/libstatssocket.map.txt +++ b/lib/libstatssocket/libstatssocket.map.txt @@ -12,6 +12,11 @@ LIBSTATSSOCKET { AStatsEvent_writeByteArray; # apex # introduced=30 AStatsEvent_writeString; # apex # introduced=30 AStatsEvent_writeAttributionChain; # apex # introduced=30 + AStatsEvent_writeInt32Array; # apex # introduced=Tiramisu + AStatsEvent_writeInt64Array; # apex # introduced=Tiramisu + AStatsEvent_writeFloatArray; # apex # introduced=Tiramisu + AStatsEvent_writeBoolArray; # apex # introduced=Tiramisu + AStatsEvent_writeStringArray; # apex # introduced=Tiramisu AStatsEvent_addBoolAnnotation; # apex # introduced=30 AStatsEvent_addInt32Annotation; # apex # introduced=30 AStatsSocket_close; # apex # introduced=30 diff --git a/lib/libstatssocket/stats_event.c b/lib/libstatssocket/stats_event.c index dcd34aaa..9bb4c52c 100644 --- a/lib/libstatssocket/stats_event.c +++ b/lib/libstatssocket/stats_event.c @@ -50,6 +50,7 @@ #define ERROR_INVALID_VALUE_TYPE 0x400 #define ERROR_STRING_NOT_NULL_TERMINATED 0x800 #define ERROR_ATOM_ID_INVALID_POSITION 0x2000 +#define ERROR_LIST_TOO_LONG 0x4000 /* TYPE IDS */ #define INT32_TYPE 0x00 @@ -264,6 +265,69 @@ void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids, } } +static bool writeArrayMetadata(AStatsEvent* event, size_t numElements, uint8_t elementTypeId) { + if (numElements > MAX_BYTE_VALUE) { + event->errors |= ERROR_LIST_TOO_LONG; + return false; + } + + start_field(event, LIST_TYPE); + append_byte(event, numElements); + append_byte(event, elementTypeId); + return true; +} + +void AStatsEvent_writeInt32Array(AStatsEvent* event, const int32_t* elements, size_t numElements) { + if (!writeArrayMetadata(event, numElements, INT32_TYPE)) { + return; + } + + for (size_t i = 0; i < numElements; i++) { + append_int32(event, elements[i]); + } +} + +void AStatsEvent_writeInt64Array(AStatsEvent* event, const int64_t* elements, size_t numElements) { + if (!writeArrayMetadata(event, numElements, INT64_TYPE)) { + return; + } + + for (size_t i = 0; i < numElements; i++) { + append_int64(event, elements[i]); + } +} + +void AStatsEvent_writeFloatArray(AStatsEvent* event, const float* elements, size_t numElements) { + if (!writeArrayMetadata(event, numElements, FLOAT_TYPE)) { + return; + } + + for (size_t i = 0; i < numElements; i++) { + append_float(event, elements[i]); + } +} + +void AStatsEvent_writeBoolArray(AStatsEvent* event, const bool* elements, size_t numElements) { + if (!writeArrayMetadata(event, numElements, BOOL_TYPE)) { + return; + } + + for (size_t i = 0; i < numElements; i++) { + append_bool(event, elements[i]); + } +} + +void AStatsEvent_writeStringArray(AStatsEvent* event, const char* const* elements, + size_t numElements) { + if (!writeArrayMetadata(event, numElements, STRING_TYPE)) { + return; + } + + for (size_t i = 0; i < numElements; i++) { + append_string(event, elements[i] == NULL ? "" : elements[i]); + } +} + // Side-effect: modifies event->errors if field has too many annotations static void increment_annotation_count(AStatsEvent* event) { uint8_t fieldType = event->buf[event->lastFieldPos] & 0x0F; diff --git a/lib/libstatssocket/tests/stats_event_test.cpp b/lib/libstatssocket/tests/stats_event_test.cpp index 2f9ccdc5..93a99f1b 100644 --- a/lib/libstatssocket/tests/stats_event_test.cpp +++ b/lib/libstatssocket/tests/stats_event_test.cpp @@ -33,6 +33,7 @@ #define ERROR_INVALID_VALUE_TYPE 0x400 #define ERROR_STRING_NOT_NULL_TERMINATED 0x800 #define ERROR_ATOM_ID_INVALID_POSITION 0x2000 +#define ERROR_LIST_TOO_LONG 0x4000 /* TYPE IDS */ #define INT32_TYPE 0x00 @@ -87,6 +88,23 @@ void checkByteArray(uint8_t** buffer, const vector<uint8_t>& expectedByteArray) *buffer += size; // move buffer past byte array we just read } +void checkArrayMetadata(uint8_t** buffer, uint8_t numElements, uint8_t elementTypeId, + uint8_t numAnnotations = 0) { + checkTypeHeader(buffer, LIST_TYPE, numAnnotations); + EXPECT_EQ(readNext<uint8_t>(buffer), numElements); + checkTypeHeader(buffer, elementTypeId); +} + +template <class T> +void checkScalarArray(uint8_t** buffer, uint8_t numElements, uint8_t elementTypeId, + const T* expectedArrayValues, uint8_t numAnnotations = 0) { + checkArrayMetadata(buffer, numElements, elementTypeId, numAnnotations); + + for (int i = 0; i < numElements; i++) { + checkScalar(buffer, expectedArrayValues[i]); + } +} + template <class T> void checkAnnotation(uint8_t** buffer, uint8_t annotationId, uint8_t typeId, T annotationValue) { EXPECT_EQ(readNext<uint8_t>(buffer), annotationId); @@ -259,6 +277,61 @@ TEST(StatsEventTest, TestNullByteArrays) { AStatsEvent_release(event); } +TEST(StatsEventTest, TestAllArrays) { + uint32_t atomId = 100; + + uint8_t numElements = 3; + int32_t int32Array[3] = {3, 6, 9}; + int64_t int64Array[3] = {1000L, 1001L, 1002L}; + float floatArray[3] = {0.1f, 0.3f, 0.09f}; + bool boolArray[3] = {0, 1, 1}; + + vector<string> stringArray = {"str1", "str2", "str3"}; + const char* cStringArray[3]; + for (int i = 0; i < numElements; i++) { + cStringArray[i] = stringArray[i].c_str(); + } + + int64_t startTime = android::elapsedRealtimeNano(); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomId); + AStatsEvent_writeInt32Array(event, int32Array, numElements); + AStatsEvent_writeInt64Array(event, int64Array, numElements); + AStatsEvent_writeFloatArray(event, floatArray, numElements); + AStatsEvent_writeBoolArray(event, boolArray, numElements); + AStatsEvent_writeStringArray(event, cStringArray, numElements); + AStatsEvent_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); + uint8_t* bufferEnd = buffer + bufferSize; + + checkMetadata(&buffer, /*numTopLevelElements=*/5, startTime, endTime, atomId); + + // check int32Array element + checkScalarArray(&buffer, numElements, INT32_TYPE, int32Array); + + // check int64Array element + checkScalarArray(&buffer, numElements, INT64_TYPE, int64Array); + + // check floatArray element + checkScalarArray(&buffer, numElements, FLOAT_TYPE, floatArray); + + // check boolArray element + checkScalarArray(&buffer, numElements, BOOL_TYPE, boolArray); + + // check stringArray element + checkArrayMetadata(&buffer, numElements, STRING_TYPE); + for (int i = 0; i < numElements; i++) { + checkString(&buffer, stringArray[i]); + } + + EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer + EXPECT_EQ(AStatsEvent_getErrors(event), 0); + AStatsEvent_release(event); +} + TEST(StatsEventTest, TestAttributionChains) { uint32_t atomId = 100; @@ -354,6 +427,43 @@ TEST(StatsEventTest, TestFieldAnnotations) { AStatsEvent_release(event); } +TEST(StatsEventTest, TestArrayFieldAnnotations) { + uint32_t atomId = 100; + + // array annotation info + uint8_t boolAnnotationId = 1; + uint8_t int32AnnotationId = 2; + bool boolAnnotationValue = true; + int32_t int32AnnotationValue = 4; + + uint8_t numElements = 3; + int32_t int32Array[3] = {3, 6, 9}; + + int64_t startTime = android::elapsedRealtimeNano(); + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomId); + AStatsEvent_writeInt32Array(event, int32Array, numElements); + AStatsEvent_addBoolAnnotation(event, boolAnnotationId, boolAnnotationValue); + AStatsEvent_addInt32Annotation(event, int32AnnotationId, int32AnnotationValue); + AStatsEvent_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize); + uint8_t* bufferEnd = buffer + bufferSize; + + checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); + + // check first element + checkScalarArray(&buffer, numElements, INT32_TYPE, int32Array, /*numAnnotations=*/2); + checkAnnotation(&buffer, boolAnnotationId, BOOL_TYPE, boolAnnotationValue); + checkAnnotation(&buffer, int32AnnotationId, INT32_TYPE, int32AnnotationValue); + + EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer + EXPECT_EQ(AStatsEvent_getErrors(event), 0); + AStatsEvent_release(event); +} + TEST(StatsEventTest, TestAtomLevelAnnotations) { uint32_t atomId = 100; // atom-level annotation information @@ -514,3 +624,43 @@ TEST(StatsEventTest, TestOverwriteTimestamp) { EXPECT_EQ(AStatsEvent_getErrors(event), 0); AStatsEvent_release(event); } + +TEST(StatsEventTest, TestAttributionChainTooLongError) { + uint32_t atomId = 100; + uint8_t numNodes = 128; + uint32_t uids[numNodes]; + vector<string> tags(numNodes); // storage that cTag elements point to + const char* cTags[numNodes]; + for (int i = 0; i < (int)numNodes; i++) { + uids[i] = i; + if (0 == i) { + tags.push_back(""); + cTags[i] = nullptr; + } else { + tags.push_back("test" + std::to_string(i)); + cTags[i] = tags[i].c_str(); + } + } + + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomId); + AStatsEvent_writeAttributionChain(event, uids, cTags, numNodes); + AStatsEvent_build(event); + + uint32_t errors = AStatsEvent_getErrors(event); + EXPECT_EQ(errors & ERROR_ATTRIBUTION_CHAIN_TOO_LONG, ERROR_ATTRIBUTION_CHAIN_TOO_LONG); +} + +TEST(StatsEventTest, TestListTooLongError) { + uint32_t atomId = 100; + uint8_t numElements = 128; + int32_t int32Array[128] = {1}; + + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, atomId); + AStatsEvent_writeInt32Array(event, int32Array, numElements); + AStatsEvent_build(event); + + uint32_t errors = AStatsEvent_getErrors(event); + EXPECT_EQ(errors & ERROR_LIST_TOO_LONG, ERROR_LIST_TOO_LONG); +} diff --git a/statsd/src/FieldValue.cpp b/statsd/src/FieldValue.cpp index 9e4e4504..406c19f0 100644 --- a/statsd/src/FieldValue.cpp +++ b/statsd/src/FieldValue.cpp @@ -137,6 +137,10 @@ bool isUidField(const FieldValue& fieldValue) { return fieldValue.mAnnotations.isUidField(); } +bool isPrimitiveRepeatedField(const Field& field) { + return field.getDepth() == 1; +} + Value::Value(const Value& from) { type = from.getType(); switch (type) { @@ -496,6 +500,19 @@ bool HasPositionALL(const FieldMatcher& matcher) { return false; } +bool HasPrimitiveRepeatedField(const FieldMatcher& matcher) { + for (const auto& child : matcher.child()) { + if (child.has_position() && child.child_size() == 0) { + return true; + } + } + return false; +} + +bool ShouldUseNestedDimensions(const FieldMatcher& matcher) { + return HasPositionALL(matcher) || HasPrimitiveRepeatedField(matcher); +} + size_t getSize(const std::vector<FieldValue>& fieldValues) { size_t totalSize = 0; for (const FieldValue& fieldValue : fieldValues) { diff --git a/statsd/src/FieldValue.h b/statsd/src/FieldValue.h index 81fe28c5..979264c6 100644 --- a/statsd/src/FieldValue.h +++ b/statsd/src/FieldValue.h @@ -194,13 +194,13 @@ public: * It contains all information needed to match one or more leaf node. * All information is encoded in a Field(2 ints) and a bit mask(1 int). * - * For example, to match the first/any/last uid field in attribution chain in Atom 10, + * For example, to match the first/all/last uid field in attribution chain in Atom 10, * we have the following FieldMatcher in statsd_config * FieldMatcher { * field:10 * FieldMatcher { * field:1 - * position: any/last/first + * position: all/last/first * FieldMatcher { * field:1 * } @@ -210,7 +210,6 @@ public: * We translate the FieldMatcher into a Field, and mask * First: [Matcher Field] 0x02010101 [Mask]0xff7f7f7f * Last: [Matcher Field] 0x02018001 [Mask]0xff7f807f - * Any: [Matcher Field] 0x02010001 [Mask]0xff7f007f * All: [Matcher Field] 0x02010001 [Mask]0xff7f7f7f * * [To match a log Field with a Matcher] we apply the bit mask to the log Field and check if @@ -242,15 +241,7 @@ struct Matcher { } bool hasAllPositionMatcher() const { - return mMatcher.getDepth() == 2 && getRawMaskAtDepth(1) == 0x7f; - } - - bool hasAnyPositionMatcher(int* prefix) const { - if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(1) == 0) { - (*prefix) = mMatcher.getPrefix(1); - return true; - } - return false; + return mMatcher.getDepth() >= 1 && getRawMaskAtDepth(1) == 0x7f; } inline bool operator!=(const Matcher& that) const { @@ -450,6 +441,8 @@ struct FieldValue { bool HasPositionANY(const FieldMatcher& matcher); bool HasPositionALL(const FieldMatcher& matcher); +bool HasPrimitiveRepeatedField(const FieldMatcher& matcher); +bool ShouldUseNestedDimensions(const FieldMatcher& matcher); bool isAttributionUidField(const FieldValue& value); @@ -460,6 +453,7 @@ void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* ou bool isAttributionUidField(const Field& field, const Value& value); bool isUidField(const FieldValue& fieldValue); +bool isPrimitiveRepeatedField(const Field& field); bool equalDimensions(const std::vector<Matcher>& dimension_a, const std::vector<Matcher>& dimension_b); diff --git a/statsd/src/StatsLogProcessor.cpp b/statsd/src/StatsLogProcessor.cpp index 1f0f5644..b90a5b77 100644 --- a/statsd/src/StatsLogProcessor.cpp +++ b/statsd/src/StatsLogProcessor.cpp @@ -139,9 +139,9 @@ void StatsLogProcessor::onPeriodicAlarmFired( } void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const { - if (std::pair<int, int> indexRange; event->hasAttributionChain(&indexRange)) { + if (std::pair<size_t, size_t> indexRange; event->hasAttributionChain(&indexRange)) { vector<FieldValue>* const fieldValues = event->getMutableValues(); - for (int i = indexRange.first; i <= indexRange.second; i++) { + for (size_t i = indexRange.first; i <= indexRange.second; i++) { FieldValue& fieldValue = fieldValues->at(i); if (isAttributionUidField(fieldValue)) { const int hostUid = mUidMap->getHostUidOrSelf(fieldValue.mValue.int_value); diff --git a/statsd/src/StatsLogProcessor.h b/statsd/src/StatsLogProcessor.h index 761748c6..231b4118 100644 --- a/statsd/src/StatsLogProcessor.h +++ b/statsd/src/StatsLogProcessor.h @@ -322,6 +322,7 @@ private: FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain); FRIEND_TEST(GaugeMetricE2ePushedTest, TestMultipleFieldsForPushedEvent); + FRIEND_TEST(GaugeMetricE2ePushedTest, TestRepeatedFieldsForPushedEvent); FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvents); FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvent_LateAlarm); FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsWithActivation); @@ -359,6 +360,7 @@ private: FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap); FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates); FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields); + FRIEND_TEST(CountMetricE2eTest, TestRepeatedFieldsAndEmptyArrays); FRIEND_TEST(DurationMetricE2eTest, TestOneBucket); FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets); @@ -372,6 +374,7 @@ private: FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat); FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset); FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold); + FRIEND_TEST(DurationMetricE2eTest, TestConditionOnRepeatedEnumField); FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges); FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents); @@ -380,6 +383,9 @@ private: FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions); FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions); + FRIEND_TEST(ValueMetricE2eTest, TestInitWithValueFieldPositionALL); + + FRIEND_TEST(KllMetricE2eTest, TestInitWithKllFieldPositionALL); }; } // namespace statsd diff --git a/statsd/src/external/puller_util.cpp b/statsd/src/external/puller_util.cpp index dc7cdce9..b0c68f41 100644 --- a/statsd/src/external/puller_util.cpp +++ b/statsd/src/external/puller_util.cpp @@ -51,7 +51,7 @@ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const int tagId, const vector<int>& additiveFieldsVec) { // Check the first LogEvent for attribution chain or a uid field as either all atoms with this // tagId have them or none of them do. - std::pair<int, int> attrIndexRange; + std::pair<size_t, size_t> attrIndexRange; const bool hasAttributionChain = data[0]->hasAttributionChain(&attrIndexRange); const uint8_t numUidFields = data[0]->getNumUidFields(); @@ -68,7 +68,7 @@ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const } if (hasAttributionChain) { vector<FieldValue>* const fieldValues = event->getMutableValues(); - for (int i = attrIndexRange.first; i <= attrIndexRange.second; i++) { + for (size_t i = attrIndexRange.first; i <= attrIndexRange.second; i++) { FieldValue& fieldValue = fieldValues->at(i); if (isAttributionUidField(fieldValue)) { const int hostUid = uidMap->getHostUidOrSelf(fieldValue.mValue.int_value); @@ -101,12 +101,15 @@ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const bool needMerge = true; // 3. do the merge. - // The loop invariant is this: for every event, check if it differs on - // non-additive fields, or have different attribution chain length. - // If so, no need to merge, add itself to the result. - // Otherwise, merge the value onto the one immediately next to it. + // The loop invariant is this: for every event, + // - check if it has a different length (means different attribution chains or repeated fields) + // - check if fields are different + // - check if non-additive field values are different (non-additive is default for repeated + // fields) + // If any are true, no need to merge, add itself to the result. Otherwise, merge the + // value onto the one immediately next to it. for (int i = 0; i < (int)data.size() - 1; i++) { - // Size different, must be different chains. + // Size different, must be different chains or repeated fields. if (data[i]->size() != data[i + 1]->size()) { mergedData.push_back(data[i]); continue; @@ -115,10 +118,16 @@ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const vector<FieldValue>* rhsValues = data[i + 1]->getMutableValues(); needMerge = true; for (int p = 0; p < (int)lhsValues->size(); p++) { - if ((*lhsValues)[p] != (*rhsValues)[p]) { + if ((*lhsValues)[p].mField != (*rhsValues)[p].mField) { + needMerge = false; + break; + } + if ((*lhsValues)[p].mValue != (*rhsValues)[p].mValue) { int pos = (*lhsValues)[p].mField.getPosAtDepth(0); // Differ on non-additive field, abort. - if (additiveFields.find(pos) == additiveFields.end()) { + // Repeated additive fields are treated as non-additive fields. + if (isPrimitiveRepeatedField((*lhsValues)[p].mField) || + (additiveFields.find(pos) == additiveFields.end())) { needMerge = false; break; } @@ -131,7 +140,9 @@ void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const // This should be infrequent operation. for (int p = 0; p < (int)lhsValues->size(); p++) { int pos = (*lhsValues)[p].mField.getPosAtDepth(0); - if (additiveFields.find(pos) != additiveFields.end()) { + // Don't merge repeated fields. + if (!isPrimitiveRepeatedField((*lhsValues)[p].mField) && + (additiveFields.find(pos) != additiveFields.end())) { (*rhsValues)[p].mValue += (*lhsValues)[p].mValue; } } diff --git a/statsd/src/logd/LogEvent.cpp b/statsd/src/logd/LogEvent.cpp index e89d067b..398ce3e6 100644 --- a/statsd/src/logd/LogEvent.cpp +++ b/statsd/src/logd/LogEvent.cpp @@ -39,34 +39,6 @@ using android::util::ProtoOutputStream; using std::string; using std::vector; -// stats_event.h socket types. Keep in sync. -/* ERRORS */ -#define ERROR_NO_TIMESTAMP 0x1 -#define ERROR_NO_ATOM_ID 0x2 -#define ERROR_OVERFLOW 0x4 -#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8 -#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10 -#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20 -#define ERROR_INVALID_ANNOTATION_ID 0x40 -#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80 -#define ERROR_TOO_MANY_ANNOTATIONS 0x100 -#define ERROR_TOO_MANY_FIELDS 0x200 -#define ERROR_INVALID_VALUE_TYPE 0x400 -#define ERROR_STRING_NOT_NULL_TERMINATED 0x800 - -/* TYPE IDS */ -#define INT32_TYPE 0x00 -#define INT64_TYPE 0x01 -#define STRING_TYPE 0x02 -#define LIST_TYPE 0x03 -#define FLOAT_TYPE 0x04 -#define BOOL_TYPE 0x05 -#define BYTE_ARRAY_TYPE 0x06 -#define OBJECT_TYPE 0x07 -#define KEY_VALUE_PAIRS_TYPE 0x08 -#define ATTRIBUTION_CHAIN_TYPE 0x09 -#define ERROR_TYPE 0x0F - LogEvent::LogEvent(int32_t uid, int32_t pid) : mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) { } @@ -203,8 +175,11 @@ void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8 void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { - const unsigned int firstUidInChainIndex = mValues.size(); - const int32_t numNodes = readNextValue<uint8_t>(); + std::optional<size_t> firstUidInChainIndex = mValues.size(); + const uint8_t numNodes = readNextValue<uint8_t>(); + + if (numNodes > INT8_MAX) mValid = false; + for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) { last[1] = (pos[1] == numNodes); @@ -218,39 +193,96 @@ void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0); } - if (mValues.size() - 1 > INT8_MAX) { - mValid = false; - } else if (mValues.size() - 1 > firstUidInChainIndex) { + if (mValues.size() > (firstUidInChainIndex.value() + 1)) { // At least one node was successfully parsed. - mAttributionChainStartIndex = static_cast<int8_t>(firstUidInChainIndex); - mAttributionChainEndIndex = static_cast<int8_t>(mValues.size() - 1); + mAttributionChainStartIndex = firstUidInChainIndex; + mAttributionChainEndIndex = mValues.size() - 1; + } else { + firstUidInChainIndex = std::nullopt; + mValid = false; } if (mValid) { - parseAnnotations(numAnnotations, firstUidInChainIndex); + parseAnnotations(numAnnotations, /*numElements*/ std::nullopt, firstUidInChainIndex); } pos[1] = pos[2] = 1; last[1] = last[2] = false; } +void LogEvent::parseArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { + const uint8_t numElements = readNextValue<uint8_t>(); + const uint8_t typeInfo = readNextValue<uint8_t>(); + const uint8_t typeId = getTypeId(typeInfo); + + if (numElements > INT8_MAX) mValid = false; + + for (pos[1] = 1; pos[1] <= numElements; pos[1]++) { + last[1] = (pos[1] == numElements); + + // The top-level array is at depth 0, and all of its elements are at depth 1. + // Once nested fields are supported, array elements will be at top-level depth + 1. + + switch (typeId) { + case INT32_TYPE: + parseInt32(pos, /*depth=*/1, last, /*numAnnotations=*/0); + break; + case INT64_TYPE: + parseInt64(pos, /*depth=*/1, last, /*numAnnotations=*/0); + break; + case FLOAT_TYPE: + parseFloat(pos, /*depth=*/1, last, /*numAnnotations=*/0); + break; + case BOOL_TYPE: + parseBool(pos, /*depth=*/1, last, /*numAnnotations=*/0); + break; + case STRING_TYPE: + parseString(pos, /*depth=*/1, last, /*numAnnotations=*/0); + break; + default: + mValid = false; + break; + } + } + + parseAnnotations(numAnnotations, numElements); + + pos[1] = 1; + last[1] = false; +} + // Assumes that mValues is not empty bool LogEvent::checkPreviousValueType(Type expected) { return mValues[mValues.size() - 1].mValue.getType() == expected; } -void LogEvent::parseIsUidAnnotation(uint8_t annotationType) { - if (mValues.empty() || mValues.size() - 1 > INT8_MAX || !checkPreviousValueType(INT) - || annotationType != BOOL_TYPE) { +void LogEvent::parseIsUidAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements) { + // Need to set numElements if not an array. + if (!numElements) { + numElements = 1; + } + + // If array is empty, skip uid parsing. + if (numElements == 0 && annotationType == BOOL_TYPE) { + readNextValue<uint8_t>(); + return; + } + + // Allowed types: INT, repeated INT + if (numElements > mValues.size() || !checkPreviousValueType(INT) || + annotationType != BOOL_TYPE) { mValid = false; return; } bool isUid = readNextValue<uint8_t>(); if (isUid) { - mNumUidFields++; + mNumUidFields += numElements.value(); + } + + for (int i = 1; i <= numElements; i++) { + mValues[mValues.size() - i].mAnnotations.setUidField(isUid); } - mValues[mValues.size() - 1].mAnnotations.setUidField(isUid); } void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) { @@ -262,8 +294,11 @@ void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) { mTruncateTimestamp = readNextValue<uint8_t>(); } -void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType) { - if (mValues.empty() || annotationType != BOOL_TYPE) { +void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType, + std::optional<uint8_t> numElements, + std::optional<size_t> firstUidInChainIndex) { + // Allowed types: all types except for attribution chains and repeated fields. + if (mValues.empty() || annotationType != BOOL_TYPE || firstUidInChainIndex || numElements) { mValid = false; return; } @@ -273,41 +308,42 @@ void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType) { } void LogEvent::parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, - int firstUidInChainIndex) { - if (mValues.empty() || annotationType != BOOL_TYPE || -1 == firstUidInChainIndex) { + std::optional<size_t> firstUidInChainIndex) { + // Allowed types: attribution chains + if (mValues.empty() || annotationType != BOOL_TYPE || !firstUidInChainIndex) { mValid = false; return; } - if (static_cast<int>(mValues.size() - 1) < firstUidInChainIndex) { // AttributionChain is empty. + if (mValues.size() < firstUidInChainIndex.value() + 1) { // AttributionChain is empty. mValid = false; android_errorWriteLog(0x534e4554, "174485572"); return; } const bool primaryField = readNextValue<uint8_t>(); - mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(primaryField); + mValues[firstUidInChainIndex.value()].mAnnotations.setPrimaryField(primaryField); } -void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType) { - if (mValues.empty() || annotationType != BOOL_TYPE) { - mValid = false; - return; - } - - if (mValues.size() - 1 > INT8_MAX) { - android_errorWriteLog(0x534e4554, "174488848"); +void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType, + std::optional<uint8_t> numElements) { + // Allowed types: INT + if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) || + numElements) { mValid = false; return; } const bool exclusiveState = readNextValue<uint8_t>(); - mExclusiveStateFieldIndex = static_cast<int8_t>(mValues.size() - 1); - mValues[getExclusiveStateFieldIndex()].mAnnotations.setExclusiveState(exclusiveState); + mExclusiveStateFieldIndex = mValues.size() - 1; + mValues[getExclusiveStateFieldIndex().value()].mAnnotations.setExclusiveState(exclusiveState); } -void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) { - if (mValues.empty() || annotationType != INT32_TYPE) { +void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType, + std::optional<uint8_t> numElements) { + // Allowed types: INT + if (mValues.empty() || annotationType != INT32_TYPE || !checkPreviousValueType(INT) || + numElements) { mValid = false; return; } @@ -315,8 +351,11 @@ void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) { mResetState = readNextValue<int32_t>(); } -void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) { - if (mValues.empty() || annotationType != BOOL_TYPE) { +void LogEvent::parseStateNestedAnnotation(uint8_t annotationType, + std::optional<uint8_t> numElements) { + // Allowed types: INT + if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) || + numElements) { mValid = false; return; } @@ -327,32 +366,34 @@ void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) { // firstUidInChainIndex is a default parameter that is only needed when parsing // annotations for attribution chains. -void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) { +// numElements is a default param that is only needed when parsing annotations for repeated fields +void LogEvent::parseAnnotations(uint8_t numAnnotations, std::optional<uint8_t> numElements, + std::optional<size_t> firstUidInChainIndex) { for (uint8_t i = 0; i < numAnnotations; i++) { uint8_t annotationId = readNextValue<uint8_t>(); uint8_t annotationType = readNextValue<uint8_t>(); switch (annotationId) { case ANNOTATION_ID_IS_UID: - parseIsUidAnnotation(annotationType); + parseIsUidAnnotation(annotationType, numElements); break; case ANNOTATION_ID_TRUNCATE_TIMESTAMP: parseTruncateTimestampAnnotation(annotationType); break; case ANNOTATION_ID_PRIMARY_FIELD: - parsePrimaryFieldAnnotation(annotationType); + parsePrimaryFieldAnnotation(annotationType, numElements, firstUidInChainIndex); break; case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID: parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex); break; case ANNOTATION_ID_EXCLUSIVE_STATE: - parseExclusiveStateAnnotation(annotationType); + parseExclusiveStateAnnotation(annotationType, numElements); break; case ANNOTATION_ID_TRIGGER_STATE_RESET: - parseTriggerStateResetAnnotation(annotationType); + parseTriggerStateResetAnnotation(annotationType, numElements); break; case ANNOTATION_ID_STATE_NESTED: - parseStateNestedAnnotation(annotationType); + parseStateNestedAnnotation(annotationType, numElements); break; default: mValid = false; @@ -375,7 +416,7 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { if (getTypeId(typeInfo) != OBJECT_TYPE) mValid = false; uint8_t numElements = readNextValue<uint8_t>(); - if (numElements < 2 || numElements > 127) mValid = false; + if (numElements < 2 || numElements > INT8_MAX) mValid = false; typeInfo = readNextValue<uint8_t>(); if (getTypeId(typeInfo) != INT64_TYPE) mValid = false; @@ -419,6 +460,9 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { case ATTRIBUTION_CHAIN_TYPE: parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); break; + case LIST_TYPE: + parseArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); + break; case ERROR_TYPE: /* mErrorBitmask =*/ readNextValue<int32_t>(); mValid = false; @@ -583,14 +627,14 @@ void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput); } -bool LogEvent::hasAttributionChain(std::pair<int, int>* indexRange) const { - if (mAttributionChainStartIndex == -1 || mAttributionChainEndIndex == -1) { +bool LogEvent::hasAttributionChain(std::pair<size_t, size_t>* indexRange) const { + if (!mAttributionChainStartIndex || !mAttributionChainEndIndex) { return false; } if (nullptr != indexRange) { - indexRange->first = static_cast<int>(mAttributionChainStartIndex); - indexRange->second = static_cast<int>(mAttributionChainEndIndex); + indexRange->first = mAttributionChainStartIndex.value(); + indexRange->second = mAttributionChainEndIndex.value(); } return true; diff --git a/statsd/src/logd/LogEvent.h b/statsd/src/logd/LogEvent.h index 92541d7b..88dd9b70 100644 --- a/statsd/src/logd/LogEvent.h +++ b/statsd/src/logd/LogEvent.h @@ -16,18 +16,49 @@ #pragma once -#include "FieldValue.h" - #include <android/util/ProtoOutputStream.h> #include <private/android_logger.h> +#include <optional> #include <string> #include <vector> +#include "FieldValue.h" + namespace android { namespace os { namespace statsd { +// stats_event.h socket types. Keep in sync. +/* ERRORS */ +#define ERROR_NO_TIMESTAMP 0x1 +#define ERROR_NO_ATOM_ID 0x2 +#define ERROR_OVERFLOW 0x4 +#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8 +#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10 +#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20 +#define ERROR_INVALID_ANNOTATION_ID 0x40 +#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80 +#define ERROR_TOO_MANY_ANNOTATIONS 0x100 +#define ERROR_TOO_MANY_FIELDS 0x200 +#define ERROR_INVALID_VALUE_TYPE 0x400 +#define ERROR_STRING_NOT_NULL_TERMINATED 0x800 +#define ERROR_ATOM_ID_INVALID_POSITION 0x2000 +#define ERROR_LIST_TOO_LONG 0x4000 + +/* TYPE IDS */ +#define INT32_TYPE 0x00 +#define INT64_TYPE 0x01 +#define STRING_TYPE 0x02 +#define LIST_TYPE 0x03 +#define FLOAT_TYPE 0x04 +#define BOOL_TYPE 0x05 +#define BYTE_ARRAY_TYPE 0x06 +#define OBJECT_TYPE 0x07 +#define KEY_VALUE_PAIRS_TYPE 0x08 +#define ATTRIBUTION_CHAIN_TYPE 0x09 +#define ERROR_TYPE 0x0F + struct InstallTrainInfo { int64_t trainVersionCode; std::string trainName; @@ -156,20 +187,20 @@ public: // Returns whether this LogEvent has an AttributionChain. // If it does and indexRange is not a nullptr, populate indexRange with the start and end index // of the AttributionChain within mValues. - bool hasAttributionChain(std::pair<int, int>* indexRange = nullptr) const; + bool hasAttributionChain(std::pair<size_t, size_t>* indexRange = nullptr) const; // Returns the index of the exclusive state field within the FieldValues vector if // an exclusive state exists. If there is no exclusive state field, returns -1. // // If the index within the atom definition is desired, do the following: - // int vectorIndex = LogEvent.getExclusiveStateFieldIndex(); - // if (vectorIndex != -1) { - // FieldValue& v = LogEvent.getValues()[vectorIndex]; + // const std::optional<size_t>& vectorIndex = LogEvent.getExclusiveStateFieldIndex(); + // if (!vectorIndex) { + // FieldValue& v = LogEvent.getValues()[vectorIndex.value()]; // int atomIndex = v.mField.getPosAtDepth(0); // } // Note that atomIndex is 1-indexed. - inline int getExclusiveStateFieldIndex() const { - return static_cast<int>(mExclusiveStateFieldIndex); + inline std::optional<size_t> getExclusiveStateFieldIndex() const { + return mExclusiveStateFieldIndex; } // If a reset state is not sent in the StatsEvent, returns -1. Note that a @@ -212,15 +243,20 @@ private: void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); + void parseArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations); - void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1); - void parseIsUidAnnotation(uint8_t annotationType); + void parseAnnotations(uint8_t numAnnotations, std::optional<uint8_t> numElements = std::nullopt, + std::optional<size_t> firstUidInChainIndex = std::nullopt); + void parseIsUidAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements); void parseTruncateTimestampAnnotation(uint8_t annotationType); - void parsePrimaryFieldAnnotation(uint8_t annotationType); - void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, int firstUidInChainIndex); - void parseExclusiveStateAnnotation(uint8_t annotationType); - void parseTriggerStateResetAnnotation(uint8_t annotationType); - void parseStateNestedAnnotation(uint8_t annotationType); + void parsePrimaryFieldAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements, + std::optional<size_t> firstUidInChainIndex); + void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, + std::optional<size_t> firstUidInChainIndex); + void parseExclusiveStateAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements); + void parseTriggerStateResetAnnotation(uint8_t annotationType, + std::optional<uint8_t> numElements); + void parseStateNestedAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements); bool checkPreviousValueType(Type expected); /** @@ -266,10 +302,8 @@ private: template <class T> void addToValues(int32_t* pos, int32_t depth, T& value, bool* last) { Field f = Field(mTagId, pos, depth); - // do not decorate last position at depth 0 - for (int i = 1; i < depth; i++) { - if (last[i]) f.decorateLastPos(i); - } + // only decorate last position for depths with repeated fields (depth 1) + if (depth > 0 && last[1]) f.decorateLastPos(1); Value v = Value(value); mValues.push_back(FieldValue(f, v)); @@ -302,13 +336,11 @@ private: bool mTruncateTimestamp = false; int mResetState = -1; - uint8_t mNumUidFields = 0; + size_t mNumUidFields = 0; - // Indexes within the FieldValue vector can be stored in 7 bits because - // that's the assumption enforced by the encoding used in FieldValue. - int8_t mAttributionChainStartIndex = -1; - int8_t mAttributionChainEndIndex = -1; - int8_t mExclusiveStateFieldIndex = -1; + std::optional<size_t> mAttributionChainStartIndex; + std::optional<size_t> mAttributionChainEndIndex; + std::optional<size_t> mExclusiveStateFieldIndex; }; void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); diff --git a/statsd/src/matchers/matcher_util.cpp b/statsd/src/matchers/matcher_util.cpp index 20450ddd..2951debc 100644 --- a/statsd/src/matchers/matcher_util.cpp +++ b/statsd/src/matchers/matcher_util.cpp @@ -267,18 +267,13 @@ bool matchesSimple(const sp<UidMap>& uidMap, const FieldValueMatcher& matcher, case FieldValueMatcher::ValueMatcherCase::kNeqAnyString: { const auto& str_list = matcher.neq_any_string(); for (int i = start; i < end; i++) { - bool notEqAll = true; for (const auto& str : str_list.str_value()) { if (tryMatchString(uidMap, values[i], str)) { - notEqAll = false; - break; + return false; } } - if (notEqAll) { - return true; - } } - return false; + return true; } case FieldValueMatcher::ValueMatcherCase::kEqAnyString: { const auto& str_list = matcher.eq_any_string(); diff --git a/statsd/src/metrics/CountMetricProducer.cpp b/statsd/src/metrics/CountMetricProducer.cpp index 9e003a2e..67a72507 100644 --- a/statsd/src/metrics/CountMetricProducer.cpp +++ b/statsd/src/metrics/CountMetricProducer.cpp @@ -90,7 +90,7 @@ CountMetricProducer::CountMetricProducer( mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); } - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); + mShouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what()); if (metric.links().size() > 0) { for (const auto& link : metric.links()) { @@ -224,8 +224,8 @@ void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); - // Fills the dimension path if not slicing by ALL. - if (!mSliceByPositionALL) { + // Fills the dimension path if not slicing by a primitive repeated field or position ALL. + if (!mShouldUseNestedDimensions) { if (!mDimensionsInWhat.empty()) { uint64_t dimenPathToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); @@ -244,7 +244,7 @@ void CountMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. - if (mSliceByPositionALL) { + if (mShouldUseNestedDimensions) { uint64_t dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); diff --git a/statsd/src/metrics/DurationMetricProducer.cpp b/statsd/src/metrics/DurationMetricProducer.cpp index 090da99a..b32a1706 100644 --- a/statsd/src/metrics/DurationMetricProducer.cpp +++ b/statsd/src/metrics/DurationMetricProducer.cpp @@ -117,7 +117,7 @@ DurationMetricProducer::DurationMetricProducer( mValid = false; } - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); + mShouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what()); if (metric.links().size() > 0) { for (const auto& link : metric.links()) { @@ -508,7 +508,7 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); - if (!mSliceByPositionALL) { + if (!mShouldUseNestedDimensions) { if (!mDimensionsInWhat.empty()) { uint64_t dimenPathToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); @@ -529,7 +529,7 @@ void DurationMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. - if (mSliceByPositionALL) { + if (mShouldUseNestedDimensions) { uint64_t dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); diff --git a/statsd/src/metrics/GaugeMetricProducer.cpp b/statsd/src/metrics/GaugeMetricProducer.cpp index 7b6f67fe..faa284c7 100644 --- a/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/statsd/src/metrics/GaugeMetricProducer.cpp @@ -128,7 +128,7 @@ GaugeMetricProducer::GaugeMetricProducer( } mConditionSliced = true; } - mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()); + mShouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what()); flushIfNeededLocked(startTimeNs); // Kicks off the puller immediately. @@ -257,8 +257,8 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); - // Fills the dimension path if not slicing by ALL. - if (!mSliceByPositionALL) { + // Fills the dimension path if not slicing by a primitive repeated field or position ALL. + if (!mShouldUseNestedDimensions) { if (!mDimensionsInWhat.empty()) { uint64_t dimenPathToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); @@ -295,7 +295,7 @@ void GaugeMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. - if (mSliceByPositionALL) { + if (mShouldUseNestedDimensions) { uint64_t dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput); diff --git a/statsd/src/metrics/MetricProducer.cpp b/statsd/src/metrics/MetricProducer.cpp index bce5c0be..75c24762 100644 --- a/statsd/src/metrics/MetricProducer.cpp +++ b/statsd/src/metrics/MetricProducer.cpp @@ -66,7 +66,7 @@ MetricProducer::MetricProducer( mConditionSliced(false), mWizard(wizard), mContainANYPositionInDimensionsInWhat(false), - mSliceByPositionALL(false), + mShouldUseNestedDimensions(false), mHasLinksToAllConditionDimensionsInTracker(false), mEventActivationMap(eventActivationMap), mEventDeactivationMap(eventDeactivationMap), diff --git a/statsd/src/metrics/MetricProducer.h b/statsd/src/metrics/MetricProducer.h index 5a85161b..ab749512 100644 --- a/statsd/src/metrics/MetricProducer.h +++ b/statsd/src/metrics/MetricProducer.h @@ -520,7 +520,9 @@ protected: bool mContainANYPositionInDimensionsInWhat; - bool mSliceByPositionALL; + // Metrics slicing by primitive repeated field and/or position ALL need to use nested + // dimensions. + bool mShouldUseNestedDimensions; vector<Matcher> mDimensionsInWhat; // The dimensions_in_what defined in statsd_config diff --git a/statsd/src/metrics/ValueMetricProducer.cpp b/statsd/src/metrics/ValueMetricProducer.cpp index c2046a64..05002842 100644 --- a/statsd/src/metrics/ValueMetricProducer.cpp +++ b/statsd/src/metrics/ValueMetricProducer.cpp @@ -101,7 +101,7 @@ ValueMetricProducer<AggregatedValue, DimExtras>::ValueMetricProducer( translateFieldMatcher(whatOptions.dimensionsInWhat, &mDimensionsInWhat); } mContainANYPositionInDimensionsInWhat = whatOptions.containsAnyPositionInDimensionsInWhat; - mSliceByPositionALL = whatOptions.sliceByPositionAll; + mShouldUseNestedDimensions = whatOptions.shouldUseNestedDimensions; if (conditionOptions.conditionLinks.size() > 0) { for (const auto& link : conditionOptions.conditionLinks) { @@ -319,8 +319,8 @@ void ValueMetricProducer<AggregatedValue, DimExtras>::onDumpReportLocked( } protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs); - // Fills the dimension path if not slicing by ALL. - if (!mSliceByPositionALL) { + // Fills the dimension path if not slicing by a primitive repeated field or position ALL. + if (!mShouldUseNestedDimensions) { if (!mDimensionsInWhat.empty()) { uint64_t dimenPathToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT); @@ -359,7 +359,7 @@ void ValueMetricProducer<AggregatedValue, DimExtras>::onDumpReportLocked( protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA); // First fill dimension. - if (mSliceByPositionALL) { + if (mShouldUseNestedDimensions) { uint64_t dimensionToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT); writeDimensionToProto(metricDimensionKey.getDimensionKeyInWhat(), strSet, protoOutput); diff --git a/statsd/src/metrics/ValueMetricProducer.h b/statsd/src/metrics/ValueMetricProducer.h index 06408401..80d0bb67 100644 --- a/statsd/src/metrics/ValueMetricProducer.h +++ b/statsd/src/metrics/ValueMetricProducer.h @@ -83,7 +83,7 @@ public: struct WhatOptions { const bool containsAnyPositionInDimensionsInWhat; - const bool sliceByPositionAll; + const bool shouldUseNestedDimensions; const int whatMatcherIndex; const sp<EventMatcherWizard>& matcherWizard; const FieldMatcher& dimensionsInWhat; diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp index fd211ae4..915c4aef 100644 --- a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp +++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp @@ -655,6 +655,11 @@ optional<sp<MetricProducer>> createNumericValueMetricProducerAndUpdateMetadata( ALOGE("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id()); return nullopt; } + if (HasPositionALL(metric.value_field())) { + ALOGE("value field with position ALL is not supported. ValueMetric \"%lld\"", + (long long)metric.id()); + return nullopt; + } std::vector<Matcher> fieldMatchers; translateFieldMatcher(metric.value_field(), &fieldMatchers); if (fieldMatchers.size() < 1) { @@ -728,7 +733,7 @@ optional<sp<MetricProducer>> createNumericValueMetricProducerAndUpdateMetadata( MillisToNano(TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), bucketSizeTimeUnit)); const bool containsAnyPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); - const bool sliceByPositionAll = HasPositionALL(metric.dimensions_in_what()); + const bool shouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what()); const auto [dimensionSoftLimit, dimensionHardLimit] = StatsdStats::getAtomDimensionKeySizeLimits(pullTagId); @@ -743,8 +748,8 @@ optional<sp<MetricProducer>> createNumericValueMetricProducerAndUpdateMetadata( key, metric, metricHash, {pullTagId, pullerManager}, {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(), conditionCorrectionThresholdNs, getAppUpgradeBucketSplit(metric)}, - {containsAnyPositionInDimensionsInWhat, sliceByPositionAll, trackerIndex, matcherWizard, - metric.dimensions_in_what(), fieldMatchers}, + {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, trackerIndex, + matcherWizard, metric.dimensions_in_what(), fieldMatchers}, {conditionIndex, metric.links(), initialConditionCache, wizard}, {metric.state_link(), slicedStateAtoms, stateGroupMap}, {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit}); @@ -776,6 +781,11 @@ optional<sp<MetricProducer>> createKllMetricProducerAndUpdateMetadata( ALOGE("cannot find \"kll_field\" in KllMetric \"%lld\"", (long long)metric.id()); return nullopt; } + if (HasPositionALL(metric.kll_field())) { + ALOGE("kll field with position ALL is not supported. KllMetric \"%lld\"", + (long long)metric.id()); + return nullopt; + } std::vector<Matcher> fieldMatchers; translateFieldMatcher(metric.kll_field(), &fieldMatchers); if (fieldMatchers.empty()) { @@ -845,7 +855,7 @@ optional<sp<MetricProducer>> createKllMetricProducerAndUpdateMetadata( MillisToNano(TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), bucketSizeTimeUnit)); const bool containsAnyPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); - const bool sliceByPositionAll = HasPositionALL(metric.dimensions_in_what()); + const bool shouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what()); sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex); const int atomTagId = *(atomMatcher->getAtomIds().begin()); @@ -856,8 +866,8 @@ optional<sp<MetricProducer>> createKllMetricProducerAndUpdateMetadata( key, metric, metricHash, {/*pullTagId=*/-1, pullerManager}, {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(), /*conditionCorrectionThresholdNs=*/nullopt, getAppUpgradeBucketSplit(metric)}, - {containsAnyPositionInDimensionsInWhat, sliceByPositionAll, trackerIndex, matcherWizard, - metric.dimensions_in_what(), fieldMatchers}, + {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, trackerIndex, + matcherWizard, metric.dimensions_in_what(), fieldMatchers}, {conditionIndex, metric.links(), initialConditionCache, wizard}, {metric.state_link(), slicedStateAtoms, stateGroupMap}, {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit}); diff --git a/statsd/src/state/StateTracker.cpp b/statsd/src/state/StateTracker.cpp index 719ff3f2..ebba22a1 100644 --- a/statsd/src/state/StateTracker.cpp +++ b/statsd/src/state/StateTracker.cpp @@ -176,13 +176,13 @@ void StateTracker::notifyListeners(const int64_t eventTimeNs, } bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output) { - const int exclusiveStateFieldIndex = event.getExclusiveStateFieldIndex(); - if (-1 == exclusiveStateFieldIndex) { + const std::optional<size_t>& exclusiveStateFieldIndex = event.getExclusiveStateFieldIndex(); + if (!exclusiveStateFieldIndex) { ALOGE("error extracting state from log event. Missing exclusive state field."); return false; } - *output = event.getValues()[exclusiveStateFieldIndex]; + *output = event.getValues()[exclusiveStateFieldIndex.value()]; return true; } diff --git a/statsd/src/stats_log_util.cpp b/statsd/src/stats_log_util.cpp index 801c0810..65eecaa7 100644 --- a/statsd/src/stats_log_util.cpp +++ b/statsd/src/stats_log_util.cpp @@ -103,7 +103,7 @@ const int FIELD_ID_BUCKET_COUNT = 12; namespace { void writeDimensionToProtoHelper(const std::vector<FieldValue>& dims, size_t* index, int depth, - int prefix, std::set<string> *str_set, + int prefix, std::set<string>* str_set, ProtoOutputStream* protoOutput) { size_t count = dims.size(); while (*index < count) { @@ -116,7 +116,9 @@ void writeDimensionToProtoHelper(const std::vector<FieldValue>& dims, size_t* in return; } - if (depth == valueDepth && valuePrefix == prefix) { + // If valueDepth == 1, we're writing a repeated field. Use fieldNum at depth 0 instead + // of valueDepth. + if ((depth == valueDepth || valueDepth == 1) && valuePrefix == prefix) { uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE); protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); @@ -139,9 +141,8 @@ void writeDimensionToProtoHelper(const std::vector<FieldValue>& dims, size_t* in dim.mValue.str_value); } else { str_set->insert(dim.mValue.str_value); - protoOutput->write( - FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH, - (long long)Hash64(dim.mValue.str_value)); + protoOutput->write(FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH, + (long long)Hash64(dim.mValue.str_value)); } break; default: @@ -151,10 +152,10 @@ void writeDimensionToProtoHelper(const std::vector<FieldValue>& dims, size_t* in protoOutput->end(token); } (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { + } else if (valueDepth == depth + 2 && valuePrefix == prefix) { // Writing the sub tree - uint64_t dimensionToken = protoOutput->start( - FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE); + uint64_t dimensionToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + DIMENSIONS_VALUE_TUPLE_VALUE); protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); uint64_t tupleToken = protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE); @@ -170,9 +171,8 @@ void writeDimensionToProtoHelper(const std::vector<FieldValue>& dims, size_t* in } void writeDimensionLeafToProtoHelper(const std::vector<FieldValue>& dims, - const int dimensionLeafField, - size_t* index, int depth, - int prefix, std::set<string> *str_set, + const int dimensionLeafField, size_t* index, int depth, + int prefix, std::set<string>* str_set, ProtoOutputStream* protoOutput) { size_t count = dims.size(); while (*index < count) { @@ -184,7 +184,8 @@ void writeDimensionLeafToProtoHelper(const std::vector<FieldValue>& dims, return; } - if (depth == valueDepth && valuePrefix == prefix) { + // If valueDepth == 1, we're writing a repeated field. + if ((depth == valueDepth || valueDepth == 1) && valuePrefix == prefix) { uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | dimensionLeafField); switch (dim.mValue.getType()) { @@ -206,9 +207,8 @@ void writeDimensionLeafToProtoHelper(const std::vector<FieldValue>& dims, dim.mValue.str_value); } else { str_set->insert(dim.mValue.str_value); - protoOutput->write( - FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH, - (long long)Hash64(dim.mValue.str_value)); + protoOutput->write(FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH, + (long long)Hash64(dim.mValue.str_value)); } break; default: @@ -218,7 +218,7 @@ void writeDimensionLeafToProtoHelper(const std::vector<FieldValue>& dims, protoOutput->end(token); } (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { + } else if (valueDepth == depth + 2 && valuePrefix == prefix) { writeDimensionLeafToProtoHelper(dims, dimensionLeafField, index, valueDepth, dim.mField.getPrefix(valueDepth), str_set, protoOutput); @@ -243,7 +243,7 @@ void writeDimensionPathToProtoHelper(const std::vector<Matcher>& fieldMatchers, return; } - if (depth == valueDepth && valuePrefix == prefix) { + if ((depth == valueDepth || valueDepth == 1) && valuePrefix == prefix) { uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE); protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum); @@ -251,7 +251,7 @@ void writeDimensionPathToProtoHelper(const std::vector<Matcher>& fieldMatchers, protoOutput->end(token); } (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { + } else if (valueDepth == depth + 2 && valuePrefix == prefix) { // Writing the sub tree uint64_t dimensionToken = protoOutput->start( FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE); @@ -312,8 +312,8 @@ void writeDimensionPathToProto(const std::vector<Matcher>& fieldMatchers, // Supported Atoms format // XYZ_Atom { // repeated SubMsg field_1 = 1; -// SubMsg2 field_2 = 2; -// int32/float/string/int63 field_3 = 3; +// repeated int32/float/string/int64 field_2 = 2; +// optional int32/float/string/int64 field_3 = 3; // } // logd's msg format, doesn't allow us to distinguish between the 2 cases below // Case (1): @@ -344,25 +344,31 @@ void writeFieldValueTreeToStreamHelper(int tagId, const std::vector<FieldValue>& const int valueDepth = dim.mField.getDepth(); const int valuePrefix = dim.mField.getPrefix(depth); const int fieldNum = dim.mField.getPosAtDepth(depth); + const uint64_t repeatedFieldMask = (valueDepth == 1) ? FIELD_COUNT_REPEATED : 0; if (valueDepth > 2) { ALOGE("Depth > 2 not supported"); return; } - if (depth == valueDepth && valuePrefix == prefix) { + // If valueDepth == 1, we're writing a repeated field. Use fieldNum at depth 0 instead + // of valueDepth. + if ((depth == valueDepth || valueDepth == 1) && valuePrefix == prefix) { switch (dim.mValue.getType()) { case INT: - protoOutput->write(FIELD_TYPE_INT32 | fieldNum, dim.mValue.int_value); + protoOutput->write(FIELD_TYPE_INT32 | repeatedFieldMask | fieldNum, + dim.mValue.int_value); break; case LONG: - protoOutput->write(FIELD_TYPE_INT64 | fieldNum, + protoOutput->write(FIELD_TYPE_INT64 | repeatedFieldMask | fieldNum, (long long)dim.mValue.long_value); break; case FLOAT: - protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, dim.mValue.float_value); + protoOutput->write(FIELD_TYPE_FLOAT | repeatedFieldMask | fieldNum, + dim.mValue.float_value); break; case STRING: { - protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value); + protoOutput->write(FIELD_TYPE_STRING | repeatedFieldMask | fieldNum, + dim.mValue.str_value); break; } case STORAGE: @@ -374,15 +380,10 @@ void writeFieldValueTreeToStreamHelper(int tagId, const std::vector<FieldValue>& break; } (*index)++; - } else if (valueDepth > depth && valuePrefix == prefix) { + } else if (valueDepth == depth + 2 && valuePrefix == prefix) { // Writing the sub tree uint64_t msg_token = 0ULL; - if (valueDepth == depth + 2) { - msg_token = - protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum); - } else if (valueDepth == depth + 1) { - msg_token = protoOutput->start(FIELD_TYPE_MESSAGE | fieldNum); - } + msg_token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum); // Directly jump to the leaf value because the repeated position field is implied // by the position of the sub msg in the parent field. writeFieldValueTreeToStreamHelper(tagId, dims, index, valueDepth, diff --git a/statsd/tests/FieldValue_test.cpp b/statsd/tests/FieldValue_test.cpp index f4ac8e5e..ff3e54af 100644 --- a/statsd/tests/FieldValue_test.cpp +++ b/statsd/tests/FieldValue_test.cpp @@ -34,6 +34,7 @@ namespace os { namespace statsd { namespace { + void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp, const vector<int>& attributionUids, const vector<string>& attributionTags, const string& name) { @@ -59,6 +60,14 @@ void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timest parseStatsEventToLogEvent(statsEvent, logEvent); } + +void makeRepeatedIntLogEvent(LogEvent* logEvent, const int32_t atomId, + const vector<int>& intArray) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_writeInt32Array(statsEvent, intArray.data(), intArray.size()); + parseStatsEventToLogEvent(statsEvent, logEvent); +} } // anonymous namespace TEST(AtomMatcherTest, TestFieldTranslation) { @@ -148,6 +157,76 @@ TEST(AtomMatcherTest, TestFilter_ALL) { EXPECT_EQ("some value", output.getValues()[6].mValue.str_value); } +TEST(AtomMatcherTest, TestFilterRepeated_FIRST) { + FieldMatcher matcher; + matcher.set_field(123); + FieldMatcher* child = matcher.add_child(); + child->set_field(1); + child->set_position(Position::FIRST); + + vector<Matcher> matchers; + translateFieldMatcher(matcher, &matchers); + + LogEvent event(/*uid=*/0, /*pid=*/0); + vector<int> intArray = {21, 9, 13}; + makeRepeatedIntLogEvent(&event, 123, intArray); + + HashableDimensionKey output; + EXPECT_TRUE(filterValues(matchers, event.getValues(), &output)); + + ASSERT_EQ((size_t)1, output.getValues().size()); + EXPECT_EQ((int32_t)0x01010100, output.getValues()[0].mField.getField()); + EXPECT_EQ((int32_t)21, output.getValues()[0].mValue.int_value); +} + +TEST(AtomMatcherTest, TestFilterRepeated_LAST) { + FieldMatcher matcher; + matcher.set_field(123); + FieldMatcher* child = matcher.add_child(); + child->set_field(1); + child->set_position(Position::LAST); + + vector<Matcher> matchers; + translateFieldMatcher(matcher, &matchers); + + LogEvent event(/*uid=*/0, /*pid=*/0); + vector<int> intArray = {21, 9, 13}; + makeRepeatedIntLogEvent(&event, 123, intArray); + + HashableDimensionKey output; + EXPECT_TRUE(filterValues(matchers, event.getValues(), &output)); + + ASSERT_EQ((size_t)1, output.getValues().size()); + EXPECT_EQ((int32_t)0x01018000, output.getValues()[0].mField.getField()); + EXPECT_EQ((int32_t)13, output.getValues()[0].mValue.int_value); +} + +TEST(AtomMatcherTest, TestFilterRepeated_ALL) { + FieldMatcher matcher; + matcher.set_field(123); + FieldMatcher* child = matcher.add_child(); + child->set_field(1); + child->set_position(Position::ALL); + + vector<Matcher> matchers; + translateFieldMatcher(matcher, &matchers); + + LogEvent event(/*uid=*/0, /*pid=*/0); + vector<int> intArray = {21, 9, 13}; + makeRepeatedIntLogEvent(&event, 123, intArray); + + HashableDimensionKey output; + EXPECT_TRUE(filterValues(matchers, event.getValues(), &output)); + + ASSERT_EQ((size_t)3, output.getValues().size()); + EXPECT_EQ((int32_t)0x01010100, output.getValues()[0].mField.getField()); + EXPECT_EQ((int32_t)21, output.getValues()[0].mValue.int_value); + EXPECT_EQ((int32_t)0x01010200, output.getValues()[1].mField.getField()); + EXPECT_EQ((int32_t)9, output.getValues()[1].mValue.int_value); + EXPECT_EQ((int32_t)0x01010300, output.getValues()[2].mField.getField()); + EXPECT_EQ((int32_t)13, output.getValues()[2].mValue.int_value); +} + TEST(AtomMatcherTest, TestSubDimension) { HashableDimensionKey dim; @@ -230,21 +309,25 @@ TEST(AtomMatcherTest, TestMetric2ConditionLink) { } TEST(AtomMatcherTest, TestWriteDimensionPath) { - for (auto position : {Position::ANY, Position::ALL, Position::FIRST, Position::LAST}) { + for (auto position : {Position::ALL, Position::FIRST, Position::LAST}) { FieldMatcher matcher1; matcher1.set_field(10); + + // Repeated nested fields (attribution chain). FieldMatcher* child = matcher1.add_child(); child->set_field(2); child->set_position(position); child->add_child()->set_field(1); child->add_child()->set_field(3); + // Primitive field. child = matcher1.add_child(); child->set_field(4); + // Repeated primitive field. child = matcher1.add_child(); child->set_field(6); - child->add_child()->set_field(2); + child->set_position(position); vector<Matcher> matchers; translateFieldMatcher(matcher1, &matchers); @@ -285,9 +368,6 @@ TEST(AtomMatcherTest, TestWriteDimensionPath) { const auto& dim3 = result.value_tuple().dimensions_value(2); EXPECT_EQ(6, dim3.field()); - ASSERT_EQ(1, dim3.value_tuple().dimensions_value_size()); - const auto& dim31 = dim3.value_tuple().dimensions_value(0); - EXPECT_EQ(2, dim31.field()); } } @@ -513,6 +593,48 @@ TEST(AtomMatcherTest, TestWriteAtomToProto) { EXPECT_EQ(999, atom.num_results()); } +TEST(AtomMatcherTest, TestWriteAtomWithRepeatedFieldsToProto) { + vector<int> intArray = {3, 6}; + vector<int64_t> longArray = {1000L, 10002L}; + vector<float> floatArray = {0.3f, 0.09f}; + vector<string> stringArray = {"str1", "str2"}; + int boolArrayLength = 2; + bool boolArray[boolArrayLength]; + boolArray[0] = 1; + boolArray[1] = 0; + vector<bool> boolArrayVector = {1, 0}; + vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF}; + + unique_ptr<LogEvent> event = CreateTestAtomReportedEventVariableRepeatedFields( + 12345, intArray, longArray, floatArray, stringArray, boolArray, boolArrayLength, + enumArray); + + android::util::ProtoOutputStream protoOutput; + writeFieldValueTreeToStream(event->GetTagId(), event->getValues(), &protoOutput); + + vector<uint8_t> outData; + outData.resize(protoOutput.size()); + size_t pos = 0; + sp<ProtoReader> reader = protoOutput.data(); + while (reader->readBuffer() != NULL) { + size_t toRead = reader->currentToRead(); + std::memcpy(&(outData[pos]), reader->readBuffer(), toRead); + pos += toRead; + reader->move(toRead); + } + + Atom result; + ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size())); + EXPECT_EQ(Atom::PushedCase::kTestAtomReported, result.pushed_case()); + TestAtomReported atom = result.test_atom_reported(); + EXPECT_THAT(atom.repeated_int_field(), ElementsAreArray(intArray)); + EXPECT_THAT(atom.repeated_long_field(), ElementsAreArray(longArray)); + EXPECT_THAT(atom.repeated_float_field(), ElementsAreArray(floatArray)); + EXPECT_THAT(atom.repeated_string_field(), ElementsAreArray(stringArray)); + EXPECT_THAT(atom.repeated_boolean_field(), ElementsAreArray(boolArrayVector)); + EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray)); +} + /* * Test two Matchers is not a subset of one Matcher. * Test one Matcher is subset of two Matchers. @@ -645,6 +767,31 @@ TEST(AtomMatcherTest, TestSubsetDimensions4) { EXPECT_FALSE(subsetDimensions(matchers2, matchers1)); } +TEST(AtomMatcherTest, TestIsPrimitiveRepeatedField) { + int pos1[] = {1, 1, 1}; // attribution uid + int pos2[] = {1, 1, 2}; // attribution tag + int pos3[] = {1, 2, 1}; // attribution uid - second node + int pos4[] = {1, 2, 2}; // attribution tag - second node + int pos5[] = {2, 1, 1}; // repeated field first element + int pos6[] = {2, 2, 1}; // repeated field second element + int pos7[] = {3, 1, 1}; // top-level field + Field field1(10, pos1, 2); + Field field2(10, pos2, 2); + Field field3(10, pos3, 2); + Field field4(10, pos4, 2); + Field field5(10, pos5, 1); + Field field6(10, pos6, 1); + Field field7(10, pos7, 0); + + EXPECT_FALSE(isPrimitiveRepeatedField(field1)); + EXPECT_FALSE(isPrimitiveRepeatedField(field2)); + EXPECT_FALSE(isPrimitiveRepeatedField(field3)); + EXPECT_FALSE(isPrimitiveRepeatedField(field4)); + EXPECT_TRUE(isPrimitiveRepeatedField(field5)); + EXPECT_TRUE(isPrimitiveRepeatedField(field6)); + EXPECT_FALSE(isPrimitiveRepeatedField(field7)); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/statsd/tests/LogEntryMatcher_test.cpp b/statsd/tests/LogEntryMatcher_test.cpp index 21368d43..249f452a 100644 --- a/statsd/tests/LogEntryMatcher_test.cpp +++ b/statsd/tests/LogEntryMatcher_test.cpp @@ -107,6 +107,36 @@ void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t ti parseStatsEventToLogEvent(statsEvent, logEvent); } +void makeRepeatedIntLogEvent(LogEvent* logEvent, const int32_t atomId, + const vector<int>& intArray) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_writeInt32Array(statsEvent, intArray.data(), intArray.size()); + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeRepeatedUidLogEvent(LogEvent* logEvent, const int32_t atomId, + const vector<int>& intArray) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_writeInt32Array(statsEvent, intArray.data(), intArray.size()); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + parseStatsEventToLogEvent(statsEvent, logEvent); +} + +void makeRepeatedStringLogEvent(LogEvent* logEvent, const int32_t atomId, + const vector<string>& stringArray) { + vector<const char*> cStringArray(stringArray.size()); + for (int i = 0; i < cStringArray.size(); i++) { + cStringArray[i] = stringArray[i].c_str(); + } + + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_writeStringArray(statsEvent, cStringArray.data(), stringArray.size()); + parseStatsEventToLogEvent(statsEvent, logEvent); +} + } // anonymous namespace TEST(AtomMatcherTest, TestSimpleMatcher) { @@ -389,11 +419,99 @@ TEST(AtomMatcherTest, TestUidFieldMatcher) { EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); // Event has is_uid annotation, but uid maps to different package name. - simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2"); + simpleMatcher->mutable_field_value_matcher(0)->set_eq_string( + "pkg2"); // package names are normalized EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)); } -TEST(AtomMatcherTest, TestNeqAnyStringMatcher) { +TEST(AtomMatcherTest, TestRepeatedUidFieldMatcher) { + sp<UidMap> uidMap = new UidMap(); + uidMap->updateMap( + 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, + {android::String16("v1"), android::String16("v1"), android::String16("v2"), + android::String16("v1"), android::String16("v2")}, + {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"), + android::String16("Pkg2"), android::String16("PkG3")} /* package name list */, + {android::String16(""), android::String16(""), android::String16(""), + android::String16(""), android::String16("")}, + /* certificateHash */ {{}, {}, {}, {}, {}}); + + // Set up matcher. + AtomMatcher matcher; + SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(FIELD_ID_1); + + // No is_uid annotation, no mapping from uid to package name. + vector<int> intArray = {1111, 3333, 2222}; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeRepeatedIntLogEvent(&event1, TAG_ID, intArray); + + fieldValueMatcher->set_position(Position::FIRST); + fieldValueMatcher->set_eq_string("pkg0"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); + + fieldValueMatcher->set_position(Position::LAST); + fieldValueMatcher->set_eq_string("pkg1"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); + + fieldValueMatcher->set_position(Position::ANY); + fieldValueMatcher->set_eq_string("pkg2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); + + // is_uid annotation, mapping from uid to package name. + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeRepeatedUidLogEvent(&event2, TAG_ID, intArray); + + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)); + fieldValueMatcher->set_eq_string("pkg0"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); + + fieldValueMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)); + fieldValueMatcher->set_eq_string("pkg1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); + + fieldValueMatcher->set_position(Position::ANY); + fieldValueMatcher->set_eq_string("pkg"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)); + fieldValueMatcher->set_eq_string("pkg2"); // package names are normalized + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2)); +} + +TEST(AtomMatcherTest, TestNeqAnyStringMatcher_SingleString) { + sp<UidMap> uidMap = new UidMap(); + + // Set up the matcher + AtomMatcher matcher; + SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(FIELD_ID_1); + StringListMatcher* neqStringList = fieldValueMatcher->mutable_neq_any_string(); + neqStringList->add_str_value("some value"); + neqStringList->add_str_value("another value"); + + // First string matched. + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeStringLogEvent(&event1, TAG_ID, 0, "some value"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1)); + + // Second string matched. + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeStringLogEvent(&event2, TAG_ID, 0, "another value"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2)); + + // No strings matched. + LogEvent event3(/*uid=*/0, /*pid=*/0); + makeStringLogEvent(&event3, TAG_ID, 0, "foo"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3)); +} + +TEST(AtomMatcherTest, TestNeqAnyStringMatcher_AttributionUids) { sp<UidMap> uidMap = new UidMap(); uidMap->updateMap( 1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */, @@ -571,6 +689,284 @@ TEST(AtomMatcherTest, TestStringMatcher) { EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); } +TEST(AtomMatcherTest, TestIntMatcher_EmptyRepeatedField) { + sp<UidMap> uidMap = new UidMap(); + + // Set up the log event. + LogEvent event(/*uid=*/0, /*pid=*/0); + makeRepeatedIntLogEvent(&event, TAG_ID, {}); + + // Set up the matcher. + AtomMatcher matcher; + SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(FIELD_ID_1); + + // Match first int. + fieldValueMatcher->set_position(Position::FIRST); + fieldValueMatcher->set_eq_int(9); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match last int. + fieldValueMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match any int. + fieldValueMatcher->set_position(Position::ANY); + fieldValueMatcher->set_eq_int(13); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestIntMatcher_RepeatedIntField) { + sp<UidMap> uidMap = new UidMap(); + + // Set up the log event. + LogEvent event(/*uid=*/0, /*pid=*/0); + vector<int> intArray = {21, 9}; + makeRepeatedIntLogEvent(&event, TAG_ID, intArray); + + // Set up the matcher. + AtomMatcher matcher; + SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + // Match first int. + FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(FIELD_ID_1); + fieldValueMatcher->set_position(Position::FIRST); + fieldValueMatcher->set_eq_int(9); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_eq_int(21); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match last int. + fieldValueMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_eq_int(9); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match any int. + fieldValueMatcher->set_position(Position::ANY); + fieldValueMatcher->set_eq_int(13); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_eq_int(21); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_eq_int(9); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestLtIntMatcher_RepeatedIntField) { + sp<UidMap> uidMap = new UidMap(); + + // Set up the log event. + LogEvent event(/*uid=*/0, /*pid=*/0); + vector<int> intArray = {21, 9}; + makeRepeatedIntLogEvent(&event, TAG_ID, intArray); + + // Set up the matcher. + AtomMatcher matcher; + SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + // Match first int. + FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(FIELD_ID_1); + fieldValueMatcher->set_position(Position::FIRST); + fieldValueMatcher->set_lt_int(9); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_lt_int(21); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_lt_int(23); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match last int. + fieldValueMatcher->set_position(Position::LAST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_lt_int(9); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_lt_int(8); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match any int. + fieldValueMatcher->set_position(Position::ANY); + fieldValueMatcher->set_lt_int(21); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_lt_int(8); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_lt_int(23); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestStringMatcher_RepeatedStringField) { + sp<UidMap> uidMap = new UidMap(); + + // Set up the log event. + LogEvent event(/*uid=*/0, /*pid=*/0); + vector<string> strArray = {"str1", "str2", "str3"}; + makeRepeatedStringLogEvent(&event, TAG_ID, strArray); + + // Set up the matcher. + AtomMatcher matcher; + SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + // Match first int. + FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(FIELD_ID_1); + fieldValueMatcher->set_position(Position::FIRST); + fieldValueMatcher->set_eq_string("str2"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_eq_string("str1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match last int. + fieldValueMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_eq_string("str3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + // Match any int. + fieldValueMatcher->set_position(Position::ANY); + fieldValueMatcher->set_eq_string("str4"); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_eq_string("str1"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_eq_string("str2"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + fieldValueMatcher->set_eq_string("str3"); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestEqAnyStringMatcher_RepeatedStringField) { + sp<UidMap> uidMap = new UidMap(); + + // Set up the log event. + LogEvent event(/*uid=*/0, /*pid=*/0); + vector<string> strArray = {"str1", "str2", "str3"}; + makeRepeatedStringLogEvent(&event, TAG_ID, strArray); + + // Set up the matcher. + AtomMatcher matcher; + SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(FIELD_ID_1); + StringListMatcher* eqStringList = fieldValueMatcher->mutable_eq_any_string(); + + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + eqStringList->add_str_value("str4"); + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + eqStringList->add_str_value("str2"); + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + eqStringList->add_str_value("str3"); + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + eqStringList->add_str_value("str1"); + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); +} + +TEST(AtomMatcherTest, TestNeqAnyStringMatcher_RepeatedStringField) { + sp<UidMap> uidMap = new UidMap(); + + // Set up the log event. + LogEvent event(/*uid=*/0, /*pid=*/0); + vector<string> strArray = {"str1", "str2", "str3"}; + makeRepeatedStringLogEvent(&event, TAG_ID, strArray); + + // Set up the matcher. + AtomMatcher matcher; + SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher(); + simpleMatcher->set_atom_id(TAG_ID); + + FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher(); + fieldValueMatcher->set_field(FIELD_ID_1); + StringListMatcher* neqStringList = fieldValueMatcher->mutable_neq_any_string(); + + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + neqStringList->add_str_value("str4"); + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + + neqStringList->add_str_value("str2"); + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + neqStringList->add_str_value("str3"); + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + + neqStringList->add_str_value("str1"); + fieldValueMatcher->set_position(Position::FIRST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::LAST); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); + fieldValueMatcher->set_position(Position::ANY); + EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event)); +} + TEST(AtomMatcherTest, TestMultiFieldsMatcher) { sp<UidMap> uidMap = new UidMap(); // Set up the matcher diff --git a/statsd/tests/LogEvent_test.cpp b/statsd/tests/LogEvent_test.cpp index 4b4c6bc9..d87b20bc 100644 --- a/statsd/tests/LogEvent_test.cpp +++ b/statsd/tests/LogEvent_test.cpp @@ -37,46 +37,111 @@ namespace { Field getField(int32_t tag, const vector<int32_t>& pos, int32_t depth, const vector<bool>& last) { Field f(tag, (int32_t*)pos.data(), depth); - // For loop starts at 1 because the last field at depth 0 is not decorated. - for (int i = 1; i < depth; i++) { - if (last[i]) f.decorateLastPos(i); - } + // only decorate last position for depths with repeated fields (depth 1) + if (depth > 0 && last[1]) f.decorateLastPos(1); return f; } -void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, - bool annotationValue) { - AStatsEvent* statsEvent = AStatsEvent_obtain(); +void createStatsEvent(AStatsEvent* statsEvent, uint8_t typeId) { AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); - AStatsEvent_writeInt32(statsEvent, 10); + + int int32Array[2] = {3, 6}; + uint32_t uids[] = {1001, 1002}; + const char* tags[] = {"tag1", "tag2"}; + + switch (typeId) { + case INT32_TYPE: + AStatsEvent_writeInt32(statsEvent, 10); + break; + case INT64_TYPE: + AStatsEvent_writeInt64(statsEvent, 1000L); + break; + case STRING_TYPE: + AStatsEvent_writeString(statsEvent, "test"); + break; + case LIST_TYPE: + AStatsEvent_writeInt32Array(statsEvent, int32Array, 2); + break; + case FLOAT_TYPE: + AStatsEvent_writeFloat(statsEvent, 1.3f); + break; + case BOOL_TYPE: + AStatsEvent_writeBool(statsEvent, 1); + break; + case BYTE_ARRAY_TYPE: + AStatsEvent_writeByteArray(statsEvent, (uint8_t*)"test", strlen("test")); + break; + case ATTRIBUTION_CHAIN_TYPE: + AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2); + break; + default: + break; + } +} + +void createFieldWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId, + bool annotationValue, bool parseBufferResult) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + createStatsEvent(statsEvent, typeId); AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue); AStatsEvent_build(statsEvent); size_t size; uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - EXPECT_TRUE(logEvent->parseBuffer(buf, size)); + EXPECT_EQ(parseBufferResult, logEvent->parseBuffer(buf, size)); AStatsEvent_release(statsEvent); } -void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId, - int annotationValue) { +void createFieldWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId, + int annotationValue, bool parseBufferResult) { AStatsEvent* statsEvent = AStatsEvent_obtain(); - AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); - AStatsEvent_writeInt32(statsEvent, 10); + createStatsEvent(statsEvent, typeId); AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue); AStatsEvent_build(statsEvent); size_t size; uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); - EXPECT_TRUE(logEvent->parseBuffer(buf, size)); + EXPECT_EQ(parseBufferResult, logEvent->parseBuffer(buf, size)); AStatsEvent_release(statsEvent); } } // anonymous namespace +// Setup for parameterized tests. +class LogEventTestBadAnnotationFieldTypes : public testing::TestWithParam<int> { +public: + static std::string ToString(testing::TestParamInfo<int> info) { + switch (info.param) { + case INT32_TYPE: + return "Int32"; + case INT64_TYPE: + return "Int64"; + case STRING_TYPE: + return "String"; + case LIST_TYPE: + return "List"; + case FLOAT_TYPE: + return "Float"; + case BYTE_ARRAY_TYPE: + return "ByteArray"; + case ATTRIBUTION_CHAIN_TYPE: + return "AttributionChain"; + default: + return "Unknown"; + } + } +}; + +// TODO(b/222539899): Add BOOL_TYPE value once parseAnnotations is updated to check specific +// typeIds. BOOL_TYPE should be a bad field type for is_uid, nested, and reset state annotations. +INSTANTIATE_TEST_SUITE_P(BadAnnotationFieldTypes, LogEventTestBadAnnotationFieldTypes, + testing::Values(INT32_TYPE, INT64_TYPE, STRING_TYPE, LIST_TYPE, FLOAT_TYPE, + BYTE_ARRAY_TYPE, ATTRIBUTION_CHAIN_TYPE), + LogEventTestBadAnnotationFieldTypes::ToString); + TEST(LogEventTest, TestPrimitiveParsing) { AStatsEvent* event = AStatsEvent_obtain(); AStatsEvent_setAtomId(event, 100); @@ -225,6 +290,25 @@ TEST(LogEventTest, TestByteArrayWithNullCharacter) { AStatsEvent_release(event); } +TEST(LogEventTest, TestTooManyTopLevelElements) { + int32_t numElements = 128; + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + + for (int i = 0; i < numElements; i++) { + AStatsEvent_writeInt32(event, i); + } + + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + + AStatsEvent_release(event); +} + TEST(LogEventTest, TestAttributionChain) { AStatsEvent* event = AStatsEvent_obtain(); AStatsEvent_setAtomId(event, 100); @@ -251,7 +335,7 @@ TEST(LogEventTest, TestAttributionChain) { const vector<FieldValue>& values = logEvent.getValues(); ASSERT_EQ(4, values.size()); // 2 per attribution node - std::pair<int, int> attrIndexRange; + std::pair<size_t, size_t> attrIndexRange; EXPECT_TRUE(logEvent.hasAttributionChain(&attrIndexRange)); EXPECT_EQ(0, attrIndexRange.first); EXPECT_EQ(3, attrIndexRange.second); @@ -285,9 +369,236 @@ TEST(LogEventTest, TestAttributionChain) { AStatsEvent_release(event); } +TEST(LogEventTest, TestEmptyAttributionChain) { + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + + AStatsEvent_writeAttributionChain(event, {}, {}, 0); + AStatsEvent_writeInt32(event, 10); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestAttributionChainTooManyElements) { + int32_t numNodes = 128; + uint32_t uids[numNodes]; + vector<string> tags(numNodes); // storage that cTag elements point to + const char* cTags[numNodes]; + + for (int i = 0; i < numNodes; i++) { + uids[i] = i; + tags.push_back("test"); + cTags[i] = tags[i].c_str(); + } + + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeAttributionChain(event, uids, cTags, numNodes); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestArrayParsing) { + size_t numElements = 2; + int32_t int32Array[2] = {3, 6}; + int64_t int64Array[2] = {1000L, 1002L}; + float floatArray[2] = {0.3f, 0.09f}; + bool boolArray[2] = {0, 1}; + + vector<string> stringArray = {"str1", "str2"}; + const char* cStringArray[2]; + for (int i = 0; i < numElements; i++) { + cStringArray[i] = stringArray[i].c_str(); + } + + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32Array(event, int32Array, numElements); + AStatsEvent_writeInt64Array(event, int64Array, numElements); + AStatsEvent_writeFloatArray(event, floatArray, numElements); + AStatsEvent_writeBoolArray(event, boolArray, numElements); + AStatsEvent_writeStringArray(event, cStringArray, numElements); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + EXPECT_FALSE(logEvent.hasAttributionChain()); + + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(10, values.size()); // 2 for each array type + + const FieldValue& int32ArrayItem1 = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 1, {false, false, false}); + EXPECT_EQ(expectedField, int32ArrayItem1.mField); + EXPECT_EQ(Type::INT, int32ArrayItem1.mValue.getType()); + EXPECT_EQ(3, int32ArrayItem1.mValue.int_value); + + const FieldValue& int32ArrayItem2 = values[1]; + expectedField = getField(100, {1, 2, 1}, 1, {false, true, false}); + EXPECT_EQ(expectedField, int32ArrayItem2.mField); + EXPECT_EQ(Type::INT, int32ArrayItem2.mValue.getType()); + EXPECT_EQ(6, int32ArrayItem2.mValue.int_value); + + const FieldValue& int64ArrayItem1 = values[2]; + expectedField = getField(100, {2, 1, 1}, 1, {false, false, false}); + EXPECT_EQ(expectedField, int64ArrayItem1.mField); + EXPECT_EQ(Type::LONG, int64ArrayItem1.mValue.getType()); + EXPECT_EQ(1000L, int64ArrayItem1.mValue.long_value); + + const FieldValue& int64ArrayItem2 = values[3]; + expectedField = getField(100, {2, 2, 1}, 1, {false, true, false}); + EXPECT_EQ(expectedField, int64ArrayItem2.mField); + EXPECT_EQ(Type::LONG, int64ArrayItem2.mValue.getType()); + EXPECT_EQ(1002L, int64ArrayItem2.mValue.long_value); + + const FieldValue& floatArrayItem1 = values[4]; + expectedField = getField(100, {3, 1, 1}, 1, {false, false, false}); + EXPECT_EQ(expectedField, floatArrayItem1.mField); + EXPECT_EQ(Type::FLOAT, floatArrayItem1.mValue.getType()); + EXPECT_EQ(0.3f, floatArrayItem1.mValue.float_value); + + const FieldValue& floatArrayItem2 = values[5]; + expectedField = getField(100, {3, 2, 1}, 1, {false, true, false}); + EXPECT_EQ(expectedField, floatArrayItem2.mField); + EXPECT_EQ(Type::FLOAT, floatArrayItem2.mValue.getType()); + EXPECT_EQ(0.09f, floatArrayItem2.mValue.float_value); + + const FieldValue& boolArrayItem1 = values[6]; + expectedField = getField(100, {4, 1, 1}, 1, {false, false, false}); + EXPECT_EQ(expectedField, boolArrayItem1.mField); + EXPECT_EQ(Type::INT, + boolArrayItem1.mValue.getType()); // FieldValue does not support boolean type + EXPECT_EQ(false, boolArrayItem1.mValue.int_value); + + const FieldValue& boolArrayItem2 = values[7]; + expectedField = getField(100, {4, 2, 1}, 1, {false, true, false}); + EXPECT_EQ(expectedField, boolArrayItem2.mField); + EXPECT_EQ(Type::INT, + boolArrayItem2.mValue.getType()); // FieldValue does not support boolean type + EXPECT_EQ(true, boolArrayItem2.mValue.int_value); + + const FieldValue& stringArrayItem1 = values[8]; + expectedField = getField(100, {5, 1, 1}, 1, {true, false, false}); + EXPECT_EQ(expectedField, stringArrayItem1.mField); + EXPECT_EQ(Type::STRING, stringArrayItem1.mValue.getType()); + EXPECT_EQ("str1", stringArrayItem1.mValue.str_value); + + const FieldValue& stringArrayItem2 = values[9]; + expectedField = getField(100, {5, 2, 1}, 1, {true, true, false}); + EXPECT_EQ(expectedField, stringArrayItem2.mField); + EXPECT_EQ(Type::STRING, stringArrayItem2.mValue.getType()); + EXPECT_EQ("str2", stringArrayItem2.mValue.str_value); +} + +TEST(LogEventTest, TestEmptyStringArray) { + const char* cStringArray[2]; + string empty = ""; + cStringArray[0] = empty.c_str(); + cStringArray[1] = empty.c_str(); + + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeStringArray(event, cStringArray, 2); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(2, values.size()); + + const FieldValue& stringArrayItem1 = values[0]; + Field expectedField = getField(100, {1, 1, 1}, 1, {true, false, false}); + EXPECT_EQ(expectedField, stringArrayItem1.mField); + EXPECT_EQ(Type::STRING, stringArrayItem1.mValue.getType()); + EXPECT_EQ(empty, stringArrayItem1.mValue.str_value); + + const FieldValue& stringArrayItem2 = values[1]; + expectedField = getField(100, {1, 2, 1}, 1, {true, true, false}); + EXPECT_EQ(expectedField, stringArrayItem2.mField); + EXPECT_EQ(Type::STRING, stringArrayItem2.mValue.getType()); + EXPECT_EQ(empty, stringArrayItem2.mValue.str_value); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestArrayTooManyElements) { + int32_t numElements = 128; + int32_t int32Array[numElements]; + + for (int i = 0; i < numElements; i++) { + int32Array[i] = 1; + } + + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32Array(event, int32Array, numElements); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + + AStatsEvent_release(event); +} + +TEST(LogEventTest, TestEmptyArray) { + int32_t int32Array[0] = {}; + + AStatsEvent* event = AStatsEvent_obtain(); + AStatsEvent_setAtomId(event, 100); + AStatsEvent_writeInt32Array(event, int32Array, 0); + AStatsEvent_build(event); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(event, &size); + + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + + EXPECT_EQ(100, logEvent.GetTagId()); + EXPECT_EQ(1000, logEvent.GetUid()); + EXPECT_EQ(1001, logEvent.GetPid()); + + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(0, values.size()); + + AStatsEvent_release(event); +} + TEST(LogEventTest, TestAnnotationIdIsUid) { LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true); + createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID, true, + /*parseBufferResult*/ true); ASSERT_EQ(event.getNumUidFields(), 1); @@ -296,33 +607,214 @@ TEST(LogEventTest, TestAnnotationIdIsUid) { EXPECT_TRUE(isUidField(values.at(0))); } +TEST(LogEventTest, TestAnnotationIdIsUid_RepeatedIntAndOtherFields) { + size_t numElements = 2; + int32_t int32Array[2] = {3, 6}; + + vector<string> stringArray = {"str1", "str2"}; + const char* cStringArray[2]; + for (int i = 0; i < numElements; i++) { + cStringArray[i] = stringArray[i].c_str(); + } + + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 100); + AStatsEvent_writeInt32(statsEvent, 5); + AStatsEvent_writeInt32Array(statsEvent, int32Array, numElements); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_writeStringArray(statsEvent, cStringArray, numElements); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + EXPECT_EQ(2, logEvent.getNumUidFields()); + + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(values.size(), 5); + EXPECT_FALSE(isUidField(values.at(0))); + EXPECT_TRUE(isUidField(values.at(1))); + EXPECT_TRUE(isUidField(values.at(2))); + EXPECT_FALSE(isUidField(values.at(3))); + EXPECT_FALSE(isUidField(values.at(4))); +} + +TEST(LogEventTest, TestAnnotationIdIsUid_RepeatedIntOneEntry) { + size_t numElements = 1; + int32_t int32Array[1] = {3}; + + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 100); + AStatsEvent_writeInt32Array(statsEvent, int32Array, numElements); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + EXPECT_EQ(1, logEvent.getNumUidFields()); + + const vector<FieldValue>& values = logEvent.getValues(); + ASSERT_EQ(values.size(), 1); + EXPECT_TRUE(isUidField(values.at(0))); +} + +TEST(LogEventTest, TestAnnotationIdIsUid_EmptyIntArray) { + int32_t int32Array[0] = {}; + + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, 100); + AStatsEvent_writeInt32Array(statsEvent, int32Array, /*numElements*/ 0); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_writeInt32(statsEvent, 5); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); + EXPECT_EQ(0, logEvent.getNumUidFields()); + + const vector<FieldValue>& values = logEvent.getValues(); + EXPECT_EQ(values.size(), 1); +} + +TEST(LogEventTest, TestAnnotationIdIsUid_BadRepeatedInt64) { + int64_t int64Array[2] = {1000L, 1002L}; + + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); + AStatsEvent_writeInt64Array(statsEvent, int64Array, /*numElements*/ 2); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + EXPECT_EQ(0, logEvent.getNumUidFields()); + + AStatsEvent_release(statsEvent); +} + +TEST(LogEventTest, TestAnnotationIdIsUid_BadRepeatedString) { + size_t numElements = 2; + vector<string> stringArray = {"str1", "str2"}; + const char* cStringArray[2]; + for (int i = 0; i < numElements; i++) { + cStringArray[i] = stringArray[i].c_str(); + } + + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, /*atomId=*/100); + AStatsEvent_writeStringArray(statsEvent, cStringArray, /*numElements*/ 2); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_build(statsEvent); + + size_t size; + uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size); + LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); + + EXPECT_FALSE(logEvent.parseBuffer(buf, size)); + EXPECT_EQ(0, logEvent.getNumUidFields()); + + AStatsEvent_release(statsEvent); +} + +TEST_P(LogEventTestBadAnnotationFieldTypes, TestAnnotationIdIsUid) { + LogEvent event(/*uid=*/0, /*pid=*/0); + + if (GetParam() != INT32_TYPE && GetParam() != LIST_TYPE) { + createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_IS_UID, true, + /*parseBufferResult*/ false); + } +} + +TEST(LogEventTest, TestAnnotationIdIsUid_NotIntAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID, 10, + /*parseBufferResult*/ false); +} + TEST(LogEventTest, TestAnnotationIdStateNested) { LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true); + createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_STATE_NESTED, true, + /*parseBufferResult*/ true); const vector<FieldValue>& values = event.getValues(); ASSERT_EQ(values.size(), 1); EXPECT_TRUE(values[0].mAnnotations.isNested()); } +TEST_P(LogEventTestBadAnnotationFieldTypes, TestAnnotationIdStateNested) { + LogEvent event(/*uid=*/0, /*pid=*/0); + + if (GetParam() != INT32_TYPE) { + createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_STATE_NESTED, true, + /*parseBufferResult*/ false); + } +} + +TEST(LogEventTest, TestAnnotationIdStateNested_NotIntAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_STATE_NESTED, 10, + /*parseBufferResult*/ false); +} + TEST(LogEventTest, TestPrimaryFieldAnnotation) { LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_PRIMARY_FIELD, true); + createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_PRIMARY_FIELD, true, + /*parseBufferResult*/ true); const vector<FieldValue>& values = event.getValues(); ASSERT_EQ(values.size(), 1); EXPECT_TRUE(values[0].mAnnotations.isPrimaryField()); } +TEST_P(LogEventTestBadAnnotationFieldTypes, TestPrimaryFieldAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + + if (GetParam() == LIST_TYPE || GetParam() == ATTRIBUTION_CHAIN_TYPE) { + createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_PRIMARY_FIELD, true, + /*parseBufferResult*/ false); + } +} + +TEST(LogEventTest, TestPrimaryFieldAnnotation_NotIntAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_PRIMARY_FIELD, 10, + /*parseBufferResult*/ false); +} + TEST(LogEventTest, TestExclusiveStateAnnotation) { LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_EXCLUSIVE_STATE, true); + createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_EXCLUSIVE_STATE, true, + /*parseBufferResult*/ true); const vector<FieldValue>& values = event.getValues(); ASSERT_EQ(values.size(), 1); EXPECT_TRUE(values[0].mAnnotations.isExclusiveState()); } +TEST_P(LogEventTestBadAnnotationFieldTypes, TestExclusiveStateAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + + if (GetParam() != INT32_TYPE) { + createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_EXCLUSIVE_STATE, + true, + /*parseBufferResult*/ false); + } +} + +TEST(LogEventTest, TestExclusiveStateAnnotation_NotIntAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_EXCLUSIVE_STATE, 10, + /*parseBufferResult*/ false); +} + TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) { // Event has 10 ints and then an attribution chain int numInts = 10; @@ -355,100 +847,72 @@ TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) { EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField()); } +TEST_P(LogEventTestBadAnnotationFieldTypes, TestPrimaryFieldFirstUidAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + + if (GetParam() != ATTRIBUTION_CHAIN_TYPE) { + createFieldWithBoolAnnotationLogEvent(&event, GetParam(), + ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true, + /*parseBufferResult*/ false); + } +} + +TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation_NotIntAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createFieldWithIntAnnotationLogEvent(&event, ATTRIBUTION_CHAIN_TYPE, + ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, 10, + /*parseBufferResult*/ false); +} + TEST(LogEventTest, TestResetStateAnnotation) { int32_t resetState = 10; LogEvent event(/*uid=*/0, /*pid=*/0); - createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_TRIGGER_STATE_RESET, resetState); + createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_TRIGGER_STATE_RESET, + resetState, /*parseBufferResult*/ true); const vector<FieldValue>& values = event.getValues(); ASSERT_EQ(values.size(), 1); EXPECT_EQ(event.getResetState(), resetState); } -TEST(LogEventTest, TestExclusiveStateAnnotationAfterTooManyFields) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - - const unsigned int numAttributionNodes = 64; - - uint32_t uids[numAttributionNodes]; - const char* tags[numAttributionNodes]; +TEST_P(LogEventTestBadAnnotationFieldTypes, TestResetStateAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + int32_t resetState = 10; - for (unsigned int i = 1; i <= numAttributionNodes; i++) { - uids[i-1] = i; - tags[i-1] = std::to_string(i).c_str(); + if (GetParam() != INT32_TYPE) { + createFieldWithIntAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_TRIGGER_STATE_RESET, + resetState, + /*parseBufferResult*/ false); } - - AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes); - AStatsEvent_writeInt32(event, 1); - AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_EXCLUSIVE_STATE, true); - - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_FALSE(logEvent.parseBuffer(buf, size)); - EXPECT_EQ(-1, logEvent.getExclusiveStateFieldIndex()); - - AStatsEvent_release(event); } -TEST(LogEventTest, TestUidAnnotationAfterTooManyFields) { - AStatsEvent* event = AStatsEvent_obtain(); - AStatsEvent_setAtomId(event, 100); - - const unsigned int numAttributionNodes = 64; +TEST(LogEventTest, TestResetStateAnnotation_NotBoolAnnotation) { + LogEvent event(/*uid=*/0, /*pid=*/0); + createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_TRIGGER_STATE_RESET, + true, + /*parseBufferResult*/ false); +} - uint32_t uids[numAttributionNodes]; - const char* tags[numAttributionNodes]; +TEST(LogEventTest, TestUidAnnotationWithInt8MaxValues) { + int32_t numElements = INT8_MAX; + int32_t int32Array[numElements]; - for (unsigned int i = 1; i <= numAttributionNodes; i++) { - uids[i-1] = i; - tags[i-1] = std::to_string(i).c_str(); + for (int i = 0; i < numElements; i++) { + int32Array[i] = i; } - AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes); - AStatsEvent_writeInt32(event, 1); - AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_IS_UID, true); - - AStatsEvent_build(event); - - size_t size; - uint8_t* buf = AStatsEvent_getBuffer(event, &size); - - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_FALSE(logEvent.parseBuffer(buf, size)); - EXPECT_EQ(0, logEvent.getNumUidFields()); - - AStatsEvent_release(event); -} - -TEST(LogEventTest, TestAttributionChainEndIndexAfterTooManyFields) { AStatsEvent* event = AStatsEvent_obtain(); AStatsEvent_setAtomId(event, 100); - - const unsigned int numAttributionNodes = 65; - - uint32_t uids[numAttributionNodes]; - const char* tags[numAttributionNodes]; - - for (unsigned int i = 1; i <= numAttributionNodes; i++) { - uids[i-1] = i; - tags[i-1] = std::to_string(i).c_str(); - } - - AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes); - + AStatsEvent_writeInt32Array(event, int32Array, numElements); + AStatsEvent_writeInt32(event, 10); + AStatsEvent_writeInt32(event, 11); + AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_IS_UID, true); AStatsEvent_build(event); size_t size; uint8_t* buf = AStatsEvent_getBuffer(event, &size); - LogEvent logEvent(/*uid=*/1000, /*pid=*/1001); - EXPECT_FALSE(logEvent.parseBuffer(buf, size)); - EXPECT_FALSE(logEvent.hasAttributionChain()); + EXPECT_TRUE(logEvent.parseBuffer(buf, size)); AStatsEvent_release(event); } diff --git a/statsd/tests/StatsLogProcessor_test.cpp b/statsd/tests/StatsLogProcessor_test.cpp index 77597fa8..4bca4528 100644 --- a/statsd/tests/StatsLogProcessor_test.cpp +++ b/statsd/tests/StatsLogProcessor_test.cpp @@ -21,11 +21,11 @@ #include "StatsService.h" #include "config/ConfigKey.h" -#include "src/stats_log.pb.h" -#include "src/statsd_config.pb.h" #include "guardrail/StatsdStats.h" #include "logd/LogEvent.h" #include "packages/UidMap.h" +#include "src/stats_log.pb.h" +#include "src/statsd_config.pb.h" #include "statslog_statsdtest.h" #include "storage/StorageManager.h" #include "tests/statsd_test_util.h" @@ -1890,6 +1890,86 @@ TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogIsolatedUidAttributionCha EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value); } +/* * + * Test cases for repeated uid fields: + * - empty field + * - single host uid + * - single isolated uid + * - multiple host uids + * - multiple isolated uids + * - multiple host and isolated uids + */ +TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogRepeatedUidField) { + int hostUid1 = 21; + int hostUid2 = 22; + int isolatedUid1 = 31; + int isolatedUid2 = 32; + uint64_t eventTimeNs = 12355; + int atomId = 89; + int field1 = 90; + int field2 = 28; + sp<MockUidMap> mockUidMap = + makeMockUidMapForHosts({{hostUid1, {isolatedUid1}}, {hostUid2, {isolatedUid2}}}); + + ConfigKey cfgKey; + StatsdConfig config = MakeConfig(false); + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap); + + // Empty repeated uid field. + shared_ptr<LogEvent> logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {}); + processor->OnLogEvent(logEvent.get()); + + const vector<FieldValue>* actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(0, actualFieldValues->size()); + + // Single host uid. + logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {hostUid1}); + processor->OnLogEvent(logEvent.get()); + + actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(1, actualFieldValues->size()); + EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value); + + // Single isolated uid. + logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {isolatedUid1}); + processor->OnLogEvent(logEvent.get()); + + actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(1, actualFieldValues->size()); + EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value); + + // Multiple host uids. + logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {hostUid1, hostUid2}); + processor->OnLogEvent(logEvent.get()); + + actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(2, actualFieldValues->size()); + EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value); + + // Multiple isolated uids. + logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {isolatedUid1, isolatedUid2}); + processor->OnLogEvent(logEvent.get()); + + actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(2, actualFieldValues->size()); + EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value); + + // Multiple host and isolated uids. + logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, + {isolatedUid1, hostUid2, isolatedUid2, hostUid1}); + processor->OnLogEvent(logEvent.get()); + + actualFieldValues = &logEvent->getValues(); + ASSERT_EQ(4, actualFieldValues->size()); + EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostUid2, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(hostUid1, actualFieldValues->at(3).mValue.int_value); +} + TEST_P(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimestamp) { int hostUid = 20; int isolatedUid = 30; diff --git a/statsd/tests/e2e/CountMetric_e2e_test.cpp b/statsd/tests/e2e/CountMetric_e2e_test.cpp index 8ea8861a..ef183b7a 100644 --- a/statsd/tests/e2e/CountMetric_e2e_test.cpp +++ b/statsd/tests/e2e/CountMetric_e2e_test.cpp @@ -965,6 +965,788 @@ TEST(CountMetricE2eTest, TestUploadThreshold) { 3); } +TEST(CountMetricE2eTest, TestRepeatedFieldsAndEmptyArrays) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedAtomMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedAtomMatcher; + + int64_t metricId = 123456; + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(testAtomReportedAtomMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + + // Initialize StatsLogProcessor. + ConfigKey cfgKey(123, 987); + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + vector<int> intArray = {3, 6}; + vector<int64_t> longArray = {1000L, 10002L}; + vector<float> floatArray = {0.3f, 0.09f}; + vector<string> stringArray = {"str1", "str2"}; + int boolArrayLength = 2; + bool boolArray[boolArrayLength]; + boolArray[0] = 1; + boolArray[1] = 0; + vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 10 * NS_PER_SEC, intArray, longArray, floatArray, stringArray, + boolArray, boolArrayLength, enumArray)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {})); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(1, countMetrics.data_size()); + + CountMetricData data = countMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 2); +} + +TEST(CountMetricE2eTest, TestMatchRepeatedFieldPositionAny) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedStateAnyOnAtomMatcher = + CreateTestAtomRepeatedStateAnyOnAtomMatcher(); + *config.add_atom_matcher() = testAtomReportedStateAnyOnAtomMatcher; + + int64_t metricId = 123456; + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(testAtomReportedStateAnyOnAtomMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + + // Initialize StatsLogProcessor. + ConfigKey cfgKey(123, 987); + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + vector<int> enumArrayOnFirst = {TestAtomReported::ON, TestAtomReported::OFF}; + vector<int> enumArrayOnLast = {TestAtomReported::OFF, TestAtomReported::ON}; + vector<int> enumArrayNoOn = {TestAtomReported::OFF, TestAtomReported::OFF}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnFirst)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayNoOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 60 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnLast)); + // No matching is done on empty array. + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 80 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {})); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(1, countMetrics.data_size()); + + CountMetricData data = countMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 2); +} + +TEST(CountMetricE2eTest, TestRepeatedFieldDimension_PositionFirst) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedAtomMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedAtomMatcher; + + int64_t metricId = 123456; + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(testAtomReportedAtomMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions( + util::TEST_ATOM_REPORTED, {14 /*repeated_enum_field*/}, {Position::FIRST}); + + // Initialize StatsLogProcessor. + ConfigKey cfgKey(2000, 921); + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + vector<int> enumArrayOnOff = {TestAtomReported::ON, TestAtomReported::OFF}; + vector<int> enumArrayOnOn = {TestAtomReported::ON, TestAtomReported::ON}; + vector<int> enumArrayOffOn = {TestAtomReported::OFF, TestAtomReported::ON}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOff)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 60 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 80 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {})); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(3, countMetrics.data_size()); + + // Empty dimensions case. + CountMetricData data = countMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 0); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), + TestAtomReported::OFF); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 2); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), + TestAtomReported::ON); +} + +TEST(CountMetricE2eTest, TestRepeatedFieldDimension_PositionLast) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedAtomMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedAtomMatcher; + + int64_t metricId = 123456; + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(testAtomReportedAtomMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions( + util::TEST_ATOM_REPORTED, {14 /*repeated_enum_field*/}, {Position::LAST}); + + // Initialize StatsLogProcessor. + ConfigKey cfgKey(2000, 921); + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + vector<int> enumArrayOnOff = {TestAtomReported::ON, TestAtomReported::OFF}; + vector<int> enumArrayOffOff = {TestAtomReported::OFF, TestAtomReported::OFF}; + vector<int> enumArrayOffOn = {TestAtomReported::OFF, TestAtomReported::ON}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOff)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOff)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 60 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn)); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(2, countMetrics.data_size()); + + CountMetricData data = countMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 2); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), + TestAtomReported::OFF); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), + TestAtomReported::ON); +} + +TEST(CountMetricE2eTest, TestRepeatedFieldDimension_PositionAll) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedAtomMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedAtomMatcher; + + int64_t metricId = 123456; + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(testAtomReportedAtomMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions( + util::TEST_ATOM_REPORTED, {14 /*repeated_enum_field*/}, {Position::ALL}); + + // Initialize StatsLogProcessor. + ConfigKey cfgKey(2000, 921); + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + vector<int> enumArrayOnOff = {TestAtomReported::ON, TestAtomReported::OFF}; + vector<int> enumArrayOnOn = {TestAtomReported::ON, TestAtomReported::ON}; + vector<int> enumArrayOffOn = {TestAtomReported::OFF, TestAtomReported::ON}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOff)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 60 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOff)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn)); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + // Don't need to backfill dimension path because dimensions with position ALL are not encoded + // with the path format. + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(3, countMetrics.data_size()); + + CountMetricData data = countMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 3); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), + TestAtomReported::OFF); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), + TestAtomReported::ON); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 2); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), + TestAtomReported::ON); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), + TestAtomReported::OFF); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), + TestAtomReported::ON); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), + TestAtomReported::ON); +} + +TEST(CountMetricE2eTest, TestMultipleRepeatedFieldDimensions_PositionFirst) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedAtomMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedAtomMatcher; + + int64_t metricId = 123456; + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(testAtomReportedAtomMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions( + util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/, 14 /*repeated_enum_field*/}, + {Position::FIRST, Position::FIRST}); + + // Initialize StatsLogProcessor. + ConfigKey cfgKey(2000, 921); + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + vector<int> intArrayThree = {3, 6, 9}; + vector<int> intArraySix = {6, 9}; + vector<int> enumArrayOn = {TestAtomReported::ON, TestAtomReported::OFF}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, intArrayThree, {}, {}, {}, {}, 0, enumArrayOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 40 * NS_PER_SEC, intArraySix, {}, {}, {}, {}, 0, enumArrayOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 60 * NS_PER_SEC, intArrayThree, {}, {}, {}, {}, 0, enumArrayOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 80 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOn)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 100 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {})); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 120 * NS_PER_SEC, intArraySix, {}, {}, {}, {}, 0, {})); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); + + CountMetricData data = countMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 0); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 6); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), + TestAtomReported::ON); + + data = countMetrics.data(3); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 2); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 3); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), + TestAtomReported::ON); + + data = countMetrics.data(4); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 6); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), + TestAtomReported::ON); +} + +TEST(CountMetricE2eTest, TestMultipleRepeatedFieldDimensions_PositionAll) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedAtomMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedAtomMatcher; + + int64_t metricId = 123456; + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(testAtomReportedAtomMatcher.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions( + util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/, 14 /*repeated_enum_field*/}, + {Position::ALL, Position::ALL}); + + // Initialize StatsLogProcessor. + ConfigKey cfgKey(2000, 921); + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + vector<int> intArray1 = {3, 6}; + vector<int> intArray2 = {6, 9}; + vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF}; + + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, intArray1, {}, {}, {}, {}, 0, enumArray)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 40 * NS_PER_SEC, intArray2, {}, {}, {}, {}, 0, enumArray)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 80 * NS_PER_SEC, intArray1, {}, {}, {}, {}, 0, enumArray)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 100 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArray)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 120 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {})); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 140 * NS_PER_SEC, intArray2, {}, {}, {}, {}, 0, {})); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(5, countMetrics.data_size()); + + CountMetricData data = countMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 0); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 6); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), 9); + + data = countMetrics.data(2); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), + TestAtomReported::ON); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), + TestAtomReported::OFF); + + data = countMetrics.data(3); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 2); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 4); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 3); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), 6); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(2).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(2).value_int(), + TestAtomReported::ON); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(3).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(3).value_int(), + TestAtomReported::OFF); + + data = countMetrics.data(4); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field()); + ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 4); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 6); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), 9); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(2).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(2).value_int(), + TestAtomReported::ON); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(3).field(), 14); + EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(3).value_int(), + TestAtomReported::OFF); +} + +TEST(CountMetricE2eTest, TestConditionSlicedByRepeatedUidWithUidDimension) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher uidProcessStateChangedAtomMatcher = CreateUidProcessStateChangedAtomMatcher(); + AtomMatcher repeatedStateFirstOffAtomMatcher = CreateTestAtomRepeatedStateFirstOffAtomMatcher(); + AtomMatcher repeatedStateFirstOnAtomMatcher = CreateTestAtomRepeatedStateFirstOnAtomMatcher(); + *config.add_atom_matcher() = uidProcessStateChangedAtomMatcher; + *config.add_atom_matcher() = repeatedStateFirstOffAtomMatcher; + *config.add_atom_matcher() = repeatedStateFirstOnAtomMatcher; + + Predicate testAtomRepeatedStateFirstOffPerUidPredicate = + CreateTestAtomRepeatedStateFirstOffPredicate(); + FieldMatcher* dimensions = + testAtomRepeatedStateFirstOffPerUidPredicate.mutable_simple_predicate() + ->mutable_dimensions(); + *dimensions = CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /* repeated uid*/}, + {Position::FIRST}); + *config.add_predicate() = testAtomRepeatedStateFirstOffPerUidPredicate; + + int64_t metricId = 123456; + CountMetric* countMetric = config.add_count_metric(); + countMetric->set_id(metricId); + countMetric->set_what(uidProcessStateChangedAtomMatcher.id()); + countMetric->set_condition(testAtomRepeatedStateFirstOffPerUidPredicate.id()); + countMetric->set_bucket(TimeUnit::FIVE_MINUTES); + *countMetric->mutable_dimensions_in_what() = + CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/}); + MetricConditionLink* links = countMetric->add_links(); + links->set_condition(testAtomRepeatedStateFirstOffPerUidPredicate.id()); + *links->mutable_fields_in_what() = + CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /* uid*/}); + *links->mutable_fields_in_condition() = CreateRepeatedDimensions( + util::TEST_ATOM_REPORTED, {9 /* repeated uid*/}, {Position::FIRST}); + + // Initialize StatsLogProcessor. + ConfigKey cfgKey(2000, 921); + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + const uint64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL; + const uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + vector<int> intArray1 = {1, 2}; + vector<int> intArray2 = {2, 1}; + vector<int> enumArrayOn = {TestAtomReported::ON, TestAtomReported::OFF}; + vector<int> enumArrayOff = {TestAtomReported::OFF, TestAtomReported::ON}; + + std::vector<std::unique_ptr<LogEvent>> events; + // Set condition to true for uid 1. + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, intArray1, {}, {}, {}, {}, 0, enumArrayOff)); + + // Uid 1 process state changed. + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 40 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); + // Uid 2 process state changed. Should not be counted. + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 60 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); + + // Set condition to true for uid 2. + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 80 * NS_PER_SEC, intArray2, {}, {}, {}, {}, 0, enumArrayOff)); + // Uid 1 process state changed. + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 100 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); + // Uid 2 process state changed. + events.push_back(CreateUidProcessStateChangedEvent( + bucketStartTimeNs + 120 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); + + // Bucket 2 + // Set condition to false for uid 1. + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucket2StartTimeNs + 20 * NS_PER_SEC, intArray1, {}, {}, {}, {}, 0, enumArrayOn)); + // Uid 1 process state changed. Should not be counted. + events.push_back(CreateUidProcessStateChangedEvent( + bucket2StartTimeNs + 40 * NS_PER_SEC, 1 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); + // Uid 2 process state changed. + events.push_back(CreateUidProcessStateChangedEvent( + bucket2StartTimeNs + 60 * NS_PER_SEC, 2 /*uid*/, + android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + // Check dump report. + vector<uint8_t> buffer; + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucket2StartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP, + FAST, &buffer); + ASSERT_GT(buffer.size(), 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics()); + StatsLogReport::CountMetricDataWrapper countMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics); + ASSERT_EQ(2, countMetrics.data_size()); + + CountMetricData data = countMetrics.data(0); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(1, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 2); + + data = countMetrics.data(1); + ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size()); + EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value(0).field()); + EXPECT_EQ(2, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int()); + ASSERT_EQ(2, data.bucket_info_size()); + ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs, + 1); + ValidateCountBucket(data.bucket_info(1), bucket2StartTimeNs, bucket2StartTimeNs + bucketSizeNs, + 1); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/statsd/tests/e2e/DurationMetric_e2e_test.cpp index 8a4c9627..32023688 100644 --- a/statsd/tests/e2e/DurationMetric_e2e_test.cpp +++ b/statsd/tests/e2e/DurationMetric_e2e_test.cpp @@ -1562,6 +1562,93 @@ TEST(DurationMetricE2eTest, TestUploadThreshold) { EXPECT_EQ(baseTimeNs + bucketSizeNs * 2, data.bucket_info(0).end_bucket_elapsed_nanos()); } +TEST(DurationMetricE2eTest, TestConditionOnRepeatedEnumField) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher repeatedStateFirstOffAtomMatcher = CreateTestAtomRepeatedStateFirstOffAtomMatcher(); + AtomMatcher repeatedStateFirstOnAtomMatcher = CreateTestAtomRepeatedStateFirstOnAtomMatcher(); + *config.add_atom_matcher() = repeatedStateFirstOffAtomMatcher; + *config.add_atom_matcher() = repeatedStateFirstOnAtomMatcher; + + Predicate durationPredicate = CreateTestAtomRepeatedStateFirstOffPredicate(); + *config.add_predicate() = durationPredicate; + + int64_t metricId = 123456; + DurationMetric* durationMetric = config.add_duration_metric(); + durationMetric->set_id(metricId); + durationMetric->set_what(durationPredicate.id()); + durationMetric->set_bucket(FIVE_MINUTES); + durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM); + + const int64_t baseTimeNs = 0; // 0:00 + const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01 + const int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL; + + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey); + + vector<int> intArray = {3, 6}; + vector<int64_t> longArray = {1000L, 10002L}; + vector<float> floatArray = {0.3f, 0.09f}; + vector<string> stringArray = {"str1", "str2"}; + int boolArrayLength = 2; + bool boolArray[boolArrayLength]; + boolArray[0] = 1; + boolArray[1] = 0; + vector<int> enumArrayOff = {TestAtomReported::OFF, TestAtomReported::ON}; + vector<int> enumArrayOn = {TestAtomReported::ON, TestAtomReported::OFF}; + + std::vector<std::unique_ptr<LogEvent>> events; + uint64_t falseDurationStartNs = configAddedTimeNs + 10 * NS_PER_SEC; + uint64_t durationStartNs = configAddedTimeNs + 20 * NS_PER_SEC; + uint64_t durationEndNs = durationStartNs + 50 * NS_PER_SEC; + + // Condition false + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(falseDurationStartNs, {}, {}, + {}, {}, {}, 0, enumArrayOn)); + // Condition true - start collecting duration. + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(durationStartNs, {}, {}, {}, + {}, {}, 0, enumArrayOff)); + // Condition false - stop collecting duration. + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(durationEndNs, {}, {}, {}, + {}, {}, 0, enumArrayOn)); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + vector<uint8_t> buffer; + processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true, + ADB_DUMP, FAST, &buffer); // 10:01 + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStartEndTimestamp(&reports); + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id()); + EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics()); + + StatsLogReport::DurationMetricDataWrapper durationMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(), + &durationMetrics); + ASSERT_EQ(1, durationMetrics.data_size()); + + DurationMetricData data = durationMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos()); + EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos()); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/statsd/tests/e2e/EventMetric_e2e_test.cpp b/statsd/tests/e2e/EventMetric_e2e_test.cpp index 0f66a5a0..81a6d912 100644 --- a/statsd/tests/e2e/EventMetric_e2e_test.cpp +++ b/statsd/tests/e2e/EventMetric_e2e_test.cpp @@ -101,6 +101,162 @@ TEST_F(EventMetricE2eTest, TestEventMetricDataAggregated) { EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl2"); } +TEST_F(EventMetricE2eTest, TestRepeatedFieldsAndEmptyArrays) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedAtomMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedAtomMatcher; + + EventMetric testAtomReportedEventMetric = + createEventMetric("EventTestAtomReported", testAtomReportedAtomMatcher.id(), nullopt); + *config.add_event_metric() = testAtomReportedEventMetric; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + // Initialize log events before update. + std::vector<std::unique_ptr<LogEvent>> events; + + vector<int> intArray = {3, 6}; + vector<int64_t> longArray = {1000L, 10002L}; + vector<float> floatArray = {0.3f, 0.09f}; + vector<string> stringArray = {"str1", "str2"}; + int boolArrayLength = 2; + bool boolArray[boolArrayLength]; + boolArray[0] = 1; + boolArray[1] = 0; + vector<bool> boolArrayVector = {1, 0}; + vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF}; + + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 10 * NS_PER_SEC, intArray, longArray, floatArray, stringArray, + boolArray, boolArrayLength, enumArray)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {})); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 30 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArray)); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + uint64_t dumpTimeNs = bucketStartTimeNs + 100 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector<uint8_t> buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + backfillAggregatedAtoms(&reports); + ASSERT_EQ(reports.reports_size(), 1); + + ConfigMetricsReport report = reports.reports(0); + ASSERT_EQ(report.metrics_size(), 1); + StatsLogReport testAtomEventMetricReport = report.metrics(0); + EXPECT_EQ(testAtomEventMetricReport.metric_id(), testAtomReportedEventMetric.id()); + EXPECT_TRUE(testAtomEventMetricReport.has_event_metrics()); + ASSERT_EQ(testAtomEventMetricReport.event_metrics().data_size(), 3); + + EventMetricData data = testAtomEventMetricReport.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 10 * NS_PER_SEC); + TestAtomReported atom = data.atom().test_atom_reported(); + EXPECT_THAT(atom.repeated_int_field(), ElementsAreArray(intArray)); + EXPECT_THAT(atom.repeated_long_field(), ElementsAreArray(longArray)); + EXPECT_THAT(atom.repeated_float_field(), ElementsAreArray(floatArray)); + EXPECT_THAT(atom.repeated_string_field(), ElementsAreArray(stringArray)); + EXPECT_THAT(atom.repeated_boolean_field(), ElementsAreArray(boolArrayVector)); + EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray)); + + data = testAtomEventMetricReport.event_metrics().data(1); + atom = data.atom().test_atom_reported(); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 20 * NS_PER_SEC); + EXPECT_EQ(atom.repeated_int_field_size(), 0); + EXPECT_EQ(atom.repeated_long_field_size(), 0); + EXPECT_EQ(atom.repeated_float_field_size(), 0); + EXPECT_EQ(atom.repeated_string_field_size(), 0); + EXPECT_EQ(atom.repeated_boolean_field_size(), 0); + EXPECT_EQ(atom.repeated_enum_field_size(), 0); + + data = testAtomEventMetricReport.event_metrics().data(2); + atom = data.atom().test_atom_reported(); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 30 * NS_PER_SEC); + EXPECT_EQ(atom.repeated_int_field_size(), 0); + EXPECT_EQ(atom.repeated_long_field_size(), 0); + EXPECT_EQ(atom.repeated_float_field_size(), 0); + EXPECT_EQ(atom.repeated_string_field_size(), 0); + EXPECT_EQ(atom.repeated_boolean_field_size(), 0); + EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray)); +} + +TEST_F(EventMetricE2eTest, TestMatchRepeatedFieldPositionFirst) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedStateFirstOnAtomMatcher = + CreateTestAtomRepeatedStateFirstOnAtomMatcher(); + *config.add_atom_matcher() = testAtomReportedStateFirstOnAtomMatcher; + + EventMetric testAtomReportedEventMetric = createEventMetric( + "EventTestAtomReported", testAtomReportedStateFirstOnAtomMatcher.id(), nullopt); + *config.add_event_metric() = testAtomReportedEventMetric; + + ConfigKey key(123, 987); + uint64_t bucketStartTimeNs = 10000000000; // 0:10 + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key); + + // Initialize log events before update. + std::vector<std::unique_ptr<LogEvent>> events; + + vector<int> enumArrayNoMatch = {TestAtomReported::OFF, TestAtomReported::ON}; + vector<int> enumArrayMatch = {TestAtomReported::ON, TestAtomReported::OFF}; + + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 10 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayNoMatch)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayMatch)); + // No matching is done on an empty array. + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 30 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {})); + + // Send log events to StatsLogProcessor. + for (auto& event : events) { + processor->OnLogEvent(event.get()); + } + + uint64_t dumpTimeNs = bucketStartTimeNs + 100 * NS_PER_SEC; + ConfigMetricsReportList reports; + vector<uint8_t> buffer; + processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + backfillAggregatedAtoms(&reports); + ASSERT_EQ(reports.reports_size(), 1); + + ConfigMetricsReport report = reports.reports(0); + ASSERT_EQ(report.metrics_size(), 1); + StatsLogReport testAtomEventMetricReport = report.metrics(0); + EXPECT_EQ(testAtomEventMetricReport.metric_id(), testAtomReportedEventMetric.id()); + EXPECT_TRUE(testAtomEventMetricReport.has_event_metrics()); + ASSERT_EQ(testAtomEventMetricReport.event_metrics().data_size(), 1); + + EventMetricData data = testAtomEventMetricReport.event_metrics().data(0); + EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 20 * NS_PER_SEC); + TestAtomReported atom = data.atom().test_atom_reported(); + ASSERT_EQ(atom.repeated_int_field_size(), 0); + ASSERT_EQ(atom.repeated_long_field_size(), 0); + ASSERT_EQ(atom.repeated_float_field_size(), 0); + ASSERT_EQ(atom.repeated_string_field_size(), 0); + ASSERT_EQ(atom.repeated_boolean_field_size(), 0); + EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArrayMatch)); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp index a4489bac..f5a01582 100644 --- a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp +++ b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp @@ -69,6 +69,50 @@ StatsdConfig CreateStatsdConfigForPushedEvent(const GaugeMetric::SamplingType sa return config; } +StatsdConfig CreateStatsdConfigForRepeatedFieldsPushedEvent( + const GaugeMetric::SamplingType sampling_type) { + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedAtomMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedAtomMatcher; + + GaugeMetric* gaugeMetric = config.add_gauge_metric(); + gaugeMetric->set_id(123456); + gaugeMetric->set_what(testAtomReportedAtomMatcher.id()); + gaugeMetric->set_sampling_type(sampling_type); + FieldMatcher* fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); + fieldMatcher->set_field(util::TEST_ATOM_REPORTED); + + FieldMatcher* childFieldMatcher = fieldMatcher->add_child(); + childFieldMatcher->set_field(9); // repeated_int_field + childFieldMatcher->set_position(Position::FIRST); + + childFieldMatcher = fieldMatcher->add_child(); + childFieldMatcher->set_field(10); // repeated_long_field + childFieldMatcher->set_position(Position::LAST); + + childFieldMatcher = fieldMatcher->add_child(); + childFieldMatcher->set_field(11); // repeated_float_field + childFieldMatcher->set_position(Position::ALL); + + childFieldMatcher = fieldMatcher->add_child(); + childFieldMatcher->set_field(12); // repeated_string_field + childFieldMatcher->set_position(Position::FIRST); + + childFieldMatcher = fieldMatcher->add_child(); + childFieldMatcher->set_field(13); // repeated_boolean_field + childFieldMatcher->set_position(Position::LAST); + + childFieldMatcher = fieldMatcher->add_child(); + childFieldMatcher->set_field(14); // repeated_enum_field + childFieldMatcher->set_position(Position::ALL); + + gaugeMetric->set_bucket(FIVE_MINUTES); + return config; +} + } // namespace // Setup for test fixture. @@ -277,6 +321,99 @@ TEST_F(GaugeMetricE2ePushedTest, TestMultipleFieldsForPushedEvent) { } } +TEST_F(GaugeMetricE2ePushedTest, TestRepeatedFieldsForPushedEvent) { + for (const auto& sampling_type : + {GaugeMetric::FIRST_N_SAMPLES, GaugeMetric::RANDOM_ONE_SAMPLE}) { + StatsdConfig config = CreateStatsdConfigForRepeatedFieldsPushedEvent(sampling_type); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + std::vector<std::unique_ptr<LogEvent>> events; + + vector<int> intArray = {3, 6}; + vector<int64_t> longArray = {1000L, 10002L}; + vector<float> floatArray = {0.3f, 0.09f}; + vector<string> stringArray = {"str1", "str2"}; + int boolArrayLength = 2; + bool boolArray[boolArrayLength]; + boolArray[0] = 1; + boolArray[1] = 0; + vector<bool> boolArrayVector = {1, 0}; + vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF}; + + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 10 * NS_PER_SEC, intArray, longArray, floatArray, stringArray, + boolArray, boolArrayLength, enumArray)); + events.push_back(CreateTestAtomReportedEventVariableRepeatedFields( + bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {})); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + + ConfigMetricsReportList reports; + vector<uint8_t> buffer; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, false, true, ADB_DUMP, + FAST, &buffer); + EXPECT_TRUE(buffer.size() > 0); + EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size())); + backfillDimensionPath(&reports); + backfillStringInReport(&reports); + backfillStartEndTimestamp(&reports); + backfillAggregatedAtoms(&reports); + + ASSERT_EQ(1, reports.reports_size()); + ASSERT_EQ(1, reports.reports(0).metrics_size()); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), + &gaugeMetrics); + ASSERT_EQ(1, gaugeMetrics.data_size()); + + GaugeMetricData data = gaugeMetrics.data(0); + ASSERT_EQ(1, data.bucket_info_size()); + if (sampling_type == GaugeMetric::FIRST_N_SAMPLES) { + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, + data.bucket_info(0).end_bucket_elapsed_nanos()); + ASSERT_EQ(2, data.bucket_info(0).atom_size()); + + TestAtomReported atom = data.bucket_info(0).atom(0).test_atom_reported(); + EXPECT_THAT(atom.repeated_int_field(), ElementsAreArray({3})); + EXPECT_THAT(atom.repeated_long_field(), ElementsAreArray({10002L})); + EXPECT_THAT(atom.repeated_float_field(), ElementsAreArray(floatArray)); + EXPECT_THAT(atom.repeated_string_field(), ElementsAreArray({"str1"})); + EXPECT_THAT(atom.repeated_boolean_field(), ElementsAreArray({0})); + EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray)); + + atom = data.bucket_info(0).atom(1).test_atom_reported(); + EXPECT_EQ(atom.repeated_int_field_size(), 0); + EXPECT_EQ(atom.repeated_long_field_size(), 0); + EXPECT_EQ(atom.repeated_float_field_size(), 0); + EXPECT_EQ(atom.repeated_string_field_size(), 0); + EXPECT_EQ(atom.repeated_boolean_field_size(), 0); + EXPECT_EQ(atom.repeated_enum_field_size(), 0); + } else { + EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos()); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, + data.bucket_info(0).end_bucket_elapsed_nanos()); + ASSERT_EQ(1, data.bucket_info(0).atom_size()); + + TestAtomReported atom = data.bucket_info(0).atom(0).test_atom_reported(); + EXPECT_THAT(atom.repeated_int_field(), ElementsAreArray({3})); + EXPECT_THAT(atom.repeated_long_field(), ElementsAreArray({10002L})); + EXPECT_THAT(atom.repeated_float_field(), ElementsAreArray(floatArray)); + EXPECT_THAT(atom.repeated_string_field(), ElementsAreArray({"str1"})); + EXPECT_THAT(atom.repeated_boolean_field(), ElementsAreArray({0})); + EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray)); + } + } +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/statsd/tests/e2e/KllMetric_e2e_test.cpp b/statsd/tests/e2e/KllMetric_e2e_test.cpp index aae5bae9..b62a133a 100644 --- a/statsd/tests/e2e/KllMetric_e2e_test.cpp +++ b/statsd/tests/e2e/KllMetric_e2e_test.cpp @@ -89,6 +89,36 @@ TEST_F(KllMetricE2eTest, TestSimpleMetric) { EXPECT_EQ(metricReport.kll_metrics().skipped_size(), 0); } +TEST_F(KllMetricE2eTest, TestInitWithKllFieldPositionALL) { + // Create config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedMatcher; + + // Create kll metric. + int64_t metricId = 123456; + KllMetric* kllMetric = config.add_kll_metric(); + kllMetric->set_id(metricId); + kllMetric->set_bucket(TimeUnit::FIVE_MINUTES); + kllMetric->set_what(testAtomReportedMatcher.id()); + *kllMetric->mutable_kll_field() = CreateRepeatedDimensions( + util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::ALL}); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Config initialization fails. + ASSERT_EQ(0, processor->mMetricsManagers.size()); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp index 48fa64c2..385a4730 100644 --- a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp +++ b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp @@ -764,6 +764,36 @@ TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions) { ASSERT_EQ(0, processor->mMetricsManagers.size()); } +TEST(ValueMetricE2eTest, TestInitWithValueFieldPositionALL) { + // Create config. + StatsdConfig config; + config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root. + + AtomMatcher testAtomReportedMatcher = + CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED); + *config.add_atom_matcher() = testAtomReportedMatcher; + + // Create value metric. + int64_t metricId = 123456; + ValueMetric* valueMetric = config.add_value_metric(); + valueMetric->set_id(metricId); + valueMetric->set_bucket(TimeUnit::FIVE_MINUTES); + valueMetric->set_what(testAtomReportedMatcher.id()); + *valueMetric->mutable_value_field() = CreateRepeatedDimensions( + util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::ALL}); + + // Initialize StatsLogProcessor. + const uint64_t bucketStartTimeNs = 10000000000; // 0:10 + int uid = 12345; + int64_t cfgId = 98765; + ConfigKey cfgKey(uid, cfgId); + sp<StatsLogProcessor> processor = + CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey); + + // Config initialization fails. + ASSERT_EQ(0, processor->mMetricsManagers.size()); +} + #else GTEST_LOG_(INFO) << "This test does nothing.\n"; #endif diff --git a/statsd/tests/external/puller_util_test.cpp b/statsd/tests/external/puller_util_test.cpp index 0fca512c..25c0c7eb 100644 --- a/statsd/tests/external/puller_util_test.cpp +++ b/statsd/tests/external/puller_util_test.cpp @@ -430,6 +430,217 @@ TEST(PullerUtilTest, MultipleIsolatedUidToOneHostUidAttributionChain) { actualFieldValues->at(5).mValue.int_value); } +// Test that repeated fields are treated as non-additive fields even when marked as additive. +TEST(PullerUtilTest, RepeatedAdditiveField) { + vector<int> int32Array1 = {3, 6}; + vector<int> int32Array2 = {6, 9}; + + vector<shared_ptr<LogEvent>> data = { + // 30->22->{3,6} + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData, + int32Array1), + + // 30->22->{6,9} + makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData, + int32Array2), + + // 20->22->{3,6} + makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, int32Array1), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(2, (int)data.size()); + // Events 1 and 3 are merged - non-additive fields, including the repeated additive field, are + // equal. + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(4, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(3, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(6, actualFieldValues->at(3).mValue.int_value); + + // Event 2 isn't merged - repeated additive field is not equal. + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(4, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(6, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(9, actualFieldValues->at(3).mValue.int_value); +} + +// Test that repeated uid events are sorted and merged correctly. +TEST(PullerUtilTest, RepeatedUidField) { + vector<int> uidArray1 = {isolatedUid1, hostUid}; + vector<int> uidArray2 = {isolatedUid1, isolatedUid3}; + vector<int> uidArray3 = {isolatedUid1, hostUid, isolatedUid2}; + + vector<shared_ptr<LogEvent>> data = { + // {30, 20}->22->21 + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostNonAdditiveData, + hostAdditiveData), + + // {30, 3000}->22->21 (different uid, not merged) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray2, hostNonAdditiveData, + hostAdditiveData), + + // {30, 20}->22->31 (different additive field, merged) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostNonAdditiveData, + isolatedAdditiveData), + + // {30, 20}->32->21 (different non-additive field, not merged) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, isolatedNonAdditiveData, + hostAdditiveData), + + // {30, 20, 40}->22->21 (different repeated uid length, not merged) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray3, hostNonAdditiveData, + hostAdditiveData), + + // {30, 20}->22->21 (same as first event, merged) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostNonAdditiveData, + hostAdditiveData), + }; + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields); + + ASSERT_EQ(4, (int)data.size()); + // Events 1 and 3 and 6 are merged. + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(4, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(hostAdditiveData + isolatedAdditiveData + hostAdditiveData, + actualFieldValues->at(3).mValue.int_value); + + // Event 4 isn't merged - different non-additive data. + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(4, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(3).mValue.int_value); + + // Event 2 isn't merged - different uid. + actualFieldValues = &data[2]->getValues(); + ASSERT_EQ(4, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(3).mValue.int_value); + + // Event 5 isn't merged - different repeated uid length. + actualFieldValues = &data[3]->getValues(); + ASSERT_EQ(5, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(3).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(4).mValue.int_value); +} + +// Test that repeated uid events with multiple repeated non-additive fields are sorted and merged +// correctly. +TEST(PullerUtilTest, MultipleRepeatedFields) { + vector<int> uidArray1 = {isolatedUid1, hostUid}; + vector<int> uidArray2 = {isolatedUid1, isolatedUid3}; + vector<int> uidArray3 = {isolatedUid1, hostUid, isolatedUid2}; + + vector<int> nonAdditiveArray1 = {1, 2, 3}; + vector<int> nonAdditiveArray2 = {1, 5, 3}; + vector<int> nonAdditiveArray3 = {1, 2}; + + const vector<int> secondAdditiveField = {2}; + + vector<shared_ptr<LogEvent>> data = { + // TODO: Once b/224880904 is fixed, can use different additive data without + // having the sort order messed up. + + // Event 1 {30, 20}->21->{1, 2, 3} (merged with event 4) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostAdditiveData, + nonAdditiveArray1), + + // Event 2 {30, 3000}->21->{1, 2, 3} (different uid, not merged) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray2, hostAdditiveData, + nonAdditiveArray1), + + // Event 3 {30, 20, 40}->21->{1, 2} (different repeated fields with total length equal + // to event 1, merged with event 6) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray3, hostAdditiveData, + nonAdditiveArray3), + + // Event 4 {30, 20}->21->{1, 2, 3} (merged with event 1) + // TODO: once sorting bug is fixed, can change this additive field + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostAdditiveData, + nonAdditiveArray1), + + // Event 5 {30, 20}->21->{1, 5, 3} (different repeated field, not merged) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostAdditiveData, + nonAdditiveArray2), + + // Event 6 {30, 20, 40}->22->{1, 2} (different repeated fields with total length equal + // to event 1, merged with event 3) + makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray3, isolatedAdditiveData, + nonAdditiveArray3), + }; + + // Expected event ordering after the sort: + // Event 3 {30, 20, 40}->21->{1, 2} (total size equal to event 1, merged with event 6) + // Event 6 {30, 20, 40}->22->{1, 2} (total size equal to event 1, merged with event 3) + // Event 1 {30, 20}->21->{1, 2, 3} + // Event 4 {30, 20}->21->{1, 2, 3} (merged with event 1) + // Event 5 {30, 20}->21->{1, 5, 3} (different repeated field, not merged) + // Event 2 {30, 3000}->21->{1, 2, 3} (different uid, not merged) + + sp<MockUidMap> uidMap = makeMockUidMap(); + mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, secondAdditiveField); + + ASSERT_EQ(4, (int)data.size()); + + // Events 3 and 6 are merged. Not merged with event 1 because different repeated uids and + // fields, though length is same. + const vector<FieldValue>* actualFieldValues = &data[0]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(3).mValue.int_value); + EXPECT_EQ(1, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(2, actualFieldValues->at(5).mValue.int_value); + + // Events 1 and 4 are merged. + actualFieldValues = &data[1]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData + hostAdditiveData, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(1, actualFieldValues->at(3).mValue.int_value); + EXPECT_EQ(2, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(3, actualFieldValues->at(5).mValue.int_value); + + // Event 5 isn't merged - different repeated field. + actualFieldValues = &data[2]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(1, actualFieldValues->at(3).mValue.int_value); + EXPECT_EQ(5, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(3, actualFieldValues->at(5).mValue.int_value); + + // Event 2 isn't merged - different uid. + actualFieldValues = &data[3]->getValues(); + ASSERT_EQ(6, actualFieldValues->size()); + EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value); + EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value); + EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value); + EXPECT_EQ(1, actualFieldValues->at(3).mValue.int_value); + EXPECT_EQ(2, actualFieldValues->at(4).mValue.int_value); + EXPECT_EQ(3, actualFieldValues->at(5).mValue.int_value); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/statsd/tests/metrics/KllMetricProducer_test.cpp b/statsd/tests/metrics/KllMetricProducer_test.cpp index 334a21b8..e84efb04 100644 --- a/statsd/tests/metrics/KllMetricProducer_test.cpp +++ b/statsd/tests/metrics/KllMetricProducer_test.cpp @@ -121,7 +121,8 @@ public: TimeUnitToBucketSizeInMillisGuardrailed(kConfigKey.GetUid(), metric.bucket())); const bool containsAnyPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); - const bool sliceByPositionAll = HasPositionALL(metric.dimensions_in_what()); + const bool shouldUseNestedDimensions = + ShouldUseNestedDimensions(metric.dimensions_in_what()); vector<Matcher> fieldMatchers; translateFieldMatcher(metric.kll_field(), &fieldMatchers); @@ -139,7 +140,8 @@ public: kConfigKey, metric, protoHash, {/*pullAtomId=*/-1, /*pullerManager=*/nullptr}, {timeBaseNs, startTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(), /*conditionCorrectionThresholdNs=*/nullopt, metric.split_bucket_for_app_upgrade()}, - {containsAnyPositionInDimensionsInWhat, sliceByPositionAll, logEventMatcherIndex, + {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, + logEventMatcherIndex, /*eventMatcherWizard=*/nullptr, metric.dimensions_in_what(), fieldMatchers}, {conditionIndex, metric.links(), initialConditionCache, wizard}, {metric.state_link(), slicedStateAtoms, stateGroupMap}, diff --git a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp index b5f091ec..1b5abaf1 100644 --- a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp +++ b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp @@ -186,7 +186,8 @@ public: TimeUnitToBucketSizeInMillisGuardrailed(kConfigKey.GetUid(), metric.bucket())); const bool containsAnyPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what()); - const bool sliceByPositionAll = HasPositionALL(metric.dimensions_in_what()); + const bool shouldUseNestedDimensions = + ShouldUseNestedDimensions(metric.dimensions_in_what()); vector<Matcher> fieldMatchers; translateFieldMatcher(metric.value_field(), &fieldMatchers); @@ -210,8 +211,9 @@ public: kConfigKey, metric, protoHash, {pullAtomId, pullerManager}, {timeBaseNs, startTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(), conditionCorrectionThresholdNs, metric.split_bucket_for_app_upgrade()}, - {containsAnyPositionInDimensionsInWhat, sliceByPositionAll, logEventMatcherIndex, - eventMatcherWizard, metric.dimensions_in_what(), fieldMatchers}, + {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, + logEventMatcherIndex, eventMatcherWizard, metric.dimensions_in_what(), + fieldMatchers}, {conditionIndex, metric.links(), initialConditionCache, wizard}, {metric.state_link(), slicedStateAtoms, stateGroupMap}, {/*eventActivationMap=*/{}, /*eventDeactivationMap=*/{}}, @@ -253,6 +255,20 @@ public: metric.add_slice_by_state(StringToId(state)); return metric; } + + static ValueMetric createMetricWithRepeatedValueField() { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + FieldMatcher* valueChild = metric.mutable_value_field()->add_child(); + valueChild->set_field(3); + valueChild->set_position(Position::FIRST); + metric.set_max_pull_delay_sec(INT_MAX); + metric.set_split_bucket_for_app_upgrade(true); + metric.set_aggregation_type(ValueMetric_AggregationType_SUM); + return metric; + } }; // Setup for parameterized tests. @@ -7410,6 +7426,85 @@ TEST(NumericValueMetricProducerTest, TestSubsetDimensions) { ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, dumpReportTimeNs, {26}, -1, 0); } +TEST(NumericValueMetricProducerTest, TestRepeatedValueFieldAndDimensions) { + ValueMetric metric = NumericValueMetricProducerTestHelper::createMetricWithRepeatedValueField(); + metric.mutable_dimensions_in_what()->set_field(tagId); + FieldMatcher* valueChild = metric.mutable_dimensions_in_what()->add_child(); + valueChild->set_field(1); + valueChild->set_position(Position::FIRST); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + + EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) + // First field is a dimension field (repeated, position FIRST). + // Third field is the value field (repeated, position FIRST). + // NumericValueMetricProducer initialized. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + data->push_back( + makeRepeatedUidLogEvent(tagId, bucketStartTimeNs + 1, {1, 10}, 5, {2, 3})); + data->push_back( + makeRepeatedUidLogEvent(tagId, bucketStartTimeNs + 1, {2, 10}, 5, {3, 4})); + return true; + })) + // Dump report pull. + .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + data->push_back(makeRepeatedUidLogEvent(tagId, bucket2StartTimeNs + 10000000000, + {1, 10}, 5, {10, 3})); + data->push_back(makeRepeatedUidLogEvent(tagId, bucket2StartTimeNs + 10000000000, + {2, 10}, 5, {14, 4})); + return true; + })); + + sp<NumericValueMetricProducer> valueProducer = + NumericValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, + metric); + + // Bucket 2 start. + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + allData.push_back(makeRepeatedUidLogEvent(tagId, bucket2StartTimeNs + 1, {1, 10}, 5, {5, 7})); + allData.push_back(makeRepeatedUidLogEvent(tagId, bucket2StartTimeNs + 1, {2, 10}, 5, {7, 5})); + valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucket2StartTimeNs + 10000000000; + valueProducer->onDumpReport(dumpReportTimeNs, true /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + backfillDimensionPath(&report); + backfillStartEndTimestamp(&report); + EXPECT_TRUE(report.has_value_metrics()); + StatsLogReport::ValueMetricDataWrapper valueMetrics; + sortMetricDataByDimensionsValue(report.value_metrics(), &valueMetrics); + ASSERT_EQ(2, valueMetrics.data_size()); + EXPECT_EQ(0, report.value_metrics().skipped_size()); + + // Check data keyed to uid 1. + ValueMetricData data = valueMetrics.data(0); + ValidateUidDimension(data.dimensions_in_what(), tagId, 1); + ASSERT_EQ(2, data.bucket_info_size()); + ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs, {3}, -1, + 0); // Summed diffs of 2, 5 + ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, dumpReportTimeNs, {5}, -1, + 0); // Summed diffs of 5, 10 + + // Check data keyed to uid 2. + data = valueMetrics.data(1); + ValidateUidDimension(data.dimensions_in_what(), tagId, 2); + ASSERT_EQ(2, data.bucket_info_size()); + ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs, {4}, -1, + 0); // Summed diffs of 3, 7 + ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, dumpReportTimeNs, {7}, -1, + 0); // Summed diffs of 7, 14 +} + } // namespace statsd } // namespace os } // namespace android diff --git a/statsd/tests/statsd_test_util.cpp b/statsd/tests/statsd_test_util.cpp index e64b5c3f..33b6b568 100644 --- a/statsd/tests/statsd_test_util.cpp +++ b/statsd/tests/statsd_test_util.cpp @@ -252,6 +252,35 @@ AtomMatcher CreateAppStartOccurredAtomMatcher() { return CreateSimpleAtomMatcher("AppStartOccurredMatcher", util::APP_START_OCCURRED); } +AtomMatcher CreateTestAtomRepeatedStateAtomMatcher(const string& name, + TestAtomReported::State state, + Position position) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(util::TEST_ATOM_REPORTED); + auto field_value_matcher = simple_atom_matcher->add_field_value_matcher(); + field_value_matcher->set_field(14); // Repeated enum field. + field_value_matcher->set_eq_int(state); + field_value_matcher->set_position(position); + return atom_matcher; +} + +AtomMatcher CreateTestAtomRepeatedStateFirstOffAtomMatcher() { + return CreateTestAtomRepeatedStateAtomMatcher("TestFirstStateOff", TestAtomReported::OFF, + Position::FIRST); +} + +AtomMatcher CreateTestAtomRepeatedStateFirstOnAtomMatcher() { + return CreateTestAtomRepeatedStateAtomMatcher("TestFirstStateOn", TestAtomReported::ON, + Position::FIRST); +} + +AtomMatcher CreateTestAtomRepeatedStateAnyOnAtomMatcher() { + return CreateTestAtomRepeatedStateAtomMatcher("TestAnyStateOn", TestAtomReported::ON, + Position::ANY); +} + void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher) { combinationMatcher->mutable_combination()->add_matcher(matcher.id()); } @@ -320,6 +349,14 @@ Predicate CreateIsInBackgroundPredicate() { return predicate; } +Predicate CreateTestAtomRepeatedStateFirstOffPredicate() { + Predicate predicate; + predicate.set_id(StringToId("TestFirstStateIsOff")); + predicate.mutable_simple_predicate()->set_start(StringToId("TestFirstStateOff")); + predicate.mutable_simple_predicate()->set_stop(StringToId("TestFirstStateOn")); + return predicate; +} + State CreateScreenState() { State state; state.set_id(StringToId("ScreenState")); @@ -450,6 +487,22 @@ FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields) return dimensions; } +FieldMatcher CreateRepeatedDimensions(const int atomId, const std::vector<int>& fields, + const std::vector<Position>& positions) { + FieldMatcher dimensions; + if (fields.size() != positions.size()) { + return dimensions; + } + + dimensions.set_field(atomId); + for (size_t i = 0; i < fields.size(); i++) { + auto child = dimensions.add_child(); + child->set_field(fields[i]); + child->set_position(positions[i]); + } + return dimensions; +} + FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId, const std::vector<Position>& positions, const std::vector<int>& fields) { @@ -744,6 +797,19 @@ AStatsEvent* makeUidStatsEvent(int atomId, int64_t eventTimeNs, int uid, int dat return statsEvent; } +AStatsEvent* makeUidStatsEvent(int atomId, int64_t eventTimeNs, int uid, int data1, + const vector<int>& data2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + AStatsEvent_writeInt32(statsEvent, uid); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_writeInt32(statsEvent, data1); + AStatsEvent_writeInt32Array(statsEvent, data2.data(), data2.size()); + + return statsEvent; +} + shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, int data2) { AStatsEvent* statsEvent = makeUidStatsEvent(atomId, eventTimeNs, uid, data1, data2); @@ -753,6 +819,15 @@ shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, i return logEvent; } +shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, + const vector<int>& data2) { + AStatsEvent* statsEvent = makeUidStatsEvent(atomId, eventTimeNs, uid, data1, data2); + + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + shared_ptr<LogEvent> makeExtraUidsLogEvent(int atomId, int64_t eventTimeNs, int uid1, int data1, int data2, const vector<int>& extraUids) { AStatsEvent* statsEvent = makeUidStatsEvent(atomId, eventTimeNs, uid1, data1, data2); @@ -766,6 +841,53 @@ shared_ptr<LogEvent> makeExtraUidsLogEvent(int atomId, int64_t eventTimeNs, int return logEvent; } +shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs, + const vector<int>& uids) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size()); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + + return logEvent; +} + +shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs, + const vector<int>& uids, int data1, int data2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size()); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_writeInt32(statsEvent, data1); + AStatsEvent_writeInt32(statsEvent, data2); + + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + + return logEvent; +} + +shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs, + const vector<int>& uids, int data1, + const vector<int>& data2) { + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, atomId); + AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs); + AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size()); + AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true); + AStatsEvent_writeInt32(statsEvent, data1); + AStatsEvent_writeInt32Array(statsEvent, data2.data(), data2.size()); + + shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + + return logEvent; +} + shared_ptr<LogEvent> makeAttributionLogEvent(int atomId, int64_t eventTimeNs, const vector<int>& uids, const vector<string>& tags, int data1, int data2) { @@ -898,6 +1020,57 @@ std::unique_ptr<LogEvent> CreateFinishScheduledJobEvent(uint64_t timestampNs, ScheduledJobStateChanged::FINISHED, timestampNs); } +std::unique_ptr<LogEvent> CreateTestAtomReportedEventVariableRepeatedFields( + uint64_t timestampNs, const vector<int>& repeatedIntField, + const vector<int64_t>& repeatedLongField, const vector<float>& repeatedFloatField, + const vector<string>& repeatedStringField, const bool* repeatedBoolField, + const size_t repeatedBoolFieldLength, const vector<int>& repeatedEnumField) { + return CreateTestAtomReportedEvent(timestampNs, {1001, 1002}, {"app1", "app2"}, 5, 1000l, 21.9f, + "string", 1, TestAtomReported::ON, {8, 1, 8, 2, 8, 3}, + repeatedIntField, repeatedLongField, repeatedFloatField, + repeatedStringField, repeatedBoolField, + repeatedBoolFieldLength, repeatedEnumField); +} + +std::unique_ptr<LogEvent> CreateTestAtomReportedEvent( + uint64_t timestampNs, const vector<int>& attributionUids, + const vector<string>& attributionTags, const int intField, const long longField, + const float floatField, const string& stringField, const bool boolField, + const TestAtomReported::State enumField, const vector<uint8_t>& bytesField, + const vector<int>& repeatedIntField, const vector<int64_t>& repeatedLongField, + const vector<float>& repeatedFloatField, const vector<string>& repeatedStringField, + const bool* repeatedBoolField, const size_t repeatedBoolFieldLength, + const vector<int>& repeatedEnumField) { + vector<const char*> cRepeatedStringField(repeatedStringField.size()); + for (int i = 0; i < cRepeatedStringField.size(); i++) { + cRepeatedStringField[i] = repeatedStringField[i].c_str(); + } + + AStatsEvent* statsEvent = AStatsEvent_obtain(); + AStatsEvent_setAtomId(statsEvent, util::TEST_ATOM_REPORTED); + AStatsEvent_overwriteTimestamp(statsEvent, timestampNs); + + writeAttribution(statsEvent, attributionUids, attributionTags); + AStatsEvent_writeInt32(statsEvent, intField); + AStatsEvent_writeInt64(statsEvent, longField); + AStatsEvent_writeFloat(statsEvent, floatField); + AStatsEvent_writeString(statsEvent, stringField.c_str()); + AStatsEvent_writeBool(statsEvent, boolField); + AStatsEvent_writeInt32(statsEvent, enumField); + AStatsEvent_writeByteArray(statsEvent, bytesField.data(), bytesField.size()); + AStatsEvent_writeInt32Array(statsEvent, repeatedIntField.data(), repeatedIntField.size()); + AStatsEvent_writeInt64Array(statsEvent, repeatedLongField.data(), repeatedLongField.size()); + AStatsEvent_writeFloatArray(statsEvent, repeatedFloatField.data(), repeatedFloatField.size()); + AStatsEvent_writeStringArray(statsEvent, cRepeatedStringField.data(), + repeatedStringField.size()); + AStatsEvent_writeBoolArray(statsEvent, repeatedBoolField, repeatedBoolFieldLength); + AStatsEvent_writeInt32Array(statsEvent, repeatedEnumField.data(), repeatedEnumField.size()); + + std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0); + parseStatsEventToLogEvent(statsEvent, logEvent.get()); + return logEvent; +} + std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(uint64_t timestampNs, const vector<int>& attributionUids, const vector<string>& attributionTags, @@ -1563,14 +1736,13 @@ void backfillStringInReport(ConfigMetricsReportList *config_report_list) { bool backfillDimensionPath(const DimensionsValue& path, const google::protobuf::RepeatedPtrField<DimensionsValue>& leafValues, - int* leafIndex, - DimensionsValue* dimension) { + int* leafIndex, DimensionsValue* dimension) { dimension->set_field(path.field()); if (path.has_value_tuple()) { for (int i = 0; i < path.value_tuple().dimensions_value_size(); ++i) { - if (!backfillDimensionPath( - path.value_tuple().dimensions_value(i), leafValues, leafIndex, - dimension->mutable_value_tuple()->add_dimensions_value())) { + if (!backfillDimensionPath(path.value_tuple().dimensions_value(i), leafValues, + leafIndex, + dimension->mutable_value_tuple()->add_dimensions_value())) { return false; } } diff --git a/statsd/tests/statsd_test_util.h b/statsd/tests/statsd_test_util.h index 1f594f70..c4fa1fe4 100644 --- a/statsd/tests/statsd_test_util.h +++ b/statsd/tests/statsd_test_util.h @@ -155,6 +155,20 @@ AtomMatcher CreateProcessCrashAtomMatcher(); // Create AtomMatcher proto for app launches. AtomMatcher CreateAppStartOccurredAtomMatcher(); +// Create AtomMatcher proto for test atom repeated state. +AtomMatcher CreateTestAtomRepeatedStateAtomMatcher(const string& name, + TestAtomReported::State state, + Position position); + +// Create AtomMatcher proto for test atom repeated state is off, first position. +AtomMatcher CreateTestAtomRepeatedStateFirstOffAtomMatcher(); + +// Create AtomMatcher proto for test atom repeated state is on, first position. +AtomMatcher CreateTestAtomRepeatedStateFirstOnAtomMatcher(); + +// Create AtomMatcher proto for test atom repeated state is on, any position. +AtomMatcher CreateTestAtomRepeatedStateAnyOnAtomMatcher(); + // Add an AtomMatcher to a combination AtomMatcher. void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher); @@ -182,6 +196,9 @@ Predicate CreateIsSyncingPredicate(); // Create a Predicate proto for app is in background. Predicate CreateIsInBackgroundPredicate(); +// Create a Predicate proto for test atom repeated state field is off. +Predicate CreateTestAtomRepeatedStateFirstOffPredicate(); + // Create State proto for screen state atom. State CreateScreenState(); @@ -221,6 +238,10 @@ void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* c // Create dimensions from primitive fields. FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields); +// Create dimensions from repeated primitive fields. +FieldMatcher CreateRepeatedDimensions(const int atomId, const std::vector<int>& fields, + const std::vector<Position>& positions); + // Create dimensions by attribution uid and tag. FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId, const std::vector<Position>& positions); @@ -307,12 +328,28 @@ void CreateNoValuesLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs) AStatsEvent* makeUidStatsEvent(int atomId, int64_t eventTimeNs, int uid, int data1, int data2); +AStatsEvent* makeUidStatsEvent(int atomId, int64_t eventTimeNs, int uid, int data1, + const vector<int>& data2); + std::shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, int data2); +std::shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1, + const vector<int>& data2); + shared_ptr<LogEvent> makeExtraUidsLogEvent(int atomId, int64_t eventTimeNs, int uid1, int data1, int data2, const std::vector<int>& extraUids); +std::shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs, + const std::vector<int>& uids); + +shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs, + const vector<int>& uids, int data1, int data2); + +shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs, + const vector<int>& uids, int data1, + const vector<int>& data2); + std::shared_ptr<LogEvent> makeAttributionLogEvent(int atomId, int64_t eventTimeNs, const vector<int>& uids, const vector<string>& tags, int data1, int data2); @@ -404,6 +441,22 @@ std::unique_ptr<LogEvent> CreateAppStartOccurredEvent( AppStartOccurred::TransitionType type, const string& activity_name, const string& calling_pkg_name, const bool is_instant_app, int64_t activity_start_msec); +std::unique_ptr<LogEvent> CreateTestAtomReportedEventVariableRepeatedFields( + uint64_t timestampNs, const vector<int>& repeatedIntField, + const vector<int64_t>& repeatedLongField, const vector<float>& repeatedFloatField, + const vector<string>& repeatedStringField, const bool* repeatedBoolField, + const size_t repeatedBoolFieldLength, const vector<int>& repeatedEnumField); + +std::unique_ptr<LogEvent> CreateTestAtomReportedEvent( + uint64_t timestampNs, const vector<int>& attributionUids, + const vector<string>& attributionTags, const int intField, const long longField, + const float floatField, const string& stringField, const bool boolField, + const TestAtomReported::State enumField, const vector<uint8_t>& bytesField, + const vector<int>& repeatedIntField, const vector<int64_t>& repeatedLongField, + const vector<float>& repeatedFloatField, const vector<string>& repeatedStringField, + const bool* repeatedBoolField, const size_t repeatedBoolFieldLength, + const vector<int>& repeatedEnumField); + // Create a statsd log event processor upon the start time in seconds, config and key. sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs, const StatsdConfig& config, const ConfigKey& key, diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml index 451ad733..be8a12ab 100644 --- a/tests/AndroidTest.xml +++ b/tests/AndroidTest.xml @@ -20,6 +20,7 @@ <option name="config-descriptor:metadata" key="parameter" value="instant_app" /> <option name="config-descriptor:metadata" key="parameter" value="multi_abi" /> <option name="config-descriptor:metadata" key="parameter" value="secondary_user" /> + <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" /> <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > <option name="jar" value="CtsStatsdHostTestCases.jar" /> </test> |