blob: 7ea2c1b020406cf7465a51ecf72a0cde592b6ca5 [file] [log] [blame]
/*
* Copyright (C) 2022 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.server.cpu;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND;
import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.os.SystemClock;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.SparseArray;
import android.util.SparseArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.utils.PriorityDump;
import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/** Service to monitor CPU availability and usage. */
public final class CpuMonitorService extends SystemService {
static final String TAG = CpuMonitorService.class.getSimpleName();
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// TODO(b/267500110): Make these constants resource overlay properties.
/** Default monitoring interval when no monitoring is in progress. */
static final long DEFAULT_MONITORING_INTERVAL_MILLISECONDS = -1;
/** Monitoring interval when callbacks are registered and the CPU load is normal. */
private static final long NORMAL_MONITORING_INTERVAL_MILLISECONDS =
TimeUnit.SECONDS.toMillis(5);
/**
* Monitoring interval when no registered callbacks and the build is either user-debug or eng.
*/
private static final long DEBUG_MONITORING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1);
/**
* Size of the in-memory cache relative to the current uptime.
*
* On user-debug or eng builds, continuously cache stats with a bigger cache size for debugging
* purposes.
*/
private static final long CACHE_DURATION_MILLISECONDS = Build.IS_USERDEBUG || Build.IS_ENG
? TimeUnit.MINUTES.toMillis(30) : TimeUnit.MINUTES.toMillis(10);
// TODO(b/267500110): Investigate whether this duration should change when the monitoring
// interval is updated. When the CPU is under heavy load, the monitoring will happen less
// frequently. Should this duration be increased as well when this happens?
private static final long LATEST_AVAILABILITY_DURATION_MILLISECONDS =
TimeUnit.SECONDS.toMillis(30);
private final Context mContext;
private final HandlerThread mHandlerThread;
private final CpuInfoReader mCpuInfoReader;
private final boolean mShouldDebugMonitor;
private final long mNormalMonitoringIntervalMillis;
private final long mDebugMonitoringIntervalMillis;
private final long mLatestAvailabilityDurationMillis;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final SparseArrayMap<CpuMonitorInternal.CpuAvailabilityCallback,
CpuAvailabilityCallbackInfo> mAvailabilityCallbackInfosByCallbacksByCpuset;
@GuardedBy("mLock")
private final SparseArray<CpusetInfo> mCpusetInfosByCpuset;
private final Runnable mMonitorCpuStats = this::monitorCpuStats;
@GuardedBy("mLock")
private long mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
private Handler mHandler;
private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() {
@Override
public void addCpuAvailabilityCallback(Executor executor,
CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) {
Objects.requireNonNull(callback, "Callback must be non-null");
Objects.requireNonNull(config, "Config must be non-null");
CpuAvailabilityCallbackInfo callbackInfo;
synchronized (mLock) {
// Verify all CPUSET entries before adding the callback because this will help
// delete any previously added callback for a different CPUSET.
for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i);
callbackInfo = mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset,
callback);
if (callbackInfo != null) {
Slogf.i(TAG, "Overwriting the existing %s", callbackInfo);
}
}
callbackInfo = newCallbackInfoLocked(config, callback, executor);
}
asyncNotifyMonitoringIntervalChangeToClient(callbackInfo);
if (DEBUG) {
Slogf.d(TAG, "Successfully added %s", callbackInfo);
}
}
@Override
public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) {
synchronized (mLock) {
for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i);
CpuAvailabilityCallbackInfo callbackInfo =
mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset, callback);
if (callbackInfo != null) {
if (DEBUG) {
Slogf.d(TAG, "Successfully removed %s", callbackInfo);
}
checkAndStopMonitoringLocked();
return;
}
}
Slogf.w(TAG, "CpuAvailabilityCallback was not previously added. Ignoring the remove"
+ " request");
}
}
};
public CpuMonitorService(Context context) {
this(context, new CpuInfoReader(), new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, /* allowIo= */ true),
Build.IS_USERDEBUG || Build.IS_ENG, NORMAL_MONITORING_INTERVAL_MILLISECONDS,
DEBUG_MONITORING_INTERVAL_MILLISECONDS, LATEST_AVAILABILITY_DURATION_MILLISECONDS);
}
@VisibleForTesting
CpuMonitorService(Context context, CpuInfoReader cpuInfoReader, HandlerThread handlerThread,
boolean shouldDebugMonitor, long normalMonitoringIntervalMillis,
long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis) {
super(context);
mContext = context;
mHandlerThread = handlerThread;
mShouldDebugMonitor = shouldDebugMonitor;
mNormalMonitoringIntervalMillis = normalMonitoringIntervalMillis;
mDebugMonitoringIntervalMillis = debugMonitoringIntervalMillis;
mLatestAvailabilityDurationMillis = latestAvailabilityDurationMillis;
mCpuInfoReader = cpuInfoReader;
mCpusetInfosByCpuset = new SparseArray<>(2);
mCpusetInfosByCpuset.append(CPUSET_ALL, new CpusetInfo(CPUSET_ALL));
mCpusetInfosByCpuset.append(CPUSET_BACKGROUND, new CpusetInfo(CPUSET_BACKGROUND));
mAvailabilityCallbackInfosByCallbacksByCpuset = new SparseArrayMap<>();
}
@Override
public void onStart() {
// Initialize CPU info reader and perform the first read to make sure the CPU stats are
// readable without any issues.
if (!mCpuInfoReader.init() || mCpuInfoReader.readCpuInfos() == null) {
Slogf.wtf(TAG, "Failed to initialize CPU info reader. This happens when the CPU "
+ "frequency stats are not available or the sysfs interface has changed in "
+ "the Kernel. Cannot monitor CPU without these stats. Terminating CPU monitor "
+ "service");
return;
}
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
publishLocalService(CpuMonitorInternal.class, mLocalService);
publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL);
Watchdog.getInstance().addThread(mHandler);
synchronized (mLock) {
if (mShouldDebugMonitor && !mHandler.hasCallbacks(mMonitorCpuStats)) {
mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis;
Slogf.i(TAG, "Starting debug monitoring");
mHandler.post(mMonitorCpuStats);
}
}
}
@VisibleForTesting
long getCurrentMonitoringIntervalMillis() {
synchronized (mLock) {
return mCurrentMonitoringIntervalMillis;
}
}
private void doDump(IndentingPrintWriter writer) {
writer.printf("*%s*\n", getClass().getSimpleName());
writer.increaseIndent();
mCpuInfoReader.dump(writer);
writer.printf("mShouldDebugMonitor = %s\n", mShouldDebugMonitor ? "Yes" : "No");
writer.printf("mNormalMonitoringIntervalMillis = %d\n", mNormalMonitoringIntervalMillis);
writer.printf("mDebugMonitoringIntervalMillis = %d\n", mDebugMonitoringIntervalMillis);
writer.printf("mLatestAvailabilityDurationMillis = %d\n",
mLatestAvailabilityDurationMillis);
synchronized (mLock) {
writer.printf("mCurrentMonitoringIntervalMillis = %d\n",
mCurrentMonitoringIntervalMillis);
if (hasClientCallbacksLocked()) {
writer.println("CPU availability change callbacks:");
writer.increaseIndent();
mAvailabilityCallbackInfosByCallbacksByCpuset.forEach(
(callbackInfo) -> writer.printf("%s\n", callbackInfo));
writer.decreaseIndent();
}
if (mCpusetInfosByCpuset.size() > 0) {
writer.println("Cpuset infos:");
writer.increaseIndent();
for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
writer.printf("%s\n", mCpusetInfosByCpuset.valueAt(i));
}
writer.decreaseIndent();
}
}
writer.decreaseIndent();
}
private void monitorCpuStats() {
long uptimeMillis = SystemClock.uptimeMillis();
// Remove duplicate callbacks caused by switching form debug to normal monitoring.
// The removal of the duplicate callback done in the {@link newCallbackInfoLocked} method
// may result in a no-op when a duplicate execution of this callback has already started
// on the handler thread.
mHandler.removeCallbacks(mMonitorCpuStats);
SparseArray<CpuInfoReader.CpuInfo> cpuInfosByCoreId = mCpuInfoReader.readCpuInfos();
if (cpuInfosByCoreId == null) {
// This shouldn't happen because the CPU infos are read & verified during
// the {@link onStart} call.
Slogf.wtf(TAG, "Failed to read CPU info from device");
synchronized (mLock) {
stopMonitoringCpuStatsLocked();
}
// Monitoring is stopped but no client callback is removed.
// TODO(b/267500110): Identify whether the clients should be notified about this state.
return;
}
synchronized (mLock) {
// 1. Populate the {@link mCpusetInfosByCpuset} with the latest cpuInfo.
for (int i = 0; i < cpuInfosByCoreId.size(); i++) {
CpuInfoReader.CpuInfo cpuInfo = cpuInfosByCoreId.valueAt(i);
for (int j = 0; j < mCpusetInfosByCpuset.size(); j++) {
mCpusetInfosByCpuset.valueAt(j).appendCpuInfo(uptimeMillis, cpuInfo);
}
}
// 2. Verify whether any monitoring thresholds are crossed and notify the corresponding
// clients.
for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
CpusetInfo cpusetInfo = mCpusetInfosByCpuset.valueAt(i);
cpusetInfo.populateLatestCpuAvailabilityInfo(uptimeMillis,
mLatestAvailabilityDurationMillis);
checkClientThresholdsAndNotifyLocked(cpusetInfo);
}
// TODO(b/267500110): Detect heavy CPU load. On detecting heavy CPU load, increase
// the monitoring interval and notify the clients.
// 3. Continue monitoring only when either there is at least one registered client
// callback or debug monitoring is enabled.
if (mCurrentMonitoringIntervalMillis > 0
&& (hasClientCallbacksLocked() || mShouldDebugMonitor)) {
mHandler.postAtTime(mMonitorCpuStats,
uptimeMillis + mCurrentMonitoringIntervalMillis);
} else {
stopMonitoringCpuStatsLocked();
}
}
}
@GuardedBy("mLock")
private void checkClientThresholdsAndNotifyLocked(CpusetInfo cpusetInfo) {
int prevAvailabilityPercent = cpusetInfo.getPrevCpuAvailabilityPercent();
CpuAvailabilityInfo latestAvailabilityInfo = cpusetInfo.getLatestCpuAvailabilityInfo();
if (latestAvailabilityInfo == null || prevAvailabilityPercent < 0
|| mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKey(
cpusetInfo.cpuset) == 0) {
// When either the current or the previous CPU availability percents are
// missing, skip the current cpuset as there is not enough data to verify
// whether the CPU availability has crossed any monitoring threshold.
return;
}
for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
for (int j = 0; j < mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt(
i); j++) {
CpuAvailabilityCallbackInfo callbackInfo =
mAvailabilityCallbackInfosByCallbacksByCpuset.valueAt(i, j);
if (callbackInfo.config.cpuset != cpusetInfo.cpuset) {
continue;
}
if (didCrossAnyThreshold(prevAvailabilityPercent,
latestAvailabilityInfo.latestAvgAvailabilityPercent,
callbackInfo.config.getThresholds())) {
asyncNotifyCpuAvailabilityToClient(latestAvailabilityInfo, callbackInfo);
}
}
}
}
private void asyncNotifyMonitoringIntervalChangeToClient(
CpuAvailabilityCallbackInfo callbackInfo) {
if (callbackInfo.executor == null) {
mHandler.post(callbackInfo.notifyMonitoringIntervalChangeRunnable);
} else {
callbackInfo.executor.execute(callbackInfo.notifyMonitoringIntervalChangeRunnable);
}
}
private void asyncNotifyCpuAvailabilityToClient(CpuAvailabilityInfo availabilityInfo,
CpuAvailabilityCallbackInfo callbackInfo) {
callbackInfo.notifyCpuAvailabilityChangeRunnable.prepare(availabilityInfo);
if (callbackInfo.executor == null) {
mHandler.post(callbackInfo.notifyCpuAvailabilityChangeRunnable);
} else {
callbackInfo.executor.execute(callbackInfo.notifyCpuAvailabilityChangeRunnable);
}
}
@GuardedBy("mLock")
private CpuAvailabilityCallbackInfo newCallbackInfoLocked(
CpuAvailabilityMonitoringConfig config,
CpuMonitorInternal.CpuAvailabilityCallback callback, Executor executor) {
CpuAvailabilityCallbackInfo callbackInfo = new CpuAvailabilityCallbackInfo(this, config,
callback, executor);
String cpusetStr = CpuAvailabilityMonitoringConfig.toCpusetString(
callbackInfo.config.cpuset);
CpusetInfo cpusetInfo = mCpusetInfosByCpuset.get(callbackInfo.config.cpuset);
Preconditions.checkState(cpusetInfo != null, "Missing cpuset info for cpuset %s",
cpusetStr);
boolean hasExistingClientCallbacks = hasClientCallbacksLocked();
mAvailabilityCallbackInfosByCallbacksByCpuset.add(callbackInfo.config.cpuset,
callbackInfo.callback, callbackInfo);
if (DEBUG) {
Slogf.d(TAG, "Added a CPU availability callback: %s", callbackInfo);
}
CpuAvailabilityInfo latestInfo = cpusetInfo.getLatestCpuAvailabilityInfo();
if (latestInfo != null) {
asyncNotifyCpuAvailabilityToClient(latestInfo, callbackInfo);
}
if (hasExistingClientCallbacks && mHandler.hasCallbacks(mMonitorCpuStats)) {
return callbackInfo;
}
// Remove existing callbacks to ensure any debug monitoring (if started) is stopped before
// starting normal monitoring.
mHandler.removeCallbacks(mMonitorCpuStats);
mCurrentMonitoringIntervalMillis = mNormalMonitoringIntervalMillis;
mHandler.post(mMonitorCpuStats);
return callbackInfo;
}
@GuardedBy("mLock")
private void checkAndStopMonitoringLocked() {
if (hasClientCallbacksLocked()) {
return;
}
if (mShouldDebugMonitor) {
if (DEBUG) {
Slogf.e(TAG, "Switching to debug monitoring");
}
mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis;
} else {
stopMonitoringCpuStatsLocked();
}
}
@GuardedBy("mLock")
private boolean hasClientCallbacksLocked() {
for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) {
if (mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt(i) > 0) {
return true;
}
}
return false;
}
@GuardedBy("mLock")
private void stopMonitoringCpuStatsLocked() {
mHandler.removeCallbacks(mMonitorCpuStats);
mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS;
// When the monitoring is stopped, the latest CPU availability info and the snapshots in
// {@code mCpusetInfosByCpuset} will become obsolete soon. So, remove them.
for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) {
mCpusetInfosByCpuset.valueAt(i).clear();
}
}
private static boolean containsCpuset(@CpuInfoReader.CpusetCategory int cpusetCategories,
@CpuAvailabilityMonitoringConfig.Cpuset int expectedCpuset) {
switch (expectedCpuset) {
case CPUSET_ALL:
return (cpusetCategories & FLAG_CPUSET_CATEGORY_TOP_APP) != 0;
case CPUSET_BACKGROUND:
return (cpusetCategories & FLAG_CPUSET_CATEGORY_BACKGROUND) != 0;
default:
Slogf.wtf(TAG, "Provided invalid expectedCpuset %d", expectedCpuset);
}
return false;
}
private static boolean didCrossAnyThreshold(int prevAvailabilityPercent,
int curAvailabilityPercent, IntArray thresholds) {
if (prevAvailabilityPercent == curAvailabilityPercent) {
return false;
}
for (int i = 0; i < thresholds.size(); i++) {
int threshold = thresholds.get(i);
// TODO(b/267500110): Identify whether or not the clients need to be notified when
// the CPU availability jumps too frequently around the provided thresholds.
// A. Should the client be notified twice - once when the availability reaches
// the threshold and once when it moves away (increase/decrease) from the threshold
// immediately?
// B. Should there be some sort of rate-limiting to avoid notifying the client too
// frequently? Should the client be able to config the rate-limit?
if (prevAvailabilityPercent < threshold && curAvailabilityPercent >= threshold) {
return true;
}
if (prevAvailabilityPercent >= threshold && curAvailabilityPercent < threshold) {
return true;
}
}
return false;
}
private static final class CpuAvailabilityCallbackInfo {
public final CpuMonitorService service;
public final CpuAvailabilityMonitoringConfig config;
public final CpuMonitorInternal.CpuAvailabilityCallback callback;
@Nullable
public final Executor executor;
public final Runnable notifyMonitoringIntervalChangeRunnable = new Runnable() {
@Override
public void run() {
callback.onMonitoringIntervalChanged(service.getCurrentMonitoringIntervalMillis());
}
};
public final NotifyCpuAvailabilityChangeRunnable notifyCpuAvailabilityChangeRunnable =
new NotifyCpuAvailabilityChangeRunnable();
CpuAvailabilityCallbackInfo(CpuMonitorService service,
CpuAvailabilityMonitoringConfig config,
CpuMonitorInternal.CpuAvailabilityCallback callback, @Nullable Executor executor) {
this.service = service;
this.config = config;
this.callback = callback;
this.executor = executor;
}
@Override
public String toString() {
return "CpuAvailabilityCallbackInfo{config = " + config + ", callback = " + callback
+ ", mExecutor = " + executor + '}';
}
private final class NotifyCpuAvailabilityChangeRunnable implements Runnable {
private final Object mLock = new Object();
@GuardedBy("mLock")
private CpuAvailabilityInfo mCpuAvailabilityInfo;
public void prepare(CpuAvailabilityInfo cpuAvailabilityInfo) {
synchronized (mLock) {
mCpuAvailabilityInfo = cpuAvailabilityInfo;
}
}
@Override
public void run() {
synchronized (mLock) {
callback.onAvailabilityChanged(mCpuAvailabilityInfo);
}
}
}
}
private final class CpuMonitorBinder extends Binder {
private final PriorityDump.PriorityDumper mPriorityDumper =
new PriorityDump.PriorityDumper() {
@Override
public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
boolean asProto) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)
|| asProto) {
return;
}
try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) {
doDump(ipw);
}
}
};
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
}
private static final class CpusetInfo {
@CpuAvailabilityMonitoringConfig.Cpuset
public final int cpuset;
private final LongSparseArray<Snapshot> mSnapshotsByUptime;
@Nullable
private CpuAvailabilityInfo mLatestCpuAvailabilityInfo;
CpusetInfo(int cpuset) {
this.cpuset = cpuset;
mSnapshotsByUptime = new LongSparseArray<>();
}
public void appendCpuInfo(long uptimeMillis, CpuInfoReader.CpuInfo cpuInfo) {
if (!containsCpuset(cpuInfo.cpusetCategories, cpuset)) {
return;
}
Snapshot currentSnapshot = mSnapshotsByUptime.get(uptimeMillis);
if (currentSnapshot == null) {
currentSnapshot = new Snapshot(uptimeMillis);
mSnapshotsByUptime.append(uptimeMillis, currentSnapshot);
if (mSnapshotsByUptime.size() > 0
&& (uptimeMillis - mSnapshotsByUptime.valueAt(0).uptimeMillis)
> CACHE_DURATION_MILLISECONDS) {
mSnapshotsByUptime.removeAt(0);
}
}
currentSnapshot.appendCpuInfo(cpuInfo);
}
@Nullable
public CpuAvailabilityInfo getLatestCpuAvailabilityInfo() {
return mLatestCpuAvailabilityInfo;
}
public void populateLatestCpuAvailabilityInfo(long currentUptimeMillis,
long latestAvailabilityDurationMillis) {
int numSnapshots = mSnapshotsByUptime.size();
if (numSnapshots == 0) {
mLatestCpuAvailabilityInfo = null;
return;
}
Snapshot latestSnapshot = mSnapshotsByUptime.valueAt(numSnapshots - 1);
if (latestSnapshot.uptimeMillis != currentUptimeMillis) {
// When the cpuset has no stats available for the current polling, the uptime will
// mismatch. When this happens, return {@code null} to avoid returning stale
// information.
if (DEBUG) {
Slogf.d(TAG, "Skipping stale CPU availability information for cpuset %s",
CpuAvailabilityMonitoringConfig.toCpusetString(cpuset));
}
mLatestCpuAvailabilityInfo = null;
return;
}
// Avoid constructing {@link mLatestCpuAvailabilityInfo} if the uptime hasn't changed.
if (mLatestCpuAvailabilityInfo != null
&& mLatestCpuAvailabilityInfo.dataTimestampUptimeMillis
== latestSnapshot.uptimeMillis) {
return;
}
long earliestUptimeMillis = currentUptimeMillis - latestAvailabilityDurationMillis;
mLatestCpuAvailabilityInfo = new CpuAvailabilityInfo(cpuset,
latestSnapshot.uptimeMillis, latestSnapshot.getAverageAvailableCpuFreqPercent(),
getCumulativeAvgAvailabilityPercent(earliestUptimeMillis),
latestAvailabilityDurationMillis);
}
public int getPrevCpuAvailabilityPercent() {
int numSnapshots = mSnapshotsByUptime.size();
if (numSnapshots < 2) {
return -1;
}
return mSnapshotsByUptime.valueAt(numSnapshots - 2).getAverageAvailableCpuFreqPercent();
}
private int getCumulativeAvgAvailabilityPercent(long earliestUptimeMillis) {
long totalAvailableCpuFreqKHz = 0;
long totalOnlineMaxCpuFreqKHz = 0;
int totalAccountedSnapshots = 0;
long earliestSeenUptimeMillis = Long.MAX_VALUE;
for (int i = mSnapshotsByUptime.size() - 1; i >= 0; i--) {
Snapshot snapshot = mSnapshotsByUptime.valueAt(i);
earliestSeenUptimeMillis = snapshot.uptimeMillis;
if (snapshot.uptimeMillis <= earliestUptimeMillis) {
break;
}
totalAccountedSnapshots++;
totalAvailableCpuFreqKHz += snapshot.totalNormalizedAvailableCpuFreqKHz;
totalOnlineMaxCpuFreqKHz += snapshot.totalOnlineMaxCpuFreqKHz;
}
// The cache must have at least 2 snapshots within the given duration and
// the {@link earliestSeenUptimeMillis} must be earlier than (i,e., less than) the given
// {@link earliestUptimeMillis}. Otherwise, the cache doesn't have enough data to
// calculate the cumulative average for the given duration.
// TODO(b/267500110): Investigate whether the cumulative average duration should be
// shrunk when not enough data points are available.
if (earliestSeenUptimeMillis > earliestUptimeMillis || totalAccountedSnapshots < 2) {
return CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT;
}
return (int) ((totalAvailableCpuFreqKHz * 100.0) / totalOnlineMaxCpuFreqKHz);
}
public void clear() {
mLatestCpuAvailabilityInfo = null;
mSnapshotsByUptime.clear();
}
@Override
public String toString() {
return "CpusetInfo{cpuset = " + CpuAvailabilityMonitoringConfig.toCpusetString(cpuset)
+ ", mSnapshotsByUptime = " + mSnapshotsByUptime
+ ", mLatestCpuAvailabilityInfo = " + mLatestCpuAvailabilityInfo + '}';
}
private static final class Snapshot {
public final long uptimeMillis;
public int totalOnlineCpus;
public int totalOfflineCpus;
public long totalNormalizedAvailableCpuFreqKHz;
public long totalOnlineMaxCpuFreqKHz;
public long totalOfflineMaxCpuFreqKHz;
Snapshot(long uptimeMillis) {
this.uptimeMillis = uptimeMillis;
}
public void appendCpuInfo(CpuInfoReader.CpuInfo cpuInfo) {
if (!cpuInfo.isOnline) {
totalOfflineCpus++;
totalOfflineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz;
return;
}
++totalOnlineCpus;
totalNormalizedAvailableCpuFreqKHz += cpuInfo.getNormalizedAvailableCpuFreqKHz();
totalOnlineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz;
}
public int getAverageAvailableCpuFreqPercent() {
int percent = (int) ((totalNormalizedAvailableCpuFreqKHz * 100.0)
/ totalOnlineMaxCpuFreqKHz);
if (percent < 0) {
// TODO(b/279478586): This case should never happen. But this case happens
// rarely on certain hardware, which indicates a deeper issue. Once this
// issue is reproduced, use this log to debug the issue and fix it.
Slogf.wtf(TAG, "Computed negative CPU availability percent(%d) for %s ",
percent, toString());
return 0;
}
return percent;
}
@Override
public String toString() {
return "Snapshot{uptimeMillis = " + uptimeMillis + ", totalOnlineCpus = "
+ totalOnlineCpus + ", totalOfflineCpus = " + totalOfflineCpus
+ ", totalNormalizedAvailableCpuFreqKHz = "
+ totalNormalizedAvailableCpuFreqKHz
+ ", totalOnlineMaxCpuFreqKHz = " + totalOnlineMaxCpuFreqKHz
+ ", totalOfflineMaxCpuFreqKHz = " + totalOfflineMaxCpuFreqKHz + '}';
}
}
}
}