diff options
| -rw-r--r-- | cmds/statsd/src/FieldValue.h | 52 | ||||
| -rw-r--r-- | cmds/statsd/src/annotations.h | 35 | ||||
| -rw-r--r-- | cmds/statsd/src/logd/LogEvent.cpp | 100 | ||||
| -rw-r--r-- | cmds/statsd/src/logd/LogEvent.h | 39 | ||||
| -rw-r--r-- | cmds/statsd/tests/LogEvent_test.cpp | 118 |
5 files changed, 330 insertions, 14 deletions
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index 967fd323e5a0..3536e5a5c962 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 @@ struct Value { 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 @@ struct FieldValue { 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 000000000000..1e9390e49ae1 --- /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 3b3d0b64bfb3..a6ae3899e4de 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::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8 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 @@ void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last, 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 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { 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 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { 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 6537f13c4089..0a89be4ce335 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -206,6 +206,32 @@ public: 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 @@ 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 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 @@ private: // 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 7458cbf9e9a1..41e21e4afb37 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 @@ TEST(LogEventTest, TestAttributionChain) { 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 |