Merge "Improve how procstats history/checkins are managed."
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 6425693..0bd0d8e 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -1822,7 +1822,6 @@
         mBatteryStatsService.getActiveStatistics().setCallback(this);
 
         mProcessTracker = new ProcessTracker(new File(systemDir, "procstats"));
-        mProcessTracker.readLocked();
 
         mUsageStatsService = new UsageStatsService(new File(systemDir, "usagestats").toString());
         mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"));
diff --git a/services/java/com/android/server/am/ProcessTracker.java b/services/java/com/android/server/am/ProcessTracker.java
index fbdbdd9..488582d 100644
--- a/services/java/com/android/server/am/ProcessTracker.java
+++ b/services/java/com/android/server/am/ProcessTracker.java
@@ -22,6 +22,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -137,19 +138,21 @@
 
     static final String CSV_SEP = "\t";
 
+    static final int MAX_HISTORIC_STATES = 4;   // Maximum number of historic states we will keep.
+    static final String STATE_FILE_PREFIX = "state-"; // Prefix to use for state filenames.
     static long WRITE_PERIOD = 30*60*1000;      // Write file every 30 minutes or so.
+    static long COMMIT_PERIOD = 24*60*60*1000;  // Commit current stats every day.
 
     final File mBaseDir;
-    final AtomicFile mFile;
-    final State mState = new State();
-    long mLastWriteTime;
+    State mState;
+    boolean mCommitPending;
     boolean mShuttingDown;
 
