summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2022-08-15 22:05:04 -0700
committerXin Li <delphij@google.com>2022-08-15 22:05:04 -0700
commit3a0243f6fe0b68ab278b651b455d6c35eff075fb (patch)
treef9c85ab726b614ed574eb4f021bd68b8f28fbb4f
parentcae0fc21641f82c85bda220b19d60a24bdf9035e (diff)
parent687a9d54628a01ecd92043127fe19a28fd4b504b (diff)
downloadStatsD-3a0243f6fe0b68ab278b651b455d6c35eff075fb.tar.gz
DO NOT MERGE - Merge Android 13
Bug: 242648940 Merged-In: Id1e6c4772f30df1072350be14f96e371ffbb497f Change-Id: I9bd9833489d4f2d7e0a184bc8d1b1116106664c3
-rw-r--r--framework/test/unittests/Android.bp1
-rw-r--r--framework/test/unittests/src/android/util/StatsEventTest.java25
-rw-r--r--lib/libstatssocket/include/stats_event.h43
-rw-r--r--lib/libstatssocket/libstatssocket.map.txt5
-rw-r--r--lib/libstatssocket/stats_event.c64
-rw-r--r--lib/libstatssocket/tests/stats_event_test.cpp150
-rw-r--r--statsd/src/FieldValue.cpp17
-rw-r--r--statsd/src/FieldValue.h18
-rw-r--r--statsd/src/StatsLogProcessor.cpp4
-rw-r--r--statsd/src/StatsLogProcessor.h6
-rw-r--r--statsd/src/external/puller_util.cpp31
-rw-r--r--statsd/src/logd/LogEvent.cpp188
-rw-r--r--statsd/src/logd/LogEvent.h82
-rw-r--r--statsd/src/matchers/matcher_util.cpp9
-rw-r--r--statsd/src/metrics/CountMetricProducer.cpp8
-rw-r--r--statsd/src/metrics/DurationMetricProducer.cpp6
-rw-r--r--statsd/src/metrics/GaugeMetricProducer.cpp8
-rw-r--r--statsd/src/metrics/MetricProducer.cpp2
-rw-r--r--statsd/src/metrics/MetricProducer.h4
-rw-r--r--statsd/src/metrics/ValueMetricProducer.cpp8
-rw-r--r--statsd/src/metrics/ValueMetricProducer.h2
-rw-r--r--statsd/src/metrics/parsing_utils/metrics_manager_util.cpp22
-rw-r--r--statsd/src/state/StateTracker.cpp6
-rw-r--r--statsd/src/stats_log_util.cpp65
-rw-r--r--statsd/tests/FieldValue_test.cpp157
-rw-r--r--statsd/tests/LogEntryMatcher_test.cpp400
-rw-r--r--statsd/tests/LogEvent_test.cpp642
-rw-r--r--statsd/tests/StatsLogProcessor_test.cpp84
-rw-r--r--statsd/tests/e2e/CountMetric_e2e_test.cpp782
-rw-r--r--statsd/tests/e2e/DurationMetric_e2e_test.cpp87
-rw-r--r--statsd/tests/e2e/EventMetric_e2e_test.cpp156
-rw-r--r--statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp137
-rw-r--r--statsd/tests/e2e/KllMetric_e2e_test.cpp30
-rw-r--r--statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp30
-rw-r--r--statsd/tests/external/puller_util_test.cpp211
-rw-r--r--statsd/tests/metrics/KllMetricProducer_test.cpp6
-rw-r--r--statsd/tests/metrics/NumericValueMetricProducer_test.cpp101
-rw-r--r--statsd/tests/statsd_test_util.cpp182
-rw-r--r--statsd/tests/statsd_test_util.h53
-rw-r--r--tests/AndroidTest.xml1
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>