Add LongArrayMultiStateCounter

Bug: 197162116
Test: atest FrameworksCoreTests:LongArrayMultiStateCounterTest
Test: atest CorePerfTests:LongArrayMultiStateCounterPerfTest

Change-Id: I3e20aa1a64b6bb89658c57c74487f95681d4f0ff
This commit is contained in:
Dmitri Plotnikov 2021-08-13 14:12:39 -07:00
parent 6fd7e85cdb
commit 2f618d1bab
6 changed files with 551 additions and 0 deletions

View File

@ -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;
}
}
}

View File

@ -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:
* <pre>
* // 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}
* </pre>
*
* 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);
}

View File

@ -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",

View File

@ -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),

View File

@ -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 <nativehelper/ScopedPrimitiveArray.h>
#include <cstring>
#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<uint64_t>(arrayLength), timestamp);
return reinterpret_cast<jlong>(counter);
}
static void native_dispose(void *nativePtr) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
delete counter;
}
static jlong native_getReleaseFunc() {
return reinterpret_cast<jlong>(native_dispose);
}
static void native_setState(jlong nativePtr, jint state, jlong timestamp) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
counter->setState(state, timestamp);
}
static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
jlong timestamp) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
std::vector<uint64_t> *vector =
reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
counter->updateValue(*vector, timestamp);
}
static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, jint state) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
std::vector<uint64_t> *vector =
reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
*vector = counter->getCount(state);
}
static jobject native_toString(JNIEnv *env, jlong nativePtr, jobject self) {
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
return env->NewStringUTF(counter->toString().c_str());
}
static jlong native_init_LongArrayContainer(jint length) {
return reinterpret_cast<jlong>(new std::vector<uint64_t>(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<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
delete vector;
}
static jlong native_getReleaseFunc_LongArrayContainer() {
return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
}
static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr,
jlongArray jarray) {
std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
ScopedLongArrayRO scopedArray(env, jarray);
const uint64_t *array = reinterpret_cast<const uint64_t *>(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<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(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

View File

@ -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});
}
}