/*
* Copyright (C) 2021 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.car.watchdog;
import static com.android.car.watchdog.WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS;
import static com.android.car.watchdog.WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA;
import android.automotive.watchdog.PerStateBytes;
import android.automotive.watchdog.internal.ApplicationCategoryType;
import android.automotive.watchdog.internal.ComponentType;
import android.automotive.watchdog.internal.IoOveruseConfiguration;
import android.automotive.watchdog.internal.PackageMetadata;
import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
import android.car.builtin.util.Slogf;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.car.watchdog.PerformanceDump.IoThresholdByAppCategory;
import com.android.car.watchdog.PerformanceDump.IoThresholdByComponent;
import com.android.car.watchdog.PerformanceDump.IoThresholdByPackage;
import com.android.car.watchdog.PerformanceDump.OveruseConfigurationCacheDump;
import com.android.car.watchdog.PerformanceDump.PackageByAppCategory;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
/**
* Cache to store overuse configurations in memory.
*
*
It assumes that the error checking and loading/merging initial configs are done prior to
* setting the cache.
*/
public final class OveruseConfigurationCache {
private static final String TAG = OveruseConfigurationCache.class.getSimpleName();
static final PerStateBytes DEFAULT_THRESHOLD =
constructPerStateBytes(Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE);
private final Object mLock = new Object();
@GuardedBy("mLock")
private final ArraySet mSafeToKillSystemPackages = new ArraySet<>();
@GuardedBy("mLock")
private final ArraySet mSafeToKillVendorPackages = new ArraySet<>();
@GuardedBy("mLock")
private final List mVendorPackagePrefixes = new ArrayList<>();
@GuardedBy("mLock")
private final SparseArray> mPackagesByAppCategoryType = new SparseArray<>();
@GuardedBy("mLock")
private final SparseArray mGenericIoThresholdsByComponent = new SparseArray<>();
@GuardedBy("mLock")
private final ArrayMap mIoThresholdsBySystemPackages = new ArrayMap<>();
@GuardedBy("mLock")
private final ArrayMap mIoThresholdsByVendorPackages = new ArrayMap<>();
@GuardedBy("mLock")
private final SparseArray mIoThresholdsByAppCategoryType = new SparseArray<>();
/** Dumps the contents of the cache. */
public void dump(IndentingPrintWriter writer) {
writer.println("*" + getClass().getSimpleName() + "*");
writer.increaseIndent();
synchronized (mLock) {
writer.println("mSafeToKillSystemPackages: " + mSafeToKillSystemPackages);
writer.println("mSafeToKillVendorPackages: " + mSafeToKillVendorPackages);
writer.println("mVendorPackagePrefixes: " + mVendorPackagePrefixes);
writer.println("mPackagesByAppCategoryType: ");
writer.increaseIndent();
for (int i = 0; i < mPackagesByAppCategoryType.size(); ++i) {
writer.print("App category: "
+ toApplicationCategoryTypeString(mPackagesByAppCategoryType.keyAt(i)));
writer.println(", Packages: " + mPackagesByAppCategoryType.valueAt(i));
}
writer.decreaseIndent();
writer.println("mGenericIoThresholdsByComponent: ");
writer.increaseIndent();
for (int i = 0; i < mGenericIoThresholdsByComponent.size(); ++i) {
writer.print("Component type: "
+ toComponentTypeString(mGenericIoThresholdsByComponent.keyAt(i)));
writer.print(", Threshold: ");
dumpPerStateBytes(mGenericIoThresholdsByComponent.valueAt(i), writer);
}
writer.decreaseIndent();
writer.println("mIoThresholdsBySystemPackages: ");
writer.increaseIndent();
for (int i = 0; i < mIoThresholdsBySystemPackages.size(); ++i) {
writer.print("Package name: " + mIoThresholdsBySystemPackages.keyAt(i));
writer.print(", Threshold: ");
dumpPerStateBytes(mIoThresholdsBySystemPackages.valueAt(i), writer);
}
writer.decreaseIndent();
writer.println("mIoThresholdsByVendorPackages: ");
writer.increaseIndent();
for (int i = 0; i < mIoThresholdsByVendorPackages.size(); ++i) {
writer.print("Package name: " + mIoThresholdsByVendorPackages.keyAt(i));
writer.print(", Threshold: ");
dumpPerStateBytes(mIoThresholdsByVendorPackages.valueAt(i), writer);
}
writer.decreaseIndent();
writer.println("mIoThresholdsByAppCategoryType: ");
writer.increaseIndent();
for (int i = 0; i < mIoThresholdsByAppCategoryType.size(); ++i) {
writer.print("App category: "
+ toApplicationCategoryTypeString(mIoThresholdsByAppCategoryType.keyAt(i)));
writer.print(", Threshold: ");
dumpPerStateBytes(mIoThresholdsByAppCategoryType.valueAt(i), writer);
}
writer.decreaseIndent();
}
writer.decreaseIndent();
}
/** Dumps the contents of the cache in proto format. */
public void dumpProto(ProtoOutputStream proto) {
synchronized (mLock) {
long overuseConfigurationCacheDumpToken = proto.start(
PerformanceDump.OVERUSE_CONFIGURATION_CACHE_DUMP);
for (int i = 0; i < mSafeToKillSystemPackages.size(); i++) {
proto.write(OveruseConfigurationCacheDump.SAFE_TO_KILL_SYSTEM_PACKAGES,
mSafeToKillSystemPackages.valueAt(i));
}
for (int i = 0; i < mSafeToKillVendorPackages.size(); i++) {
proto.write(OveruseConfigurationCacheDump.SAFE_TO_KILL_VENDOR_PACKAGES,
mSafeToKillVendorPackages.valueAt(i));
}
for (int i = 0; i < mVendorPackagePrefixes.size(); i++) {
proto.write(OveruseConfigurationCacheDump.VENDOR_PACKAGE_PREFIXES,
mVendorPackagePrefixes.get(i));
}
for (int i = 0; i < mPackagesByAppCategoryType.size(); i++) {
long packageByAppCategoryToken = proto.start(
OveruseConfigurationCacheDump.PACKAGES_BY_APP_CATEGORY);
proto.write(PackageByAppCategory.APPLICATION_CATEGORY,
toProtoApplicationCategory(mPackagesByAppCategoryType.keyAt(i)));
ArraySet packages = mPackagesByAppCategoryType.valueAt(i);
for (int j = 0; j < packages.size(); j++) {
proto.write(PackageByAppCategory.PACKAGE_NAME,
packages.valueAt(j));
}
proto.end(packageByAppCategoryToken);
}
for (int i = 0; i < mGenericIoThresholdsByComponent.size(); i++) {
long ioThresholdByComponentToken = proto.start(
OveruseConfigurationCacheDump.GENERIC_IO_THRESHOLDS_BY_COMPONENT);
proto.write(IoThresholdByComponent.COMPONENT_TYPE,
toProtoComponentType(mGenericIoThresholdsByComponent.keyAt(i)));
long perStateBytesToken = proto.start(IoThresholdByComponent.THRESHOLD);
PerStateBytes perStateBytes = mGenericIoThresholdsByComponent.valueAt(i);
proto.write(PerformanceDump.PerStateBytes.FOREGROUND_BYTES,
perStateBytes.foregroundBytes);
proto.write(PerformanceDump.PerStateBytes.BACKGROUND_BYTES,
perStateBytes.backgroundBytes);
proto.write(PerformanceDump.PerStateBytes.GARAGEMODE_BYTES,
perStateBytes.garageModeBytes);
proto.end(perStateBytesToken);
proto.end(ioThresholdByComponentToken);
}
for (int i = 0; i < mIoThresholdsBySystemPackages.size(); i++) {
long ioThresholdByPackageToken = proto.start(
OveruseConfigurationCacheDump.IO_THRESHOLDS_BY_PACKAGE);
proto.write(IoThresholdByPackage.PACKAGE_TYPE, PerformanceDump.SYSTEM);
long perStateBytesToken = proto.start(IoThresholdByComponent.THRESHOLD);
PerStateBytes perStateBytes = mIoThresholdsBySystemPackages.valueAt(i);
proto.write(PerformanceDump.PerStateBytes.FOREGROUND_BYTES,
perStateBytes.foregroundBytes);
proto.write(PerformanceDump.PerStateBytes.BACKGROUND_BYTES,
perStateBytes.backgroundBytes);
proto.write(PerformanceDump.PerStateBytes.GARAGEMODE_BYTES,
perStateBytes.garageModeBytes);
proto.end(perStateBytesToken);
proto.write(IoThresholdByPackage.PACKAGE_NAME,
mIoThresholdsBySystemPackages.keyAt(i));
proto.end(ioThresholdByPackageToken);
}
for (int i = 0; i < mIoThresholdsByVendorPackages.size(); i++) {
long ioThresholdByPackageToken = proto.start(
OveruseConfigurationCacheDump.IO_THRESHOLDS_BY_PACKAGE);
proto.write(IoThresholdByPackage.PACKAGE_TYPE, PerformanceDump.VENDOR);
long perStateBytesToken = proto.start(IoThresholdByComponent.THRESHOLD);
PerStateBytes perStateBytes = mIoThresholdsByVendorPackages.valueAt(i);
proto.write(PerformanceDump.PerStateBytes.FOREGROUND_BYTES,
perStateBytes.foregroundBytes);
proto.write(PerformanceDump.PerStateBytes.BACKGROUND_BYTES,
perStateBytes.backgroundBytes);
proto.write(PerformanceDump.PerStateBytes.GARAGEMODE_BYTES,
perStateBytes.garageModeBytes);
proto.end(perStateBytesToken);
proto.write(IoThresholdByPackage.PACKAGE_NAME,
mIoThresholdsByVendorPackages.keyAt(i));
proto.end(ioThresholdByPackageToken);
}
for (int i = 0; i < mIoThresholdsByAppCategoryType.size(); i++) {
long ioThresholdsByAppCategoryTypeToken = proto.start(
OveruseConfigurationCacheDump.THRESHOLDS_BY_APP_CATEGORY);
proto.write(IoThresholdByAppCategory.APPLICATION_CATEGORY,
toProtoApplicationCategory(mIoThresholdsByAppCategoryType.keyAt(i)));
long perStateBytesToken = proto.start(IoThresholdByAppCategory.THRESHOLD);
PerStateBytes perStateBytes = mIoThresholdsByAppCategoryType.valueAt(i);
proto.write(PerformanceDump.PerStateBytes.FOREGROUND_BYTES,
perStateBytes.foregroundBytes);
proto.write(PerformanceDump.PerStateBytes.BACKGROUND_BYTES,
perStateBytes.backgroundBytes);
proto.write(PerformanceDump.PerStateBytes.GARAGEMODE_BYTES,
perStateBytes.garageModeBytes);
proto.end(perStateBytesToken);
proto.end(ioThresholdsByAppCategoryTypeToken);
}
proto.end(overuseConfigurationCacheDumpToken);
}
}
/** Overwrites the configurations in the cache. */
public void set(List configs) {
synchronized (mLock) {
clearLocked();
for (int i = 0; i < configs.size(); i++) {
ResourceOveruseConfiguration config = configs.get(i);
switch (config.componentType) {
case ComponentType.SYSTEM:
mSafeToKillSystemPackages.addAll(config.safeToKillPackages);
break;
case ComponentType.VENDOR:
mSafeToKillVendorPackages.addAll(config.safeToKillPackages);
mVendorPackagePrefixes.addAll(config.vendorPackagePrefixes);
for (int j = 0; j < config.packageMetadata.size(); ++j) {
PackageMetadata meta = config.packageMetadata.get(j);
ArraySet packages =
mPackagesByAppCategoryType.get(meta.appCategoryType);
if (packages == null) {
packages = new ArraySet<>();
}
packages.add(meta.packageName);
mPackagesByAppCategoryType.append(meta.appCategoryType, packages);
}
break;
default:
// All third-party apps are killable.
break;
}
for (int j = 0; j < config.resourceSpecificConfigurations.size(); ++j) {
if (config.resourceSpecificConfigurations.get(j).getTag()
== ResourceSpecificConfiguration.ioOveruseConfiguration) {
setIoThresholdsLocked(config.componentType,
config.resourceSpecificConfigurations.get(j)
.getIoOveruseConfiguration());
}
}
}
}
}
/** Returns the threshold for the given package and component type. */
public PerStateBytes fetchThreshold(String genericPackageName,
@ComponentType int componentType) {
synchronized (mLock) {
PerStateBytes threshold = null;
switch (componentType) {
case ComponentType.SYSTEM:
threshold = mIoThresholdsBySystemPackages.get(genericPackageName);
if (threshold != null) {
return copyPerStateBytes(threshold);
}
break;
case ComponentType.VENDOR:
threshold = mIoThresholdsByVendorPackages.get(genericPackageName);
if (threshold != null) {
return copyPerStateBytes(threshold);
}
break;
default:
// THIRD_PARTY and UNKNOWN components are set with the
// default thresholds.
break;
}
threshold = fetchAppCategorySpecificThresholdLocked(genericPackageName);
if (threshold != null) {
return copyPerStateBytes(threshold);
}
threshold = mGenericIoThresholdsByComponent.get(componentType);
return threshold != null ? copyPerStateBytes(threshold)
: copyPerStateBytes(DEFAULT_THRESHOLD);
}
}
/** Returns whether or not the given package is safe-to-kill on resource overuse. */
public boolean isSafeToKill(String genericPackageName, @ComponentType int componentType,
List sharedPackages) {
synchronized (mLock) {
BiFunction, Set, Boolean> isSafeToKillAnyPackage =
(packages, safeToKillPackages) -> {
if (packages == null) {
return false;
}
for (int i = 0; i < packages.size(); i++) {
if (safeToKillPackages.contains(packages.get(i))) {
return true;
}
}
return false;
};
switch (componentType) {
case ComponentType.SYSTEM:
if (mSafeToKillSystemPackages.contains(genericPackageName)) {
return true;
}
return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages);
case ComponentType.VENDOR:
if (mSafeToKillVendorPackages.contains(genericPackageName)) {
return true;
}
/*
* Packages under the vendor shared UID may contain system packages because when
* CarWatchdogService derives the shared component type it attributes system
* packages as vendor packages when there is at least one vendor package.
*/
return isSafeToKillAnyPackage.apply(sharedPackages, mSafeToKillSystemPackages)
|| isSafeToKillAnyPackage.apply(sharedPackages,
mSafeToKillVendorPackages);
default:
// Third-party apps are always killable
return true;
}
}
}
/** Returns the list of vendor package prefixes. */
public List getVendorPackagePrefixes() {
synchronized (mLock) {
return new ArrayList<>(mVendorPackagePrefixes);
}
}
@GuardedBy("mLock")
private void clearLocked() {
mSafeToKillSystemPackages.clear();
mSafeToKillVendorPackages.clear();
mVendorPackagePrefixes.clear();
mPackagesByAppCategoryType.clear();
mGenericIoThresholdsByComponent.clear();
mIoThresholdsBySystemPackages.clear();
mIoThresholdsByVendorPackages.clear();
mIoThresholdsByAppCategoryType.clear();
}
@GuardedBy("mLock")
private void setIoThresholdsLocked(int componentType, IoOveruseConfiguration ioConfig) {
mGenericIoThresholdsByComponent.append(componentType,
ioConfig.componentLevelThresholds.perStateWriteBytes);
switch (componentType) {
case ComponentType.SYSTEM:
populateThresholdsByPackagesLocked(
ioConfig.packageSpecificThresholds, mIoThresholdsBySystemPackages);
break;
case ComponentType.VENDOR:
populateThresholdsByPackagesLocked(
ioConfig.packageSpecificThresholds, mIoThresholdsByVendorPackages);
setIoThresholdsByAppCategoryTypeLocked(ioConfig.categorySpecificThresholds);
break;
default:
Slogf.i(TAG, "Ignoring I/O overuse threshold for invalid component type: %d",
componentType);
}
}
@GuardedBy("mLock")
private void setIoThresholdsByAppCategoryTypeLocked(
List thresholds) {
for (int i = 0; i < thresholds.size(); ++i) {
PerStateIoOveruseThreshold threshold = thresholds.get(i);
switch(threshold.name) {
case INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS:
mIoThresholdsByAppCategoryType.append(
ApplicationCategoryType.MAPS, threshold.perStateWriteBytes);
break;
case INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA:
mIoThresholdsByAppCategoryType.append(ApplicationCategoryType.MEDIA,
threshold.perStateWriteBytes);
break;
default:
Slogf.i(TAG,
"Ignoring I/O overuse threshold for invalid application category: %s",
threshold.name);
}
}
}
@GuardedBy("mLock")
private void populateThresholdsByPackagesLocked(List thresholds,
ArrayMap thresholdsByPackages) {
for (int i = 0; i < thresholds.size(); ++i) {
thresholdsByPackages.put(
thresholds.get(i).name, thresholds.get(i).perStateWriteBytes);
}
}
@GuardedBy("mLock")
private PerStateBytes fetchAppCategorySpecificThresholdLocked(String genericPackageName) {
for (int i = 0; i < mPackagesByAppCategoryType.size(); ++i) {
if (mPackagesByAppCategoryType.valueAt(i).contains(genericPackageName)) {
return mIoThresholdsByAppCategoryType.get(mPackagesByAppCategoryType.keyAt(i));
}
}
return null;
}
private static String toApplicationCategoryTypeString(@ApplicationCategoryType int type) {
switch (type) {
case ApplicationCategoryType.MAPS:
return "ApplicationCategoryType.MAPS";
case ApplicationCategoryType.MEDIA:
return "ApplicationCategoryType.MEDIA";
case ApplicationCategoryType.OTHERS:
return "ApplicationCategoryType.OTHERS";
default:
return "Invalid ApplicationCategoryType";
}
}
private static String toComponentTypeString(@ComponentType int type) {
switch (type) {
case ComponentType.SYSTEM:
return "ComponentType.SYSTEM";
case ComponentType.VENDOR:
return "ComponentType.VENDOR";
case ComponentType.THIRD_PARTY:
return "ComponentType.THIRD_PARTY";
default:
return "ComponentType.UNKNOWN";
}
}
private static int toProtoApplicationCategory(@ApplicationCategoryType int type) {
switch (type) {
case ApplicationCategoryType.MAPS:
return PerformanceDump.MAPS;
case ApplicationCategoryType.MEDIA:
return PerformanceDump.MEDIA;
case ApplicationCategoryType.OTHERS:
return PerformanceDump.OTHERS;
default:
return PerformanceDump.APPLICATION_CATEGORY_UNSPECIFIED;
}
}
private static int toProtoComponentType(@ComponentType int type) {
switch (type) {
case ComponentType.SYSTEM:
return PerformanceDump.SYSTEM;
case ComponentType.VENDOR:
return PerformanceDump.VENDOR;
case ComponentType.THIRD_PARTY:
return PerformanceDump.THIRD_PARTY;
default:
return PerformanceDump.COMPONENT_TYPE_UNSPECIFIED;
}
}
private static void dumpPerStateBytes(PerStateBytes perStateBytes,
IndentingPrintWriter writer) {
if (perStateBytes == null) {
writer.println("{NULL}");
return;
}
writer.println("{Foreground bytes: " + perStateBytes.foregroundBytes
+ ", Background bytes: " + perStateBytes.backgroundBytes + ", Garage mode bytes: "
+ perStateBytes.garageModeBytes + '}');
}
private static PerStateBytes constructPerStateBytes(long fgBytes, long bgBytes, long gmBytes) {
return new PerStateBytes() {{
foregroundBytes = fgBytes;
backgroundBytes = bgBytes;
garageModeBytes = gmBytes;
}};
}
private static PerStateBytes copyPerStateBytes(PerStateBytes perStateBytes) {
return new PerStateBytes() {{
foregroundBytes = perStateBytes.foregroundBytes;
backgroundBytes = perStateBytes.backgroundBytes;
garageModeBytes = perStateBytes.garageModeBytes;
}};
}
}