aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVova Sharaienko <sharaienko@google.com>2023-03-21 19:42:53 +0000
committerVova Sharaienko <sharaienko@google.com>2023-04-05 21:55:49 +0000
commit9376c5e09cf0216b603752ee328ed4f824059c55 (patch)
tree29093ae5b36d8d12cb1e4a68a723c148ef9d18d0
parent8ff54fc95ff95314fb27f4ca27d591ebd0307463 (diff)
downloadmodules-utils-9376c5e09cf0216b603752ee328ed4f824059c55.tar.gz
[TeX] Added telemetry express utility static lib
- to be used by AdServices Bug: 271127104 Test: atest ExpressLogApisTests Ignore-AOSP-First: has to be merged with TeX changes into framework Change-Id: I914d6ac88e5b4445c51c09109a79ff828ada3e9b
-rw-r--r--java/com/android/modules/expresslog/Android.bp39
-rw-r--r--java/com/android/modules/expresslog/Counter.java67
-rw-r--r--java/com/android/modules/expresslog/Histogram.java227
-rw-r--r--java/com/android/modules/expresslog/OWNERS10
-rw-r--r--java/com/android/modules/expresslog/TEST_MAPPING12
-rw-r--r--java/com/android/modules/expresslog/Utils.java21
-rw-r--r--javatests/com/android/modules/expresslog/Android.bp76
-rw-r--r--javatests/com/android/modules/expresslog/AndroidManifest.xml29
-rw-r--r--javatests/com/android/modules/expresslog/OWNERS3
-rw-r--r--javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java171
-rw-r--r--javatests/com/android/modules/expresslog/TEST_MAPPING12
-rw-r--r--javatests/com/android/modules/expresslog/UniformOptionsTest.java129
-rw-r--r--javatests/com/android/modules/expresslog/jni/.clang-format17
-rw-r--r--javatests/com/android/modules/expresslog/jni/onload.cpp40
-rw-r--r--jni/expresslog/.clang-format17
-rw-r--r--jni/expresslog/Android.bp49
-rw-r--r--jni/expresslog/com_android_modules_expresslog_Utils.cpp81
17 files changed, 1000 insertions, 0 deletions
diff --git a/java/com/android/modules/expresslog/Android.bp b/java/com/android/modules/expresslog/Android.bp
new file mode 100644
index 0000000..cacc7f8
--- /dev/null
+++ b/java/com/android/modules/expresslog/Android.bp
@@ -0,0 +1,39 @@
+//
+// 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "modules-utils-expresslog",
+ defaults: ["modules-utils-defaults"],
+ min_sdk_version: "30",
+ srcs: [
+ "*.java",
+ ":statslog-expresslog-java-gen",
+ ],
+ libs: [
+ "framework-statsd",
+ ],
+}
+
+genrule {
+ name: "statslog-expresslog-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module expresslog" +
+ " --javaPackage com.android.modules.expresslog --javaClass StatsExpressLog",
+ out: ["com/android/modules/expresslog/StatsExpressLog.java"],
+}
diff --git a/java/com/android/modules/expresslog/Counter.java b/java/com/android/modules/expresslog/Counter.java
new file mode 100644
index 0000000..b788c3f
--- /dev/null
+++ b/java/com/android/modules/expresslog/Counter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+package com.android.modules.expresslog;
+
+import android.annotation.NonNull;
+
+import com.android.modules.expresslog.StatsExpressLog;
+
+/** Counter encapsulates StatsD write API calls */
+public final class Counter {
+
+ // Not instantiable.
+ private Counter() {}
+
+ /**
+ * Increments Telemetry Express Counter metric by 1
+ * @param metricId to log, no-op if metricId is not defined in the TeX catalog
+ */
+ public static void logIncrement(@NonNull String metricId) {
+ logIncrement(metricId, 1);
+ }
+
+ /**
+ * Increments Telemetry Express Counter metric by 1
+ * @param metricId to log, no-op if metricId is not defined in the TeX catalog
+ * @param uid used as a dimension for the count metric
+ */
+ public static void logIncrementWithUid(@NonNull String metricId, int uid) {
+ logIncrementWithUid(metricId, uid, 1);
+ }
+
+ /**
+ * Increments Telemetry Express Counter metric by arbitrary value
+ * @param metricId to log, no-op if metricId is not defined in the TeX catalog
+ * @param amount to increment counter
+ */
+ public static void logIncrement(@NonNull String metricId, long amount) {
+ final long metricIdHash = Utils.hashString(metricId);
+ StatsExpressLog.write(StatsExpressLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
+ }
+
+ /**
+ * Increments Telemetry Express Counter metric by arbitrary value
+ * @param metricId to log, no-op if metricId is not defined in the TeX catalog
+ * @param uid used as a dimension for the count metric
+ * @param amount to increment counter
+ */
+ public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) {
+ final long metricIdHash = Utils.hashString(metricId);
+ StatsExpressLog.write(
+ StatsExpressLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
+ }
+}
diff --git a/java/com/android/modules/expresslog/Histogram.java b/java/com/android/modules/expresslog/Histogram.java
new file mode 100644
index 0000000..be300bf
--- /dev/null
+++ b/java/com/android/modules/expresslog/Histogram.java
@@ -0,0 +1,227 @@
+/*
+ * 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.
+ */
+
+package com.android.modules.expresslog;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+
+import com.android.modules.expresslog.StatsExpressLog;
+
+import java.util.Arrays;
+
+/** Histogram encapsulates StatsD write API calls */
+public final class Histogram {
+
+ private final long mMetricIdHash;
+ private final BinOptions mBinOptions;
+
+ /**
+ * Creates Histogram metric logging wrapper
+ *
+ * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog
+ * @param binOptions to calculate bin index for samples
+ */
+ public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) {
+ mMetricIdHash = Utils.hashString(metricId);
+ mBinOptions = binOptions;
+ }
+
+ /**
+ * Logs increment sample count for automatically calculated bin
+ *
+ * @param sample value
+ */
+ public void logSample(float sample) {
+ final int binIndex = mBinOptions.getBinForSample(sample);
+ StatsExpressLog.write(StatsExpressLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash,
+ /*count*/ 1, binIndex);
+ }
+
+ /**
+ * Logs increment sample count for automatically calculated bin
+ *
+ * @param uid used as a dimension for the count metric
+ * @param sample value
+ */
+ public void logSampleWithUid(int uid, float sample) {
+ final int binIndex = mBinOptions.getBinForSample(sample);
+ StatsExpressLog.write(StatsExpressLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
+ mMetricIdHash, /*count*/ 1, binIndex, uid);
+ }
+
+ /** Used by Histogram to map data sample to corresponding bin */
+ public interface BinOptions {
+ /**
+ * Returns bins count to be used by a histogram
+ *
+ * @return bins count used to initialize Options, including overflow & underflow bins
+ */
+ int getBinsCount();
+
+ /**
+ * Returns bin index for the input sample value
+ * index == 0 stands for underflow
+ * index == getBinsCount() - 1 stands for overflow
+ *
+ * @return zero based index
+ */
+ int getBinForSample(float sample);
+ }
+
+ /** Used by Histogram to map data sample to corresponding bin for uniform bins */
+ public static final class UniformOptions implements BinOptions {
+
+ private final int mBinCount;
+ private final float mMinValue;
+ private final float mExclusiveMaxValue;
+ private final float mBinSize;
+
+ /**
+ * Creates options for uniform (linear) sized bins
+ *
+ * @param binCount amount of histogram bins. 2 bin indexes will be calculated
+ * automatically to represent underflow & overflow bins
+ * @param minValue is included in the first bin, values less than minValue
+ * go to underflow bin
+ * @param exclusiveMaxValue is included in the overflow bucket. For accurate
+ * measure up to kMax, then exclusiveMaxValue
+ * should be set to kMax + 1
+ */
+ public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
+ float exclusiveMaxValue) {
+ if (binCount < 1) {
+ throw new IllegalArgumentException("Bin count should be positive number");
+ }
+
+ if (exclusiveMaxValue <= minValue) {
+ throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)");
+ }
+
+ mMinValue = minValue;
+ mExclusiveMaxValue = exclusiveMaxValue;
+ mBinSize = (mExclusiveMaxValue - minValue) / binCount;
+
+ // Implicitly add 2 for the extra underflow & overflow bins
+ mBinCount = binCount + 2;
+ }
+
+ @Override
+ public int getBinsCount() {
+ return mBinCount;
+ }
+
+ @Override
+ public int getBinForSample(float sample) {
+ if (sample < mMinValue) {
+ // goes to underflow
+ return 0;
+ } else if (sample >= mExclusiveMaxValue) {
+ // goes to overflow
+ return mBinCount - 1;
+ }
+ return (int) ((sample - mMinValue) / mBinSize + 1);
+ }
+ }
+
+ /** Used by Histogram to map data sample to corresponding bin for scaled bins */
+ public static final class ScaledRangeOptions implements BinOptions {
+ // store minimum value per bin
+ final long[] mBins;
+
+ /**
+ * Creates options for scaled range bins
+ *
+ * @param binCount amount of histogram bins. 2 bin indexes will be calculated
+ * automatically to represent underflow & overflow bins
+ * @param minValue is included in the first bin, values less than minValue
+ * go to underflow bin
+ * @param firstBinWidth used to represent first bin width and as a reference to calculate
+ * width for consecutive bins
+ * @param scaleFactor used to calculate width for consecutive bins
+ */
+ public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
+ @FloatRange(from = 1.f) float firstBinWidth,
+ @FloatRange(from = 1.f) float scaleFactor) {
+ if (binCount < 1) {
+ throw new IllegalArgumentException("Bin count should be positive number");
+ }
+
+ if (firstBinWidth < 1.f) {
+ throw new IllegalArgumentException(
+ "First bin width invalid (should be 1.f at minimum)");
+ }
+
+ if (scaleFactor < 1.f) {
+ throw new IllegalArgumentException(
+ "Scaled factor invalid (should be 1.f at minimum)");
+ }
+
+ // precalculating bins ranges (no need to create a bin for underflow reference value)
+ mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
+ }
+
+ @Override
+ public int getBinsCount() {
+ return mBins.length + 1;
+ }
+
+ @Override
+ public int getBinForSample(float sample) {
+ if (sample < mBins[0]) {
+ // goes to underflow
+ return 0;
+ } else if (sample >= mBins[mBins.length - 1]) {
+ // goes to overflow
+ return mBins.length;
+ }
+
+ return lower_bound(mBins, (long) sample) + 1;
+ }
+
+ // To find lower bound using binary search implementation of Arrays utility class
+ private static int lower_bound(long[] array, long sample) {
+ int index = Arrays.binarySearch(array, sample);
+ // If key is not present in the array
+ if (index < 0) {
+ // Index specify the position of the key when inserted in the sorted array
+ // so the element currently present at this position will be the lower bound
+ return Math.abs(index) - 2;
+ }
+ return index;
+ }
+
+ private static long[] initBins(int count, int minValue, float firstBinWidth,
+ float scaleFactor) {
+ long[] bins = new long[count];
+ bins[0] = minValue;
+ double lastWidth = firstBinWidth;
+ for (int i = 1; i < count; i++) {
+ // current bin minValue = previous bin width * scaleFactor
+ double currentBinMinValue = bins[i - 1] + lastWidth;
+ if (currentBinMinValue > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ "Attempted to create a bucket larger than maxint");
+ }
+
+ bins[i] = (long) currentBinMinValue;
+ lastWidth *= scaleFactor;
+ }
+ return bins;
+ }
+ }
+}
diff --git a/java/com/android/modules/expresslog/OWNERS b/java/com/android/modules/expresslog/OWNERS
new file mode 100644
index 0000000..d3a5812
--- /dev/null
+++ b/java/com/android/modules/expresslog/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 719316
+# Stats/expresslog
+
+jeffreyhuang@google.com
+muhammadq@google.com
+rslawik@google.com
+sharaienko@google.com
+singhtejinder@google.com
+tsaichristine@google.com
+yaochen@google.com
diff --git a/java/com/android/modules/expresslog/TEST_MAPPING b/java/com/android/modules/expresslog/TEST_MAPPING
new file mode 100644
index 0000000..e658d7a
--- /dev/null
+++ b/java/com/android/modules/expresslog/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "ExpressLogApisTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/java/com/android/modules/expresslog/Utils.java b/java/com/android/modules/expresslog/Utils.java
new file mode 100644
index 0000000..fde90fc
--- /dev/null
+++ b/java/com/android/modules/expresslog/Utils.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+package com.android.modules.expresslog;
+
+final class Utils {
+ static native long hashString(String stringToHash);
+}
diff --git a/javatests/com/android/modules/expresslog/Android.bp b/javatests/com/android/modules/expresslog/Android.bp
new file mode 100644
index 0000000..dd52750
--- /dev/null
+++ b/javatests/com/android/modules/expresslog/Android.bp
@@ -0,0 +1,76 @@
+// 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "ExpressLogApisTests",
+
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+
+ srcs: [
+ "*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "modules-utils-expresslog",
+ ],
+
+ libs: [
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ jni_libs: [
+ "libexpresslog_test_jni",
+ ],
+
+ test_suites: [
+ "general-tests",
+ ],
+}
+
+cc_library_shared {
+ name: "libexpresslog_test_jni",
+
+ sdk_version: "current",
+ min_sdk_version: "30",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+
+ "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
+ ],
+ srcs: [
+ "jni/onload.cpp",
+ ],
+ header_libs: [
+ "liblog_headers",
+ "libnativehelper_header_only",
+ ],
+ shared_libs: [
+ "liblog",
+ ],
+ static_libs: [
+ "libexpresslog_jni",
+ "libtextclassifier_hash_static",
+ ],
+}
diff --git a/javatests/com/android/modules/expresslog/AndroidManifest.xml b/javatests/com/android/modules/expresslog/AndroidManifest.xml
new file mode 100644
index 0000000..9128796
--- /dev/null
+++ b/javatests/com/android/modules/expresslog/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="com.android.modules.expresslog" >
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.modules.expresslog"
+ android:label="Telemetry Express Logging Helper Tests" />
+
+</manifest>
diff --git a/javatests/com/android/modules/expresslog/OWNERS b/javatests/com/android/modules/expresslog/OWNERS
new file mode 100644
index 0000000..fe9652e
--- /dev/null
+++ b/javatests/com/android/modules/expresslog/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 719316
+# Stats/expresslog
+file:/java/com/android/modules/expresslog/OWNERS
diff --git a/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java b/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java
new file mode 100644
index 0000000..8defce7
--- /dev/null
+++ b/javatests/com/android/modules/expresslog/ScaledRangeOptionsTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.
+ */
+package com.android.modules.expresslog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class ScaledRangeOptionsTest {
+ static {
+ System.loadLibrary("expresslog_test_jni");
+ }
+
+ private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
+
+ @Test
+ public void testGetBinsCount() {
+ Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2);
+ assertEquals(3, options1.getBinsCount());
+
+ Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2);
+ assertEquals(12, options10.getBinsCount());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructZeroBinsCount() {
+ new Histogram.ScaledRangeOptions(0, 100, 100, 2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructNegativeBinsCount() {
+ new Histogram.ScaledRangeOptions(-1, 100, 100, 2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructNegativeFirstBinWidth() {
+ new Histogram.ScaledRangeOptions(10, 100, -100, 2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructTooSmallFirstBinWidth() {
+ new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructNegativeScaleFactor() {
+ new Histogram.ScaledRangeOptions(10, 100, 100, -2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructTooSmallScaleFactor() {
+ new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructTooBigScaleFactor() {
+ new Histogram.ScaledRangeOptions(10, 100, 100, 500.f);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructTooBigBinRange() {
+ new Histogram.ScaledRangeOptions(100, 100, 100, 10.f);
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual1() {
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1);
+ assertEquals(12, options.getBinsCount());
+
+ assertEquals(11, options.getBinForSample(11));
+
+ for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i));
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual2() {
+ // this should produce bin otpions similar to linear histogram with bin width 2
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1);
+ assertEquals(12, options.getBinsCount());
+
+ for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i * 2));
+ assertEquals(i, options.getBinForSample(i * 2 - 1));
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual5() {
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1);
+ assertEquals(4, options.getBinsCount());
+ for (int i = 0; i < 2; i++) {
+ for (int sample = 0; sample < 5; sample++) {
+ assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
+ }
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual10() {
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1);
+ assertEquals(0, options.getBinForSample(0));
+ assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
+ assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
+
+ final float binSize = (101 - 1) / 10f;
+ for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i * binSize));
+ }
+ }
+
+ @Test
+ public void testBinIndexForScaleFactor2() {
+ final int binsCount = 10;
+ final int minValue = 10;
+ final int firstBinWidth = 5;
+ final int scaledFactor = 2;
+
+ Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(
+ binsCount, minValue, firstBinWidth, scaledFactor);
+ assertEquals(binsCount + 2, options.getBinsCount());
+ long[] binCounts = new long[10];
+
+ // precalculate max valid value - start value for the overflow bin
+ int lastBinStartValue = minValue; //firstBinMin value
+ int lastBinWidth = firstBinWidth;
+ for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) {
+ lastBinStartValue = lastBinStartValue + lastBinWidth;
+ lastBinWidth *= scaledFactor;
+ }
+
+ // underflow bin
+ for (int i = 1; i < minValue; i++) {
+ assertEquals(0, options.getBinForSample(i));
+ }
+
+ for (int i = 10; i < lastBinStartValue; i++) {
+ assertTrue(options.getBinForSample(i) > 0);
+ assertTrue(options.getBinForSample(i) <= binsCount);
+ binCounts[options.getBinForSample(i) - 1]++;
+ }
+
+ // overflow bin
+ assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue));
+
+ for (int i = 1; i < binsCount; i++) {
+ assertEquals(binCounts[i], binCounts[i - 1] * 2L);
+ }
+ }
+}
diff --git a/javatests/com/android/modules/expresslog/TEST_MAPPING b/javatests/com/android/modules/expresslog/TEST_MAPPING
new file mode 100644
index 0000000..e658d7a
--- /dev/null
+++ b/javatests/com/android/modules/expresslog/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "ExpressLogApisTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
diff --git a/javatests/com/android/modules/expresslog/UniformOptionsTest.java b/javatests/com/android/modules/expresslog/UniformOptionsTest.java
new file mode 100644
index 0000000..3cc03ec
--- /dev/null
+++ b/javatests/com/android/modules/expresslog/UniformOptionsTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+package com.android.modules.expresslog;
+
+import androidx.test.filters.SmallTest;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SmallTest
+public class UniformOptionsTest {
+ static {
+ System.loadLibrary("expresslog_test_jni");
+ }
+
+ private static final String TAG = UniformOptionsTest.class.getSimpleName();
+
+ @Test
+ public void testGetBinsCount() {
+ Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
+ assertEquals(3, options1.getBinsCount());
+
+ Histogram.UniformOptions options10 = new Histogram.UniformOptions(10, 100, 1000);
+ assertEquals(12, options10.getBinsCount());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructZeroBinsCount() {
+ new Histogram.UniformOptions(0, 100, 1000);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructNegativeBinsCount() {
+ new Histogram.UniformOptions(-1, 100, 1000);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testConstructMaxValueLessThanMinValue() {
+ new Histogram.UniformOptions(10, 1000, 100);
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual1() {
+ Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
+ for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i));
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual2() {
+ Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
+ for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i * 2));
+ assertEquals(i, options.getBinForSample(i * 2 - 1));
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual5() {
+ Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
+ assertEquals(4, options.getBinsCount());
+ for (int i = 0; i < 2; i++) {
+ for (int sample = 0; sample < 5; sample++) {
+ assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
+ }
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual10() {
+ Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
+ assertEquals(0, options.getBinForSample(0));
+ assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
+ assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
+
+ final float binSize = (101 - 1) / 10f;
+ for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
+ assertEquals(i, options.getBinForSample(i * binSize));
+ }
+ }
+
+ @Test
+ public void testBinIndexForRangeEqual90() {
+ final int binCount = 10;
+ final int minValue = 100;
+ final int maxValue = 100000;
+
+ Histogram.UniformOptions options = new Histogram.UniformOptions(binCount, minValue,
+ maxValue);
+
+ // logging underflow sample
+ assertEquals(0, options.getBinForSample(minValue - 1));
+
+ // logging overflow sample
+ assertEquals(binCount + 1, options.getBinForSample(maxValue));
+ assertEquals(binCount + 1, options.getBinForSample(maxValue + 1));
+
+ // logging min edge sample
+ assertEquals(1, options.getBinForSample(minValue));
+
+ // logging max edge sample
+ assertEquals(binCount, options.getBinForSample(maxValue - 1));
+
+ // logging single valid sample per bin
+ final int binSize = (maxValue - minValue) / binCount;
+
+ for (int i = 0; i < binCount; i++) {
+ assertEquals(i + 1, options.getBinForSample(minValue + binSize * i));
+ }
+ }
+}
diff --git a/javatests/com/android/modules/expresslog/jni/.clang-format b/javatests/com/android/modules/expresslog/jni/.clang-format
new file mode 100644
index 0000000..cead3a0
--- /dev/null
+++ b/javatests/com/android/modules/expresslog/jni/.clang-format
@@ -0,0 +1,17 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+BinPackArguments: true
+BinPackParameters: true
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+AccessModifierOffset: -4
+IncludeCategories:
+ - Regex: '^"Log\.h"'
+ Priority: -1
diff --git a/javatests/com/android/modules/expresslog/jni/onload.cpp b/javatests/com/android/modules/expresslog/jni/onload.cpp
new file mode 100644
index 0000000..a112467
--- /dev/null
+++ b/javatests/com/android/modules/expresslog/jni/onload.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TeX"
+
+#include <jni.h>
+#include <log/log.h>
+
+namespace android {
+extern int register_com_android_modules_expresslog_Utils(JNIEnv* env);
+} // namespace android
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("GetEnv failed!");
+ return result;
+ }
+ ALOG_ASSERT(env, "Could not retrieve the env!");
+
+ register_com_android_modules_expresslog_Utils(env);
+ return JNI_VERSION_1_4;
+}
diff --git a/jni/expresslog/.clang-format b/jni/expresslog/.clang-format
new file mode 100644
index 0000000..cead3a0
--- /dev/null
+++ b/jni/expresslog/.clang-format
@@ -0,0 +1,17 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+BinPackArguments: true
+BinPackParameters: true
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+AccessModifierOffset: -4
+IncludeCategories:
+ - Regex: '^"Log\.h"'
+ Priority: -1
diff --git a/jni/expresslog/Android.bp b/jni/expresslog/Android.bp
new file mode 100644
index 0000000..b477c7b
--- /dev/null
+++ b/jni/expresslog/Android.bp
@@ -0,0 +1,49 @@
+//
+// 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.
+
+// JNI library for Utils.hashString
+cc_library_static {
+ name: "libexpresslog_jni",
+
+ sdk_version: "current",
+ min_sdk_version: "30",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+
+ "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
+ ],
+ srcs: [
+ "com_android_modules_expresslog_Utils.cpp",
+ ],
+ header_libs: [
+ "liblog_headers",
+ "libnativehelper_header_only",
+ "libtextclassifier_hash_headers",
+ ],
+ shared_libs: [
+ "liblog",
+ ],
+ static_libs: [
+ "libtextclassifier_hash_static",
+ ],
+ visibility: ["//visibility:public"],
+ apex_available: [
+ "//apex_available:anyapex",
+ "//apex_available:platform",
+ ],
+}
diff --git a/jni/expresslog/com_android_modules_expresslog_Utils.cpp b/jni/expresslog/com_android_modules_expresslog_Utils.cpp
new file mode 100644
index 0000000..973d946
--- /dev/null
+++ b/jni/expresslog/com_android_modules_expresslog_Utils.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#define LOG_NAMESPACE "TeX.tag."
+#define LOG_TAG "TeX"
+
+#include <log/log.h>
+#include <nativehelper/scoped_local_ref.h>
+#include <nativehelper/scoped_utf_chars.h>
+#include <utils/hash/farmhash.h>
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+static jclass gStringClass = nullptr;
+
+/**
+ * Class: com_android_modules_expresslog_Utils
+ * Method: hashString
+ * Signature: (Ljava/lang/String;)J
+ */
+static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) {
+ ScopedUtfChars name(env, metricNameObj);
+ if (name.c_str() == nullptr) {
+ return 0;
+ }
+
+ return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size()));
+}
+
+static const JNINativeMethod gMethods[] = {
+ {"hashString", "(Ljava/lang/String;)J", (void*)hashString},
+};
+
+namespace android {
+
+int register_com_android_modules_expresslog_Utils(JNIEnv* env) {
+ static const char* const kUtilsClassName = "com/android/modules/expresslog/Utils";
+ static const char* const kStringClassName = "java/lang/String";
+
+ ScopedLocalRef<jclass> utilsCls(env, env->FindClass(kUtilsClassName));
+ if (utilsCls.get() == nullptr) {
+ ALOGE("jni expresslog registration failure, class not found '%s'", kUtilsClassName);
+ return JNI_ERR;
+ }
+
+ jclass stringClass = env->FindClass(kStringClassName);
+ if (stringClass == nullptr) {
+ ALOGE("jni expresslog registration failure, class not found '%s'", kStringClassName);
+ return JNI_ERR;
+ }
+ gStringClass = static_cast<jclass>(env->NewGlobalRef(stringClass));
+ if (gStringClass == nullptr) {
+ ALOGE("jni expresslog Unable to create global reference '%s'", kStringClassName);
+ return JNI_ERR;
+ }
+
+ const jint count = sizeof(gMethods) / sizeof(gMethods[0]);
+ int status = env->RegisterNatives(utilsCls.get(), gMethods, count);
+ if (status < 0) {
+ ALOGE("jni expresslog registration failure, status: %d", status);
+ return JNI_ERR;
+ }
+ return JNI_VERSION_1_4;
+}
+
+} // namespace android