-    final Object mPendingWriteLock = new Object();
-    Parcel mPendingWrite;
     final ReentrantLock mWriteLock = new ReentrantLock();
 
     public static final class ProcessState {
+        static final int[] BAD_TABLE = new int[0];
+
         final State mState;
         final ProcessState mCommonProcess;
         final String mPackage;
@@ -257,10 +260,14 @@
             out.writeInt(mMultiPackage ? 1 : 0);
             out.writeInt(mDurationsTableSize);
             for (int i=0; i<mDurationsTableSize; i++) {
+                if (DEBUG) Slog.i(TAG, "Writing in " + mName + " dur #" + i + ": "
+                        + State.printLongOffset(mDurationsTable[i]));
                 out.writeInt(mDurationsTable[i]);
             }
             out.writeInt(mPssTableSize);
             for (int i=0; i<mPssTableSize; i++) {
+                if (DEBUG) Slog.i(TAG, "Writing in " + mName + " pss #" + i + ": "
+                        + State.printLongOffset(mPssTable[i]));
                 out.writeInt(mPssTable[i]);
             }
             out.writeInt(mNumExcessiveWake);
@@ -268,17 +275,22 @@
         }
 
         private int[] readTable(Parcel in, String what) {
-            int size = in.readInt();
+            final int size = in.readInt();
             if (size < 0) {
                 Slog.w(TAG, "Ignoring existing stats; bad " + what + " table size: " + size);
+                return BAD_TABLE;
+            }
+            if (size == 0) {
                 return null;
             }
-            int[] table = new int[size];
+            final int[] table = new int[size];
             for (int i=0; i<size; i++) {
                 table[i] = in.readInt();
+                if (DEBUG) Slog.i(TAG, "Reading in " + mName + " table #" + i + ": "
+                        + State.printLongOffset(table[i]));
                 if (!mState.validateLongOffset(table[i])) {
-                    Slog.w(TAG, "Ignoring existing stats; bad " + what + " table entry: 0x"
-                            + Integer.toHexString(table[i]));
+                    Slog.w(TAG, "Ignoring existing stats; bad " + what + " table entry: "
+                            + State.printLongOffset(table[i]));
                     return null;
                 }
             }
@@ -290,16 +302,18 @@
             if (fully) {
                 mMultiPackage = multiPackage;
             }
+            if (DEBUG) Slog.d(TAG, "Reading durations table...");
             mDurationsTable = readTable(in, "durations");
-            if (mDurationsTable == null) {
+            if (mDurationsTable == BAD_TABLE) {
                 return false;
             }
-            mDurationsTableSize = mDurationsTable.length;
+            mDurationsTableSize = mDurationsTable != null ? mDurationsTable.length : 0;
+            if (DEBUG) Slog.d(TAG, "Reading pss table...");
             mPssTable = readTable(in, "pss");
-            if (mPssTable == null) {
+            if (mPssTable == BAD_TABLE) {
                 return false;
             }
-            mPssTableSize = mPssTable.length;
+            mPssTableSize = mPssTable != null ? mPssTable.length : 0;
             mNumExcessiveWake = in.readInt();
             mNumExcessiveCpu = in.readInt();
             return true;
@@ -334,19 +348,21 @@
         void commitStateTime(long now) {
             if (mCurState != STATE_NOTHING) {
                 long dur = now - mStartTime;
-                int idx = State.binarySearch(mDurationsTable, mDurationsTableSize, mCurState);
-                int off;
-                if (idx >= 0) {
-                    off = mDurationsTable[idx];
-                } else {
-                    mState.mAddLongTable = mDurationsTable;
-                    mState.mAddLongTableSize = mDurationsTableSize;
-                    off = mState.addLongData(~idx, mCurState, 1);
-                    mDurationsTable = mState.mAddLongTable;
-                    mDurationsTableSize = mState.mAddLongTableSize;
+                if (dur > 0) {
+                    int idx = State.binarySearch(mDurationsTable, mDurationsTableSize, mCurState);
+                    int off;
+                    if (idx >= 0) {
+                        off = mDurationsTable[idx];
+                    } else {
+                        mState.mAddLongTable = mDurationsTable;
+                        mState.mAddLongTableSize = mDurationsTableSize;
+                        off = mState.addLongData(~idx, mCurState, 1);
+                        mDurationsTable = mState.mAddLongTable;
+                        mDurationsTableSize = mState.mAddLongTableSize;
+                    }
+                    long[] longs = mState.mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+                    longs[(off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK] += dur;
                 }
-                long[] longs = mState.mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
-                longs[(off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK] += dur;
             }
             mStartTime = now;
         }
@@ -570,12 +586,20 @@
 
     static final class State {
         // Current version of the parcel format.
-        public static final int PARCEL_VERSION = 1;
+        private static final int PARCEL_VERSION = 3;
         // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
         private static final int MAGIC = 0x50535453;
 
-        long mTimePeriodStart;
-        long mTimePeriodEnd;
+        final File mBaseDir;
+        final ProcessTracker mProcessTracker;
+        AtomicFile mFile;
+        String mReadError;
+
+        long mTimePeriodStartClock;
+        String mTimePeriodStartClockStr;
+        long mTimePeriodStartRealtime;
+        long mTimePeriodEndRealtime;
+        boolean mRunning;
 
         final ProcessMap<PackageState> mPackages = new ProcessMap<PackageState>();
         final ProcessMap<ProcessState> mProcesses = new ProcessMap<ProcessState>();
@@ -590,19 +614,36 @@
         int[] mAddLongTable;
         int mAddLongTableSize;
 
-        State() {
+        final Object mPendingWriteLock = new Object();
+        Parcel mPendingWrite;
+        long mLastWriteTime;
+
+        State(File baseDir, ProcessTracker tracker) {
+            mBaseDir = baseDir;
             reset();
+            mProcessTracker = tracker;
+        }
+
+        State(String file) {
+            mBaseDir = null;
+            reset();
+            mFile = new AtomicFile(new File(file));
+            mProcessTracker = null;
+            readLocked();
         }
 
         void reset() {
+            if (DEBUG && mFile != null) Slog.d(TAG, "Resetting state of " + mFile.getBaseFile());
             resetCommon();
             mPackages.getMap().clear();
             mProcesses.getMap().clear();
             mMemFactor = STATE_NOTHING;
             mStartTime = 0;
+            if (DEBUG && mFile != null) Slog.d(TAG, "State reset; now " + mFile.getBaseFile());
         }
 
         void resetSafely() {
+            if (DEBUG && mFile != null) Slog.d(TAG, "Safely resetting state of " + mFile.getBaseFile());
             resetCommon();
             long now = SystemClock.uptimeMillis();
             ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
@@ -632,10 +673,14 @@
                 }
             }
             mStartTime = SystemClock.uptimeMillis();
+            if (DEBUG && mFile != null) Slog.d(TAG, "State reset; now " + mFile.getBaseFile());
         }
 
         private void resetCommon() {
-            mTimePeriodStart = mTimePeriodEnd = System.currentTimeMillis();
+            mLastWriteTime = SystemClock.uptimeMillis();
+            mTimePeriodStartClock = System.currentTimeMillis();
+            buildTimePeriodStartClockStr();
+            mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
             mLongs.clear();
             mLongs.add(new long[LONGS_SIZE]);
             mNextLong = 0;
@@ -644,6 +689,150 @@
             mStartTime = 0;
         }
 
+        private void buildTimePeriodStartClockStr() {
+            mTimePeriodStartClockStr = DateFormat.format("yyyy-MM-dd-HH-mm-ss",
+                    mTimePeriodStartClock).toString();
+            if (mBaseDir != null) {
+                mFile = new AtomicFile(new File(mBaseDir,
+                        STATE_FILE_PREFIX + mTimePeriodStartClockStr));
+            }
+        }
+
+        static byte[] readFully(FileInputStream stream) throws java.io.IOException {
+            int pos = 0;
+            int avail = stream.available();
+            byte[] data = new byte[avail];
+            while (true) {
+                int amt = stream.read(data, pos, data.length-pos);
+                //Log.i("foo", "Read " + amt + " bytes at " + pos
+                //        + " of avail " + data.length);
+                if (amt <= 0) {
+                    //Log.i("foo", "**** FINISHED READING: pos=" + pos
+                    //        + " len=" + data.length);
+                    return data;
+                }
+                pos += amt;
+                avail = stream.available();
+                if (avail > data.length-pos) {
+                    byte[] newData = new byte[pos+avail];
+                    System.arraycopy(data, 0, newData, 0, pos);
+                    data = newData;
+                }
+            }
+        }
+
+        void readLocked() {
+            try {
+                FileInputStream stream = mFile.openRead();
+
+                byte[] raw = readFully(stream);
+                Parcel in = Parcel.obtain();
+                in.unmarshall(raw, 0, raw.length);
+                in.setDataPosition(0);
+                stream.close();
+
+                readFromParcel(in);
+                if (mReadError != null) {
+                    Slog.w(TAG, "Ignoring existing stats; " + mReadError);
+                    if (DEBUG) {
+                        ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+                        final int NPROC = procMap.size();
+                        for (int ip=0; ip<NPROC; ip++) {
+                            Slog.w(TAG, "Process: " + procMap.keyAt(ip));
+                            SparseArray<ProcessState> uids = procMap.valueAt(ip);
+                            final int NUID = uids.size();
+                            for (int iu=0; iu<NUID; iu++) {
+                                Slog.w(TAG, "  Uid " + uids.keyAt(iu) + ": " + uids.valueAt(iu));
+                            }
+                        }
+                        ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
+                        final int NPKG = pkgMap.size();
+                        for (int ip=0; ip<NPKG; ip++) {
+                            Slog.w(TAG, "Package: " + pkgMap.keyAt(ip));
+                            SparseArray<PackageState> uids = pkgMap.valueAt(ip);
+                            final int NUID = uids.size();
+                            for (int iu=0; iu<NUID; iu++) {
+                                Slog.w(TAG, "  Uid: " + uids.keyAt(iu));
+                                PackageState pkgState = uids.valueAt(iu);
+                                final int NPROCS = pkgState.mProcesses.size();
+                                for (int iproc=0; iproc<NPROCS; iproc++) {
+                                    Slog.w(TAG, "    Process " + pkgState.mProcesses.keyAt(iproc)
+                                            + ": " + pkgState.mProcesses.valueAt(iproc));
+                                }
+                                final int NSRVS = pkgState.mServices.size();
+                                for (int isvc=0; isvc<NSRVS; isvc++) {
+                                    Slog.w(TAG, "    Service " + pkgState.mServices.keyAt(isvc)
+                                            + ": " + pkgState.mServices.valueAt(isvc));
+                                }
+                            }
+                        }
+                    }
+                }
+            } catch (Throwable e) {
+                mReadError = "error reading: " + e;
+                Slog.e(TAG, "Error reading process statistics", e);
+            }
+        }
+
+        private void writeStateLocked(boolean sync, final boolean commit) {
+            synchronized (mPendingWriteLock) {
+                long now = SystemClock.uptimeMillis();
+                mPendingWrite = Parcel.obtain();
+                mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+                writeToParcel(mPendingWrite);
+                mLastWriteTime = SystemClock.uptimeMillis();
+                Slog.i(TAG, "Prepared write state in " + (SystemClock.uptimeMillis()-now) + "ms");
+                if (!sync) {
+                    BackgroundThread.getHandler().post(new Runnable() {
+                        @Override public void run() {
+                            performWriteState(commit);
+                        }
+                    });
+                    return;
+                }
+            }
+
+            performWriteState(commit);
+        }
+
+        void performWriteState(boolean commit) {
+            if (DEBUG) Slog.d(TAG, "Performing write to " + mFile.getBaseFile()
+                    + " commit=" + commit);
+            Parcel data;
+            synchronized (mPendingWriteLock) {
+                data = mPendingWrite;
+                if (data == null) {
+                    return;
+                }
+                mPendingWrite = null;
+                if (mProcessTracker != null) {
+                    mProcessTracker.mWriteLock.lock();
+                }
+            }
+
+            FileOutputStream stream = null;
+            try {
+                stream = mFile.startWrite();
+                stream.write(data.marshall());
+                stream.flush();
+                mFile.finishWrite(stream);
+                if (DEBUG) Slog.d(TAG, "Write completed successfully!");
+            } catch (IOException e) {
+                Slog.w(TAG, "Error writing process statistics", e);
+                mFile.failWrite(stream);
+            } finally {
+                data.recycle();
+                if (mProcessTracker != null) {
+                    mProcessTracker.trimHistoricStatesWriteLocked();
+                    mProcessTracker.mWriteLock.unlock();
+                }
+            }
+
+            if (commit) {
+                resetSafely();
+            }
+        }
+
         void writeToParcel(Parcel out) {
             long now = SystemClock.uptimeMillis();
             out.writeInt(MAGIC);
@@ -653,8 +842,9 @@
             out.writeInt(PSS_COUNT);
             out.writeInt(LONGS_SIZE);
 
-            out.writeLong(mTimePeriodStart);
-            out.writeLong(mTimePeriodEnd);
+            out.writeLong(mTimePeriodStartClock);
+            out.writeLong(mTimePeriodStartRealtime);
+            out.writeLong(mTimePeriodEndRealtime);
 
             out.writeInt(mLongs.size());
             out.writeInt(mNextLong);
@@ -664,6 +854,7 @@
             long[] lastLongs = mLongs.get(mLongs.size()-1);
             for (int i=0; i<mNextLong; i++) {
                 out.writeLong(lastLongs[i]);
+                if (DEBUG) Slog.d(TAG, "Writing last long #" + i + ": " + lastLongs[i]);
             }
 
             if (mMemFactor != STATE_NOTHING) {
@@ -726,13 +917,13 @@
         private boolean readCheckedInt(Parcel in, int val, String what) {
             int got;
             if ((got=in.readInt()) != val) {
-                Slog.w(TAG, "Ignoring existing stats; bad " + ": " + got);
+                mReadError = "bad " + ": " + got;
                 return false;
             }
             return true;
         }
 
-        void readFromParcel(Parcel in) {
+        private void readFromParcel(Parcel in) {
             final boolean hadData = mPackages.getMap().size() > 0
                     || mProcesses.getMap().size() > 0;
             if (hadData) {
@@ -758,11 +949,14 @@
                 return;
             }
 
-            mTimePeriodStart = in.readLong();
-            mTimePeriodEnd = in.readLong();
+            mTimePeriodStartClock = in.readLong();
+            buildTimePeriodStartClockStr();
+            mTimePeriodStartRealtime = in.readLong();
+            mTimePeriodEndRealtime = in.readLong();
 
-            int NLONGS = in.readInt();
-            int NEXTLONG = in.readInt();
+            final int NLONGS = in.readInt();
+            final int NEXTLONG = in.readInt();
+            mLongs.clear();
             for (int i=0; i<(NLONGS-1); i++) {
                 while (i >= mLongs.size()) {
                     mLongs.add(new long[LONGS_SIZE]);
@@ -773,6 +967,7 @@
             mNextLong = NEXTLONG;
             for (int i=0; i<NEXTLONG; i++) {
                 longs[i] = in.readLong();
+                if (DEBUG) Slog.d(TAG, "Reading last long #" + i + ": " + longs[i]);
             }
             mLongs.add(longs);
 
@@ -780,31 +975,31 @@
 
             int NPROC = in.readInt();
             if (NPROC < 0) {
-                Slog.w(TAG, "Ignoring existing stats; bad process count: " + NPROC);
+                mReadError = "bad process count: " + NPROC;
                 return;
             }
             while (NPROC > 0) {
                 NPROC--;
                 String procName = in.readString();
                 if (procName == null) {
-                    Slog.w(TAG, "Ignoring existing stats; bad process name");
+                    mReadError = "bad process name";
                     return;
                 }
                 int NUID = in.readInt();
                 if (NUID < 0) {
-                    Slog.w(TAG, "Ignoring existing stats; bad uid count: " + NUID);
+                    mReadError = "bad uid count: " + NUID;
                     return;
                 }
                 while (NUID > 0) {
                     NUID--;
                     int uid = in.readInt();
                     if (uid < 0) {
-                        Slog.w(TAG, "Ignoring existing stats; bad uid: " + uid);
+                        mReadError = "bad uid: " + uid;
                         return;
                     }
                     String pkgName = in.readString();
                     if (pkgName == null) {
-                        Slog.w(TAG, "Ignoring existing stats; bad process package name");
+                        mReadError = "bad process package name";
                         return;
                     }
                     ProcessState proc = hadData ? mProcesses.get(procName, uid) : null;
@@ -827,57 +1022,57 @@
 
             int NPKG = in.readInt();
             if (NPKG < 0) {
-                Slog.w(TAG, "Ignoring existing stats; bad package count: " + NPKG);
+                mReadError = "bad package count: " + NPKG;
                 return;
             }
             while (NPKG > 0) {
                 NPKG--;
                 String pkgName = in.readString();
                 if (pkgName == null) {
-                    Slog.w(TAG, "Ignoring existing stats; bad package name");
+                    mReadError = "bad package name";
                     return;
                 }
                 int NUID = in.readInt();
                 if (NUID < 0) {
-                    Slog.w(TAG, "Ignoring existing stats; bad uid count: " + NUID);
+                    mReadError = "bad uid count: " + NUID;
                     return;
                 }
                 while (NUID > 0) {
                     NUID--;
                     int uid = in.readInt();
                     if (uid < 0) {
-                        Slog.w(TAG, "Ignoring existing stats; bad uid: " + uid);
+                        mReadError = "bad uid: " + uid;
                         return;
                     }
                     PackageState pkgState = new PackageState(uid);
                     mPackages.put(pkgName, uid, pkgState);
                     int NPROCS = in.readInt();
                     if (NPROCS < 0) {
-                        Slog.w(TAG, "Ignoring existing stats; bad package process count: " + NPROCS);
+                        mReadError = "bad package process count: " + NPROCS;
                         return;
                     }
                     while (NPROCS > 0) {
                         NPROCS--;
                         String procName = in.readString();
                         if (procName == null) {
-                            Slog.w(TAG, "Ignoring existing stats; bad package process name");
+                            mReadError = "bad package process name";
                             return;
                         }
                         int hasProc = in.readInt();
                         if (DEBUG) Slog.d(TAG, "Reading package " + pkgName + " " + uid
                                 + " process " + procName + " hasProc=" + hasProc);
+                        ProcessState commonProc = mProcesses.get(procName, uid);
+                        if (DEBUG) Slog.d(TAG, "Got common proc " + procName + " " + uid
+                                + ": " + commonProc);
+                        if (commonProc == null) {
+                            mReadError = "no common proc: " + procName;
+                            return;
+                        }
                         if (hasProc != 0) {
                             // The process for this package is unique to the package; we
                             // need to load it.  We don't need to do anything about it if
                             // it is not unique because if someone later looks for it
                             // they will find and use it from the global procs.
-                            ProcessState commonProc = mProcesses.get(procName, uid);
-                            if (DEBUG) Slog.d(TAG, "Got common proc " + procName + " " + uid
-                                    + ": " + commonProc);
-                            if (commonProc == null) {
-                                Slog.w(TAG, "Ignoring existing stats; no common proc: " + procName);
-                                return;
-                            }
                             ProcessState proc = hadData ? pkgState.mProcesses.get(procName) : null;
                             if (proc != null) {
                                 if (!proc.readFromParcel(in, false)) {
@@ -892,18 +1087,22 @@
                             if (DEBUG) Slog.d(TAG, "Adding package " + pkgName + " process: "
                                     + procName + " " + uid + " " + proc);
                             pkgState.mProcesses.put(procName, proc);
+                        } else {
+                            if (DEBUG) Slog.d(TAG, "Adding package " + pkgName + " process: "
+                                    + procName + " " + uid + " " + commonProc);
+                            pkgState.mProcesses.put(procName, commonProc);
                         }
                     }
                     int NSRVS = in.readInt();
                     if (NSRVS < 0) {
-                        Slog.w(TAG, "Ignoring existing stats; bad package service count: " + NSRVS);
+                        mReadError = "bad package service count: " + NSRVS;
                         return;
                     }
                     while (NSRVS > 0) {
                         NSRVS--;
                         String serviceName = in.readString();
                         if (serviceName == null) {
-                            Slog.w(TAG, "Ignoring existing stats; bad package service name");
+                            mReadError = "bad package service name";
                             return;
                         }
                         ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null;
@@ -966,9 +1165,19 @@
             if (idx >= LONGS_SIZE) {
                 return false;
             }
+            if (DEBUG) Slog.d(TAG, "Validated long " + printLongOffset(off)
+                    + ": " + getLong(off, 0));
             return true;
         }
 
+        static String printLongOffset(int off) {
+            StringBuilder sb = new StringBuilder(16);
+            sb.append("a"); sb.append((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+            sb.append("i"); sb.append((off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK);
+            sb.append("t"); sb.append((off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK);
+            return sb.toString();
+        }
+
         void setLong(int off, int index, long value) {
             long[] longs = mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
             longs[index + ((off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK)] = value;
@@ -1032,7 +1241,7 @@
                     // The original package it was created for now needs to point
                     // to its own copy.
                     long now = SystemClock.uptimeMillis();
-                    pkgState.mProcesses.put(commonProc.mPackage, commonProc.clone(
+                    pkgState.mProcesses.put(commonProc.mName, commonProc.clone(
                             commonProc.mPackage, now));
                     ps = new ProcessState(commonProc, packageName, uid, processName, now);
                 }
@@ -1136,11 +1345,22 @@
             pw.println();
             pw.println("Run time Stats:");
             dumpSingleTime(pw, "  ", mMemFactorDurations, mMemFactor, mStartTime, now);
+            pw.println();
+            pw.print("          Start time: ");
+            pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock));
+            pw.println();
+            pw.print("  Total elapsed time: ");
+            TimeUtils.formatDuration(
+                    (mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime)
+                    - mTimePeriodStartRealtime, pw);
+            pw.println();
             if (dumpAll) {
                 pw.println();
                 pw.println("Internal state:");
+                pw.print("  mFile="); pw.println(mFile.getBaseFile());
                 pw.print("  Num long arrays: "); pw.println(mLongs.size());
                 pw.print("  Next long entry: "); pw.println(mNextLong);
+                pw.print("  mRunning="); pw.println(mRunning);
             }
         }
 
@@ -1194,10 +1414,29 @@
             return outProcs;
         }
 
+        String collapseString(String pkgName, String itemName) {
+            if (itemName.startsWith(pkgName)) {
+                final int ITEMLEN = itemName.length();
+                final int PKGLEN = pkgName.length();
+                if (ITEMLEN == PKGLEN) {
+                    return "";
+                } else if (ITEMLEN >= PKGLEN) {
+                    if (itemName.charAt(PKGLEN) == '.') {
+                        return itemName.substring(PKGLEN);
+                    }
+                }
+            }
+            return itemName;
+        }
+
         void dumpCheckinLocked(PrintWriter pw, String reqPackage) {
             final long now = SystemClock.uptimeMillis();
             ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap();
             pw.println("vers,1");
+            pw.print("period,"); pw.print(mTimePeriodStartClockStr);
+            pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(",");
+            pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
+            pw.println();
             for (int ip=0; ip<pkgMap.size(); ip++) {
                 String pkgName = pkgMap.keyAt(ip);
                 if (reqPackage != null && !reqPackage.equals(pkgName)) {
@@ -1216,7 +1455,7 @@
                         pw.print(",");
                         pw.print(uid);
                         pw.print(",");
-                        pw.print(pkgState.mProcesses.keyAt(iproc));
+                        pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
                         dumpAllProcessStateCheckin(pw, proc, now);
                         pw.println();
                         if (proc.mPssTableSize > 0) {
@@ -1225,7 +1464,7 @@
                             pw.print(",");
                             pw.print(uid);
                             pw.print(",");
-                            pw.print(pkgState.mProcesses.keyAt(iproc));
+                            pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
                             dumpAllProcessPssCheckin(pw, proc);
                             pw.println();
                         }
@@ -1235,7 +1474,7 @@
                             pw.print(",");
                             pw.print(uid);
                             pw.print(",");
-                            pw.print(pkgState.mProcesses.keyAt(iproc));
+                            pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc)));
                             pw.print(",");
                             pw.print(proc.mNumExcessiveWake);
                             pw.print(",");
@@ -1244,7 +1483,8 @@
                         }
                     }
                     for (int isvc=0; isvc<NSRVS; isvc++) {
-                        String serviceName = pkgState.mServices.keyAt(isvc);
+                        String serviceName = collapseString(pkgName,
+                                pkgState.mServices.keyAt(isvc));
                         ServiceState svc = pkgState.mServices.valueAt(isvc);
                         dumpServiceTimeCheckin(pw, "pkgsvc-start", pkgName, uid, serviceName,
                                 svc, svc.mStartedCount, svc.mStartedDurations, svc.mStartedState,
@@ -1305,7 +1545,8 @@
     public ProcessTracker(File file) {
         mBaseDir = file;
         mBaseDir.mkdirs();
-        mFile = new AtomicFile(new File(file, "current.bin"));
+        mState = new State(mBaseDir, this);
+        mState.mRunning = true;
     }
 
     public ProcessState getProcessStateLocked(String packageName, int uid, String processName) {
@@ -1359,48 +1600,18 @@
         return mState.mMemFactor != STATE_NOTHING ? mState.mMemFactor : 0;
     }
 
-    static byte[] readFully(FileInputStream stream) throws java.io.IOException {
-        int pos = 0;
-        int avail = stream.available();
-        byte[] data = new byte[avail];
-        while (true) {
-            int amt = stream.read(data, pos, data.length-pos);
-            //Log.i("foo", "Read " + amt + " bytes at " + pos
-            //        + " of avail " + data.length);
-            if (amt <= 0) {
-                //Log.i("foo", "**** FINISHED READING: pos=" + pos
-                //        + " len=" + data.length);
-                return data;
-            }
-            pos += amt;
-            avail = stream.available();
-            if (avail > data.length-pos) {
-                byte[] newData = new byte[pos+avail];
-                System.arraycopy(data, 0, newData, 0, pos);
-                data = newData;
-            }
-        }
-    }
-
     public void readLocked() {
-        try {
-            FileInputStream stream = mFile.openRead();
-
-            byte[] raw = readFully(stream);
-            Parcel in = Parcel.obtain();
-            in.unmarshall(raw, 0, raw.length);
-            in.setDataPosition(0);
-            stream.close();
-
-            mState.readFromParcel(in);
-        } catch(Throwable e) {
-            Slog.e(TAG, "Error reading process statistics", e);
-        }
-
+        mState.readLocked();
     }
 
     public boolean shouldWriteNowLocked(long now) {
-        return now > (mLastWriteTime+WRITE_PERIOD);
+        if (now > (mState.mLastWriteTime+WRITE_PERIOD)) {
+            if (SystemClock.elapsedRealtime() > (mState.mTimePeriodStartRealtime+COMMIT_PERIOD)) {
+                mCommitPending = true;
+            }
+            return true;
+        }
+        return false;
     }
 
     public void shutdownLocked() {
@@ -1421,50 +1632,39 @@
         if (mShuttingDown) {
             return;
         }
-
-        synchronized (mPendingWriteLock) {
-            long now = SystemClock.uptimeMillis();
-            mPendingWrite = Parcel.obtain();
-            mState.mTimePeriodEnd = System.currentTimeMillis();
-            mState.writeToParcel(mPendingWrite);
-            mLastWriteTime = SystemClock.uptimeMillis();
-            Slog.i(TAG, "Prepared write state in " + (SystemClock.uptimeMillis()-now) + "ms");
-            if (!sync) {
-                BackgroundThread.getHandler().post(new Runnable() {
-                    @Override public void run() {
-                        commitWriteState();
-                    }
-                });
-                return;
-            }
-        }
-
-        commitWriteState();
+        boolean commitPending = mCommitPending;
+        mCommitPending = false;
+        mState.writeStateLocked(sync, commitPending);
     }
 
-    void commitWriteState() {
-        Parcel data;
-        synchronized (mPendingWriteLock) {
-            data = mPendingWrite;
-            if (data == null) {
-                return;
-            }
-            mPendingWrite = null;
-            mWriteLock.lock();
+    private ArrayList<String> getCommittedFiles(int minNum) {
+        File[] files = mBaseDir.listFiles();
+        if (files == null || files.length <= minNum) {
+            return null;
         }
+        ArrayList<String> filesArray = new ArrayList<String>(files.length);
+        String currentFile = mState.mFile.getBaseFile().toString();
+        if (DEBUG) Slog.d(TAG, "Collecting " + files.length + " files except: " + currentFile);
+        for (int i=0; i<files.length; i++) {
+            File file = files[i];
+            if (DEBUG) Slog.d(TAG, "Collecting: " + file);
+            if (!file.toString().equals(currentFile)) {
+                filesArray.add(files[i].toString());
+            }
+        }
+        Collections.sort(filesArray);
+        return filesArray;
+    }
 
-        FileOutputStream stream = null;
-        try {
-            stream = mFile.startWrite();
-            stream.write(data.marshall());
-            stream.flush();
-            mFile.finishWrite(stream);
-        } catch (IOException e) {
-            Slog.w(TAG, "Error writing process statistics", e);
-            mFile.failWrite(stream);
-        } finally {
-            data.recycle();
-            mWriteLock.unlock();
+    public void trimHistoricStatesWriteLocked() {
+        ArrayList<String> filesArray = getCommittedFiles(MAX_HISTORIC_STATES);
+        if (filesArray == null) {
+            return;
+        }
+        while (filesArray.size() > MAX_HISTORIC_STATES) {
+            String file = filesArray.remove(0);
+            Slog.i(TAG, "Pruning old procstats: " + file);
+            (new File(file)).delete();
         }
     }
 
@@ -1970,15 +2170,17 @@
 
     static private void dumpHelp(PrintWriter pw) {
         pw.println("Process stats (procstats) dump options:");
-        pw.println("    [--checkin|--csv] [csv-screen] [csv-proc] [csv-mem]");
-        pw.println("    [--reset] [--write] [-h] [<package.name>]");
-        pw.println("  --checkin: format output for a checkin report.");
+        pw.println("    [--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]");
+        pw.println("    [--include-committed] [--commit] [--write] [-h] [<package.name>]");
+        pw.println("  --checkin: perform a checkin: print and delete old committed states.");
+        pw.println("  --c: print only state in checkin format.");
         pw.println("  --csv: output data suitable for putting in a spreadsheet.");
         pw.println("  --csv-screen: on, off.");
         pw.println("  --csv-mem: norm, mod, low, crit.");
         pw.println("  --csv-proc: pers, top, fore, vis, precept, backup,");
         pw.println("    service, home, prev, cached");
-        pw.println("  --reset: reset the stats, clearing all current data.");
+        pw.println("  --include-committed: also dump any old committed states.");
+        pw.println("  --commit: commit current stats to disk and reset to start new stats.");
         pw.println("  --write: write current in-memory stats to disk.");
         pw.println("  --read: replace current stats with last-written stats.");
         pw.println("  -a: print everything.");
@@ -1990,7 +2192,9 @@
         final long now = SystemClock.uptimeMillis();
 
         boolean isCheckin = false;
+        boolean isCompact = false;
         boolean isCsv = false;
+        boolean includeCommitted = false;
         boolean dumpAll = false;
         String reqPackage = null;
         boolean csvSepScreenStats = false;
@@ -2007,6 +2211,8 @@
                 String arg = args[i];
                 if ("--checkin".equals(arg)) {
                     isCheckin = true;
+                } else if ("-c".equals(arg)) {
+                    isCompact = true;
                 } else if ("--csv".equals(arg)) {
                     isCsv = true;
                 } else if ("--csv-screen".equals(arg)) {
@@ -2058,9 +2264,11 @@
                         return;
                     }
                     csvSepProcStats = sep[0];
-                } else if ("--reset".equals(arg)) {
-                    mState.resetSafely();
-                    pw.println("Process stats reset.");
+                } else if ("--include-committed".equals(arg)) {
+                    includeCommitted = true;
+                } else if ("--commit".equals(arg)) {
+                    mState.writeStateLocked(true, true);
+                    pw.println("Process stats committed.");
                     return;
                 } else if ("--write".equals(arg)) {
                     writeStateSyncLocked();
@@ -2075,6 +2283,7 @@
                     return;
                 } else if ("-a".equals(arg)) {
                     dumpAll = true;
+                    includeCommitted = true;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
                     pw.println("Unknown option: " + arg);
                     dumpHelp(pw);
@@ -2085,6 +2294,9 @@
                         IPackageManager pm = AppGlobals.getPackageManager();
                         if (pm.getPackageUid(arg, UserHandle.getCallingUserId()) >= 0) {
                             reqPackage = arg;
+                            // We will automatically include all committed state,
+                            // since we are going to end up with much less printed.
+                            includeCommitted = true;
                         }
                     } catch (RemoteException e) {
                     }
@@ -2141,10 +2353,48 @@
             return;
         }
 
-        if (isCheckin) {
-            mState.dumpCheckinLocked(pw, reqPackage);
-        } else {
-            mState.dumpLocked(pw, reqPackage, dumpAll);
+        boolean sepNeeded = false;
+        if (includeCommitted || isCheckin) {
+            ArrayList<String> files = getCommittedFiles(0);
+            if (files != null) {
+                for (int i=0; i<files.size(); i++) {
+                    if (DEBUG) Slog.d(TAG, "Retrieving state: " + files.get(i));
+                    try {
+                        State state = new State(files.get(i));
+                        if (isCheckin || isCompact) {
+                            state.dumpCheckinLocked(pw, reqPackage);
+                        } else {
+                            if (sepNeeded) {
+                                pw.println();
+                            } else {
+                                sepNeeded = true;
+                            }
+                            pw.print("COMMITTED STATS FROM ");
+                            pw.print(state.mTimePeriodStartClockStr);
+                            pw.println(":");
+                            state.dumpLocked(pw, reqPackage, dumpAll);
+                        }
+                        if (isCheckin) {
+                            state.mFile.delete();
+                        }
+                    } catch (Throwable e) {
+                        pw.print("**** FAILURE DUMPING STATE: "); pw.println(files.get(i));
+                        e.printStackTrace(pw);
+                    }
+                    if (DEBUG) Slog.d(TAG, "Deleting state: " + files.get(i));
+                }
+            }
+        }
+        if (!isCheckin) {
+            if (isCompact) {
+                mState.dumpCheckinLocked(pw, reqPackage);
+            } else {
+                if (sepNeeded) {
+                    pw.println();
+                    pw.println("CURRENT STATS:");
+                }
+                mState.dumpLocked(pw, reqPackage, dumpAll);
+            }
         }
     }
 }