diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp new file mode 100644 index 000000000..93539903a --- /dev/null +++ b/libstats/push_compat/Android.bp @@ -0,0 +1,54 @@ +// +// 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. +// + +// ========================================================================= +// Native library that toggles between the old and new statsd socket +// protocols. This library should only be used by DNS resolver or other +// native modules on Q that log pushed atoms to statsd. +// ========================================================================= +cc_defaults { + name: "libstatspush_compat_defaults", + srcs: ["StatsEventCompat.cpp"], + cflags: [ + "-Wall", + "-Werror", + ], + header_libs: ["libstatssocket_headers"], + static_libs: [ + "libbase", + "liblog", + "libstatssocket_q", + "libutils" + ], +} + +cc_library { + name: "libstatspush_compat", + defaults: ["libstatspush_compat_defaults"], + export_include_dirs: ["include"], + static_libs: ["libgtest_prod"], +} + +cc_test { + name: "libstatspush_compat_test", + defaults: ["libstatspush_compat_defaults"], + test_suites: ["device_tests"], + srcs: [ + "tests/StatsEventCompat_test.cpp", + ], + static_libs: ["libgmock"], +} + diff --git a/libstats/push_compat/StatsEventCompat.cpp b/libstats/push_compat/StatsEventCompat.cpp new file mode 100644 index 000000000..edfa070ed --- /dev/null +++ b/libstats/push_compat/StatsEventCompat.cpp @@ -0,0 +1,221 @@ +/* + * 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 "include/StatsEventCompat.h" +#include +#include +#include +#include +#include + +using android::base::GetProperty; + +const static int kStatsEventTag = 1937006964; + +/* Checking ro.build.version.release is fragile, as the release field is + * an opaque string without structural guarantees. However, testing confirms + * that on Q devices, the property is "10," and on R, it is "R." Until + * android_get_device_api_level() is updated, this is the only solution. + * + * TODO(b/146019024): migrate to android_get_device_api_level() + */ +const bool StatsEventCompat::mPlatformAtLeastR = + GetProperty("ro.build.version.codename", "") == "R" || + android_get_device_api_level() > __ANDROID_API_Q__; + +// definitions of static class variables +bool StatsEventCompat::mAttemptedLoad = false; +struct stats_event_api_table* StatsEventCompat::mStatsEventApi = nullptr; +std::mutex StatsEventCompat::mLoadLock; + +StatsEventCompat::StatsEventCompat() : mEventQ(kStatsEventTag) { + // guard loading because StatsEventCompat might be called from multithreaded + // environment + { + std::lock_guard lg(mLoadLock); + if (!mAttemptedLoad) { + void* handle = dlopen("libstatssocket.so", RTLD_NOW); + if (handle) { + mStatsEventApi = (struct stats_event_api_table*)dlsym(handle, "table"); + } else { + ALOGE("dlopen failed: %s\n", dlerror()); + } + } + mAttemptedLoad = true; + } + + if (mStatsEventApi) { + mEventR = mStatsEventApi->obtain(); + } else if (!mPlatformAtLeastR) { + mEventQ << android::elapsedRealtimeNano(); + } +} + +StatsEventCompat::~StatsEventCompat() { + if (mStatsEventApi) mStatsEventApi->release(mEventR); +} + +void StatsEventCompat::setAtomId(int32_t atomId) { + if (mStatsEventApi) { + mStatsEventApi->set_atom_id(mEventR, (uint32_t)atomId); + } else if (!mPlatformAtLeastR) { + mEventQ << atomId; + } +} + +void StatsEventCompat::writeInt32(int32_t value) { + if (mStatsEventApi) { + mStatsEventApi->write_int32(mEventR, value); + } else if (!mPlatformAtLeastR) { + mEventQ << value; + } +} + +void StatsEventCompat::writeInt64(int64_t value) { + if (mStatsEventApi) { + mStatsEventApi->write_int64(mEventR, value); + } else if (!mPlatformAtLeastR) { + mEventQ << value; + } +} + +void StatsEventCompat::writeFloat(float value) { + if (mStatsEventApi) { + mStatsEventApi->write_float(mEventR, value); + } else if (!mPlatformAtLeastR) { + mEventQ << value; + } +} + +void StatsEventCompat::writeBool(bool value) { + if (mStatsEventApi) { + mStatsEventApi->write_bool(mEventR, value); + } else if (!mPlatformAtLeastR) { + mEventQ << value; + } +} + +void StatsEventCompat::writeByteArray(const char* buffer, size_t length) { + if (mStatsEventApi) { + mStatsEventApi->write_byte_array(mEventR, (const uint8_t*)buffer, length); + } else if (!mPlatformAtLeastR) { + mEventQ.AppendCharArray(buffer, length); + } +} + +void StatsEventCompat::writeString(const char* value) { + if (value == nullptr) value = ""; + + if (mStatsEventApi) { + mStatsEventApi->write_string8(mEventR, value); + } else if (!mPlatformAtLeastR) { + mEventQ << value; + } +} + +void StatsEventCompat::writeAttributionChain(const int32_t* uids, size_t numUids, + const vector& tags) { + if (mStatsEventApi) { + mStatsEventApi->write_attribution_chain(mEventR, (const uint32_t*)uids, tags.data(), + (uint8_t)numUids); + } else if (!mPlatformAtLeastR) { + mEventQ.begin(); + for (size_t i = 0; i < numUids; i++) { + mEventQ.begin(); + mEventQ << uids[i]; + const char* tag = tags[i] ? tags[i] : ""; + mEventQ << tag; + mEventQ.end(); + } + mEventQ.end(); + } +} + +void StatsEventCompat::writeKeyValuePairs(const map& int32Map, + const map& int64Map, + const map& stringMap, + const map& floatMap) { + if (mStatsEventApi) { + vector pairs; + + for (const auto& it : int32Map) { + pairs.push_back({.key = it.first, .valueType = INT32_TYPE, .int32Value = it.second}); + } + for (const auto& it : int64Map) { + pairs.push_back({.key = it.first, .valueType = INT64_TYPE, .int64Value = it.second}); + } + for (const auto& it : stringMap) { + pairs.push_back({.key = it.first, .valueType = STRING_TYPE, .stringValue = it.second}); + } + for (const auto& it : floatMap) { + pairs.push_back({.key = it.first, .valueType = FLOAT_TYPE, .floatValue = it.second}); + } + + mStatsEventApi->write_key_value_pairs(mEventR, pairs.data(), (uint8_t)pairs.size()); + } + + else if (!mPlatformAtLeastR) { + mEventQ.begin(); + writeKeyValuePairMap(int32Map); + writeKeyValuePairMap(int64Map); + writeKeyValuePairMap(stringMap); + writeKeyValuePairMap(floatMap); + mEventQ.end(); + } +} + +template +void StatsEventCompat::writeKeyValuePairMap(const map& keyValuePairMap) { + for (const auto& it : keyValuePairMap) { + mEventQ.begin(); + mEventQ << it.first; + mEventQ << it.second; + mEventQ.end(); + } +} + +// explicitly specify which types we're going to use +template void StatsEventCompat::writeKeyValuePairMap(const map&); +template void StatsEventCompat::writeKeyValuePairMap(const map&); +template void StatsEventCompat::writeKeyValuePairMap(const map&); +template void StatsEventCompat::writeKeyValuePairMap(const map&); + +void StatsEventCompat::addBoolAnnotation(uint8_t annotationId, bool value) { + if (mStatsEventApi) mStatsEventApi->add_bool_annotation(mEventR, annotationId, value); + // Don't do anything if on Q. +} + +void StatsEventCompat::addInt32Annotation(uint8_t annotationId, int32_t value) { + if (mStatsEventApi) mStatsEventApi->add_int32_annotation(mEventR, annotationId, value); + // Don't do anything if on Q. +} + +int StatsEventCompat::writeToSocket() { + if (mStatsEventApi) { + mStatsEventApi->build(mEventR); + return mStatsEventApi->write(mEventR); + } + + if (!mPlatformAtLeastR) return mEventQ.write(LOG_ID_STATS); + + // We reach here only if we're on R, but libstatspush_compat was unable to + // be loaded using dlopen. + return -ENOLINK; +} + +bool StatsEventCompat::usesNewSchema() { + return mStatsEventApi != nullptr; +} diff --git a/libstats/push_compat/include/StatsEventCompat.h b/libstats/push_compat/include/StatsEventCompat.h new file mode 100644 index 000000000..a8cde681c --- /dev/null +++ b/libstats/push_compat/include/StatsEventCompat.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include "stats_event.h" +#include "stats_event_list.h" + +using std::map; +using std::vector; + +class StatsEventCompat { + public: + StatsEventCompat(); + ~StatsEventCompat(); + + void setAtomId(int32_t atomId); + void writeInt32(int32_t value); + void writeInt64(int64_t value); + void writeFloat(float value); + void writeBool(bool value); + void writeByteArray(const char* buffer, size_t length); + void writeString(const char* value); + + // Pre-condition: numUids == tags.size() + void writeAttributionChain(const int32_t* uids, size_t numUids, + const vector& tags); + + void writeKeyValuePairs(const map& int32Map, const map& int64Map, + const map& stringMap, + const map& floatMap); + + void addBoolAnnotation(uint8_t annotationId, bool value); + void addInt32Annotation(uint8_t annotationId, int32_t value); + + int writeToSocket(); + + private: + // static member variables + const static bool mPlatformAtLeastR; + static bool mAttemptedLoad; + static std::mutex mLoadLock; + static struct stats_event_api_table* mStatsEventApi; + + // non-static member variables + struct stats_event* mEventR = nullptr; + stats_event_list mEventQ; + + template + void writeKeyValuePairMap(const map& keyValuePairMap); + + bool usesNewSchema(); + FRIEND_TEST(StatsEventCompatTest, TestDynamicLoading); +}; diff --git a/libstats/push_compat/tests/StatsEventCompat_test.cpp b/libstats/push_compat/tests/StatsEventCompat_test.cpp new file mode 100644 index 000000000..2be24ec10 --- /dev/null +++ b/libstats/push_compat/tests/StatsEventCompat_test.cpp @@ -0,0 +1,38 @@ +/* + * 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 "include/StatsEventCompat.h" +#include +#include +#include + +using android::base::GetProperty; + +/* Checking ro.build.version.release is fragile, as the release field is + * an opaque string without structural guarantees. However, testing confirms + * that on Q devices, the property is "10," and on R, it is "R." Until + * android_get_device_api_level() is updated, this is the only solution. + * + * + * TODO(b/146019024): migrate to android_get_device_api_level() + */ +const static bool mPlatformAtLeastR = GetProperty("ro.build.version.release", "") == "R" || + android_get_device_api_level() > __ANDROID_API_Q__; + +TEST(StatsEventCompatTest, TestDynamicLoading) { + StatsEventCompat event; + EXPECT_EQ(mPlatformAtLeastR, event.usesNewSchema()); +} diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp index beb009cbe..bd3d9ae6f 100644 --- a/libstats/socket/Android.bp +++ b/libstats/socket/Android.bp @@ -41,3 +41,9 @@ cc_library { "liblog", ], } + +cc_library_headers { + name: "libstatssocket_headers", + export_include_dirs: ["include"], + host_supported: true, +} diff --git a/libstats/socket/include/stats_event.h b/libstats/socket/include/stats_event.h index 6a33d543d..e7117d2a0 100644 --- a/libstats/socket/include/stats_event.h +++ b/libstats/socket/include/stats_event.h @@ -85,7 +85,7 @@ struct stats_event* stats_event_obtain(); // The build function can be called multiple times without error. If the event // has been built before, this function is a no-op. void stats_event_build(struct stats_event* event); -void stats_event_write(struct stats_event* event); +int stats_event_write(struct stats_event* event); void stats_event_release(struct stats_event* event); void stats_event_set_atom_id(struct stats_event* event, uint32_t atomId); @@ -98,7 +98,7 @@ void stats_event_write_bool(struct stats_event* event, bool value); void stats_event_write_byte_array(struct stats_event* event, const uint8_t* buf, size_t numBytes); // Buf must be null-terminated. -void stats_event_write_string8(struct stats_event* event, const char* buf); +void stats_event_write_string8(struct stats_event* event, const char* value); // Tags must be null-terminated. void stats_event_write_attribution_chain(struct stats_event* event, const uint32_t* uids, @@ -127,9 +127,33 @@ void stats_event_add_int32_annotation(struct stats_event* event, uint8_t annotat int32_t value); uint32_t stats_event_get_atom_id(struct stats_event* event); +// Size is an output parameter. uint8_t* stats_event_get_buffer(struct stats_event* event, size_t* size); uint32_t stats_event_get_errors(struct stats_event* event); +// This table is used by StatsEventCompat to access the stats_event API. +struct stats_event_api_table { + struct stats_event* (*obtain)(void); + void (*build)(struct stats_event*); + int (*write)(struct stats_event*); + void (*release)(struct stats_event*); + void (*set_atom_id)(struct stats_event*, uint32_t); + void (*write_int32)(struct stats_event*, int32_t); + void (*write_int64)(struct stats_event*, int64_t); + void (*write_float)(struct stats_event*, float); + void (*write_bool)(struct stats_event*, bool); + void (*write_byte_array)(struct stats_event*, const uint8_t*, size_t); + void (*write_string8)(struct stats_event*, const char*); + void (*write_attribution_chain)(struct stats_event*, const uint32_t*, const char* const*, + uint8_t); + void (*write_key_value_pairs)(struct stats_event*, struct key_value_pair*, uint8_t); + void (*add_bool_annotation)(struct stats_event*, uint8_t, bool); + void (*add_int32_annotation)(struct stats_event*, uint8_t, int32_t); + uint32_t (*get_atom_id)(struct stats_event*); + uint8_t* (*get_buffer)(struct stats_event*, size_t*); + uint32_t (*get_errors)(struct stats_event*); +}; + #ifdef __cplusplus } #endif // __CPLUSPLUS diff --git a/libstats/socket/stats_event.c b/libstats/socket/stats_event.c index ef887e3ea..409843410 100644 --- a/libstats/socket/stats_event.c +++ b/libstats/socket/stats_event.c @@ -193,12 +193,12 @@ void stats_event_write_byte_array(struct stats_event* event, const uint8_t* buf, append_byte_array(event, buf, numBytes); } -// Buf is assumed to be encoded using UTF8 -void stats_event_write_string8(struct stats_event* event, const char* buf) { +// Value is assumed to be encoded using UTF8 +void stats_event_write_string8(struct stats_event* event, const char* value) { if (event->errors) return; start_field(event, STRING_TYPE); - append_string(event, buf); + append_string(event, value); } // Tags are assumed to be encoded using UTF8 @@ -320,8 +320,28 @@ void stats_event_build(struct stats_event* event) { event->built = true; } -void stats_event_write(struct stats_event* event) { +int stats_event_write(struct stats_event* event) { stats_event_build(event); - - write_buffer_to_statsd(&event->buf, event->size, event->atomId); + return write_buffer_to_statsd(&event->buf, event->size, event->atomId); } + +struct stats_event_api_table table = { + stats_event_obtain, + stats_event_build, + stats_event_write, + stats_event_release, + stats_event_set_atom_id, + stats_event_write_int32, + stats_event_write_int64, + stats_event_write_float, + stats_event_write_bool, + stats_event_write_byte_array, + stats_event_write_string8, + stats_event_write_attribution_chain, + stats_event_write_key_value_pairs, + stats_event_add_bool_annotation, + stats_event_add_int32_annotation, + stats_event_get_atom_id, + stats_event_get_buffer, + stats_event_get_errors, +};