diff options
author | Vova Sharaienko <sharaienko@google.com> | 2023-04-12 02:15:25 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-04-12 02:15:25 +0000 |
commit | 86d9645b9b7e2655cff4726b0fec015d9fa2b341 (patch) | |
tree | 2e3e0bdb71edae87bfbf810ccd039320f60d1fa5 | |
parent | 340a97e3bc1e6c9f2ad0d3c6f5ad5524fb84cb5c (diff) | |
parent | b9b201351dff32e4dc7f3b73d5240c6b33372a5f (diff) | |
download | modules-utils-86d9645b9b7e2655cff4726b0fec015d9fa2b341.tar.gz |
[TeX] Added telemetry express utility static lib am: b9b201351d
Original change: https://android-review.googlesource.com/c/platform/frameworks/libs/modules-utils/+/2500657
Change-Id: I867ea4a3d2c68fd0c9a482a1e3b069a2a56989c8
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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 |