Store annotation info in LogEvent/FieldValue
Test: bit statsd_test:*
Bug: 151109630
Change-Id: I9e07400baed51d5e0b507d9b11118bd29bf41708
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 967fd32..3536e5a 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -16,6 +16,7 @@
#pragma once
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "annotations.h"
namespace android {
namespace os {
@@ -357,6 +358,56 @@
Value& operator=(const Value& that);
};
+class Annotations {
+public:
+ Annotations() {}
+
+ // This enum stores where particular annotations can be found in the
+ // bitmask. Note that these pos do not correspond to annotation ids.
+ enum {
+ NESTED_POS = 0x0,
+ PRIMARY_POS = 0x1,
+ EXCLUSIVE_POS = 0x2
+ };
+
+ inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); }
+
+ inline void setPrimaryField(bool primary) { setBitmaskAtPos(PRIMARY_POS, primary); }
+
+ inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); }
+
+ inline void setResetState(int resetState) { mResetState = resetState; }
+
+ // Default value = false
+ inline bool isNested() const { return getValueFromBitmask(NESTED_POS); }
+
+ // Default value = false
+ inline bool isPrimaryField() const { return getValueFromBitmask(PRIMARY_POS); }
+
+ // Default value = false
+ inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); }
+
+ // If a reset state is not sent in the StatsEvent, returns -1. Note that a
+ // reset satate is only sent if and only if a reset should be triggered.
+ inline int getResetState() const { return mResetState; }
+
+private:
+ inline void setBitmaskAtPos(int pos, bool value) {
+ mBooleanBitmask &= ~(1 << pos); // clear
+ mBooleanBitmask |= (value << pos); // set
+ }
+
+ inline bool getValueFromBitmask(int pos) const {
+ return (mBooleanBitmask >> pos) & 0x1;
+ }
+
+ // This is a bitmask over all annotations stored in boolean form. Because
+ // there are only 3 booleans, just one byte is required.
+ uint8_t mBooleanBitmask = 0;
+
+ int mResetState = -1;
+};
+
/**
* Represents a log item, or a dimension item (They are essentially the same).
*/
@@ -384,6 +435,7 @@
Field mField;
Value mValue;
+ Annotations mAnnotations;
};
bool HasPositionANY(const FieldMatcher& matcher);
diff --git a/cmds/statsd/src/annotations.h b/cmds/statsd/src/annotations.h
new file mode 100644
index 0000000..1e9390e
--- /dev/null
+++ b/cmds/statsd/src/annotations.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const uint8_t ANNOTATION_ID_IS_UID = 1;
+const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
+const uint8_t ANNOTATION_ID_STATE_OPTION = 3;
+const uint8_t ANNOTATION_ID_RESET_STATE = 5;
+const uint8_t ANNOTATION_ID_STATE_NESTED = 6;
+
+const int32_t STATE_OPTION_PRIMARY_FIELD = 1;
+const int32_t STATE_OPTION_EXCLUSIVE_STATE = 2;
+const int32_t STATE_OPTION_PRIMARY_FIELD_FIRST_UID = 3;
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 3b3d0b6..a6ae389 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -17,6 +17,7 @@
#define DEBUG false // STOPSHIP if true
#include "logd/LogEvent.h"
+#include "annotations.h"
#include "stats_log_util.h"
#include "statslog_statsd.h"
@@ -447,6 +448,7 @@
void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last,
uint8_t numAnnotations) {
+ int firstUidInChainIndex = mValues.size();
int32_t numNodes = readNextValue<uint8_t>();
for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) {
last[1] = (pos[1] == numNodes);
@@ -461,26 +463,103 @@
parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
}
- parseAnnotations(numAnnotations);
+ parseAnnotations(numAnnotations, firstUidInChainIndex);
pos[1] = pos[2] = 1;
last[1] = last[2] = false;
}
-// TODO(b/151109630): store annotation information within LogEvent
-void LogEvent::parseAnnotations(uint8_t numAnnotations) {
+void LogEvent::parseIsUidAnnotation(uint8_t annotationType) {
+ if (mValues.empty() || annotationType != BOOL_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ bool isUid = readNextValue<uint8_t>();
+ if (isUid) mUidFieldIndex = mValues.size() - 1;
+}
+
+void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) {
+ if (!mValues.empty() || annotationType != BOOL_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ mTruncateTimestamp = readNextValue<uint8_t>();
+}
+
+void LogEvent::parseStateOptionAnnotation(uint8_t annotationType, int firstUidInChainIndex) {
+ if (mValues.empty() || annotationType != INT32_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ int32_t stateOption = readNextValue<int32_t>();
+ switch (stateOption) {
+ case STATE_OPTION_EXCLUSIVE_STATE:
+ mValues[mValues.size() - 1].mAnnotations.setExclusiveState(true);
+ break;
+ case STATE_OPTION_PRIMARY_FIELD:
+ mValues[mValues.size() - 1].mAnnotations.setPrimaryField(true);
+ break;
+ case STATE_OPTION_PRIMARY_FIELD_FIRST_UID:
+ if (firstUidInChainIndex == -1) {
+ mValid = false;
+ } else {
+ mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(true);
+ }
+ break;
+ default:
+ mValid = false;
+ }
+}
+
+void LogEvent::parseResetStateAnnotation(uint8_t annotationType) {
+ if (mValues.empty() || annotationType != INT32_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ int32_t resetState = readNextValue<int32_t>();
+ mValues[mValues.size() - 1].mAnnotations.setResetState(resetState);
+}
+
+void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) {
+ if (mValues.empty() || annotationType != BOOL_TYPE) {
+ mValid = false;
+ return;
+ }
+
+ bool nested = readNextValue<uint8_t>();
+ mValues[mValues.size() - 1].mAnnotations.setNested(nested);
+}
+
+// firstUidInChainIndex is a default parameter that is only needed when parsing
+// annotations for attribution chains.
+void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) {
for (uint8_t i = 0; i < numAnnotations; i++) {
- /*uint8_t annotationId = */ readNextValue<uint8_t>();
+ uint8_t annotationId = readNextValue<uint8_t>();
uint8_t annotationType = readNextValue<uint8_t>();
- switch (annotationType) {
- case BOOL_TYPE:
- /*bool annotationValue = */ readNextValue<uint8_t>();
+
+ switch (annotationId) {
+ case ANNOTATION_ID_IS_UID:
+ parseIsUidAnnotation(annotationType);
break;
- case INT32_TYPE:
- /*int32_t annotationValue =*/ readNextValue<int32_t>();
+ case ANNOTATION_ID_TRUNCATE_TIMESTAMP:
+ parseTruncateTimestampAnnotation(annotationType);
+ break;
+ case ANNOTATION_ID_STATE_OPTION:
+ parseStateOptionAnnotation(annotationType, firstUidInChainIndex);
+ break;
+ case ANNOTATION_ID_RESET_STATE:
+ parseResetStateAnnotation(annotationType);
+ break;
+ case ANNOTATION_ID_STATE_NESTED:
+ parseStateNestedAnnotation(annotationType);
break;
default:
mValid = false;
+ return;
}
}
}
@@ -509,8 +588,8 @@
typeInfo = readNextValue<uint8_t>();
if (getTypeId(typeInfo) != INT32_TYPE) mValid = false;
mTagId = readNextValue<int32_t>();
- parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
numElements--;
+ parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) {
@@ -544,6 +623,7 @@
break;
case ATTRIBUTION_CHAIN_TYPE:
parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+ if (mAttributionChainIndex == -1) mAttributionChainIndex = pos[0];
break;
default:
mValid = false;
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 6537f13..0a89be4 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -206,6 +206,32 @@
return &mValues;
}
+ // Default value = false
+ inline bool shouldTruncateTimestamp() {
+ return mTruncateTimestamp;
+ }
+
+ // Returns the index of the uid field within the FieldValues vector if the
+ // uid exists. If there is no uid field, returns -1.
+ //
+ // If the index within the atom definition is desired, do the following:
+ // int vectorIndex = LogEvent.getUidFieldIndex();
+ // if (vectorIndex != -1) {
+ // FieldValue& v = LogEvent.getValues()[vectorIndex];
+ // int atomIndex = v.mField.getPosAtDepth(0);
+ // }
+ // Note that atomIndex is 1-indexed.
+ inline int getUidFieldIndex() {
+ return mUidFieldIndex;
+ }
+
+ // Returns the index of (the first) attribution chain within the atom
+ // definition. Note that the value is 1-indexed. If there is no attribution
+ // chain, returns -1.
+ inline int getAttributionChainIndex() {
+ return mAttributionChainIndex;
+ }
+
inline LogEvent makeCopy() {
return LogEvent(*this);
}
@@ -240,7 +266,13 @@
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 parseAnnotations(uint8_t numAnnotations);
+
+ void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1);
+ void parseIsUidAnnotation(uint8_t annotationType);
+ void parseTruncateTimestampAnnotation(uint8_t annotationType);
+ void parseStateOptionAnnotation(uint8_t annotationType, int firstUidInChainIndex);
+ void parseResetStateAnnotation(uint8_t annotationType);
+ void parseStateNestedAnnotation(uint8_t annotationType);
/**
* The below three variables are only valid during the execution of
@@ -322,6 +354,11 @@
// The pid of the logging client (defaults to -1).
int32_t mLogPid = -1;
+
+ // Annotations
+ bool mTruncateTimestamp = false;
+ int mUidFieldIndex = -1;
+ int mAttributionChainIndex = -1;
};
void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 7458cbf..41e21e4 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -12,12 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include "src/logd/LogEvent.h"
#include <gtest/gtest.h>
-#include <log/log_event_list.h>
+
#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
#include "frameworks/base/core/proto/android/stats/launcher/launcher.pb.h"
-#include <stats_event.h>
+#include "log/log_event_list.h"
+#include "src/logd/LogEvent.h"
+#include "stats_event.h"
#ifdef __ANDROID__
@@ -243,6 +244,117 @@
AStatsEvent_release(event);
}
+void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
+ bool annotationValue) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+ AStatsEvent_writeInt32(statsEvent, 10);
+ AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ EXPECT_TRUE(logEvent->parseBuffer(buf, size));
+
+ AStatsEvent_release(statsEvent);
+}
+
+TEST(LogEventTest, TestAnnotationIdIsUid) {
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_EQ(event.getUidFieldIndex(), 0);
+}
+
+TEST(LogEventTest, TestAnnotationIdStateNested) {
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_TRUE(values[0].mAnnotations.isNested());
+}
+
+void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
+ int annotationValue) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+ AStatsEvent_writeInt32(statsEvent, 10);
+ AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ EXPECT_TRUE(logEvent->parseBuffer(buf, size));
+
+ AStatsEvent_release(statsEvent);
+}
+
+TEST(LogEventTest, TestPrimaryFieldAnnotation) {
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_STATE_OPTION,
+ STATE_OPTION_PRIMARY_FIELD);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_TRUE(values[0].mAnnotations.isPrimaryField());
+}
+
+TEST(LogEventTest, TestExclusiveStateAnnotation) {
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_STATE_OPTION,
+ STATE_OPTION_EXCLUSIVE_STATE);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_TRUE(values[0].mAnnotations.isExclusiveState());
+}
+
+TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
+ // Event has 10 ints and then an attribution chain
+ int numInts = 10;
+ int firstUidInChainIndex = numInts;
+ string tag1 = "tag1";
+ string tag2 = "tag2";
+ uint32_t uids[] = {1001, 1002};
+ const char* tags[] = {tag1.c_str(), tag2.c_str()};
+
+ // Construct AStatsEvent
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, 100);
+ for (int i = 0; i < numInts; i++) {
+ AStatsEvent_writeInt32(statsEvent, 10);
+ }
+ AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
+ AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_STATE_OPTION,
+ STATE_OPTION_PRIMARY_FIELD_FIRST_UID);
+ AStatsEvent_build(statsEvent);
+
+ // Construct LogEvent
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+ LogEvent logEvent(/*uid=*/0, /*pid=*/0);
+ EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+ AStatsEvent_release(statsEvent);
+
+ // Check annotation
+ const vector<FieldValue>& values = logEvent.getValues();
+ EXPECT_EQ(values.size(), numInts + 4);
+ EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField());
+}
+
+TEST(LogEventTest, TestResetStateAnnotation) {
+ int32_t resetState = 10;
+ LogEvent event(/*uid=*/0, /*pid=*/0);
+ createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_RESET_STATE, resetState);
+
+ const vector<FieldValue>& values = event.getValues();
+ EXPECT_EQ(values.size(), 1);
+ EXPECT_EQ(values[0].mAnnotations.getResetState(), resetState);
+}
+
} // namespace statsd
} // namespace os
} // namespace android