Improve Packagewatchdog performance

I38be25753e1be64c0f98649ba843bc09e28043d9 introduced a PackageWatchdog for system server. This cl improves the following:
1. Memory allocations
2. Lock invariants
3. Comments
4. Behavior if XML file gets corrupted

Test: Still builds
Bug: 120598832
Change-Id: I8a06761997ad5738d894504d3d11ac037cb99a82
This commit is contained in:
Zimuzo
2019-01-03 21:13:01 +00:00
parent c54ffd28f7
commit cfaed764bc

View File

@@ -19,10 +19,7 @@ package com.android.server;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -32,6 +29,7 @@ import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
@@ -50,8 +48,6 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Monitors the health of packages on the system and notifies interested observers when packages
@@ -70,7 +66,6 @@ public class PackageWatchdog {
private static final String ATTR_VERSION = "version";
private static final String ATTR_NAME = "name";
private static final String ATTR_DURATION = "duration";
private static final int MESSAGE_SAVE_FILE = 1;
private static PackageWatchdog sPackageWatchdog;
@@ -79,20 +74,21 @@ public class PackageWatchdog {
private final Context mContext;
// Handler to run package cleanup runnables
private final Handler mTimerHandler;
private final HandlerThread mIoThread = new HandlerThread("package_watchdog_io",
Process.THREAD_PRIORITY_BACKGROUND);
private final Handler mIoHandler;
// Maps observer names to package observers that have been registered since the last boot
// Contains (observer-name -> external-observer-handle) that have been registered during the
// current boot.
// It is populated when observers call #registerHealthObserver and it does not survive reboots.
@GuardedBy("mLock")
final Map<String, PackageHealthObserver> mRegisteredObservers = new ArrayMap<>();
// Maps observer names to internal observers (registered or not) loaded from file
final ArrayMap<String, PackageHealthObserver> mRegisteredObservers = new ArrayMap<>();
// Contains (observer-name -> internal-observer-handle) that have ever been registered from
// previous boots. Observers with all packages expired are periodically pruned.
// It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
@GuardedBy("mLock")
final Map<String, ObserverInternal> mAllObservers = new ArrayMap<>();
// /data/system/ directory
private final File mSystemDir = new File(Environment.getDataDirectory(), "system");
// File containing the XML data of monitored packages
final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
// File containing the XML data of monitored packages /data/system/package-watchdog.xml
private final AtomicFile mPolicyFile =
new AtomicFile(new File(mSystemDir, "package-watchdog.xml"));
new AtomicFile(new File(new File(Environment.getDataDirectory(), "system"),
"package-watchdog.xml"));
// Runnable to prune monitored packages that have expired
private final Runnable mPackageCleanup;
// Last SystemClock#uptimeMillis a package clean up was executed.
@@ -105,18 +101,19 @@ public class PackageWatchdog {
private PackageWatchdog(Context context) {
mContext = context;
mTimerHandler = new Handler(Looper.myLooper());
mIoThread.start();
mIoHandler = new IoHandler(mIoThread.getLooper());
mIoHandler = BackgroundThread.getHandler();
mPackageCleanup = this::rescheduleCleanup;
loadFromFile();
}
/** Creates or gets singleton instance of PackageWatchdog. */
public static synchronized PackageWatchdog getInstance(Context context) {
if (sPackageWatchdog == null) {
sPackageWatchdog = new PackageWatchdog(context);
public static PackageWatchdog getInstance(Context context) {
synchronized (PackageWatchdog.class) {
if (sPackageWatchdog == null) {
sPackageWatchdog = new PackageWatchdog(context);
}
return sPackageWatchdog;
}
return sPackageWatchdog;
}
/**
@@ -140,21 +137,20 @@ public class PackageWatchdog {
* {@code observer} of any package failures within the monitoring duration.
*
* <p>If {@code observer} is already monitoring a package in {@code packageNames},
* the monitoring window of that package will be reset to {@code hours}.
* the monitoring window of that package will be reset to {@code durationMs}.
*
* @throws IllegalArgumentException if {@code packageNames} is empty
* or {@code hours} is less than 1
* or {@code durationMs} is less than 1
*/
public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
int hours) {
if (packageNames.isEmpty() || hours < 1) {
int durationMs) {
if (packageNames.isEmpty() || durationMs < 1) {
throw new IllegalArgumentException("Observation not started, no packages specified"
+ "or invalid hours");
+ "or invalid duration");
}
long durationMs = TimeUnit.HOURS.toMillis(hours);
List<MonitoredPackage> packages = new ArrayList<>();
for (String packageName : packageNames) {
packages.add(new MonitoredPackage(packageName, durationMs));
for (int i = 0; i < packageNames.size(); i++) {
packages.add(new MonitoredPackage(packageNames.get(i), durationMs));
}
synchronized (mLock) {
ObserverInternal oldObserver = mAllObservers.get(observer.getName());
@@ -173,7 +169,7 @@ public class PackageWatchdog {
// Always reschedule because we may need to expire packages
// earlier than we are already scheduled for
rescheduleCleanup();
sendIoMessage(MESSAGE_SAVE_FILE);
saveToFileAsync();
}
/**
@@ -186,7 +182,7 @@ public class PackageWatchdog {
mAllObservers.remove(observer.getName());
mRegisteredObservers.remove(observer.getName());
}
sendIoMessage(MESSAGE_SAVE_FILE);
saveToFileAsync();
}
// TODO(zezeozue:) Accept current versionCodes of failing packages?
@@ -194,27 +190,43 @@ public class PackageWatchdog {
* Called when a process fails either due to a crash or ANR.
*
* <p>All registered observers for the packages contained in the process will be notified in
* order of priority unitl an observer signifies that it has taken action and other observers
* order of priority until an observer signifies that it has taken action and other observers
* should not notified.
*
* <p>This method could be called frequently if there is a severe problem on the device.
*/
public void onPackageFailure(String[] packages) {
ArrayMap<String, List<PackageHealthObserver>> packagesToReport = new ArrayMap<>();
synchronized (mLock) {
if (mRegisteredObservers.isEmpty()) {
return;
}
for (String packageName : packages) {
for (ObserverInternal observer : mAllObservers.values()) {
if (observer.onPackageFailure(packageName)) {
PackageHealthObserver activeObserver =
mRegisteredObservers.get(observer.mName);
if (activeObserver != null
&& activeObserver.onHealthCheckFailed(packageName)) {
// Observer has handled, do not notify other observers
break;
}
for (int pIndex = 0; pIndex < packages.length; pIndex++) {
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
// Observers interested in receiving packageName failures
List<PackageHealthObserver> observersToNotify = new ArrayList<>();
PackageHealthObserver activeObserver =
mRegisteredObservers.get(mAllObservers.valueAt(oIndex).mName);
if (activeObserver != null) {
observersToNotify.add(activeObserver);
}
// Save interested observers and notify them outside the lock
if (!observersToNotify.isEmpty()) {
packagesToReport.put(packages[pIndex], observersToNotify);
}
}
}
}
// Notify observers
for (int pIndex = 0; pIndex < packagesToReport.size(); pIndex++) {
List<PackageHealthObserver> observers = packagesToReport.valueAt(pIndex);
for (int oIndex = 0; oIndex < observers.size(); oIndex++) {
if (observers.get(oIndex).onHealthCheckFailed(packages[pIndex])) {
// Observer has handled, do not notify others
break;
}
}
}
@@ -225,7 +237,7 @@ public class PackageWatchdog {
/** Writes the package information to file during shutdown. */
public void writeNow() {
if (!mAllObservers.isEmpty()) {
mIoHandler.removeMessages(MESSAGE_SAVE_FILE);
mIoHandler.removeCallbacks(this::saveToFile);
pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
saveToFile();
Slog.i(TAG, "Last write to update package durations");
@@ -235,7 +247,7 @@ public class PackageWatchdog {
/** Register instances of this interface to receive notifications on package failure. */
public interface PackageHealthObserver {
/**
* Called when health check fails for the {@code packages}.
* Called when health check fails for the {@code packageName}.
* @return {@code true} if action was taken and other observers should not be notified of
* this failure, {@code false} otherwise.
*/
@@ -283,10 +295,12 @@ public class PackageWatchdog {
*/
private long getEarliestPackageExpiryLocked() {
long shortestDurationMs = Long.MAX_VALUE;
for (ObserverInternal observer : mAllObservers.values()) {
for (MonitoredPackage p : observer.mPackages.values()) {
if (p.mDurationMs < shortestDurationMs) {
shortestDurationMs = p.mDurationMs;
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
long duration = packages.valueAt(pIndex).mDurationMs;
if (duration < shortestDurationMs) {
shortestDurationMs = duration;
}
}
}
@@ -313,7 +327,7 @@ public class PackageWatchdog {
}
}
}
sendIoMessage(MESSAGE_SAVE_FILE);
saveToFileAsync();
}
/**
@@ -339,59 +353,53 @@ public class PackageWatchdog {
}
} catch (FileNotFoundException e) {
// Nothing to monitor
} catch (IOException e) {
Log.wtf(TAG, "Unable to read monitored packages", e);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Unable to parse monitored package windows", e);
} catch (XmlPullParserException e) {
Log.wtf(TAG, "Unable to parse monitored packages", e);
} catch (IOException | NumberFormatException | XmlPullParserException e) {
Log.wtf(TAG, "Unable to read monitored packages, deleting file", e);
mPolicyFile.delete();
} finally {
IoUtils.closeQuietly(infile);
}
}
/**
* Persists mAllObservers to file and ignores threshold information.
*
* <p>Note that this is <b>not</b> thread safe and should only be called on the
* single threaded IoHandler.
* Persists mAllObservers to file. Threshold information is ignored.
*/
private boolean saveToFile() {
FileOutputStream stream;
try {
stream = mPolicyFile.startWrite();
} catch (IOException e) {
Slog.w(TAG, "Cannot update monitored packages", e);
return false;
}
try {
XmlSerializer out = new FastXmlSerializer();
out.setOutput(stream, StandardCharsets.UTF_8.name());
out.startDocument(null, true);
out.startTag(null, TAG_PACKAGE_WATCHDOG);
out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
for (ObserverInternal observer : mAllObservers.values()) {
observer.write(out);
synchronized (mLock) {
FileOutputStream stream;
try {
stream = mPolicyFile.startWrite();
} catch (IOException e) {
Slog.w(TAG, "Cannot update monitored packages", e);
return false;
}
try {
XmlSerializer out = new FastXmlSerializer();
out.setOutput(stream, StandardCharsets.UTF_8.name());
out.startDocument(null, true);
out.startTag(null, TAG_PACKAGE_WATCHDOG);
out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
mAllObservers.valueAt(oIndex).write(out);
}
out.endTag(null, TAG_PACKAGE_WATCHDOG);
out.endDocument();
mPolicyFile.finishWrite(stream);
return true;
} catch (IOException e) {
Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
mPolicyFile.failWrite(stream);
return false;
} finally {
IoUtils.closeQuietly(stream);
}
out.endTag(null, TAG_PACKAGE_WATCHDOG);
out.endDocument();
mPolicyFile.finishWrite(stream);
return true;
} catch (IOException e) {
Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
mPolicyFile.failWrite(stream);
return false;
} finally {
IoUtils.closeQuietly(stream);
}
}
private void sendIoMessage(int what) {
if (!mIoHandler.hasMessages(what)) {
Message m = Message.obtain(mIoHandler, what);
mIoHandler.sendMessage(m);
}
private void saveToFileAsync() {
mIoHandler.removeCallbacks(this::saveToFile);
mIoHandler.post(this::saveToFile);
}
/**
@@ -435,7 +443,8 @@ public class PackageWatchdog {
public void updatePackages(List<MonitoredPackage> packages) {
synchronized (mName) {
for (MonitoredPackage p : packages) {
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
MonitoredPackage p = packages.get(pIndex);
mPackages.put(p.mName, p);
}
}
@@ -554,19 +563,4 @@ public class PackageWatchdog {
return mFailures >= TRIGGER_FAILURE_COUNT;
}
}
private class IoHandler extends Handler {
IoHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_SAVE_FILE:
saveToFile();
break;
}
}
}
}