Use bpf data when available for single-UID cpu stats
Update KernelSingleUidTimeReader to read BPF data rather than per-UID proc files when the BPF data is available. This is implemented by calling libtimeinstate functions via JNI. Extend KernelSingleUidTimeReaderTest to exercise both the BPF and proc file code paths. Bug: 138317993 Test: KernelSingleUidTimerTest passes Test: no regression in BatteryStatsTests Change-Id: Ie962ddd9e30d96aa0fab6104a4164af9ad02f55e Signed-off-by: Connor O'Brien <connoro@google.com>
This commit is contained in:
@@ -53,6 +53,8 @@ public class KernelSingleUidTimeReader {
|
|||||||
private int mReadErrorCounter;
|
private int mReadErrorCounter;
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private boolean mSingleUidCpuTimesAvailable = true;
|
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
|
// We use the freq count obtained from /proc/uid_time_in_state to decide how many longs
|
||||||
// to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
|
// to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
|
||||||
// correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
|
// correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
|
||||||
@@ -62,6 +64,8 @@ public class KernelSingleUidTimeReader {
|
|||||||
|
|
||||||
private final Injector mInjector;
|
private final Injector mInjector;
|
||||||
|
|
||||||
|
private static final native boolean canReadBpfTimes();
|
||||||
|
|
||||||
KernelSingleUidTimeReader(int cpuFreqsCount) {
|
KernelSingleUidTimeReader(int cpuFreqsCount) {
|
||||||
this(cpuFreqsCount, new Injector());
|
this(cpuFreqsCount, new Injector());
|
||||||
}
|
}
|
||||||
@@ -83,6 +87,18 @@ public class KernelSingleUidTimeReader {
|
|||||||
if (!mSingleUidCpuTimesAvailable) {
|
if (!mSingleUidCpuTimesAvailable) {
|
||||||
return null;
|
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.
|
// Read total cpu times from the proc file.
|
||||||
final String procFile = new StringBuilder(PROC_FILE_DIR)
|
final String procFile = new StringBuilder(PROC_FILE_DIR)
|
||||||
.append(uid)
|
.append(uid)
|
||||||
@@ -230,6 +246,8 @@ public class KernelSingleUidTimeReader {
|
|||||||
public byte[] readData(String procFile) throws IOException {
|
public byte[] readData(String procFile) throws IOException {
|
||||||
return Files.readAllBytes(Paths.get(procFile));
|
return Files.readAllBytes(Paths.get(procFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public native long[] readBpfData(int uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ cc_library_shared {
|
|||||||
"com_android_internal_os_ClassLoaderFactory.cpp",
|
"com_android_internal_os_ClassLoaderFactory.cpp",
|
||||||
"com_android_internal_os_FuseAppLoop.cpp",
|
"com_android_internal_os_FuseAppLoop.cpp",
|
||||||
"com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
|
"com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
|
||||||
|
"com_android_internal_os_KernelSingleUidTimeReader.cpp",
|
||||||
"com_android_internal_os_Zygote.cpp",
|
"com_android_internal_os_Zygote.cpp",
|
||||||
"com_android_internal_os_ZygoteInit.cpp",
|
"com_android_internal_os_ZygoteInit.cpp",
|
||||||
"hwbinder/EphemeralStorage.cpp",
|
"hwbinder/EphemeralStorage.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_ClassLoaderFactory(JNIEnv* env);
|
||||||
extern int register_com_android_internal_os_FuseAppLoop(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_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_Zygote(JNIEnv *env);
|
||||||
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
|
extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
|
||||||
extern int register_com_android_internal_util_VirtualRefBasePtr(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_content_NativeLibraryHelper),
|
||||||
REG_JNI(register_com_android_internal_os_FuseAppLoop),
|
REG_JNI(register_com_android_internal_os_FuseAppLoop),
|
||||||
REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
|
REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
|
||||||
|
REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader),
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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 <cputimeinstate.h>
|
||||||
|
|
||||||
|
namespace android {
|
||||||
|
|
||||||
|
static constexpr uint64_t NSEC_PER_MSEC = 1000000;
|
||||||
|
|
||||||
|
static jlongArray copyVecsToArray(JNIEnv *env, std::vector<std::vector<uint64_t>> &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<const jlong*>(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -31,20 +31,33 @@ import com.android.internal.os.KernelSingleUidTimeReader.Injector;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(Parameterized.class)
|
||||||
public class KernelSingleUidTimeReaderTest {
|
public class KernelSingleUidTimeReaderTest {
|
||||||
private final static int TEST_UID = 2222;
|
private final static int TEST_UID = 2222;
|
||||||
private final static int TEST_FREQ_COUNT = 5;
|
private final static int TEST_FREQ_COUNT = 5;
|
||||||
|
|
||||||
private KernelSingleUidTimeReader mReader;
|
private KernelSingleUidTimeReader mReader;
|
||||||
private TestInjector mInjector;
|
private TestInjector mInjector;
|
||||||
|
protected boolean mUseBpf;
|
||||||
|
|
||||||
|
@Parameters(name="useBpf={0}")
|
||||||
|
public static Collection<Object[]> data() {
|
||||||
|
return Arrays.asList(new Object[][] { {true}, {false} });
|
||||||
|
}
|
||||||
|
|
||||||
|
public KernelSingleUidTimeReaderTest(boolean useBpf) {
|
||||||
|
mUseBpf = useBpf;
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -273,6 +286,7 @@ public class KernelSingleUidTimeReaderTest {
|
|||||||
|
|
||||||
class TestInjector extends Injector {
|
class TestInjector extends Injector {
|
||||||
private byte[] mData;
|
private byte[] mData;
|
||||||
|
private long[] mBpfData;
|
||||||
private boolean mThrowExcpetion;
|
private boolean mThrowExcpetion;
|
||||||
|
|
||||||
@Override
|
@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) {
|
public void setData(long[] cpuTimes) {
|
||||||
final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
|
final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
|
||||||
buffer.order(ByteOrder.nativeOrder());
|
buffer.order(ByteOrder.nativeOrder());
|
||||||
@@ -291,6 +313,7 @@ public class KernelSingleUidTimeReaderTest {
|
|||||||
buffer.putLong(time / 10);
|
buffer.putLong(time / 10);
|
||||||
}
|
}
|
||||||
mData = buffer.array();
|
mData = buffer.array();
|
||||||
|
mBpfData = cpuTimes.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void letReadDataThrowException(boolean throwException) {
|
public void letReadDataThrowException(boolean throwException) {
|
||||||
|
|||||||
Reference in New Issue
Block a user