From 75f860033cf928b16f38773f5aaa41063e49ff8f Mon Sep 17 00:00:00 2001 From: Vova Sharaienko Date: Thu, 9 Feb 2023 01:51:07 +0000 Subject: [PATCH] [TeX] Introduced Telemetry Express Histogram metric Native API - added support C++ TeX Histogram logging API Bug: 268161449 Test: atest expresslog_test Change-Id: I284c6ceab42208dc9432fe3887c9ac000028d072 --- libstats/expresslog/Android.bp | 46 ++++++- libstats/expresslog/Histogram.cpp | 74 +++++++++++ libstats/expresslog/include/Histogram.h | 80 ++++++++++++ libstats/expresslog/tests/Histogram_test.cpp | 128 +++++++++++++++++++ 4 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 libstats/expresslog/Histogram.cpp create mode 100644 libstats/expresslog/include/Histogram.h create mode 100644 libstats/expresslog/tests/Histogram_test.cpp diff --git a/libstats/expresslog/Android.bp b/libstats/expresslog/Android.bp index 9cdc2c30a..004f8b9bf 100644 --- a/libstats/expresslog/Android.bp +++ b/libstats/expresslog/Android.bp @@ -18,11 +18,17 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } -cc_library { - name: "libexpresslog", +cc_defaults { + name: "expresslog_defaults", srcs: [ "Counter.cpp", + "Histogram.cpp", ], +} + +cc_library { + name: "libexpresslog", + defaults: ["expresslog_defaults"], cflags: [ "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash", "-Wall", @@ -37,6 +43,7 @@ cc_library { ], shared_libs: [ "libbase", + "liblog", "libstatssocket", ], export_include_dirs: ["include"], @@ -69,3 +76,38 @@ cc_library_static { "libstatssocket", ], } + +cc_test { + name: "expresslog_test", + defaults: ["expresslog_defaults"], + test_suites: [ + "general-tests", + ], + srcs: [ + "tests/Histogram_test.cpp", + ], + local_include_dirs: [ + "include", + ], + cflags: [ + "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash", + "-Wall", + "-Wextra", + "-Wunused", + "-Wpedantic", + "-Werror", + ], + header_libs: [ + "libtextclassifier_hash_headers", + ], + static_libs: [ + "libgmock", + "libbase", + "liblog", + "libstatslog_express", + "libtextclassifier_hash_static", + ], + shared_libs: [ + "libstatssocket", + ] +} diff --git a/libstats/expresslog/Histogram.cpp b/libstats/expresslog/Histogram.cpp new file mode 100644 index 000000000..c90282d57 --- /dev/null +++ b/libstats/expresslog/Histogram.cpp @@ -0,0 +1,74 @@ +// +// Copyright (C) 2023 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/Histogram.h" + +#define LOG_TAG "tex" + +#include +#include +#include +#include + +namespace android { +namespace expresslog { + +Histogram::UniformOptions* Histogram::UniformOptions::create(int binCount, float minValue, + float exclusiveMaxValue) { + if (binCount < 1) { + ALOGE("Bin count should be positive number"); + return nullptr; + } + + if (exclusiveMaxValue <= minValue) { + ALOGE("Bins range invalid (maxValue < minValue)"); + return nullptr; + } + + return new UniformOptions(binCount, minValue, exclusiveMaxValue); +} + +Histogram::UniformOptions::UniformOptions(int binCount, float minValue, float exclusiveMaxValue) + : // Implicitly add 2 for the extra undeflow & overflow bins + mBinCount(binCount + 2), + mMinValue(minValue), + mExclusiveMaxValue(exclusiveMaxValue), + mBinSize((exclusiveMaxValue - minValue) / binCount) { +} + +int Histogram::UniformOptions::getBinForSample(float sample) const { + if (sample < mMinValue) { + // goes to underflow + return 0; + } else if (sample >= mExclusiveMaxValue) { + // goes to overflow + return mBinCount - 1; + } + return (int)((sample - mMinValue) / mBinSize + 1); +} + +Histogram::Histogram(const char* metricName, std::shared_ptr binOptions) + : mMetricIdHash(farmhash::Fingerprint64(metricName, strlen(metricName))), + mBinOptions(std::move(binOptions)) { +} + +void Histogram::logSample(float sample) const { + const int binIndex = mBinOptions->getBinForSample(sample); + stats_write(EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, /*count*/ 1, binIndex); +} + +} // namespace expresslog +} // namespace android diff --git a/libstats/expresslog/include/Histogram.h b/libstats/expresslog/include/Histogram.h new file mode 100644 index 000000000..aba278626 --- /dev/null +++ b/libstats/expresslog/include/Histogram.h @@ -0,0 +1,80 @@ +// +// Copyright (C) 2023 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 + +namespace android { +namespace expresslog { + +/** Histogram encapsulates StatsD write API calls */ +class Histogram final { +public: + class BinOptions { + public: + virtual ~BinOptions() = default; + /** + * Returns bins count to be used by a Histogram + * + * @return bins count used to initialize Options, including overflow & underflow bins + */ + virtual int getBinsCount() const = 0; + + /** + * @return zero based index + * Calculates bin index for the input sample value + * index == 0 stands for underflow + * index == getBinsCount() - 1 stands for overflow + */ + virtual int getBinForSample(float sample) const = 0; + }; + + /** Used by Histogram to map data sample to corresponding bin for uniform bins */ + class UniformOptions : public BinOptions { + UniformOptions(int binCount, float minValue, float exclusiveMaxValue); + + public: + static UniformOptions* create(int binCount, float minValue, float exclusiveMaxValue); + + int getBinsCount() const override { + return mBinCount; + } + + int getBinForSample(float sample) const override; + + private: + const int mBinCount; + const float mMinValue; + const float mExclusiveMaxValue; + const float mBinSize; + }; + + Histogram(const char* metricName, std::shared_ptr binOptions); + + /** + * Logs increment sample count for automatically calculated bin + */ + void logSample(float sample) const; + +private: + const int64_t mMetricIdHash; + const std::shared_ptr mBinOptions; +}; + +} // namespace expresslog +} // namespace android diff --git a/libstats/expresslog/tests/Histogram_test.cpp b/libstats/expresslog/tests/Histogram_test.cpp new file mode 100644 index 000000000..813c9977d --- /dev/null +++ b/libstats/expresslog/tests/Histogram_test.cpp @@ -0,0 +1,128 @@ +// +// Copyright (C) 2023 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 "Histogram.h" + +#include + +namespace android { +namespace expresslog { + +#ifdef __ANDROID__ +TEST(UniformOptions, getBinsCount) { + const std::shared_ptr options1( + Histogram::UniformOptions::create(1, 100, 1000)); + ASSERT_EQ(3, options1->getBinsCount()); + + const std::shared_ptr options10( + Histogram::UniformOptions::create(10, 100, 1000)); + ASSERT_EQ(12, options10->getBinsCount()); +} + +TEST(UniformOptions, constructZeroBinsCount) { + const std::shared_ptr options( + Histogram::UniformOptions::create(0, 100, 1000)); + ASSERT_EQ(nullptr, options); +} + +TEST(UniformOptions, constructNegativeBinsCount) { + const std::shared_ptr options( + Histogram::UniformOptions::create(-1, 100, 1000)); + ASSERT_EQ(nullptr, options); +} + +TEST(UniformOptions, constructMaxValueLessThanMinValue) { + const std::shared_ptr options( + Histogram::UniformOptions::create(10, 1000, 100)); + ASSERT_EQ(nullptr, options); +} + +TEST(UniformOptions, testBinIndexForRangeEqual1) { + const std::shared_ptr options( + Histogram::UniformOptions::create(10, 1, 11)); + for (int i = 0, bins = options->getBinsCount(); i < bins; i++) { + ASSERT_EQ(i, options->getBinForSample(i)); + } +} + +TEST(UniformOptions, testBinIndexForRangeEqual2) { + const std::shared_ptr options( + Histogram::UniformOptions::create(10, 1, 21)); + for (int i = 0, bins = options->getBinsCount(); i < bins; i++) { + ASSERT_EQ(i, options->getBinForSample(i * 2)); + ASSERT_EQ(i, options->getBinForSample(i * 2 - 1)); + } +} + +TEST(UniformOptions, testBinIndexForRangeEqual5) { + const std::shared_ptr options( + Histogram::UniformOptions::create(2, 0, 10)); + ASSERT_EQ(4, options->getBinsCount()); + for (int i = 0; i < 2; i++) { + for (int sample = 0; sample < 5; sample++) { + ASSERT_EQ(i + 1, options->getBinForSample(i * 5 + sample)); + } + } +} + +TEST(UniformOptions, testBinIndexForRangeEqual10) { + const std::shared_ptr options( + Histogram::UniformOptions::create(10, 1, 101)); + ASSERT_EQ(0, options->getBinForSample(0)); + ASSERT_EQ(options->getBinsCount() - 2, options->getBinForSample(100)); + ASSERT_EQ(options->getBinsCount() - 1, options->getBinForSample(101)); + + const float binSize = (101 - 1) / 10.f; + for (int i = 1, bins = options->getBinsCount() - 1; i < bins; i++) { + ASSERT_EQ(i, options->getBinForSample(i * binSize)); + } +} + +TEST(UniformOptions, testBinIndexForRangeEqual90) { + const int binCount = 10; + const int minValue = 100; + const int maxValue = 100000; + + const std::shared_ptr options( + Histogram::UniformOptions::create(binCount, minValue, maxValue)); + + // logging underflow sample + ASSERT_EQ(0, options->getBinForSample(minValue - 1)); + + // logging overflow sample + ASSERT_EQ(binCount + 1, options->getBinForSample(maxValue)); + ASSERT_EQ(binCount + 1, options->getBinForSample(maxValue + 1)); + + // logging min edge sample + ASSERT_EQ(1, options->getBinForSample(minValue)); + + // logging max edge sample + ASSERT_EQ(binCount, options->getBinForSample(maxValue - 1)); + + // logging single valid sample per bin + const int binSize = (maxValue - minValue) / binCount; + + for (int i = 0; i < binCount; i++) { + ASSERT_EQ(i + 1, options->getBinForSample(minValue + binSize * i)); + } +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace expresslog +} // namespace android