script-astra/Android/Sdk/sources/android-35/com/android/internal/os/KernelSingleUidTimeReader.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

313 lines
12 KiB
Java

/*
* Copyright (C) 2017 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.
*/
package com.android.internal.os;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import android.annotation.NonNull;
import android.util.Slog;
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;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
public class KernelSingleUidTimeReader {
private static final String TAG = KernelSingleUidTimeReader.class.getName();
private static final boolean DBG = false;
private static final String PROC_FILE_DIR = "/proc/uid/";
private static final String PROC_FILE_NAME = "/time_in_state";
private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
@VisibleForTesting
public static final int TOTAL_READ_ERROR_COUNT = 5;
@GuardedBy("this")
private final int mCpuFreqsCount;
@GuardedBy("this")
private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
@GuardedBy("this")
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
// indicate whether we checked for validity or not.
@GuardedBy("this")
private boolean mCpuFreqsCountVerified;
private final Injector mInjector;
private static final native boolean canReadBpfTimes();
public KernelSingleUidTimeReader(int cpuFreqsCount) {
this(cpuFreqsCount, new Injector());
}
public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
mInjector = injector;
mCpuFreqsCount = cpuFreqsCount;
if (mCpuFreqsCount == 0) {
mSingleUidCpuTimesAvailable = false;
}
}
public boolean singleUidCpuTimesAvailable() {
return mSingleUidCpuTimesAvailable;
}
public long[] readDeltaMs(int uid) {
synchronized (this) {
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)
.append(PROC_FILE_NAME).toString();
final long[] cpuTimesMs;
try {
final byte[] data = mInjector.readData(procFile);
if (!mCpuFreqsCountVerified) {
verifyCpuFreqsCount(data.length, procFile);
}
final ByteBuffer buffer = ByteBuffer.wrap(data);
buffer.order(ByteOrder.nativeOrder());
cpuTimesMs = readCpuTimesFromByteBuffer(buffer);
} catch (Exception e) {
if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
mSingleUidCpuTimesAvailable = false;
}
if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
return null;
}
return computeDelta(uid, cpuTimesMs);
}
}
private void verifyCpuFreqsCount(int numBytes, String procFile) {
final int actualCount = (numBytes / Long.BYTES);
if (mCpuFreqsCount != actualCount) {
mSingleUidCpuTimesAvailable = false;
throw new IllegalStateException("Freq count didn't match,"
+ "count from " + UID_TIMES_PROC_FILE + "=" + mCpuFreqsCount + ", but"
+ "count from " + procFile + "=" + actualCount);
}
mCpuFreqsCountVerified = true;
}
private long[] readCpuTimesFromByteBuffer(ByteBuffer buffer) {
final long[] cpuTimesMs;
cpuTimesMs = new long[mCpuFreqsCount];
for (int i = 0; i < mCpuFreqsCount; ++i) {
// Times read will be in units of 10ms
cpuTimesMs[i] = buffer.getLong() * 10;
}
return cpuTimesMs;
}
/**
* Compute and return cpu times delta of an uid using previously read cpu times and
* {@param latestCpuTimesMs}.
*
* @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
*/
public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
synchronized (this) {
if (!mSingleUidCpuTimesAvailable) {
return null;
}
// Subtract the last read cpu times to get deltas.
final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
if (deltaTimesMs == null) {
if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
+ "; last=" + Arrays.toString(lastCpuTimesMs)
+ "; latest=" + Arrays.toString(latestCpuTimesMs));
return null;
}
// If all elements are zero, return null to avoid unnecessary work on the caller side.
boolean hasNonZero = false;
for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
if (deltaTimesMs[i] > 0) {
hasNonZero = true;
break;
}
}
if (hasNonZero) {
mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
return deltaTimesMs;
} else {
return null;
}
}
}
/**
* Returns null if the latest cpu times are not valid**, otherwise delta of
* {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
*
* **latest cpu times are considered valid if all the cpu times are +ve and
* greater than or equal to previously read cpu times.
*/
@GuardedBy("this")
@VisibleForTesting(visibility = PACKAGE)
public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
if (latestCpuTimesMs[i] < 0) {
return null;
}
}
if (lastCpuTimesMs == null) {
return latestCpuTimesMs;
}
final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
if (deltaTimesMs[i] < 0) {
return null;
}
}
return deltaTimesMs;
}
public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
synchronized (this) {
mLastUidCpuTimeMs.clear();
for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
if (cpuTimesMs != null) {
mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
}
}
}
}
public void removeUid(int uid) {
synchronized (this) {
mLastUidCpuTimeMs.delete(uid);
}
}
public void removeUidsInRange(int startUid, int endUid) {
if (endUid < startUid) {
return;
}
synchronized (this) {
mLastUidCpuTimeMs.put(startUid, null);
mLastUidCpuTimeMs.put(endUid, null);
final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
}
}
/**
* Retrieves CPU time-in-state data for the specified UID and adds the accumulated
* delta to the supplied counter.
*/
public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs) {
mInjector.addDelta(uid, counter, timestampMs, null);
}
/**
* Same as {@link #addDelta(int, LongArrayMultiStateCounter, long)}, also returning
* the delta in the supplied array container.
*/
public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
LongArrayMultiStateCounter.LongArrayContainer deltaContainer) {
mInjector.addDelta(uid, counter, timestampMs, deltaContainer);
}
@VisibleForTesting
public static class Injector {
public byte[] readData(String procFile) throws IOException {
return Files.readAllBytes(Paths.get(procFile));
}
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.
*
* The delta is also returned via the optional deltaOut parameter.
*/
public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs,
deltaOut != null ? deltaOut.mNativeObject : 0);
}
@CriticalNative
private static native boolean addDeltaFromBpf(int uid,
long longArrayMultiStateCounterNativePointer, long timestampMs,
long longArrayContainerNativePointer);
/**
* 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,
LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos,
deltaOut != null ? deltaOut.mNativeObject : 0);
}
private static native boolean addDeltaForTest(int uid,
long longArrayMultiStateCounterNativePointer, long timestampMs,
long[][] timeInFreqDataNanos, long longArrayContainerNativePointer);
}
@VisibleForTesting
public SparseArray<long[]> getLastUidCpuTimeMs() {
return mLastUidCpuTimeMs;
}
@VisibleForTesting
public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
}
}