Merge "Integrate LongArrayMultiStateCounter with eBPF based time-in-state"

This commit is contained in:
TreeHugger Robot
2021-09-13 23:11:26 +00:00
committed by Android (Google) Code Review
4 changed files with 152 additions and 6 deletions

View File

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

View File

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

View File

@@ -14,9 +14,11 @@
* limitations under the License.
*/
#include "core_jni_helpers.h"
#include <cputimeinstate.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#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<std::vector<std::vector<uint64_t>>> timeInFreqDataNanos) {
if (!timeInFreqDataNanos) {
return false;
}
battery::LongArrayMultiStateCounter *counter =
reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr);
size_t s = 0;
for (const auto &cluster : *timeInFreqDataNanos) s += cluster.size();
std::vector<uint64_t> 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<std::vector<uint64_t>>>());
}
std::vector<std::vector<uint64_t>> timeInFreqData;
jsize len = env->GetArrayLength(timeInFreqDataNanos);
for (jsize i = 0; i < len; i++) {
std::vector<uint64_t> 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) {

View File

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