diff --git a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java new file mode 100644 index 0000000000000..f3d73902414c7 --- /dev/null +++ b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2021 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 android.os; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.LongArrayMultiStateCounter; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class LongArrayMultiStateCounterPerfTest { + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + /** + * A complete line-for-line reimplementation of + * {@link }com.android.internal.os.CpuTimeInFreqMultiStateCounter}, only in Java instead of + * native. + */ + private static class TestLongArrayMultiStateCounter { + private final int mStateCount; + private final int mArrayLength; + private int mCurrentState; + private long mLastStateChangeTimestampMs; + private long mLastUpdateTimestampMs; + + private static class State { + private long mTimeInStateSinceUpdate; + private long[] mCounter; + } + + private final State[] mStates; + private final long[] mLastTimeInFreq; + private final long[] mDelta; + + TestLongArrayMultiStateCounter(int stateCount, int arrayLength, int initialState, + long timestampMs) { + mStateCount = stateCount; + mArrayLength = arrayLength; + mCurrentState = initialState; + mLastStateChangeTimestampMs = timestampMs; + mLastUpdateTimestampMs = timestampMs; + mStates = new State[stateCount]; + for (int i = 0; i < mStateCount; i++) { + mStates[i] = new State(); + mStates[i].mCounter = new long[mArrayLength]; + } + mLastTimeInFreq = new long[mArrayLength]; + mDelta = new long[mArrayLength]; + } + + public void setState(int state, long timestampMs) { + if (timestampMs >= mLastStateChangeTimestampMs) { + mStates[mCurrentState].mTimeInStateSinceUpdate += + timestampMs - mLastStateChangeTimestampMs; + } else { + for (int i = 0; i < mStateCount; i++) { + mStates[i].mTimeInStateSinceUpdate = 0; + } + } + mCurrentState = state; + mLastStateChangeTimestampMs = timestampMs; + } + + public void updateValue(long[] timeInFreq, long timestampMs) { + setState(mCurrentState, timestampMs); + + if (timestampMs > mLastUpdateTimestampMs) { + if (delta(mLastTimeInFreq, timeInFreq, mDelta)) { + long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs; + for (int i = 0; i < mStateCount; i++) { + long timeInState = mStates[i].mTimeInStateSinceUpdate; + if (timeInState > 0) { + add(mStates[i].mCounter, mDelta, timeInState, timeSinceUpdate); + mStates[i].mTimeInStateSinceUpdate = 0; + } + } + } else { + throw new RuntimeException(); + } + } else if (timestampMs < mLastUpdateTimestampMs) { + throw new RuntimeException(); + } + System.arraycopy(timeInFreq, 0, mLastTimeInFreq, 0, mArrayLength); + mLastUpdateTimestampMs = timestampMs; + } + + private boolean delta(long[] timeInFreq1, long[] timeInFreq2, long[] delta) { + if (delta.length != mArrayLength) { + throw new RuntimeException(); + } + + boolean is_delta_valid = true; + for (int i = 0; i < mStateCount; i++) { + if (timeInFreq2[i] >= timeInFreq1[i]) { + delta[i] = timeInFreq2[i] - timeInFreq1[i]; + } else { + delta[i] = 0; + is_delta_valid = false; + } + } + + return is_delta_valid; + } + + private void add(long[] counter, long[] delta, long numerator, long denominator) { + if (numerator != denominator) { + for (int i = 0; i < mArrayLength; i++) { + counter[i] += delta[i] * numerator / denominator; + } + } else { + for (int i = 0; i < mArrayLength; i++) { + counter[i] += delta[i]; + } + } + } + } + + @Test + public void javaImplementation() { + TestLongArrayMultiStateCounter counter = + new TestLongArrayMultiStateCounter(2, 4, 0, 1000); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + long time = 1000; + long[] timeInFreq = {100, 200, 300, 400}; + while (state.keepRunning()) { + counter.setState(1, time); + counter.setState(0, time + 1000); + counter.updateValue(timeInFreq, time + 2000); + time += 10000; + } + } + + @Test + public void nativeImplementation() { + LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4, 0, 1000); + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + long time = 1000; + LongArrayMultiStateCounter.LongArrayContainer timeInFreq = + new LongArrayMultiStateCounter.LongArrayContainer(4); + timeInFreq.setValues(new long[]{100, 200, 300, 400}); + while (state.keepRunning()) { + counter.setState(1, time); + counter.setState(0, time + 1000); + counter.updateValues(timeInFreq, time + 2000); + time += 10000; + } + } +} diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java new file mode 100644 index 0000000000000..1a3b29dbc90e9 --- /dev/null +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2021 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.internal.os; + +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; + +import libcore.util.NativeAllocationRegistry; + +/** + * Performs per-state counting of multi-element values over time. The class' behavior is illustrated + * by this example: + *
+ *   // At 0 ms, the state of the tracked object is 0
+ *   counter.setState(0, 0);
+ *
+ *   // At 1000 ms, the state changes to 1
+ *   counter.setState(1, 1000);
+ *
+ *   // At 3000 ms, the tracked values are updated to {100, 200}
+ *   arrayContainer.setValues(new long[]{{30, 300}};
+ *   counter.updateValues(arrayContainer, 3000);
+ *
+ *   // The values are distributed between states 0 and 1 according to the time
+ *   // spent in those respective states. In this specific case, 1000 and 2000 ms.
+ *   counter.getValues(arrayContainer, 0);
+ *   // arrayContainer now has values {10, 100}
+ *   counter.getValues(arrayContainer, 1);
+ *   // arrayContainer now has values {20, 200}
+ * 
+ * + * The tracked values are expected to increase monotonically. + * + * @hide + */ +public class LongArrayMultiStateCounter { + + /** + * Container for a native equivalent of a long[]. + */ + public static class LongArrayContainer { + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + LongArrayContainer.class.getClassLoader(), native_getReleaseFunc()); + + private final long mNativeObject; + private final int mLength; + + public LongArrayContainer(int length) { + mLength = length; + mNativeObject = native_init(length); + sRegistry.registerNativeAllocation(this, mNativeObject); + } + + /** + * Copies the supplied values into the underlying native array. + */ + public void setValues(long[] array) { + if (array.length != mLength) { + throw new IllegalArgumentException( + "Invalid array length: " + mLength + ", expected: " + mLength); + } + native_setValues(mNativeObject, array); + } + + /** + * Copies the underlying native array values to the supplied array. + */ + public void getValues(long[] array) { + if (array.length != mLength) { + throw new IllegalArgumentException( + "Invalid array length: " + mLength + ", expected: " + mLength); + } + native_getValues(mNativeObject, array); + } + + @CriticalNative + private static native long native_init(int length); + + @CriticalNative + private static native long native_getReleaseFunc(); + + @FastNative + private native void native_setValues(long nativeObject, long[] array); + + @FastNative + private native void native_getValues(long nativeObject, long[] array); + } + + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + LongArrayMultiStateCounter.class.getClassLoader(), native_getReleaseFunc()); + + private final int mStateCount; + private final int mLength; + private final long mNativeObject; + + public LongArrayMultiStateCounter(int stateCount, int arrayLength, int initialState, + long timestampMs) { + mStateCount = stateCount; + mLength = arrayLength; + mNativeObject = native_init(stateCount, arrayLength, initialState, timestampMs); + sRegistry.registerNativeAllocation(this, mNativeObject); + } + + /** + * Sets the current state to the supplied value. + */ + public void setState(int state, long timestampMs) { + if (state < 0 || state >= mStateCount) { + throw new IllegalArgumentException( + "State: " + state + ", outside the range: [0-" + mStateCount + "]"); + } + native_setState(mNativeObject, state, timestampMs); + } + + /** + * Sets the new values. The delta between the previously set values and these values + * is distributed among the state according to the time the object spent in those states + * since the previous call to updateValues. + */ + public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) { + if (longArrayContainer.mLength != mLength) { + throw new IllegalArgumentException( + "Invalid array length: " + longArrayContainer.mLength + ", expected: " + + mLength); + } + native_updateValues(mNativeObject, longArrayContainer.mNativeObject, timestampMs); + } + + /** + * Populates longArrayContainer with the accumulated counts for the specified state. + */ + public void getCounts(LongArrayContainer longArrayContainer, int state) { + if (state < 0 || state >= mStateCount) { + throw new IllegalArgumentException( + "State: " + state + ", outside the range: [0-" + mStateCount + "]"); + } + native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state); + } + + @Override + public String toString() { + return native_toString(mNativeObject); + } + + @CriticalNative + private static native long native_init(int stateCount, int arrayLength, int initialState, + long timestampMs); + + @CriticalNative + private static native long native_getReleaseFunc(); + + @CriticalNative + private static native void native_setState(long nativeObject, int state, long timestampMs); + + @CriticalNative + private static native void native_updateValues(long nativeObject, + long longArrayContainerNativeObject, long timestampMs); + + @CriticalNative + private static native void native_getCounts(long nativeObject, + long longArrayContainerNativeObject, int state); + + @FastNative + private native String native_toString(long nativeObject); +} diff --git a/core/jni/Android.bp b/core/jni/Android.bp index e9e2fffa66299..c40be23599ee4 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -216,6 +216,7 @@ cc_library_shared { "com_android_internal_os_KernelCpuUidBpfMapReader.cpp", "com_android_internal_os_KernelSingleProcessCpuThreadReader.cpp", "com_android_internal_os_KernelSingleUidTimeReader.cpp", + "com_android_internal_os_LongArrayMultiStateCounter.cpp", "com_android_internal_os_Zygote.cpp", "com_android_internal_os_ZygoteCommandBuffer.cpp", "com_android_internal_os_ZygoteInit.cpp", @@ -244,6 +245,7 @@ cc_library_shared { "av-types-aidl-cpp", "android.hardware.camera.device@3.2", "libandroidicu", + "libbattery", "libbpf_android", "libnetdbpf", "libnetdutils", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 97cac291630cd..a822dd2e05627 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -203,6 +203,7 @@ extern int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv* e extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env); extern int register_com_android_internal_os_KernelSingleProcessCpuThreadReader(JNIEnv* env); extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env); +extern int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv* env); extern int register_com_android_internal_os_Zygote(JNIEnv *env); extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); @@ -1584,6 +1585,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_content_om_OverlayConfig), REG_JNI(register_com_android_internal_net_NetworkUtilsInternal), REG_JNI(register_com_android_internal_os_ClassLoaderFactory), + REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter), REG_JNI(register_com_android_internal_os_Zygote), REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer), REG_JNI(register_com_android_internal_os_ZygoteInit), diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp new file mode 100644 index 0000000000000..953902a2d7199 --- /dev/null +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2021 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 +#include "LongArrayMultiStateCounter.h" +#include "core_jni_helpers.h" + +namespace android { + +static jlong native_init(jint stateCount, jint arrayLength, jint initialState, jlong timestamp) { + battery::LongArrayMultiStateCounter *counter = + new battery::LongArrayMultiStateCounter(stateCount, initialState, + std::vector(arrayLength), timestamp); + return reinterpret_cast(counter); +} + +static void native_dispose(void *nativePtr) { + battery::LongArrayMultiStateCounter *counter = + reinterpret_cast(nativePtr); + delete counter; +} + +static jlong native_getReleaseFunc() { + return reinterpret_cast(native_dispose); +} + +static void native_setState(jlong nativePtr, jint state, jlong timestamp) { + battery::LongArrayMultiStateCounter *counter = + reinterpret_cast(nativePtr); + counter->setState(state, timestamp); +} + +static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr, + jlong timestamp) { + battery::LongArrayMultiStateCounter *counter = + reinterpret_cast(nativePtr); + std::vector *vector = + reinterpret_cast *>(longArrayContainerNativePtr); + + counter->updateValue(*vector, timestamp); +} + +static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, jint state) { + battery::LongArrayMultiStateCounter *counter = + reinterpret_cast(nativePtr); + std::vector *vector = + reinterpret_cast *>(longArrayContainerNativePtr); + + *vector = counter->getCount(state); +} + +static jobject native_toString(JNIEnv *env, jlong nativePtr, jobject self) { + battery::LongArrayMultiStateCounter *counter = + reinterpret_cast(nativePtr); + return env->NewStringUTF(counter->toString().c_str()); +} + +static jlong native_init_LongArrayContainer(jint length) { + return reinterpret_cast(new std::vector(length)); +} + +static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = { + // @CriticalNative + {"native_init", "(IIIJ)J", (void *)native_init}, + // @CriticalNative + {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc}, + // @CriticalNative + {"native_setState", "(JIJ)V", (void *)native_setState}, + // @CriticalNative + {"native_updateValues", "(JJJ)V", (void *)native_updateValues}, + // @CriticalNative + {"native_getCounts", "(JJI)V", (void *)native_getCounts}, + // @FastNative + {"native_toString", "(J)Ljava/lang/String;", (void *)native_toString}, +}; + +/////////////////////// LongArrayMultiStateCounter.LongArrayContainer //////////////////////// + +static void native_dispose_LongArrayContainer(jlong nativePtr) { + std::vector *vector = reinterpret_cast *>(nativePtr); + delete vector; +} + +static jlong native_getReleaseFunc_LongArrayContainer() { + return reinterpret_cast(native_dispose_LongArrayContainer); +} + +static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr, + jlongArray jarray) { + std::vector *vector = reinterpret_cast *>(nativePtr); + ScopedLongArrayRO scopedArray(env, jarray); + const uint64_t *array = reinterpret_cast(scopedArray.get()); + uint8_t size = scopedArray.size(); + + // Boundary checks are performed in the Java layer + std::copy(array, array + size, vector->data()); +} + +static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr, + jlongArray jarray) { + std::vector *vector = reinterpret_cast *>(nativePtr); + ScopedLongArrayRW scopedArray(env, jarray); + + // Boundary checks are performed in the Java layer + std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get()); +} + +static const JNINativeMethod g_LongArrayContainer_methods[] = { + // @CriticalNative + {"native_init", "(I)J", (void *)native_init_LongArrayContainer}, + // @CriticalNative + {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc_LongArrayContainer}, + // @FastNative + {"native_setValues", "(J[J)V", (void *)native_setValues_LongArrayContainer}, + // @FastNative + {"native_getValues", "(J[J)V", (void *)native_getValues_LongArrayContainer}, +}; + +int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv *env) { + // 0 represents success, thus "|" and not "&" + return RegisterMethodsOrDie(env, "com/android/internal/os/LongArrayMultiStateCounter", + g_LongArrayMultiStateCounter_methods, + NELEM(g_LongArrayMultiStateCounter_methods)) | + RegisterMethodsOrDie(env, + "com/android/internal/os/LongArrayMultiStateCounter" + "$LongArrayContainer", + g_LongArrayContainer_methods, NELEM(g_LongArrayContainer_methods)); +} + +} // namespace android diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java new file mode 100644 index 0000000000000..26296f16383fa --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 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.internal.os; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LongArrayMultiStateCounterTest { + + @Test + public void setStateAndUpdateValue() { + LongArrayMultiStateCounter.LongArrayContainer longArrayContainer = + new LongArrayMultiStateCounter.LongArrayContainer(4); + LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4, 0, 1000); + counter.setState(1, 2000); + counter.setState(0, 4000); + longArrayContainer.setValues(new long[]{100, 200, 300, 400}); + counter.updateValues(longArrayContainer, 9000); + counter.getCounts(longArrayContainer, 0); + + long[] result = new long[4]; + longArrayContainer.getValues(result); + assertThat(result).isEqualTo(new long[]{75, 150, 225, 300}); + + counter.getCounts(longArrayContainer, 1); + longArrayContainer.getValues(result); + assertThat(result).isEqualTo(new long[]{25, 50, 75, 100}); + } +}