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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user