[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