summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/Watchdog.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/Watchdog.java')
-rw-r--r--services/java/com/android/server/Watchdog.java855
1 files changed, 855 insertions, 0 deletions
diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java
new file mode 100644
index 0000000..fef3598
--- /dev/null
+++ b/services/java/com/android/server/Watchdog.java
@@ -0,0 +1,855 @@
+/*
+ * Copyright (C) 2008 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;
+
+import com.android.server.am.ActivityManagerService;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.Config;
+import android.util.EventLog;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+/** This class calls its monitor every minute. Killing this process if they don't return **/
+public class Watchdog extends Thread {
+ static final String TAG = "Watchdog";
+ static final boolean localLOGV = false || Config.LOGV;
+
+ // Set this to true to use debug default values.
+ static final boolean DB = false;
+
+ static final int MONITOR = 2718;
+ static final int GLOBAL_PSS = 2719;
+
+ static final int TIME_TO_WAIT = DB ? 15*1000 : 60*1000;
+ static final int EVENT_LOG_TAG = 2802;
+ static final int EVENT_LOG_PROC_PSS_TAG = 2803;
+ static final int EVENT_LOG_SOFT_RESET_TAG = 2804;
+ static final int EVENT_LOG_HARD_RESET_TAG = 2805;
+ static final int EVENT_LOG_PSS_STATS_TAG = 2806;
+ static final int EVENT_LOG_PROC_STATS_TAG = 2807;
+ static final int EVENT_LOG_SCHEDULED_REBOOT_TAG = 2808;
+ static final int EVENT_LOG_MEMINFO_TAG = 2809;
+ static final int EVENT_LOG_VMSTAT_TAG = 2810;
+ static final int EVENT_LOG_REQUESTED_REBOOT_TAG = 2811;
+
+ static final int MEMCHECK_DEFAULT_INTERVAL = DB ? 30 : 30*60; // 30 minutes
+ static final int MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL = DB ? 60 : 2*60*60; // 2 hours
+ static final int MEMCHECK_DEFAULT_SYSTEM_SOFT_THRESHOLD = (DB ? 10:16)*1024*1024; // 16MB
+ static final int MEMCHECK_DEFAULT_SYSTEM_HARD_THRESHOLD = (DB ? 14:20)*1024*1024; // 20MB
+ static final int MEMCHECK_DEFAULT_PHONE_SOFT_THRESHOLD = (DB ? 4:8)*1024*1024; // 8MB
+ static final int MEMCHECK_DEFAULT_PHONE_HARD_THRESHOLD = (DB ? 8:12)*1024*1024; // 12MB
+
+ static final int MEMCHECK_DEFAULT_EXEC_START_TIME = 1*60*60; // 1:00am
+ static final int MEMCHECK_DEFAULT_EXEC_END_TIME = 5*60*60; // 5:00am
+ static final int MEMCHECK_DEFAULT_MIN_SCREEN_OFF = DB ? 1*60 : 5*60; // 5 minutes
+ static final int MEMCHECK_DEFAULT_MIN_ALARM = DB ? 1*60 : 3*60; // 3 minutes
+ static final int MEMCHECK_DEFAULT_RECHECK_INTERVAL = DB ? 1*60 : 5*60; // 5 minutes
+
+ static final int REBOOT_DEFAULT_INTERVAL = DB ? 1 : 0; // never force reboot
+ static final int REBOOT_DEFAULT_START_TIME = 3*60*60; // 3:00am
+ static final int REBOOT_DEFAULT_WINDOW = 60*60; // within 1 hour
+
+ static final String CHECKUP_ACTION = "com.android.service.Watchdog.CHECKUP";
+ static final String REBOOT_ACTION = "com.android.service.Watchdog.REBOOT";
+
+ static Watchdog sWatchdog;
+
+ /* This handler will be used to post message back onto the main thread */
+ final Handler mHandler;
+ final Runnable mGlobalPssCollected;
+ final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
+ ContentResolver mResolver;
+ BatteryService mBattery;
+ PowerManagerService mPower;
+ AlarmManagerService mAlarm;
+ ActivityManagerService mActivity;
+ boolean mCompleted;
+ boolean mForceKillSystem;
+ Monitor mCurrentMonitor;
+
+ PssRequestor mPhoneReq;
+ int mPhonePid;
+ int mPhonePss;
+
+ long mLastMemCheckTime = -(MEMCHECK_DEFAULT_INTERVAL*1000);
+ boolean mHavePss;
+ long mLastMemCheckRealtime = -(MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL*1000);
+ boolean mHaveGlobalPss;
+ final MemMonitor mSystemMemMonitor = new MemMonitor("system",
+ Settings.Gservices.MEMCHECK_SYSTEM_ENABLED,
+ Settings.Gservices.MEMCHECK_SYSTEM_SOFT_THRESHOLD,
+ MEMCHECK_DEFAULT_SYSTEM_SOFT_THRESHOLD,
+ Settings.Gservices.MEMCHECK_SYSTEM_HARD_THRESHOLD,
+ MEMCHECK_DEFAULT_SYSTEM_HARD_THRESHOLD);
+ final MemMonitor mPhoneMemMonitor = new MemMonitor("com.android.phone",
+ Settings.Gservices.MEMCHECK_PHONE_ENABLED,
+ Settings.Gservices.MEMCHECK_PHONE_SOFT_THRESHOLD,
+ MEMCHECK_DEFAULT_PHONE_SOFT_THRESHOLD,
+ Settings.Gservices.MEMCHECK_PHONE_HARD_THRESHOLD,
+ MEMCHECK_DEFAULT_PHONE_HARD_THRESHOLD);
+
+ final Calendar mCalendar = Calendar.getInstance();
+ long mMemcheckLastTime;
+ long mMemcheckExecStartTime;
+ long mMemcheckExecEndTime;
+ int mMinScreenOff = MEMCHECK_DEFAULT_MIN_SCREEN_OFF;
+ int mMinAlarm = MEMCHECK_DEFAULT_MIN_ALARM;
+ boolean mNeedScheduledCheck;
+ PendingIntent mCheckupIntent;
+ PendingIntent mRebootIntent;
+
+ long mBootTime;
+ int mRebootInterval;
+
+ boolean mReqRebootNoWait; // should wait for one interval before reboot?
+ int mReqRebootInterval = -1; // >= 0 if a reboot has been requested
+ int mReqRebootStartTime = -1; // >= 0 if a specific start time has been requested
+ int mReqRebootWindow = -1; // >= 0 if a specific window has been requested
+ int mReqMinScreenOff = -1; // >= 0 if a specific screen off time has been requested
+ int mReqMinNextAlarm = -1; // >= 0 if specific time to next alarm has been requested
+ int mReqRecheckInterval= -1; // >= 0 if a specific recheck interval has been requested
+
+ /**
+ * This class monitors the memory in a particular process.
+ */
+ final class MemMonitor {
+ final String mProcessName;
+ final String mEnabledSetting;
+ final String mSoftSetting;
+ final String mHardSetting;
+
+ int mSoftThreshold;
+ int mHardThreshold;
+ boolean mEnabled;
+ long mLastPss;
+
+ static final int STATE_OK = 0;
+ static final int STATE_SOFT = 1;
+ static final int STATE_HARD = 2;
+ int mState;
+
+ MemMonitor(String processName, String enabledSetting,
+ String softSetting, int defSoftThreshold,
+ String hardSetting, int defHardThreshold) {
+ mProcessName = processName;
+ mEnabledSetting = enabledSetting;
+ mSoftSetting = softSetting;
+ mHardSetting = hardSetting;
+ mSoftThreshold = defSoftThreshold;
+ mHardThreshold = defHardThreshold;
+ }
+
+ void retrieveSettings(ContentResolver resolver) {
+ mSoftThreshold = Settings.Gservices.getInt(
+ resolver, mSoftSetting, mSoftThreshold);
+ mHardThreshold = Settings.Gservices.getInt(
+ resolver, mHardSetting, mHardThreshold);
+ mEnabled = Settings.Gservices.getInt(
+ resolver, mEnabledSetting, 0) != 0;
+ }
+
+ boolean checkLocked(long curTime, int pid, int pss) {
+ mLastPss = pss;
+ if (mLastPss < mSoftThreshold) {
+ mState = STATE_OK;
+ } else if (mLastPss < mHardThreshold) {
+ mState = STATE_SOFT;
+ } else {
+ mState = STATE_HARD;
+ }
+ EventLog.writeEvent(EVENT_LOG_PROC_PSS_TAG, mProcessName, pid, mLastPss);
+
+ if (mState == STATE_OK) {
+ // Memory is good, don't recover.
+ return false;
+ }
+
+ if (mState == STATE_HARD) {
+ // Memory is really bad, kill right now.
+ EventLog.writeEvent(EVENT_LOG_HARD_RESET_TAG, mProcessName, pid,
+ mHardThreshold, mLastPss);
+ return mEnabled;
+ }
+
+ // It is time to schedule a reset...
+ // Check if we are currently within the time to kill processes due
+ // to memory use.
+ computeMemcheckTimesLocked(curTime);
+ String skipReason = null;
+ if (curTime < mMemcheckExecStartTime || curTime > mMemcheckExecEndTime) {
+ skipReason = "time";
+ } else {
+ skipReason = shouldWeBeBrutalLocked(curTime);
+ }
+ EventLog.writeEvent(EVENT_LOG_SOFT_RESET_TAG, mProcessName, pid,
+ mSoftThreshold, mLastPss, skipReason != null ? skipReason : "");
+ if (skipReason != null) {
+ mNeedScheduledCheck = true;
+ return false;
+ }
+ return mEnabled;
+ }
+
+ void clear() {
+ mLastPss = 0;
+ mState = STATE_OK;
+ }
+ }
+
+ /**
+ * Used for scheduling monitor callbacks and checking memory usage.
+ */
+ final class HeartbeatHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case GLOBAL_PSS: {
+ if (mHaveGlobalPss) {
+ // During the last pass we collected pss information, so
+ // now it is time to report it.
+ mHaveGlobalPss = false;
+ if (localLOGV) Log.v(TAG, "Received global pss, logging.");
+ logGlobalMemory();
+ }
+ } break;
+
+ case MONITOR: {
+ if (mHavePss) {
+ // During the last pass we collected pss information, so
+ // now it is time to report it.
+ mHavePss = false;
+ if (localLOGV) Log.v(TAG, "Have pss, checking memory.");
+ checkMemory();
+ }
+
+ if (mHaveGlobalPss) {
+ // During the last pass we collected pss information, so
+ // now it is time to report it.
+ mHaveGlobalPss = false;
+ if (localLOGV) Log.v(TAG, "Have global pss, logging.");
+ logGlobalMemory();
+ }
+
+ long now = SystemClock.uptimeMillis();
+
+ // See if we should force a reboot.
+ int rebootInterval = mReqRebootInterval >= 0
+ ? mReqRebootInterval : Settings.Gservices.getInt(
+ mResolver, Settings.Gservices.REBOOT_INTERVAL,
+ REBOOT_DEFAULT_INTERVAL);
+ if (mRebootInterval != rebootInterval) {
+ mRebootInterval = rebootInterval;
+ // We have been running long enough that a reboot can
+ // be considered...
+ checkReboot(false);
+ }
+
+ // See if we should check memory conditions.
+ long memCheckInterval = Settings.Gservices.getLong(
+ mResolver, Settings.Gservices.MEMCHECK_INTERVAL,
+ MEMCHECK_DEFAULT_INTERVAL) * 1000;
+ if ((mLastMemCheckTime+memCheckInterval) < now) {
+ // It is now time to collect pss information. This
+ // is async so we won't report it now. And to keep
+ // things simple, we will assume that everyone has
+ // reported back by the next MONITOR message.
+ mLastMemCheckTime = now;
+ if (localLOGV) Log.v(TAG, "Collecting memory usage.");
+ collectMemory();
+ mHavePss = true;
+
+ long memCheckRealtimeInterval = Settings.Gservices.getLong(
+ mResolver, Settings.Gservices.MEMCHECK_LOG_REALTIME_INTERVAL,
+ MEMCHECK_DEFAULT_LOG_REALTIME_INTERVAL) * 1000;
+ long realtimeNow = SystemClock.elapsedRealtime();
+ if ((mLastMemCheckRealtime+memCheckRealtimeInterval) < realtimeNow) {
+ mLastMemCheckRealtime = realtimeNow;
+ if (localLOGV) Log.v(TAG, "Collecting global memory usage.");
+ collectGlobalMemory();
+ mHaveGlobalPss = true;
+ }
+ }
+
+ final int size = mMonitors.size();
+ for (int i = 0 ; i < size ; i++) {
+ mCurrentMonitor = mMonitors.get(i);
+ mCurrentMonitor.monitor();
+ }
+
+ synchronized (Watchdog.this) {
+ mCompleted = true;
+ mCurrentMonitor = null;
+ }
+ } break;
+ }
+ }
+ }
+
+ final class GlobalPssCollected implements Runnable {
+ public void run() {
+ mHandler.sendEmptyMessage(GLOBAL_PSS);
+ }
+ }
+
+ final class CheckupReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context c, Intent intent) {
+ if (localLOGV) Log.v(TAG, "Alarm went off, checking memory.");
+ checkMemory();
+ }
+ }
+
+ final class RebootReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context c, Intent intent) {
+ if (localLOGV) Log.v(TAG, "Alarm went off, checking reboot.");
+ checkReboot(true);
+ }
+ }
+
+ final class RebootRequestReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context c, Intent intent) {
+ mReqRebootNoWait = intent.getIntExtra("nowait", 0) != 0;
+ mReqRebootInterval = intent.getIntExtra("interval", -1);
+ mReqRebootStartTime = intent.getIntExtra("startTime", -1);
+ mReqRebootWindow = intent.getIntExtra("window", -1);
+ mReqMinScreenOff = intent.getIntExtra("minScreenOff", -1);
+ mReqMinNextAlarm = intent.getIntExtra("minNextAlarm", -1);
+ mReqRecheckInterval = intent.getIntExtra("recheckInterval", -1);
+ EventLog.writeEvent(EVENT_LOG_REQUESTED_REBOOT_TAG,
+ mReqRebootNoWait ? 1 : 0, mReqRebootInterval,
+ mReqRecheckInterval, mReqRebootStartTime,
+ mReqRebootWindow, mReqMinScreenOff, mReqMinNextAlarm);
+ checkReboot(true);
+ }
+ }
+
+ public interface Monitor {
+ void monitor();
+ }
+
+ public interface PssRequestor {
+ void requestPss();
+ }
+
+ public class PssStats {
+ public int mEmptyPss;
+ public int mEmptyCount;
+ public int mBackgroundPss;
+ public int mBackgroundCount;
+ public int mServicePss;
+ public int mServiceCount;
+ public int mVisiblePss;
+ public int mVisibleCount;
+ public int mForegroundPss;
+ public int mForegroundCount;
+
+ public int mNoPssCount;
+
+ public int mProcDeaths[] = new int[10];
+ }
+
+ public static Watchdog getInstance() {
+ if (sWatchdog == null) {
+ sWatchdog = new Watchdog();
+ }
+
+ return sWatchdog;
+ }
+
+ private Watchdog() {
+ super("watchdog");
+ mHandler = new HeartbeatHandler();
+ mGlobalPssCollected = new GlobalPssCollected();
+ }
+
+ public void init(Context context, BatteryService battery,
+ PowerManagerService power, AlarmManagerService alarm,
+ ActivityManagerService activity) {
+ mResolver = context.getContentResolver();
+ mBattery = battery;
+ mPower = power;
+ mAlarm = alarm;
+ mActivity = activity;
+
+ context.registerReceiver(new CheckupReceiver(),
+ new IntentFilter(CHECKUP_ACTION));
+ mCheckupIntent = PendingIntent.getBroadcast(context,
+ 0, new Intent(CHECKUP_ACTION), 0);
+
+ context.registerReceiver(new RebootReceiver(),
+ new IntentFilter(REBOOT_ACTION));
+ mRebootIntent = PendingIntent.getBroadcast(context,
+ 0, new Intent(REBOOT_ACTION), 0);
+
+ context.registerReceiver(new RebootRequestReceiver(),
+ new IntentFilter(Intent.ACTION_REBOOT),
+ android.Manifest.permission.REBOOT, null);
+
+ mBootTime = System.currentTimeMillis();
+ }
+
+ public void processStarted(PssRequestor req, String name, int pid) {
+ synchronized (this) {
+ if ("com.android.phone".equals(name)) {
+ mPhoneReq = req;
+ mPhonePid = pid;
+ mPhonePss = 0;
+ }
+ }
+ }
+
+ public void reportPss(PssRequestor req, String name, int pss) {
+ synchronized (this) {
+ if (mPhoneReq == req) {
+ mPhonePss = pss;
+ }
+ }
+ }
+
+ public void addMonitor(Monitor monitor) {
+ synchronized (this) {
+ if (isAlive()) {
+ throw new RuntimeException("Monitors can't be added while the Watchdog is running");
+ }
+ mMonitors.add(monitor);
+ }
+ }
+
+ /**
+ * Retrieve memory usage information from specific processes being
+ * monitored. This is an async operation, so must be done before doing
+ * memory checks.
+ */
+ void collectMemory() {
+ synchronized (this) {
+ if (mPhoneReq != null) {
+ mPhoneReq.requestPss();
+ }
+ }
+ }
+
+ /**
+ * Retrieve memory usage over all application processes. This is an
+ * async operation, so must be done before doing memory checks.
+ */
+ void collectGlobalMemory() {
+ mActivity.requestPss(mGlobalPssCollected);
+ }
+
+ /**
+ * Check memory usage in the system, scheduling kills/reboots as needed.
+ * This always runs on the mHandler thread.
+ */
+ void checkMemory() {
+ boolean needScheduledCheck;
+ long curTime;
+ long nextTime = 0;
+
+ long recheckInterval = Settings.Gservices.getLong(
+ mResolver, Settings.Gservices.MEMCHECK_RECHECK_INTERVAL,
+ MEMCHECK_DEFAULT_RECHECK_INTERVAL) * 1000;
+
+ mSystemMemMonitor.retrieveSettings(mResolver);
+ mPhoneMemMonitor.retrieveSettings(mResolver);
+ retrieveBrutalityAmount();
+
+ synchronized (this) {
+ curTime = System.currentTimeMillis();
+ mNeedScheduledCheck = false;
+
+ // How is the system doing?
+ if (mSystemMemMonitor.checkLocked(curTime, Process.myPid(),
+ (int)Process.getPss(Process.myPid()))) {
+ // Not good! Time to suicide.
+ mForceKillSystem = true;
+ notifyAll();
+ return;
+ }
+
+ // How is the phone process doing?
+ if (mPhoneReq != null) {
+ if (mPhoneMemMonitor.checkLocked(curTime, mPhonePid,
+ mPhonePss)) {
+ // Just kill the phone process and let it restart.
+ Process.killProcess(mPhonePid);
+ }
+ } else {
+ mPhoneMemMonitor.clear();
+ }
+
+ needScheduledCheck = mNeedScheduledCheck;
+ if (needScheduledCheck) {
+ // Something is going bad, but now is not a good time to
+ // tear things down... schedule an alarm to check again soon.
+ nextTime = curTime + recheckInterval;
+ if (nextTime < mMemcheckExecStartTime) {
+ nextTime = mMemcheckExecStartTime;
+ } else if (nextTime >= mMemcheckExecEndTime){
+ // Need to check during next exec time... so that needs
+ // to be computed.
+ if (localLOGV) Log.v(TAG, "Computing next time range");
+ computeMemcheckTimesLocked(nextTime);
+ nextTime = mMemcheckExecStartTime;
+ }
+
+ if (localLOGV) {
+ mCalendar.setTimeInMillis(nextTime);
+ Log.v(TAG, "Next Alarm Time: " + mCalendar);
+ }
+ }
+ }
+
+ if (needScheduledCheck) {
+ if (localLOGV) Log.v(TAG, "Scheduling next memcheck alarm for "
+ + ((nextTime-curTime)/1000/60) + "m from now");
+ mAlarm.remove(mCheckupIntent);
+ mAlarm.set(AlarmManager.RTC_WAKEUP, nextTime, mCheckupIntent);
+ } else {
+ if (localLOGV) Log.v(TAG, "No need to schedule a memcheck alarm!");
+ mAlarm.remove(mCheckupIntent);
+ }
+ }
+
+ final PssStats mPssStats = new PssStats();
+ final String[] mMemInfoFields = new String[] {
+ "MemFree:", "Buffers:", "Cached:",
+ "Active:", "Inactive:",
+ "AnonPages:", "Mapped:", "Slab:",
+ "SReclaimable:", "SUnreclaim:", "PageTables:" };
+ final long[] mMemInfoSizes = new long[mMemInfoFields.length];
+ final String[] mVMStatFields = new String[] {
+ "pgfree ", "pgactivate ", "pgdeactivate ",
+ "pgfault ", "pgmajfault " };
+ final long[] mVMStatSizes = new long[mVMStatFields.length];
+ final long[] mPrevVMStatSizes = new long[mVMStatFields.length];
+ long mLastLogGlobalMemoryTime;
+
+ void logGlobalMemory() {
+ PssStats stats = mPssStats;
+ mActivity.collectPss(stats);
+ EventLog.writeEvent(EVENT_LOG_PSS_STATS_TAG,
+ stats.mEmptyPss, stats.mEmptyCount,
+ stats.mBackgroundPss, stats.mBackgroundCount,
+ stats.mServicePss, stats.mServiceCount,
+ stats.mVisiblePss, stats.mVisibleCount,
+ stats.mForegroundPss, stats.mForegroundCount,
+ stats.mNoPssCount);
+ EventLog.writeEvent(EVENT_LOG_PROC_STATS_TAG,
+ stats.mProcDeaths[0], stats.mProcDeaths[1], stats.mProcDeaths[2],
+ stats.mProcDeaths[3], stats.mProcDeaths[4]);
+ Process.readProcLines("/proc/meminfo", mMemInfoFields, mMemInfoSizes);
+ for (int i=0; i<mMemInfoSizes.length; i++) {
+ mMemInfoSizes[i] *= 1024;
+ }
+ EventLog.writeEvent(EVENT_LOG_MEMINFO_TAG,
+ (int)mMemInfoSizes[0], (int)mMemInfoSizes[1], (int)mMemInfoSizes[2],
+ (int)mMemInfoSizes[3], (int)mMemInfoSizes[4],
+ (int)mMemInfoSizes[5], (int)mMemInfoSizes[6], (int)mMemInfoSizes[7],
+ (int)mMemInfoSizes[8], (int)mMemInfoSizes[9], (int)mMemInfoSizes[10]);
+ long now = SystemClock.uptimeMillis();
+ long dur = now - mLastLogGlobalMemoryTime;
+ mLastLogGlobalMemoryTime = now;
+ Process.readProcLines("/proc/vmstat", mVMStatFields, mVMStatSizes);
+ for (int i=0; i<mVMStatSizes.length; i++) {
+ long v = mVMStatSizes[i];
+ mVMStatSizes[i] -= mPrevVMStatSizes[i];
+ mPrevVMStatSizes[i] = v;
+ }
+ EventLog.writeEvent(EVENT_LOG_VMSTAT_TAG, dur,
+ (int)mVMStatSizes[0], (int)mVMStatSizes[1], (int)mVMStatSizes[2],
+ (int)mVMStatSizes[3], (int)mVMStatSizes[4]);
+ }
+
+ void checkReboot(boolean fromAlarm) {
+ int rebootInterval = mReqRebootInterval >= 0 ? mReqRebootInterval
+ : Settings.Gservices.getInt(
+ mResolver, Settings.Gservices.REBOOT_INTERVAL,
+ REBOOT_DEFAULT_INTERVAL);
+ mRebootInterval = rebootInterval;
+ if (rebootInterval <= 0) {
+ // No reboot interval requested.
+ if (localLOGV) Log.v(TAG, "No need to schedule a reboot alarm!");
+ mAlarm.remove(mRebootIntent);
+ return;
+ }
+
+ long rebootStartTime = mReqRebootStartTime >= 0 ? mReqRebootStartTime
+ : Settings.Gservices.getLong(
+ mResolver, Settings.Gservices.REBOOT_START_TIME,
+ REBOOT_DEFAULT_START_TIME);
+ long rebootWindowMillis = (mReqRebootWindow >= 0 ? mReqRebootWindow
+ : Settings.Gservices.getLong(
+ mResolver, Settings.Gservices.REBOOT_WINDOW,
+ REBOOT_DEFAULT_WINDOW)) * 1000;
+ long recheckInterval = (mReqRecheckInterval >= 0 ? mReqRecheckInterval
+ : Settings.Gservices.getLong(
+ mResolver, Settings.Gservices.MEMCHECK_RECHECK_INTERVAL,
+ MEMCHECK_DEFAULT_RECHECK_INTERVAL)) * 1000;
+
+ retrieveBrutalityAmount();
+
+ long realStartTime;
+ long now;
+
+ synchronized (this) {
+ now = System.currentTimeMillis();
+ realStartTime = computeCalendarTime(mCalendar, now,
+ rebootStartTime);
+
+ long rebootIntervalMillis = rebootInterval*24*60*60*1000;
+ if (DB || mReqRebootNoWait ||
+ (now-mBootTime) >= (rebootIntervalMillis-rebootWindowMillis)) {
+ if (fromAlarm && rebootWindowMillis <= 0) {
+ // No reboot window -- just immediately reboot.
+ EventLog.writeEvent(EVENT_LOG_SCHEDULED_REBOOT_TAG, now,
+ (int)rebootIntervalMillis, (int)rebootStartTime*1000,
+ (int)rebootWindowMillis, "");
+ rebootSystem("Checkin scheduled forced");
+ return;
+ }
+
+ // Are we within the reboot window?
+ if (now < realStartTime) {
+ // Schedule alarm for next check interval.
+ realStartTime = computeCalendarTime(mCalendar,
+ now, rebootStartTime);
+ } else if (now < (realStartTime+rebootWindowMillis)) {
+ String doit = shouldWeBeBrutalLocked(now);
+ EventLog.writeEvent(EVENT_LOG_SCHEDULED_REBOOT_TAG, now,
+ (int)rebootInterval, (int)rebootStartTime*1000,
+ (int)rebootWindowMillis, doit != null ? doit : "");
+ if (doit == null) {
+ rebootSystem("Checked scheduled range");
+ return;
+ }
+
+ // Schedule next alarm either within the window or in the
+ // next interval.
+ if ((now+recheckInterval) >= (realStartTime+rebootWindowMillis)) {
+ realStartTime = computeCalendarTime(mCalendar,
+ now + rebootIntervalMillis, rebootStartTime);
+ } else {
+ realStartTime = now + recheckInterval;
+ }
+ } else {
+ // Schedule alarm for next check interval.
+ realStartTime = computeCalendarTime(mCalendar,
+ now + rebootIntervalMillis, rebootStartTime);
+ }
+ }
+ }
+
+ if (localLOGV) Log.v(TAG, "Scheduling next reboot alarm for "
+ + ((realStartTime-now)/1000/60) + "m from now");
+ mAlarm.remove(mRebootIntent);
+ mAlarm.set(AlarmManager.RTC_WAKEUP, realStartTime, mRebootIntent);
+ }
+
+ /**
+ * Perform a full reboot of the system.
+ */
+ void rebootSystem(String reason) {
+ Log.i(TAG, "Rebooting system because: " + reason);
+ try {
+ android.os.Power.reboot(reason);
+ } catch (IOException e) {
+ Log.e(TAG, "Reboot failed!", e);
+ }
+ }
+
+ /**
+ * Load the current Gservices settings for when
+ * {@link #shouldWeBeBrutalLocked} will allow the brutality to happen.
+ * Must not be called with the lock held.
+ */
+ void retrieveBrutalityAmount() {
+ mMinScreenOff = (mReqMinScreenOff >= 0 ? mReqMinScreenOff
+ : Settings.Gservices.getInt(
+ mResolver, Settings.Gservices.MEMCHECK_MIN_SCREEN_OFF,
+ MEMCHECK_DEFAULT_MIN_SCREEN_OFF)) * 1000;
+ mMinAlarm = (mReqMinNextAlarm >= 0 ? mReqMinNextAlarm
+ : Settings.Gservices.getInt(
+ mResolver, Settings.Gservices.MEMCHECK_MIN_ALARM,
+ MEMCHECK_DEFAULT_MIN_ALARM)) * 1000;
+ }
+
+ /**
+ * Determine whether it is a good time to kill, crash, or otherwise
+ * plunder the current situation for the overall long-term benefit of
+ * the world.
+ *
+ * @param curTime The current system time.
+ * @return Returns null if this is a good time, else a String with the
+ * text of why it is not a good time.
+ */
+ String shouldWeBeBrutalLocked(long curTime) {
+ if (mBattery == null || !mBattery.isPowered()) {
+ return "battery";
+ }
+
+ if (mMinScreenOff >= 0 && (mPower == null ||
+ mPower.timeSinceScreenOn() < mMinScreenOff)) {
+ return "screen";
+ }
+
+ if (mMinAlarm >= 0 && (mAlarm == null ||
+ mAlarm.timeToNextAlarm() < mMinAlarm)) {
+ return "alarm";
+ }
+
+ return null;
+ }
+
+ /**
+ * Compute the times during which we next would like to perform process
+ * restarts.
+ *
+ * @param curTime The current system time.
+ */
+ void computeMemcheckTimesLocked(long curTime) {
+ if (mMemcheckLastTime == curTime) {
+ return;
+ }
+
+ mMemcheckLastTime = curTime;
+
+ long memcheckExecStartTime = Settings.Gservices.getLong(
+ mResolver, Settings.Gservices.MEMCHECK_EXEC_START_TIME,
+ MEMCHECK_DEFAULT_EXEC_START_TIME);
+ long memcheckExecEndTime = Settings.Gservices.getLong(
+ mResolver, Settings.Gservices.MEMCHECK_EXEC_END_TIME,
+ MEMCHECK_DEFAULT_EXEC_END_TIME);
+
+ mMemcheckExecEndTime = computeCalendarTime(mCalendar, curTime,
+ memcheckExecEndTime);
+ if (mMemcheckExecEndTime < curTime) {
+ memcheckExecStartTime += 24*60*60;
+ memcheckExecEndTime += 24*60*60;
+ mMemcheckExecEndTime = computeCalendarTime(mCalendar, curTime,
+ memcheckExecEndTime);
+ }
+ mMemcheckExecStartTime = computeCalendarTime(mCalendar, curTime,
+ memcheckExecStartTime);
+
+ if (localLOGV) {
+ mCalendar.setTimeInMillis(curTime);
+ Log.v(TAG, "Current Time: " + mCalendar);
+ mCalendar.setTimeInMillis(mMemcheckExecStartTime);
+ Log.v(TAG, "Start Check Time: " + mCalendar);
+ mCalendar.setTimeInMillis(mMemcheckExecEndTime);
+ Log.v(TAG, "End Check Time: " + mCalendar);
+ }
+ }
+
+ static long computeCalendarTime(Calendar c, long curTime,
+ long secondsSinceMidnight) {
+
+ // start with now
+ c.setTimeInMillis(curTime);
+
+ int val = (int)secondsSinceMidnight / (60*60);
+ c.set(Calendar.HOUR_OF_DAY, val);
+ secondsSinceMidnight -= val * (60*60);
+ val = (int)secondsSinceMidnight / 60;
+ c.set(Calendar.MINUTE, val);
+ c.set(Calendar.SECOND, (int)secondsSinceMidnight - (val*60));
+ c.set(Calendar.MILLISECOND, 0);
+
+ long newTime = c.getTimeInMillis();
+ if (newTime < curTime) {
+ // The given time (in seconds since midnight) has already passed for today, so advance
+ // by one day (due to daylight savings, etc., the delta may differ from 24 hours).
+ c.add(Calendar.DAY_OF_MONTH, 1);
+ newTime = c.getTimeInMillis();
+ }
+
+ return newTime;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ mCompleted = false;
+ mHandler.sendEmptyMessage(MONITOR);
+
+ synchronized (this) {
+ long timeout = TIME_TO_WAIT;
+
+ // NOTE: We use uptimeMillis() here because we do not want to increment the time we
+ // wait while asleep. If the device is asleep then the thing that we are waiting
+ // to timeout on is asleep as well and won't have a chance to run. Causing a false
+ // positive on when to kill things.
+ long start = SystemClock.uptimeMillis();
+ do {
+ try {
+ wait(timeout);
+ } catch (InterruptedException e) {
+ if (SystemProperties.getBoolean("ro.secure", false)) {
+ // If this is a secure build, just log the error.
+ Log.e("WatchDog", "Woof! Woof! Interrupter!");
+ } else {
+ throw new AssertionError("Someone interrupted the watchdog");
+ }
+ }
+ timeout = TIME_TO_WAIT - (SystemClock.uptimeMillis() - start);
+ } while (timeout > 0 && !mForceKillSystem);
+
+ if (mCompleted && !mForceKillSystem) {
+ // The monitors have returned.
+ continue;
+ }
+ }
+
+ // If we got here, that means that the system is most likely hung.
+ // First send a SIGQUIT so that we can see where it was hung. Then
+ // kill this process so that the system will restart.
+ String name = (mCurrentMonitor != null) ? mCurrentMonitor.getClass().getName() : "null";
+ EventLog.writeEvent(EVENT_LOG_TAG, name);
+ Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT);
+
+ // Wait a bit longer before killing so we can make sure that the stacks are captured.
+ try {
+ Thread.sleep(10*1000);
+ } catch (InterruptedException e) {
+ }
+
+ // Only kill the process if the debugger is not attached.
+ if (!Debug.isDebuggerConnected()) {
+ Process.killProcess(Process.myPid());
+ }
+ }
+ }
+}