[TeX] Introduced Telemetry Express Histogram metric Native API
- added support C++ TeX Histogram logging API
Bug: 268161449
Test: atest expresslog_test
Change-Id: I284c6ceab42208dc9432fe3887c9ac000028d072
diff --git a/libstats/expresslog/Android.bp b/libstats/expresslog/Android.bp
index 9cdc2c3..004f8b9 100644
--- a/libstats/expresslog/Android.bp
+++ b/libstats/expresslog/Android.bp
@@ -18,11 +18,17 @@
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 @@
],
shared_libs: [
"libbase",
+ "liblog",
"libstatssocket",
],
export_include_dirs: ["include"],
@@ -69,3 +76,38 @@
"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 0000000..c90282d
--- /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 <log/log.h>
+#include <statslog_express.h>
+#include <string.h>
+#include <utils/hash/farmhash.h>
+
+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> 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 0000000..aba2786
--- /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 <stdint.h>
+
+#include <memory>
+
+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> binOptions);
+
+ /**
+ * Logs increment sample count for automatically calculated bin
+ */
+ void logSample(float sample) const;
+
+private:
+ const int64_t mMetricIdHash;
+ const std::shared_ptr<BinOptions> 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 0000000..813c997
--- /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 <gtest/gtest.h>
+
+namespace android {
+namespace expresslog {
+
+#ifdef __ANDROID__
+TEST(UniformOptions, getBinsCount) {
+ const std::shared_ptr<Histogram::UniformOptions> options1(
+ Histogram::UniformOptions::create(1, 100, 1000));
+ ASSERT_EQ(3, options1->getBinsCount());
+
+ const std::shared_ptr<Histogram::UniformOptions> options10(
+ Histogram::UniformOptions::create(10, 100, 1000));
+ ASSERT_EQ(12, options10->getBinsCount());
+}
+
+TEST(UniformOptions, constructZeroBinsCount) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(0, 100, 1000));
+ ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, constructNegativeBinsCount) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(-1, 100, 1000));
+ ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, constructMaxValueLessThanMinValue) {
+ const std::shared_ptr<Histogram::UniformOptions> options(
+ Histogram::UniformOptions::create(10, 1000, 100));
+ ASSERT_EQ(nullptr, options);
+}
+
+TEST(UniformOptions, testBinIndexForRangeEqual1) {
+ const std::shared_ptr<Histogram::UniformOptions> 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<Histogram::UniformOptions> 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<Histogram::UniformOptions> 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<Histogram::UniformOptions> 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<Histogram::UniformOptions> 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