Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cd834de6 authored by Connor O'Brien's avatar Connor O'Brien
Browse files

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: Ie5fa2605007e8c5f70a0383bf8adbd81d479abfe
Merged-In: Ie962ddd9e30d96aa0fab6104a4164af9ad02f55e
Signed-off-by: default avatarConnor O'Brien <connoro@google.com>
parent 57635c97
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -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/<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
+1 −0
Original line number Diff line number Diff line
@@ -208,6 +208,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",
        "com_android_internal_util_VirtualRefBasePtr.cpp",
+2 −0
Original line number Diff line number Diff line
@@ -231,6 +231,7 @@ extern int register_com_android_internal_os_AtomicDirectory(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);
@@ -1652,6 +1653,7 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_AtomicDirectory),
    REG_JNI(register_com_android_internal_os_FuseAppLoop),
    REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
    REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader),
};

/*
+54 −0
Original line number Diff line number Diff line
/*
 * 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));
}

}
+24 −1
Original line number Diff line number Diff line
@@ -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<Object[]> 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) {