blob: cb96c5cdfb6024882365100b8b4b4954fcb8dea5 [file] [log] [blame]
/*
* 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 android.annotation.Nullable;
import android.os.StrictMode;
import android.os.SystemClock;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
* Reads /proc/uid_concurrent_active_time which has the format:
* active: X (X is # cores)
* [uid0]: [time-0] [time-1] [time-2] ... (# entries = # cores)
* [uid1]: [time-0] [time-1] [time-2] ... ...
* ...
* Time-N means the CPU time a UID spent running concurrently with N other processes.
* The file contains a monotonically increasing count of time for a single boot. This class
* maintains the previous results of a call to {@link #readDelta} in order to provide a
* proper delta.
*/
public class KernelUidCpuActiveTimeReader {
private static final boolean DEBUG = false;
private static final String TAG = "KernelUidCpuActiveTimeReader";
private static final String UID_TIMES_PROC_FILE = "/proc/uid_concurrent_active_time";
private int mCoreCount;
private long mLastTimeReadMs;
private long mNowTimeMs;
private SparseArray<long[]> mLastUidCpuActiveTimeMs = new SparseArray<>();
public interface Callback {
void onUidCpuActiveTime(int uid, long cpuActiveTimeMs);
}
public void readDelta(@Nullable Callback cb) {
final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
mNowTimeMs = SystemClock.elapsedRealtime();
readDeltaInternal(reader, cb);
mLastTimeReadMs = mNowTimeMs;
} catch (IOException e) {
Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
} finally {
StrictMode.setThreadPolicyMask(oldMask);
}
}
public void removeUid(int uid) {
mLastUidCpuActiveTimeMs.delete(uid);
}
public void removeUidsInRange(int startUid, int endUid) {
if (endUid < startUid) {
Slog.w(TAG, "End UID " + endUid + " is smaller than start UID " + startUid);
return;
}
mLastUidCpuActiveTimeMs.put(startUid, null);
mLastUidCpuActiveTimeMs.put(endUid, null);
final int firstIndex = mLastUidCpuActiveTimeMs.indexOfKey(startUid);
final int lastIndex = mLastUidCpuActiveTimeMs.indexOfKey(endUid);
mLastUidCpuActiveTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
}
@VisibleForTesting
public void readDeltaInternal(BufferedReader reader, @Nullable Callback cb) throws IOException {
String line = reader.readLine();
if (line == null || !line.startsWith("active:")) {
Slog.e(TAG, String.format("Malformed proc file: %s ", UID_TIMES_PROC_FILE));
return;
}
if (mCoreCount == 0) {
mCoreCount = Integer.parseInt(line.substring(line.indexOf(' ')+1));
}
while ((line = reader.readLine()) != null) {
final int index = line.indexOf(' ');
final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
readTimesForUid(uid, line.substring(index + 1), cb);
}
}
private void readTimesForUid(int uid, String line, @Nullable Callback cb) {
long[] lastActiveTime = mLastUidCpuActiveTimeMs.get(uid);
if (lastActiveTime == null) {
lastActiveTime = new long[mCoreCount];
mLastUidCpuActiveTimeMs.put(uid, lastActiveTime);
}
final String[] timesStr = line.split(" ");
if (timesStr.length != mCoreCount) {
Slog.e(TAG, String.format("# readings don't match # cores, readings: %d, CPU cores: %d",
timesStr.length, mCoreCount));
return;
}
long sumDeltas = 0;
final long[] curActiveTime = new long[mCoreCount];
boolean notify = false;
for (int i = 0; i < mCoreCount; i++) {
// Times read will be in units of 10ms
curActiveTime[i] = Long.parseLong(timesStr[i], 10) * 10;
long delta = curActiveTime[i] - lastActiveTime[i];
if (delta < 0 || curActiveTime[i] < 0) {
if (DEBUG) {
final StringBuilder sb = new StringBuilder();
sb.append(String.format("Malformed cpu active time for UID=%d\n", uid));
sb.append(String.format("data=(%d,%d)\n", lastActiveTime[i], curActiveTime[i]));
sb.append("times=(");
TimeUtils.formatDuration(mLastTimeReadMs, sb);
sb.append(",");
TimeUtils.formatDuration(mNowTimeMs, sb);
sb.append(")");
Slog.e(TAG, sb.toString());
}
return;
}
notify |= delta > 0;
sumDeltas += delta / (i + 1);
}
if (notify) {
System.arraycopy(curActiveTime, 0, lastActiveTime, 0, mCoreCount);
if (cb != null) {
cb.onUidCpuActiveTime(uid, sumDeltas);
}
}
}
}