From 21c1f9c3148c080aa2c8756c300d8d6718d11e6b Mon Sep 17 00:00:00 2001 From: Ruchir Rastogi Date: Tue, 7 Jan 2020 09:36:04 -0800 Subject: [PATCH] stats_event.h/c tests Add unit tests for the native API to log atoms to statsd. Test: bit libstatssocket_test:* Bug: 145231901 Change-Id: If427c17319787200260cbe3b71075ca556c9a82b --- libstats/socket/Android.bp | 19 ++ libstats/socket/tests/stats_event_test.cpp | 344 +++++++++++++++++++++ 2 files changed, 363 insertions(+) create mode 100644 libstats/socket/tests/stats_event_test.cpp diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp index 3b6efbb55..9fd9fbc20 100644 --- a/libstats/socket/Android.bp +++ b/libstats/socket/Android.bp @@ -75,3 +75,22 @@ cc_benchmark { "libgtest_prod", ], } + +cc_test { + name: "libstatssocket_test", + srcs: ["tests/stats_event_test.cpp"], + cflags: [ + "-Wall", + "-Werror", + ], + static_libs: [ + "libgmock", + "libstatssocket", + ], + shared_libs: [ + "libcutils", + "liblog", + "libutils", + ], + test_suites: ["device_tests"], +} diff --git a/libstats/socket/tests/stats_event_test.cpp b/libstats/socket/tests/stats_event_test.cpp new file mode 100644 index 000000000..cf0592c3a --- /dev/null +++ b/libstats/socket/tests/stats_event_test.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2019 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. + */ + +#include "stats_event.h" +#include +#include + +using std::string; +using std::vector; + +// Side-effect: this function moves the start of the buffer past the read value +template +T readNext(uint8_t** buffer) { + T value = *(T*)(*buffer); + *buffer += sizeof(T); + return value; +} + +void checkTypeHeader(uint8_t** buffer, uint8_t typeId, uint8_t numAnnotations = 0) { + uint8_t typeHeader = (numAnnotations << 4) | typeId; + EXPECT_EQ(readNext(buffer), typeHeader); +} + +template +void checkScalar(uint8_t** buffer, T expectedValue) { + EXPECT_EQ(readNext(buffer), expectedValue); +} + +void checkString(uint8_t** buffer, const string& expectedString) { + uint32_t size = readNext(buffer); + string parsedString((char*)(*buffer), size); + EXPECT_EQ(parsedString, expectedString); + *buffer += size; // move buffer past string we just read +} + +void checkByteArray(uint8_t** buffer, const vector& expectedByteArray) { + uint32_t size = readNext(buffer); + vector parsedByteArray(*buffer, *buffer + size); + EXPECT_EQ(parsedByteArray, expectedByteArray); + *buffer += size; // move buffer past byte array we just read +} + +template +void checkAnnotation(uint8_t** buffer, uint8_t annotationId, uint8_t typeId, T annotationValue) { + EXPECT_EQ(readNext(buffer), annotationId); + EXPECT_EQ(readNext(buffer), typeId); + checkScalar(buffer, annotationValue); +} + +void checkMetadata(uint8_t** buffer, uint8_t numElements, int64_t startTime, int64_t endTime, + uint32_t atomId) { + // All events start with OBJECT_TYPE id. + checkTypeHeader(buffer, OBJECT_TYPE); + + // We increment by 2 because the number of elements listed in the + // serialization accounts for the timestamp and atom id as well. + checkScalar(buffer, static_cast(numElements + 2)); + + // Check timestamp + checkTypeHeader(buffer, INT64_TYPE); + int64_t timestamp = readNext(buffer); + EXPECT_GE(timestamp, startTime); + EXPECT_LE(timestamp, endTime); + + // Check atom id + checkTypeHeader(buffer, INT32_TYPE); + checkScalar(buffer, atomId); +} + +TEST(StatsEventTest, TestScalars) { + uint32_t atomId = 100; + int32_t int32Value = -5; + int64_t int64Value = -2 * android::elapsedRealtimeNano(); + float floatValue = 2.0; + bool boolValue = false; + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_int32(event, int32Value); + stats_event_write_int64(event, int64Value); + stats_event_write_float(event, floatValue); + stats_event_write_bool(event, boolValue); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(event, &bufferSize); + uint8_t* bufferEnd = buffer + bufferSize; + + checkMetadata(&buffer, /*numElements=*/4, startTime, endTime, atomId); + + // check int32 element + checkTypeHeader(&buffer, INT32_TYPE); + checkScalar(&buffer, int32Value); + + // check int64 element + checkTypeHeader(&buffer, INT64_TYPE); + checkScalar(&buffer, int64Value); + + // check float element + checkTypeHeader(&buffer, FLOAT_TYPE); + checkScalar(&buffer, floatValue); + + // check bool element + checkTypeHeader(&buffer, BOOL_TYPE); + checkScalar(&buffer, boolValue); + + EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer + EXPECT_EQ(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestStrings) { + uint32_t atomId = 100; + string str = "test_string"; + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_string8(event, str.c_str()); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(event, &bufferSize); + uint8_t* bufferEnd = buffer + bufferSize; + + checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); + + checkTypeHeader(&buffer, STRING_TYPE); + checkString(&buffer, str); + + EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer + EXPECT_EQ(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestByteArrays) { + uint32_t atomId = 100; + vector message = {'b', 'y', 't', '\0', 'e', 's'}; + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_byte_array(event, message.data(), message.size()); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(event, &bufferSize); + uint8_t* bufferEnd = buffer + bufferSize; + + checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); + + checkTypeHeader(&buffer, BYTE_ARRAY_TYPE); + checkByteArray(&buffer, message); + + EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer + EXPECT_EQ(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestAttributionChains) { + uint32_t atomId = 100; + + uint8_t numNodes = 50; + uint32_t uids[numNodes]; + vector tags(numNodes); // storage that cTag elements point to + const char* cTags[numNodes]; + for (int i = 0; i < (int)numNodes; i++) { + uids[i] = i; + tags.push_back("test" + std::to_string(i)); + cTags[i] = tags[i].c_str(); + } + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_attribution_chain(event, uids, cTags, numNodes); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(event, &bufferSize); + uint8_t* bufferEnd = buffer + bufferSize; + + checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); + + checkTypeHeader(&buffer, ATTRIBUTION_CHAIN_TYPE); + checkScalar(&buffer, numNodes); + for (int i = 0; i < numNodes; i++) { + checkScalar(&buffer, uids[i]); + checkString(&buffer, tags[i]); + } + + EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer + EXPECT_EQ(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestKeyValuePairs) { + uint32_t atomId = 100; + + uint8_t numPairs = 4; + struct key_value_pair pairs[numPairs]; + pairs[0] = {.key = 0, .valueType = INT32_TYPE, .int32Value = -1}; + pairs[1] = {.key = 1, .valueType = INT64_TYPE, .int64Value = 0x123456789}; + pairs[2] = {.key = 2, .valueType = FLOAT_TYPE, .floatValue = 5.5}; + string str = "test_key_value_pair_string"; + pairs[3] = {.key = 3, .valueType = STRING_TYPE, .stringValue = str.c_str()}; + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, atomId); + stats_event_write_key_value_pairs(event, pairs, numPairs); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(event, &bufferSize); + uint8_t* bufferEnd = buffer + bufferSize; + + checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId); + + checkTypeHeader(&buffer, KEY_VALUE_PAIRS_TYPE); + checkScalar(&buffer, numPairs); + + // first pair + checkScalar(&buffer, pairs[0].key); + checkTypeHeader(&buffer, pairs[0].valueType); + checkScalar(&buffer, pairs[0].int32Value); + + // second pair + checkScalar(&buffer, pairs[1].key); + checkTypeHeader(&buffer, pairs[1].valueType); + checkScalar(&buffer, pairs[1].int64Value); + + // third pair + checkScalar(&buffer, pairs[2].key); + checkTypeHeader(&buffer, pairs[2].valueType); + checkScalar(&buffer, pairs[2].floatValue); + + // fourth pair + checkScalar(&buffer, pairs[3].key); + checkTypeHeader(&buffer, pairs[3].valueType); + checkString(&buffer, str); + + EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer + EXPECT_EQ(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestAnnotations) { + uint32_t atomId = 100; + + // first element information + bool boolValue = false; + uint8_t boolAnnotation1Id = 1; + uint8_t boolAnnotation2Id = 2; + bool boolAnnotation1Value = true; + int32_t boolAnnotation2Value = 3; + + // second element information + float floatValue = -5.0; + uint8_t floatAnnotation1Id = 3; + uint8_t floatAnnotation2Id = 4; + int32_t floatAnnotation1Value = 8; + bool floatAnnotation2Value = false; + + int64_t startTime = android::elapsedRealtimeNano(); + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, 100); + stats_event_write_bool(event, boolValue); + stats_event_add_bool_annotation(event, boolAnnotation1Id, boolAnnotation1Value); + stats_event_add_int32_annotation(event, boolAnnotation2Id, boolAnnotation2Value); + stats_event_write_float(event, floatValue); + stats_event_add_int32_annotation(event, floatAnnotation1Id, floatAnnotation1Value); + stats_event_add_bool_annotation(event, floatAnnotation2Id, floatAnnotation2Value); + stats_event_build(event); + int64_t endTime = android::elapsedRealtimeNano(); + + size_t bufferSize; + uint8_t* buffer = stats_event_get_buffer(event, &bufferSize); + uint8_t* bufferEnd = buffer + bufferSize; + + checkMetadata(&buffer, /*numElements=*/2, startTime, endTime, atomId); + + // check first element + checkTypeHeader(&buffer, BOOL_TYPE, /*numAnnotations=*/2); + checkScalar(&buffer, boolValue); + checkAnnotation(&buffer, boolAnnotation1Id, BOOL_TYPE, boolAnnotation1Value); + checkAnnotation(&buffer, boolAnnotation2Id, INT32_TYPE, boolAnnotation2Value); + + // check second element + checkTypeHeader(&buffer, FLOAT_TYPE, /*numAnnotations=*/2); + checkScalar(&buffer, floatValue); + checkAnnotation(&buffer, floatAnnotation1Id, INT32_TYPE, floatAnnotation1Value); + checkAnnotation(&buffer, floatAnnotation2Id, BOOL_TYPE, floatAnnotation2Value); + + EXPECT_EQ(buffer, bufferEnd); // ensure that we have read the entire buffer + EXPECT_EQ(stats_event_get_errors(event), 0); + stats_event_release(event); +} + +TEST(StatsEventTest, TestNoAtomIdError) { + struct stats_event* event = stats_event_obtain(); + // Don't set the atom id in order to trigger the error. + stats_event_build(event); + + uint32_t errors = stats_event_get_errors(event); + EXPECT_NE(errors | ERROR_NO_ATOM_ID, 0); + + stats_event_release(event); +} + +TEST(StatsEventTest, TestOverflowError) { + struct stats_event* event = stats_event_obtain(); + stats_event_set_atom_id(event, 100); + // Add 1000 int32s to the event. Each int32 takes 5 bytes so this will + // overflow the 4068 byte buffer. + for (int i = 0; i < 1000; i++) { + stats_event_write_int32(event, 0); + } + stats_event_build(event); + + uint32_t errors = stats_event_get_errors(event); + EXPECT_NE(errors | ERROR_OVERFLOW, 0); + + stats_event_release(event); +}