/* * Copyright (C) 2013 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.am; import android.app.AppGlobals; import android.content.pm.IPackageManager; import android.os.Parcel; 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; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.server.ProcessMap; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.concurrent.locks.ReentrantLock; public final class ProcessTracker { static final String TAG = "ProcessTracker"; static final boolean DEBUG = false; public static final int STATE_NOTHING = -1; public static final int STATE_PERSISTENT = 0; public static final int STATE_TOP = 1; public static final int STATE_FOREGROUND = 2; public static final int STATE_VISIBLE = 3; public static final int STATE_PERCEPTIBLE = 4; public static final int STATE_BACKUP = 5; public static final int STATE_SERVICE = 6; public static final int STATE_HOME = 7; public static final int STATE_PREVIOUS = 8; public static final int STATE_CACHED = 9; public static final int STATE_COUNT = STATE_CACHED+1; static final int[] ALL_PROC_STATES = new int[] { STATE_PERSISTENT, STATE_TOP, STATE_FOREGROUND, STATE_VISIBLE, STATE_PERCEPTIBLE, STATE_BACKUP, STATE_SERVICE, STATE_HOME, STATE_PREVIOUS, STATE_CACHED }; public static final int PSS_SAMPLE_COUNT = 0; public static final int PSS_MINIMUM = 1; public static final int PSS_AVERAGE = 2; public static final int PSS_MAXIMUM = 3; public static final int PSS_COUNT = PSS_MAXIMUM+1; public static final int ADJ_NOTHING = -1; public static final int ADJ_MEM_FACTOR_NORMAL = 0; public static final int ADJ_MEM_FACTOR_MODERATE = 1; public static final int ADJ_MEM_FACTOR_LOW = 2; public static final int ADJ_MEM_FACTOR_CRITICAL = 3; public static final int ADJ_MEM_FACTOR_COUNT = ADJ_MEM_FACTOR_CRITICAL+1; public static final int ADJ_SCREEN_MOD = ADJ_MEM_FACTOR_COUNT; public static final int ADJ_SCREEN_OFF = 0; public static final int ADJ_SCREEN_ON = ADJ_SCREEN_MOD; public static final int ADJ_COUNT = ADJ_SCREEN_ON*2; static final int[] ALL_SCREEN_ADJ = new int[] { ADJ_SCREEN_OFF, ADJ_SCREEN_ON }; static final int[] ALL_MEM_ADJ = new int[] { ADJ_MEM_FACTOR_NORMAL, ADJ_MEM_FACTOR_MODERATE, ADJ_MEM_FACTOR_LOW, ADJ_MEM_FACTOR_CRITICAL }; // Most data is kept in a sparse data structure: an integer array which integer // holds the type of the entry, and the identifier for a long array that data // exists in and the offset into the array to find it. The constants below // define the encoding of that data in an integer. // Where the "type"/"state" part of the data appears in an offset integer. static int OFFSET_TYPE_SHIFT = 0; static int OFFSET_TYPE_MASK = 0xff; // Where the "which array" part of the data appears in an offset integer. static int OFFSET_ARRAY_SHIFT = 8; static int OFFSET_ARRAY_MASK = 0xff; // Where the "index into array" part of the data appears in an offset integer. static int OFFSET_INDEX_SHIFT = 16; static int OFFSET_INDEX_MASK = 0xffff; static final String[] STATE_NAMES = new String[] { "Persistent ", "Top ", "Foreground ", "Visible ", "Perceptible", "Backup ", "Service ", "Home ", "Previous ", "Cached " }; static final String[] ADJ_SCREEN_NAMES_CSV = new String[] { "off", "on" }; static final String[] ADJ_MEM_NAMES_CSV = new String[] { "norm", "mod", "low", "crit" }; static final String[] STATE_NAMES_CSV = new String[] { "pers", "top", "fore", "vis", "percept", "backup", "service", "home", "prev", "cached" }; static final String[] ADJ_SCREEN_TAGS = new String[] { "0", "1" }; static final String[] ADJ_MEM_TAGS = new String[] { "n", "m", "l", "c" }; static final String[] STATE_TAGS = new String[] { "y", "t", "f", "v", "r", "b", "s", "h", "p", "c" }; 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; State mState; boolean mCommitPending; boolean mShuttingDown; 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; final int mUid; final String mName; int[] mDurationsTable; int mDurationsTableSize; //final long[] mDurations = new long[STATE_COUNT*ADJ_COUNT]; int mCurState = STATE_NOTHING; long mStartTime; int mLastPssState = STATE_NOTHING; long mLastPssTime; int[] mPssTable; int mPssTableSize; int mNumExcessiveWake; int mNumExcessiveCpu; boolean mMultiPackage; long mTmpTotalTime; /** * Create a new top-level process state, for the initial case where there is only * a single package running in a process. The initial state is not running. */ public ProcessState(State state, String pkg, int uid, String name) { mState = state; mCommonProcess = this; mPackage = pkg; mUid = uid; mName = name; } /** * Create a new per-package process state for an existing top-level process * state. The current running state of the top-level process is also copied, * marked as started running at 'now'. */ public ProcessState(ProcessState commonProcess, String pkg, int uid, String name, long now) { mState = commonProcess.mState; mCommonProcess = commonProcess; mPackage = pkg; mUid = uid; mName = name; mCurState = commonProcess.mCurState; mStartTime = now; } ProcessState clone(String pkg, long now) { ProcessState pnew = new ProcessState(this, pkg, mUid, mName, now); if (mDurationsTable != null) { mState.mAddLongTable = new int[mDurationsTable.length]; mState.mAddLongTableSize = 0; for (int i=0; i>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK; int newOff = mState.addLongData(i, type, 1); mState.mAddLongTable[i] = newOff | type; mState.setLong(newOff, 0, mState.getLong(origEnt, 0)); } pnew.mDurationsTable = mState.mAddLongTable; pnew.mDurationsTableSize = mState.mAddLongTableSize; } /* if (mPssTable != null) { mState.mAddLongTable = new int[mPssTable.length]; mState.mAddLongTableSize = 0; for (int i=0; i>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK; int newOff = mState.addLongData(i, type, PSS_COUNT); mState.mAddLongTable[i] = newOff | type; for (int j=0; j pkgList) { if (state != STATE_NOTHING) { state += memFactor*STATE_COUNT; } // First update the common process. mCommonProcess.setState(state, now); // If the common process is not multi-package, there is nothing else to do. if (!mCommonProcess.mMultiPackage) { return; } for (int ip=pkgList.size()-1; ip>=0; ip--) { pullFixedProc(pkgList, ip).setState(state, now); } } void setState(int state, long now) { if (mCurState != state) { commitStateTime(now); mCurState = state; } } void commitStateTime(long now) { if (mCurState != STATE_NOTHING) { long dur = now - mStartTime; 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; } } mStartTime = now; } public void addPss(long pss, boolean always) { if (!always) { if (mLastPssState == mCurState && SystemClock.uptimeMillis() < (mLastPssTime+(30*1000))) { return; } } mLastPssState = mCurState; mLastPssTime = SystemClock.uptimeMillis(); if (mCurState != STATE_NOTHING) { int idx = State.binarySearch(mPssTable, mPssTableSize, mCurState); int off; if (idx >= 0) { off = mPssTable[idx]; } else { mState.mAddLongTable = mPssTable; mState.mAddLongTableSize = mPssTableSize; off = mState.addLongData(~idx, mCurState, PSS_COUNT); mPssTable = mState.mAddLongTable; mPssTableSize = mState.mAddLongTableSize; } long[] longs = mState.mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK); idx = (off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK; long count = longs[idx+PSS_SAMPLE_COUNT]; if (count == 0) { longs[idx+PSS_SAMPLE_COUNT] = 1; longs[idx+PSS_MINIMUM] = pss; longs[idx+PSS_AVERAGE] = pss; longs[idx+PSS_MAXIMUM] = pss; } else { longs[idx+PSS_SAMPLE_COUNT] = count+1; if (longs[idx+PSS_MINIMUM] > pss) { longs[idx+PSS_MINIMUM] = pss; } longs[idx+PSS_AVERAGE] = (long)( ((longs[idx+PSS_AVERAGE]*(double)count)+pss) / (count+1) ); if (longs[idx+PSS_MAXIMUM] < pss) { longs[idx+PSS_MAXIMUM] = pss; } } } } public void reportExcessiveWake(ArrayMap pkgList) { mCommonProcess.mNumExcessiveWake++; if (!mCommonProcess.mMultiPackage) { return; } for (int ip=pkgList.size()-1; ip>=0; ip--) { pullFixedProc(pkgList, ip).mNumExcessiveWake++; } } public void reportExcessiveCpu(ArrayMap pkgList) { mCommonProcess.mNumExcessiveCpu++; if (!mCommonProcess.mMultiPackage) { return; } for (int ip=pkgList.size()-1; ip>=0; ip--) { pullFixedProc(pkgList, ip).mNumExcessiveCpu++; } } private ProcessState pullFixedProc(ArrayMap pkgList, int index) { ProcessState proc = pkgList.valueAt(index); if (proc.mMultiPackage) { // The array map is still pointing to a common process state // that is now shared across packages. Update it to point to // the new per-package state. proc = mState.mPackages.get(pkgList.keyAt(index), proc.mUid).mProcesses.get(proc.mName); if (proc == null) { throw new IllegalStateException("Didn't create per-package process"); } pkgList.setValueAt(index, proc); } return proc; } long getDuration(int state, long now) { int idx = State.binarySearch(mDurationsTable, mDurationsTableSize, state); long time = idx >= 0 ? mState.getLong(mDurationsTable[idx], 0) : 0; if (mCurState == state) { time += now - mStartTime; } return time; } long getPssSampleCount(int state) { int idx = State.binarySearch(mPssTable, mPssTableSize, state); return idx >= 0 ? mState.getLong(mPssTable[idx], PSS_SAMPLE_COUNT) : 0; } long getPssMinimum(int state) { int idx = State.binarySearch(mPssTable, mPssTableSize, state); return idx >= 0 ? mState.getLong(mPssTable[idx], PSS_MINIMUM) : 0; } long getPssAverage(int state) { int idx = State.binarySearch(mPssTable, mPssTableSize, state); return idx >= 0 ? mState.getLong(mPssTable[idx], PSS_AVERAGE) : 0; } long getPssMaximum(int state) { int idx = State.binarySearch(mPssTable, mPssTableSize, state); return idx >= 0 ? mState.getLong(mPssTable[idx], PSS_MAXIMUM) : 0; } } public static final class ServiceState { final long[] mStartedDurations = new long[ADJ_COUNT]; int mStartedCount; int mStartedState = STATE_NOTHING; long mStartedStartTime; final long[] mBoundDurations = new long[ADJ_COUNT]; int mBoundCount; int mBoundState = STATE_NOTHING; long mBoundStartTime; final long[] mExecDurations = new long[ADJ_COUNT]; int mExecCount; int mExecState = STATE_NOTHING; long mExecStartTime; void resetSafely(long now) { for (int i=0; i mProcesses = new ArrayMap(); final ArrayMap mServices = new ArrayMap(); final int mUid; public PackageState(int uid) { mUid = uid; } } static final class State { // Current version of the parcel format. 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; final File mBaseDir; final ProcessTracker mProcessTracker; AtomicFile mFile; String mReadError; long mTimePeriodStartClock; String mTimePeriodStartClockStr; long mTimePeriodStartRealtime; long mTimePeriodEndRealtime; boolean mRunning; final ProcessMap mPackages = new ProcessMap(); final ProcessMap mProcesses = new ProcessMap(); final long[] mMemFactorDurations = new long[ADJ_COUNT]; int mMemFactor = STATE_NOTHING; long mStartTime; static final int LONGS_SIZE = 4096; final ArrayList mLongs = new ArrayList(); int mNextLong; int[] mAddLongTable; int mAddLongTableSize; 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> procMap = mProcesses.getMap(); final int NPROC = procMap.size(); for (int ip=0; ip uids = procMap.valueAt(ip); final int NUID = uids.size(); for (int iu=0; iu> pkgMap = mPackages.getMap(); final int NPKG = pkgMap.size(); for (int ip=0; ip uids = pkgMap.valueAt(ip); final int NUID = uids.size(); for (int iu=0; iu> procMap = mProcesses.getMap(); final int NPROC = procMap.size(); for (int ip=0; ip uids = procMap.valueAt(ip); final int NUID = uids.size(); for (int iu=0; iu> pkgMap = mPackages.getMap(); final int NPKG = pkgMap.size(); for (int ip=0; ip uids = pkgMap.valueAt(ip); final int NUID = uids.size(); for (int iu=0; iu> procMap = mProcesses.getMap(); final int NPROC = procMap.size(); out.writeInt(NPROC); for (int ip=0; ip uids = procMap.valueAt(ip); final int NUID = uids.size(); out.writeInt(NUID); for (int iu=0; iu> pkgMap = mPackages.getMap(); final int NPKG = pkgMap.size(); out.writeInt(NPKG); for (int ip=0; ip uids = pkgMap.valueAt(ip); final int NUID = uids.size(); out.writeInt(NUID); for (int iu=0; iu 0 || mProcesses.getMap().size() > 0; if (hadData) { resetSafely(); } if (!readCheckedInt(in, MAGIC, "magic number")) { return; } if (!readCheckedInt(in, PARCEL_VERSION, "version")) { return; } if (!readCheckedInt(in, STATE_COUNT, "state count")) { return; } if (!readCheckedInt(in, ADJ_COUNT, "adj count")) { return; } if (!readCheckedInt(in, PSS_COUNT, "pss count")) { return; } if (!readCheckedInt(in, LONGS_SIZE, "longs size")) { return; } mTimePeriodStartClock = in.readLong(); buildTimePeriodStartClockStr(); mTimePeriodStartRealtime = in.readLong(); mTimePeriodEndRealtime = in.readLong(); 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]); } in.readLongArray(mLongs.get(i)); } long[] longs = new long[LONGS_SIZE]; mNextLong = NEXTLONG; for (int i=0; i 0) { NPROC--; String procName = in.readString(); if (procName == null) { mReadError = "bad process name"; return; } int NUID = in.readInt(); if (NUID < 0) { mReadError = "bad uid count: " + NUID; return; } while (NUID > 0) { NUID--; int uid = in.readInt(); if (uid < 0) { mReadError = "bad uid: " + uid; return; } String pkgName = in.readString(); if (pkgName == null) { mReadError = "bad process package name"; return; } ProcessState proc = hadData ? mProcesses.get(procName, uid) : null; if (proc != null) { if (!proc.readFromParcel(in, false)) { return; } } else { proc = new ProcessState(this, pkgName, uid, procName); if (!proc.readFromParcel(in, true)) { return; } } if (DEBUG) Slog.d(TAG, "Adding process: " + procName + " " + uid + " " + proc); mProcesses.put(procName, uid, proc); } } if (DEBUG) Slog.d(TAG, "Read " + mProcesses.getMap().size() + " processes"); int NPKG = in.readInt(); if (NPKG < 0) { mReadError = "bad package count: " + NPKG; return; } while (NPKG > 0) { NPKG--; String pkgName = in.readString(); if (pkgName == null) { mReadError = "bad package name"; return; } int NUID = in.readInt(); if (NUID < 0) { mReadError = "bad uid count: " + NUID; return; } while (NUID > 0) { NUID--; int uid = in.readInt(); if (uid < 0) { mReadError = "bad uid: " + uid; return; } PackageState pkgState = new PackageState(uid); mPackages.put(pkgName, uid, pkgState); int NPROCS = in.readInt(); if (NPROCS < 0) { mReadError = "bad package process count: " + NPROCS; return; } while (NPROCS > 0) { NPROCS--; String procName = in.readString(); if (procName == null) { 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 proc = hadData ? pkgState.mProcesses.get(procName) : null; if (proc != null) { if (!proc.readFromParcel(in, false)) { return; } } else { proc = new ProcessState(commonProc, pkgName, uid, procName, 0); if (!proc.readFromParcel(in, true)) { return; } } 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) { mReadError = "bad package service count: " + NSRVS; return; } while (NSRVS > 0) { NSRVS--; String serviceName = in.readString(); if (serviceName == null) { mReadError = "bad package service name"; return; } ServiceState serv = hadData ? pkgState.mServices.get(serviceName) : null; if (serv == null) { serv = new ServiceState(); } if (!serv.readFromParcel(in)) { return; } if (DEBUG) Slog.d(TAG, "Adding package " + pkgName + " service: " + serviceName + " " + uid + " " + serv); pkgState.mServices.put(serviceName, serv); } } } if (DEBUG) Slog.d(TAG, "Successfully read procstats!"); } int addLongData(int index, int type, int num) { int tableLen = mAddLongTable != null ? mAddLongTable.length : 0; if (mAddLongTableSize >= tableLen) { int newSize = ArrayUtils.idealIntArraySize(tableLen + 1); int[] newTable = new int[newSize]; if (tableLen > 0) { System.arraycopy(mAddLongTable, 0, newTable, 0, tableLen); } mAddLongTable = newTable; } if (mAddLongTableSize > 0 && mAddLongTableSize - index != 0) { System.arraycopy(mAddLongTable, index, mAddLongTable, index + 1, mAddLongTableSize - index); } int off = allocLongData(num); mAddLongTable[index] = type | off; mAddLongTableSize++; return off; } int allocLongData(int num) { int whichLongs = mLongs.size()-1; long[] longs = mLongs.get(whichLongs); if (mNextLong + num > longs.length) { longs = new long[LONGS_SIZE]; mLongs.add(longs); whichLongs++; mNextLong = 0; } int off = (whichLongs<>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK; if (arr >= mLongs.size()) { return false; } int idx = (off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK; 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; } long getLong(int off, int index) { long[] longs = mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK); return longs[index + ((off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK)]; } static int binarySearch(int[] array, int size, int value) { int lo = 0; int hi = size - 1; while (lo <= hi) { int mid = (lo + hi) >>> 1; int midVal = (array[mid] >> OFFSET_TYPE_SHIFT) & OFFSET_TYPE_MASK; if (midVal < value) { lo = mid + 1; } else if (midVal > value) { hi = mid - 1; } else { return mid; // value found } } return ~lo; // value not present } PackageState getPackageStateLocked(String packageName, int uid) { PackageState as = mPackages.get(packageName, uid); if (as != null) { return as; } as = new PackageState(uid); mPackages.put(packageName, uid, as); return as; } ProcessState getProcessStateLocked(String packageName, int uid, String processName) { final PackageState pkgState = getPackageStateLocked(packageName, uid); ProcessState ps = pkgState.mProcesses.get(processName); if (ps != null) { return ps; } ProcessState commonProc = mProcesses.get(processName, uid); if (commonProc == null) { commonProc = new ProcessState(this, packageName, uid, processName); mProcesses.put(processName, uid, commonProc); } if (!commonProc.mMultiPackage) { if (packageName.equals(commonProc.mPackage)) { // This common process is not in use by multiple packages, and // is for the calling package, so we can just use it directly. ps = commonProc; } else { // This common process has not been in use by multiple packages, // but it was created for a different package than the caller. // We need to convert it to a multi-package process. commonProc.mMultiPackage = true; // The original package it was created for now needs to point // to its own copy. long now = SystemClock.uptimeMillis(); pkgState.mProcesses.put(commonProc.mName, commonProc.clone( commonProc.mPackage, now)); ps = new ProcessState(commonProc, packageName, uid, processName, now); } } else { // The common process is for multiple packages, we need to create a // separate object for the per-package data. ps = new ProcessState(commonProc, packageName, uid, processName, SystemClock.uptimeMillis()); } pkgState.mProcesses.put(processName, ps); return ps; } void dumpLocked(PrintWriter pw, String reqPackage, boolean dumpAll) { final long now = SystemClock.uptimeMillis(); ArrayMap> pkgMap = mPackages.getMap(); boolean printedHeader = false; for (int ip=0; ip uids = pkgMap.valueAt(ip); for (int iu=0; iu 0 || NSRVS > 0) { if (!printedHeader) { pw.println("Per-Package Process Stats:"); printedHeader = true; } pw.print(" * "); pw.print(pkgName); pw.print(" / "); UserHandle.formatUid(pw, uid); pw.println(":"); } for (int iproc=0; iproc= PKGLEN) { if (itemName.charAt(PKGLEN) == '.') { return itemName.substring(PKGLEN); } } } return itemName; } void dumpCheckinLocked(PrintWriter pw, String reqPackage) { final long now = SystemClock.uptimeMillis(); ArrayMap> 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 uids = pkgMap.valueAt(ip); for (int iu=0; iu 0) { pw.print("pkgpss,"); pw.print(pkgName); pw.print(","); pw.print(uid); pw.print(","); pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); dumpAllProcessPssCheckin(pw, proc); pw.println(); } if (proc.mNumExcessiveWake > 0 || proc.mNumExcessiveCpu > 0) { pw.print("pkgkills,"); pw.print(pkgName); pw.print(","); pw.print(uid); pw.print(","); pw.print(collapseString(pkgName, pkgState.mProcesses.keyAt(iproc))); pw.print(","); pw.print(proc.mNumExcessiveWake); pw.print(","); pw.print(proc.mNumExcessiveCpu); pw.println(); } } for (int isvc=0; isvc> procMap = mProcesses.getMap(); for (int ip=0; ip uids = procMap.valueAt(ip); for (int iu=0; iu 0) { pw.print("proc,"); pw.print(procName); pw.print(","); pw.print(uid); dumpAllProcessStateCheckin(pw, procState, now); pw.println(); } if (procState.mPssTableSize > 0) { pw.print("pss,"); pw.print(procName); pw.print(","); pw.print(uid); dumpAllProcessPssCheckin(pw, procState); pw.println(); } if (procState.mNumExcessiveWake > 0 || procState.mNumExcessiveCpu > 0) { pw.print("kills,"); pw.print(procName); pw.print(","); pw.print(uid); pw.print(","); pw.print(procState.mNumExcessiveWake); pw.print(","); pw.print(procState.mNumExcessiveCpu); pw.println(); } } } pw.print("total"); dumpAdjTimesCheckin(pw, ",", mMemFactorDurations, mMemFactor, mStartTime, now); pw.println(); } } public ProcessTracker(File file) { mBaseDir = file; mBaseDir.mkdirs(); mState = new State(mBaseDir, this); mState.mRunning = true; } public ProcessState getProcessStateLocked(String packageName, int uid, String processName) { return mState.getProcessStateLocked(packageName, uid, processName); } public ServiceState getServiceStateLocked(String packageName, int uid, String className) { final PackageState as = mState.getPackageStateLocked(packageName, uid); ServiceState ss = as.mServices.get(className); if (ss != null) { return ss; } ss = new ServiceState(); as.mServices.put(className, ss); return ss; } public boolean setMemFactorLocked(int memFactor, boolean screenOn, long now) { if (screenOn) { memFactor += ADJ_SCREEN_ON; } if (memFactor != mState.mMemFactor) { if (mState.mMemFactor != STATE_NOTHING) { mState.mMemFactorDurations[mState.mMemFactor] += now - mState.mStartTime; } mState.mMemFactor = memFactor; mState.mStartTime = now; ArrayMap> pmap = mState.mPackages.getMap(); for (int i=0; i uids = pmap.valueAt(i); for (int j=0; j services = pkg.mServices; for (int k=0; k (mState.mLastWriteTime+WRITE_PERIOD)) { if (SystemClock.elapsedRealtime() > (mState.mTimePeriodStartRealtime+COMMIT_PERIOD)) { mCommitPending = true; } return true; } return false; } public void shutdownLocked() { Slog.w(TAG, "Writing process stats before shutdown..."); writeStateSyncLocked(); mShuttingDown = true; } public void writeStateAsyncLocked() { writeStateLocked(false); } public void writeStateSyncLocked() { writeStateLocked(true); } private void writeStateLocked(boolean sync) { if (mShuttingDown) { return; } boolean commitPending = mCommitPending; mCommitPending = false; mState.writeStateLocked(sync, commitPending); } private ArrayList getCommittedFiles(int minNum) { File[] files = mBaseDir.listFiles(); if (files == null || files.length <= minNum) { return null; } ArrayList filesArray = new ArrayList(files.length); String currentFile = mState.mFile.getBaseFile().toString(); if (DEBUG) Slog.d(TAG, "Collecting " + files.length + " files except: " + currentFile); for (int i=0; i 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(); } } static private void printScreenLabel(PrintWriter pw, int offset) { switch (offset) { case ADJ_NOTHING: pw.print(" "); break; case ADJ_SCREEN_OFF: pw.print("Screen Off / "); break; case ADJ_SCREEN_ON: pw.print("Screen On / "); break; default: pw.print("?????????? / "); break; } } static private void printScreenLabelCsv(PrintWriter pw, int offset) { switch (offset) { case ADJ_NOTHING: break; case ADJ_SCREEN_OFF: pw.print(ADJ_SCREEN_NAMES_CSV[0]); break; case ADJ_SCREEN_ON: pw.print(ADJ_SCREEN_NAMES_CSV[1]); break; default: pw.print("???"); break; } } static private void printMemLabel(PrintWriter pw, int offset) { switch (offset) { case ADJ_NOTHING: pw.print(" "); break; case ADJ_MEM_FACTOR_NORMAL: pw.print("Norm / "); break; case ADJ_MEM_FACTOR_MODERATE: pw.print("Mod / "); break; case ADJ_MEM_FACTOR_LOW: pw.print("Low / "); break; case ADJ_MEM_FACTOR_CRITICAL: pw.print("Crit / "); break; default: pw.print("???? / "); break; } } static private void printMemLabelCsv(PrintWriter pw, int offset) { if (offset >= ADJ_MEM_FACTOR_NORMAL) { if (offset <= ADJ_MEM_FACTOR_CRITICAL) { pw.print(ADJ_MEM_NAMES_CSV[offset]); } else { pw.print("???"); } } } static void dumpSingleTime(PrintWriter pw, String prefix, long[] durations, int curState, long curStartTime, long now) { long totalTime = 0; int printedScreen = -1; for (int iscreen=0; iscreen 1) { printScreenLabel(pw, printedScreen != iscreen ? iscreen : STATE_NOTHING); printedScreen = iscreen; } if (memStates.length > 1) { printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING); printedMem = imem; } pw.print(STATE_NAMES[procStates[ip]]); pw.print(": "); TimeUtils.formatDuration(time, pw); pw.println(running); totalTime += time; } } } } if (totalTime != 0) { pw.print(prefix); if (screenStates.length > 1) { printScreenLabel(pw, STATE_NOTHING); } if (memStates.length > 1) { printMemLabel(pw, STATE_NOTHING); } pw.print("TOTAL : "); TimeUtils.formatDuration(totalTime, pw); pw.println(); } } static void dumpProcessPss(PrintWriter pw, String prefix, ProcessState proc, int[] screenStates, int[] memStates, int[] procStates) { boolean printedHeader = false; int printedScreen = -1; for (int is=0; is 0) { if (!printedHeader) { pw.print(prefix); pw.print("PSS ("); pw.print(proc.mPssTableSize); pw.println(" entries):"); printedHeader = true; } pw.print(prefix); pw.print(" "); if (screenStates.length > 1) { printScreenLabel(pw, printedScreen != iscreen ? iscreen : STATE_NOTHING); printedScreen = iscreen; } if (memStates.length > 1) { printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING); printedMem = imem; } pw.print(STATE_NAMES[procStates[ip]]); pw.print(": "); pw.print(count); pw.print(" samples "); pw.print(proc.getPssMinimum(bucket)); pw.print("kB "); pw.print(proc.getPssAverage(bucket)); pw.print("kB "); pw.print(proc.getPssMaximum(bucket)); pw.println("kB"); } } } } if (proc.mNumExcessiveWake != 0) { pw.print(prefix); pw.print("Killed for excessive wake locks: "); pw.print(proc.mNumExcessiveWake); pw.println(" times"); } if (proc.mNumExcessiveCpu != 0) { pw.print(prefix); pw.print("Killed for excessive CPU use: "); pw.print(proc.mNumExcessiveCpu); pw.println(" times"); } } static void dumpStateHeadersCsv(PrintWriter pw, String sep, int[] screenStates, int[] memStates, int[] procStates) { final int NS = screenStates != null ? screenStates.length : 1; final int NM = memStates != null ? memStates.length : 1; final int NP = procStates != null ? procStates.length : 1; for (int is=0; is 1) { printScreenLabelCsv(pw, screenStates[is]); printed = true; } if (memStates != null && memStates.length > 1) { if (printed) { pw.print("-"); } printMemLabelCsv(pw, memStates[im]); printed = true; } if (procStates != null && procStates.length > 1) { if (printed) { pw.print("-"); } pw.print(STATE_NAMES_CSV[procStates[ip]]); } } } } } static void dumpProcessStateCsv(PrintWriter pw, ProcessState proc, boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates, boolean sepProcStates, int[] procStates, long now) { final int NSS = sepScreenStates ? screenStates.length : 1; final int NMS = sepMemStates ? memStates.length : 1; final int NPS = sepProcStates ? procStates.length : 1; for (int iss=0; iss procs, int[] screenStates, int[] memStates, int[] procStates, long now) { String innerPrefix = prefix + " "; for (int i=procs.size()-1; i>=0; i--) { ProcessState proc = procs.get(i); pw.print(prefix); pw.print(proc.mName); pw.print(" / "); UserHandle.formatUid(pw, proc.mUid); pw.print(" ("); pw.print(proc.mDurationsTableSize); pw.print(" entries)"); pw.println(":"); dumpProcessState(pw, innerPrefix, proc, screenStates, memStates, procStates, now); if (proc.mPssTableSize > 0) { dumpProcessPss(pw, innerPrefix, proc, screenStates, memStates, procStates); } } } static void dumpProcessListCsv(PrintWriter pw, ArrayList procs, boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates, boolean sepProcStates, int[] procStates, long now) { pw.print("process"); pw.print(CSV_SEP); pw.print("uid"); dumpStateHeadersCsv(pw, CSV_SEP, sepScreenStates ? screenStates : null, sepMemStates ? memStates : null, sepProcStates ? procStates : null); pw.println(); for (int i=procs.size()-1; i>=0; i--) { ProcessState proc = procs.get(i); pw.print(proc.mName); pw.print(CSV_SEP); UserHandle.formatUid(pw, proc.mUid); dumpProcessStateCsv(pw, proc, sepScreenStates, screenStates, sepMemStates, memStates, sepProcStates, procStates, now); pw.println(); } } boolean dumpFilteredProcessesCsvLocked(PrintWriter pw, String header, boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates, boolean sepProcStates, int[] procStates, long now, String reqPackage) { ArrayList procs = mState.collectProcessesLocked(screenStates, memStates, procStates, now, reqPackage); if (procs.size() > 0) { if (header != null) { pw.println(header); } dumpProcessListCsv(pw, procs, sepScreenStates, screenStates, sepMemStates, memStates, sepProcStates, procStates, now); return true; } return false; } static int printArrayEntry(PrintWriter pw, String[] array, int value, int mod) { int index = value/mod; if (index >= 0 && index < array.length) { pw.print(array[index]); } else { pw.print('?'); } return value - index*mod; } static void printProcStateTag(PrintWriter pw, int state) { state = printArrayEntry(pw, ADJ_SCREEN_TAGS, state, ADJ_SCREEN_MOD*STATE_COUNT); state = printArrayEntry(pw, ADJ_MEM_TAGS, state, STATE_COUNT); printArrayEntry(pw, STATE_TAGS, state, 1); } static void printAdjTag(PrintWriter pw, int state) { state = printArrayEntry(pw, ADJ_SCREEN_TAGS, state, ADJ_SCREEN_MOD); printArrayEntry(pw, ADJ_MEM_TAGS, state, 1); } static void printProcStateTagAndValue(PrintWriter pw, int state, long value) { pw.print(','); printProcStateTag(pw, state); pw.print(':'); pw.print(value); } static void printAdjTagAndValue(PrintWriter pw, int state, long value) { pw.print(','); printAdjTag(pw, state); pw.print(':'); pw.print(value); } static void dumpAllProcessStateCheckin(PrintWriter pw, ProcessState proc, long now) { boolean didCurState = false; for (int i=0; i>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK; long time = proc.mState.getLong(off, 0); if (proc.mCurState == type) { didCurState = true; time += now - proc.mStartTime; } printProcStateTagAndValue(pw, type, time); } if (!didCurState && proc.mCurState != STATE_NOTHING) { printProcStateTagAndValue(pw, proc.mCurState, now - proc.mStartTime); } } static void dumpAllProcessPssCheckin(PrintWriter pw, ProcessState proc) { for (int i=0; i>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK; long count = proc.mState.getLong(off, PSS_SAMPLE_COUNT); long min = proc.mState.getLong(off, PSS_MINIMUM); long avg = proc.mState.getLong(off, PSS_AVERAGE); long max = proc.mState.getLong(off, PSS_MAXIMUM); pw.print(','); printProcStateTag(pw, type); pw.print(':'); pw.print(count); pw.print(':'); pw.print(min); pw.print(':'); pw.print(avg); pw.print(':'); pw.print(max); } } static int[] parseStateList(String[] states, int mult, String arg, boolean[] outSep, String[] outError) { ArrayList res = new ArrayList(); int lastPos = 0; for (int i=0; i<=arg.length(); i++) { char c = i < arg.length() ? arg.charAt(i) : 0; if (c != ',' && c != '+' && c != ' ' && c != 0) { continue; } boolean isSep = c == ','; if (lastPos == 0) { // We now know the type of op. outSep[0] = isSep; } else if (c != 0 && outSep[0] != isSep) { outError[0] = "inconsistent separators (can't mix ',' with '+')"; return null; } if (lastPos < (i-1)) { String str = arg.substring(lastPos, i); for (int j=0; j]"); 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(" --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."); pw.println(" -h: print this help text."); pw.println(" : optional name of package to filter output by."); } public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) { 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; int[] csvScreenStats = new int[] {ADJ_SCREEN_OFF, ADJ_SCREEN_ON}; boolean csvSepMemStats = false; int[] csvMemStats = new int[] {ADJ_MEM_FACTOR_CRITICAL}; boolean csvSepProcStats = true; int[] csvProcStats = new int[] { STATE_PERSISTENT, STATE_TOP, STATE_FOREGROUND, STATE_VISIBLE, STATE_PERCEPTIBLE, STATE_BACKUP, STATE_SERVICE, STATE_HOME, STATE_PREVIOUS, STATE_CACHED }; if (args != null) { for (int i=0; i= args.length) { pw.println("Error: argument required for --csv-screen"); dumpHelp(pw); return; } boolean[] sep = new boolean[1]; String[] error = new String[1]; csvScreenStats = parseStateList(ADJ_SCREEN_NAMES_CSV, ADJ_SCREEN_MOD, args[i], sep, error); if (csvScreenStats == null) { pw.println("Error in \"" + args[i] + "\": " + error[0]); dumpHelp(pw); return; } csvSepScreenStats = sep[0]; } else if ("--csv-mem".equals(arg)) { i++; if (i >= args.length) { pw.println("Error: argument required for --csv-mem"); dumpHelp(pw); return; } boolean[] sep = new boolean[1]; String[] error = new String[1]; csvMemStats = parseStateList(ADJ_MEM_NAMES_CSV, 1, args[i], sep, error); if (csvMemStats == null) { pw.println("Error in \"" + args[i] + "\": " + error[0]); dumpHelp(pw); return; } csvSepMemStats = sep[0]; } else if ("--csv-proc".equals(arg)) { i++; if (i >= args.length) { pw.println("Error: argument required for --csv-proc"); dumpHelp(pw); return; } boolean[] sep = new boolean[1]; String[] error = new String[1]; csvProcStats = parseStateList(STATE_NAMES_CSV, 1, args[i], sep, error); if (csvProcStats == null) { pw.println("Error in \"" + args[i] + "\": " + error[0]); dumpHelp(pw); return; } csvSepProcStats = sep[0]; } 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(); pw.println("Process stats written."); return; } else if ("--read".equals(arg)) { readLocked(); pw.println("Process stats read."); return; } else if ("-h".equals(arg)) { dumpHelp(pw); 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); return; } else { // Not an option, last argument must be a package name. try { 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) { } if (reqPackage == null) { pw.println("Unknown package: " + arg); dumpHelp(pw); return; } } } } if (isCsv) { pw.print("Processes running summed over"); if (!csvSepScreenStats) { for (int i=0; i files = getCommittedFiles(0); if (files != null) { for (int i=0; i