From abe852ba45dc4269f65833a53c9f6381c9798c06 Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Thu, 2 Sep 2021 19:01:58 -0700 Subject: [PATCH] Integrate LongArrayMultiStateCounter with eBPF based time-in-state Bug: 197162116 Test: atest FrameworksCoreTests:KernelSingleUidTimeReaderTest Change-Id: If4c661a76c85f51e73da416150580d9832358582 --- .../os/KernelSingleUidTimeReader.java | 28 ++++++++ .../os/LongArrayMultiStateCounter.java | 7 +- ..._internal_os_KernelSingleUidTimeReader.cpp | 70 ++++++++++++++++++- .../os/KernelSingleUidTimeReaderTest.java | 53 +++++++++++++- 4 files changed, 152 insertions(+), 6 deletions(-) diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java index fc0ce6fb8c467..31952ebbaf698 100644 --- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java @@ -24,6 +24,8 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import dalvik.annotation.optimization.CriticalNative; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -248,6 +250,32 @@ public class KernelSingleUidTimeReader { } public native long[] readBpfData(int uid); + + /** + * Reads CPU time-in-state data for the specified UID and adds the delta since the + * previous call to the current state stats in the LongArrayMultiStateCounter. + */ + public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs) { + return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs); + } + + @CriticalNative + private static native boolean addDeltaFromBpf(int uid, + long longArrayMultiStateCounterNativePointer, long timestampMs); + + /** + * Used for testing. + * + * Takes mock cpu-time-in-frequency data and uses it the same way eBPF data would be used. + */ + public boolean addDeltaForTest(int uid, LongArrayMultiStateCounter counter, + long timestampMs, long[][] timeInFreqDataNanos) { + return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos); + } + + private static native boolean addDeltaForTest(int uid, + long longArrayMultiStateCounterNativePointer, long timestampMs, + long[][] timeInFreqDataNanos); } @VisibleForTesting diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index 1a3b29dbc90e9..46e6ba09a2471 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -31,7 +31,7 @@ import libcore.util.NativeAllocationRegistry; * // At 1000 ms, the state changes to 1 * counter.setState(1, 1000); * - * // At 3000 ms, the tracked values are updated to {100, 200} + * // At 3000 ms, the tracked values are updated to {30, 300} * arrayContainer.setValues(new long[]{{30, 300}}; * counter.updateValues(arrayContainer, 3000); * @@ -107,7 +107,10 @@ public class LongArrayMultiStateCounter { private final int mStateCount; private final int mLength; - private final long mNativeObject; + + // Visible to other objects in this package so that it can be passed to @CriticalNative + // methods. + final long mNativeObject; public LongArrayMultiStateCounter(int stateCount, int arrayLength, int initialState, long timestampMs) { diff --git a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp index c0ecf33bd521b..689b25987cbaa 100644 --- a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp +++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp @@ -14,9 +14,11 @@ * limitations under the License. */ -#include "core_jni_helpers.h" - #include +#include + +#include "LongArrayMultiStateCounter.h" +#include "core_jni_helpers.h" namespace android { @@ -42,8 +44,70 @@ static jlongArray getUidCpuFreqTimeMs(JNIEnv *env, jclass, jint uid) { return copyVecsToArray(env, out.value()); } +/** + * Computes delta of CPU time-in-freq from the previously supplied counts and adds the delta + * to the supplied multi-state counter in accordance with the counter's state. + */ +static jboolean addCpuTimeInFreqDelta( + jint uid, jlong counterNativePtr, jlong timestampMs, + std::optional>> timeInFreqDataNanos) { + if (!timeInFreqDataNanos) { + return false; + } + + battery::LongArrayMultiStateCounter *counter = + reinterpret_cast(counterNativePtr); + size_t s = 0; + for (const auto &cluster : *timeInFreqDataNanos) s += cluster.size(); + + std::vector flattened; + flattened.reserve(s); + auto offset = flattened.begin(); + for (const auto &cluster : *timeInFreqDataNanos) { + flattened.insert(offset, cluster.begin(), cluster.end()); + offset += cluster.size(); + } + for (size_t i = 0; i < s; ++i) { + flattened[i] /= NSEC_PER_MSEC; + } + counter->updateValue(flattened, timestampMs); + return true; +} + +static jboolean addDeltaFromBpf(jint uid, jlong counterNativePtr, jlong timestampMs) { + return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, + android::bpf::getUidCpuFreqTimes(uid)); +} + +static jboolean addDeltaForTest(JNIEnv *env, jclass, jint uid, jlong counterNativePtr, + jlong timestampMs, jobjectArray timeInFreqDataNanos) { + if (!timeInFreqDataNanos) { + return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, + std::optional>>()); + } + + std::vector> timeInFreqData; + jsize len = env->GetArrayLength(timeInFreqDataNanos); + for (jsize i = 0; i < len; i++) { + std::vector cluster; + ScopedLongArrayRO row(env, (jlongArray)env->GetObjectArrayElement(timeInFreqDataNanos, i)); + cluster.reserve(row.size()); + for (size_t j = 0; j < row.size(); j++) { + cluster.push_back(row[j]); + } + timeInFreqData.push_back(cluster); + } + return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, std::optional(timeInFreqData)); +} + static const JNINativeMethod g_single_methods[] = { - {"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs}, + {"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs}, + + // @CriticalNative + {"addDeltaFromBpf", "(IJJ)Z", (void *)addDeltaFromBpf}, + + // Used for testing + {"addDeltaForTest", "(IJJ[[J)Z", (void *)addDeltaForTest}, }; int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) { diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java index dac35e5ec4b17..eb3bae042fff5 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -24,7 +26,6 @@ import static org.junit.Assert.assertTrue; import android.util.SparseArray; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.KernelSingleUidTimeReader.Injector; @@ -279,6 +280,46 @@ public class KernelSingleUidTimeReaderTest { 0, lastUidCpuTimes.size()); } + @Test + public void testAddDeltaFromBpf() { + LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 5, 0, 0); + + // Nanoseconds + mInjector.setCpuTimeInStatePerClusterNs( + new long[][]{ + {1_000_000, 2_000_000, 3_000_000}, + {4_000_000, 5_000_000}}); + + boolean success = mInjector.addDelta(TEST_UID, counter, 2000); + assertThat(success).isTrue(); + + LongArrayMultiStateCounter.LongArrayContainer array = + new LongArrayMultiStateCounter.LongArrayContainer(5); + long[] out = new long[5]; + + counter.getCounts(array, 0); + array.getValues(out); + assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5}); + + counter.setState(1, 3000); + + mInjector.setCpuTimeInStatePerClusterNs( + new long[][]{ + {11_000_000, 22_000_000, 33_000_000}, + {44_000_000, 55_000_000}}); + + success = mInjector.addDelta(TEST_UID, counter, 4000); + assertThat(success).isTrue(); + + counter.getCounts(array, 0); + array.getValues(out); + assertThat(out).isEqualTo(new long[]{1 + 5, 2 + 10, 3 + 15, 4 + 20, 5 + 25}); + + counter.getCounts(array, 1); + array.getValues(out); + assertThat(out).isEqualTo(new long[]{5, 10, 15, 20, 25}); + } + private void assertCpuTimesEqual(long[] expected, long[] actual) { assertArrayEquals("Expected=" + Arrays.toString(expected) + ", Actual=" + Arrays.toString(actual), expected, actual); @@ -288,6 +329,7 @@ public class KernelSingleUidTimeReaderTest { private byte[] mData; private long[] mBpfData; private boolean mThrowExcpetion; + private long[][] mCpuTimeInStatePerClusterNs; @Override public byte[] readData(String procFile) throws IOException { @@ -316,8 +358,17 @@ public class KernelSingleUidTimeReaderTest { mBpfData = cpuTimes.clone(); } + public void setCpuTimeInStatePerClusterNs(long[][] cpuTimeInStatePerClusterNs) { + mCpuTimeInStatePerClusterNs = cpuTimeInStatePerClusterNs; + } + public void letReadDataThrowException(boolean throwException) { mThrowExcpetion = throwException; } + + @Override + public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs) { + return addDeltaForTest(uid, counter, timestampMs, mCpuTimeInStatePerClusterNs); + } } }