diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java index 3c43a11a8e9fa..fc0ce6fb8c467 100644 --- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java @@ -53,6 +53,8 @@ public class KernelSingleUidTimeReader { private int mReadErrorCounter; @GuardedBy("this") private boolean mSingleUidCpuTimesAvailable = true; + @GuardedBy("this") + private boolean mBpfTimesAvailable = true; // We use the freq count obtained from /proc/uid_time_in_state to decide how many longs // to read from each /proc/uid//time_in_state. On the first read, verify if this is // correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will @@ -62,6 +64,8 @@ public class KernelSingleUidTimeReader { private final Injector mInjector; + private static final native boolean canReadBpfTimes(); + KernelSingleUidTimeReader(int cpuFreqsCount) { this(cpuFreqsCount, new Injector()); } @@ -83,6 +87,18 @@ public class KernelSingleUidTimeReader { if (!mSingleUidCpuTimesAvailable) { return null; } + if (mBpfTimesAvailable) { + final long[] cpuTimesMs = mInjector.readBpfData(uid); + if (cpuTimesMs.length == 0) { + mBpfTimesAvailable = false; + } else if (!mCpuFreqsCountVerified && cpuTimesMs.length != mCpuFreqsCount) { + mSingleUidCpuTimesAvailable = false; + return null; + } else { + mCpuFreqsCountVerified = true; + return computeDelta(uid, cpuTimesMs); + } + } // Read total cpu times from the proc file. final String procFile = new StringBuilder(PROC_FILE_DIR) .append(uid) @@ -230,6 +246,8 @@ public class KernelSingleUidTimeReader { public byte[] readData(String procFile) throws IOException { return Files.readAllBytes(Paths.get(procFile)); } + + public native long[] readBpfData(int uid); } @VisibleForTesting diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 4bc690a957e4c..adfe0db373a72 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -200,6 +200,7 @@ cc_library_shared { "com_android_internal_os_ClassLoaderFactory.cpp", "com_android_internal_os_FuseAppLoop.cpp", "com_android_internal_os_KernelCpuUidBpfMapReader.cpp", + "com_android_internal_os_KernelSingleUidTimeReader.cpp", "com_android_internal_os_Zygote.cpp", "com_android_internal_os_ZygoteInit.cpp", "hwbinder/EphemeralStorage.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index b5cfdb7b0ccff..3a682c47ae4c7 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -190,6 +190,7 @@ extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env); +extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env); extern int register_com_android_internal_os_Zygote(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); @@ -1562,6 +1563,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_content_NativeLibraryHelper), REG_JNI(register_com_android_internal_os_FuseAppLoop), REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader), + REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader), }; /* diff --git a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp new file mode 100644 index 0000000000000..c0ecf33bd521b --- /dev/null +++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 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 "core_jni_helpers.h" + +#include + +namespace android { + +static constexpr uint64_t NSEC_PER_MSEC = 1000000; + +static jlongArray copyVecsToArray(JNIEnv *env, std::vector> &vec) { + jsize s = 0; + for (const auto &subVec : vec) s += subVec.size(); + jlongArray ar = env->NewLongArray(s); + jsize start = 0; + for (auto &subVec : vec) { + for (uint32_t i = 0; i < subVec.size(); ++i) subVec[i] /= NSEC_PER_MSEC; + env->SetLongArrayRegion(ar, start, subVec.size(), + reinterpret_cast(subVec.data())); + start += subVec.size(); + } + return ar; +} + +static jlongArray getUidCpuFreqTimeMs(JNIEnv *env, jclass, jint uid) { + auto out = android::bpf::getUidCpuFreqTimes(uid); + if (!out) return env->NewLongArray(0); + return copyVecsToArray(env, out.value()); +} + +static const JNINativeMethod g_single_methods[] = { + {"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs}, +}; + +int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) { + return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleUidTimeReader$Injector", + g_single_methods, NELEM(g_single_methods)); +} + +} 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 479e19e10ba0c..dac35e5ec4b17 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java @@ -31,20 +31,33 @@ import com.android.internal.os.KernelSingleUidTimeReader.Injector; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; +import java.util.Collection; @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(Parameterized.class) public class KernelSingleUidTimeReaderTest { private final static int TEST_UID = 2222; private final static int TEST_FREQ_COUNT = 5; private KernelSingleUidTimeReader mReader; private TestInjector mInjector; + protected boolean mUseBpf; + + @Parameters(name="useBpf={0}") + public static Collection data() { + return Arrays.asList(new Object[][] { {true}, {false} }); + } + + public KernelSingleUidTimeReaderTest(boolean useBpf) { + mUseBpf = useBpf; + } @Before public void setUp() { @@ -273,6 +286,7 @@ public class KernelSingleUidTimeReaderTest { class TestInjector extends Injector { private byte[] mData; + private long[] mBpfData; private boolean mThrowExcpetion; @Override @@ -284,6 +298,14 @@ public class KernelSingleUidTimeReaderTest { } } + @Override + public long[] readBpfData(int uid) { + if (!mUseBpf || mBpfData == null) { + return new long[0]; + } + return mBpfData; + } + public void setData(long[] cpuTimes) { final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES); buffer.order(ByteOrder.nativeOrder()); @@ -291,6 +313,7 @@ public class KernelSingleUidTimeReaderTest { buffer.putLong(time / 10); } mData = buffer.array(); + mBpfData = cpuTimes.clone(); } public void letReadDataThrowException(boolean throwException) {