1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.am;
18 
19 import android.content.pm.PackageManager;
20 import android.os.Binder;
21 import android.os.Parcel;
22 import android.os.ParcelFileDescriptor;
23 import android.os.RemoteException;
24 import android.os.SystemClock;
25 import android.os.SystemProperties;
26 import android.util.ArrayMap;
27 import android.util.AtomicFile;
28 import android.util.Slog;
29 import android.util.SparseArray;
30 import android.util.TimeUtils;
31 import com.android.internal.app.procstats.DumpUtils;
32 import com.android.internal.app.procstats.IProcessStats;
33 import com.android.internal.app.procstats.ProcessState;
34 import com.android.internal.app.procstats.ProcessStats;
35 import com.android.internal.app.procstats.ServiceState;
36 import com.android.internal.os.BackgroundThread;
37 
38 import java.io.File;
39 import java.io.FileDescriptor;
40 import java.io.FileInputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.concurrent.locks.ReentrantLock;
49 
50 public final class ProcessStatsService extends IProcessStats.Stub {
51     static final String TAG = "ProcessStatsService";
52     static final boolean DEBUG = false;
53 
54     // Most data is kept in a sparse data structure: an integer array which integer
55     // holds the type of the entry, and the identifier for a long array that data
56     // exists in and the offset into the array to find it.  The constants below
57     // define the encoding of that data in an integer.
58 
59     static final int MAX_HISTORIC_STATES = 8;   // Maximum number of historic states we will keep.
60     static final String STATE_FILE_PREFIX = "state-"; // Prefix to use for state filenames.
61     static final String STATE_FILE_SUFFIX = ".bin"; // Suffix to use for state filenames.
62     static final String STATE_FILE_CHECKIN_SUFFIX = ".ci"; // State files that have checked in.
63     static long WRITE_PERIOD = 30*60*1000;      // Write file every 30 minutes or so.
64 
65     final ActivityManagerService mAm;
66     final File mBaseDir;
67     ProcessStats mProcessStats;
68     AtomicFile mFile;
69     boolean mCommitPending;
70     boolean mShuttingDown;
71     int mLastMemOnlyState = -1;
72     boolean mMemFactorLowered;
73 
74     final ReentrantLock mWriteLock = new ReentrantLock();
75     final Object mPendingWriteLock = new Object();
76     AtomicFile mPendingWriteFile;
77     Parcel mPendingWrite;
78     boolean mPendingWriteCommitted;
79     long mLastWriteTime;
80 
ProcessStatsService(ActivityManagerService am, File file)81     public ProcessStatsService(ActivityManagerService am, File file) {
82         mAm = am;
83         mBaseDir = file;
84         mBaseDir.mkdirs();
85         mProcessStats = new ProcessStats(true);
86         updateFile();
87         SystemProperties.addChangeCallback(new Runnable() {
88             @Override public void run() {
89                 synchronized (mAm) {
90                     if (mProcessStats.evaluateSystemProperties(false)) {
91                         mProcessStats.mFlags |= ProcessStats.FLAG_SYSPROPS;
92                         writeStateLocked(true, true);
93                         mProcessStats.evaluateSystemProperties(true);
94                     }
95                 }
96             }
97         });
98     }
99 
100     @Override
onTransact(int code, Parcel data, Parcel reply, int flags)101     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
102             throws RemoteException {
103         try {
104             return super.onTransact(code, data, reply, flags);
105         } catch (RuntimeException e) {
106             if (!(e instanceof SecurityException)) {
107                 Slog.wtf(TAG, "Process Stats Crash", e);
108             }
109             throw e;
110         }
111     }
112 
getProcessStateLocked(String packageName, int uid, int versionCode, String processName)113     public ProcessState getProcessStateLocked(String packageName,
114             int uid, int versionCode, String processName) {
115         return mProcessStats.getProcessStateLocked(packageName, uid, versionCode, processName);
116     }
117 
getServiceStateLocked(String packageName, int uid, int versionCode, String processName, String className)118     public ServiceState getServiceStateLocked(String packageName, int uid,
119             int versionCode, String processName, String className) {
120         return mProcessStats.getServiceStateLocked(packageName, uid, versionCode, processName,
121                 className);
122     }
123 
isMemFactorLowered()124     public boolean isMemFactorLowered() {
125         return mMemFactorLowered;
126     }
127 
setMemFactorLocked(int memFactor, boolean screenOn, long now)128     public boolean setMemFactorLocked(int memFactor, boolean screenOn, long now) {
129         mMemFactorLowered = memFactor < mLastMemOnlyState;
130         mLastMemOnlyState = memFactor;
131         if (screenOn) {
132             memFactor += ProcessStats.ADJ_SCREEN_ON;
133         }
134         if (memFactor != mProcessStats.mMemFactor) {
135             if (mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING) {
136                 mProcessStats.mMemFactorDurations[mProcessStats.mMemFactor]
137                         += now - mProcessStats.mStartTime;
138             }
139             mProcessStats.mMemFactor = memFactor;
140             mProcessStats.mStartTime = now;
141             final ArrayMap<String, SparseArray<SparseArray<ProcessStats.PackageState>>> pmap
142                     = mProcessStats.mPackages.getMap();
143             for (int ipkg=pmap.size()-1; ipkg>=0; ipkg--) {
144                 final SparseArray<SparseArray<ProcessStats.PackageState>> uids = pmap.valueAt(ipkg);
145                 for (int iuid=uids.size()-1; iuid>=0; iuid--) {
146                     final SparseArray<ProcessStats.PackageState> vers = uids.valueAt(iuid);
147                     for (int iver=vers.size()-1; iver>=0; iver--) {
148                         final ProcessStats.PackageState pkg = vers.valueAt(iver);
149                         final ArrayMap<String, ServiceState> services = pkg.mServices;
150                         for (int isvc=services.size()-1; isvc>=0; isvc--) {
151                             final ServiceState service = services.valueAt(isvc);
152                             service.setMemFactor(memFactor, now);
153                         }
154                     }
155                 }
156             }
157             return true;
158         }
159         return false;
160     }
161 
getMemFactorLocked()162     public int getMemFactorLocked() {
163         return mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING ? mProcessStats.mMemFactor : 0;
164     }
165 
addSysMemUsageLocked(long cachedMem, long freeMem, long zramMem, long kernelMem, long nativeMem)166     public void addSysMemUsageLocked(long cachedMem, long freeMem, long zramMem, long kernelMem,
167             long nativeMem) {
168         mProcessStats.addSysMemUsage(cachedMem, freeMem, zramMem, kernelMem, nativeMem);
169     }
170 
shouldWriteNowLocked(long now)171     public boolean shouldWriteNowLocked(long now) {
172         if (now > (mLastWriteTime+WRITE_PERIOD)) {
173             if (SystemClock.elapsedRealtime()
174                     > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD) &&
175                     SystemClock.uptimeMillis()
176                     > (mProcessStats.mTimePeriodStartUptime+ProcessStats.COMMIT_UPTIME_PERIOD)) {
177                 mCommitPending = true;
178             }
179             return true;
180         }
181         return false;
182     }
183 
shutdownLocked()184     public void shutdownLocked() {
185         Slog.w(TAG, "Writing process stats before shutdown...");
186         mProcessStats.mFlags |= ProcessStats.FLAG_SHUTDOWN;
187         writeStateSyncLocked();
188         mShuttingDown = true;
189     }
190 
writeStateAsyncLocked()191     public void writeStateAsyncLocked() {
192         writeStateLocked(false);
193     }
194 
writeStateSyncLocked()195     public void writeStateSyncLocked() {
196         writeStateLocked(true);
197     }
198 
writeStateLocked(boolean sync)199     private void writeStateLocked(boolean sync) {
200         if (mShuttingDown) {
201             return;
202         }
203         boolean commitPending = mCommitPending;
204         mCommitPending = false;
205         writeStateLocked(sync, commitPending);
206     }
207 
writeStateLocked(boolean sync, final boolean commit)208     public void writeStateLocked(boolean sync, final boolean commit) {
209         synchronized (mPendingWriteLock) {
210             long now = SystemClock.uptimeMillis();
211             if (mPendingWrite == null || !mPendingWriteCommitted) {
212                 mPendingWrite = Parcel.obtain();
213                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
214                 mProcessStats.mTimePeriodEndUptime = now;
215                 if (commit) {
216                     mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
217                 }
218                 mProcessStats.writeToParcel(mPendingWrite, 0);
219                 mPendingWriteFile = new AtomicFile(mFile.getBaseFile());
220                 mPendingWriteCommitted = commit;
221             }
222             if (commit) {
223                 mProcessStats.resetSafely();
224                 updateFile();
225             }
226             mLastWriteTime = SystemClock.uptimeMillis();
227             Slog.i(TAG, "Prepared write state in " + (SystemClock.uptimeMillis()-now) + "ms");
228             if (!sync) {
229                 BackgroundThread.getHandler().post(new Runnable() {
230                     @Override public void run() {
231                         performWriteState();
232                     }
233                 });
234                 return;
235             }
236         }
237 
238         performWriteState();
239     }
240 
updateFile()241     private void updateFile() {
242         mFile = new AtomicFile(new File(mBaseDir, STATE_FILE_PREFIX
243                 + mProcessStats.mTimePeriodStartClockStr + STATE_FILE_SUFFIX));
244         mLastWriteTime = SystemClock.uptimeMillis();
245     }
246 
performWriteState()247     void performWriteState() {
248         if (DEBUG) Slog.d(TAG, "Performing write to " + mFile.getBaseFile());
249         Parcel data;
250         AtomicFile file;
251         synchronized (mPendingWriteLock) {
252             data = mPendingWrite;
253             file = mPendingWriteFile;
254             mPendingWriteCommitted = false;
255             if (data == null) {
256                 return;
257             }
258             mPendingWrite = null;
259             mPendingWriteFile = null;
260             mWriteLock.lock();
261         }
262 
263         FileOutputStream stream = null;
264         try {
265             stream = file.startWrite();
266             stream.write(data.marshall());
267             stream.flush();
268             file.finishWrite(stream);
269             if (DEBUG) Slog.d(TAG, "Write completed successfully!");
270         } catch (IOException e) {
271             Slog.w(TAG, "Error writing process statistics", e);
272             file.failWrite(stream);
273         } finally {
274             data.recycle();
275             trimHistoricStatesWriteLocked();
276             mWriteLock.unlock();
277         }
278     }
279 
readLocked(ProcessStats stats, AtomicFile file)280     boolean readLocked(ProcessStats stats, AtomicFile file) {
281         try {
282             FileInputStream stream = file.openRead();
283             stats.read(stream);
284             stream.close();
285             if (stats.mReadError != null) {
286                 Slog.w(TAG, "Ignoring existing stats; " + stats.mReadError);
287                 if (DEBUG) {
288                     ArrayMap<String, SparseArray<ProcessState>> procMap = stats.mProcesses.getMap();
289                     final int NPROC = procMap.size();
290                     for (int ip=0; ip<NPROC; ip++) {
291                         Slog.w(TAG, "Process: " + procMap.keyAt(ip));
292                         SparseArray<ProcessState> uids = procMap.valueAt(ip);
293                         final int NUID = uids.size();
294                         for (int iu=0; iu<NUID; iu++) {
295                             Slog.w(TAG, "  Uid " + uids.keyAt(iu) + ": " + uids.valueAt(iu));
296                         }
297                     }
298                     ArrayMap<String, SparseArray<SparseArray<ProcessStats.PackageState>>> pkgMap
299                             = stats.mPackages.getMap();
300                     final int NPKG = pkgMap.size();
301                     for (int ip=0; ip<NPKG; ip++) {
302                         Slog.w(TAG, "Package: " + pkgMap.keyAt(ip));
303                         SparseArray<SparseArray<ProcessStats.PackageState>> uids
304                                 = pkgMap.valueAt(ip);
305                         final int NUID = uids.size();
306                         for (int iu=0; iu<NUID; iu++) {
307                             Slog.w(TAG, "  Uid: " + uids.keyAt(iu));
308                             SparseArray<ProcessStats.PackageState> vers = uids.valueAt(iu);
309                             final int NVERS = vers.size();
310                             for (int iv=0; iv<NVERS; iv++) {
311                                 Slog.w(TAG, "    Vers: " + vers.keyAt(iv));
312                                 ProcessStats.PackageState pkgState = vers.valueAt(iv);
313                                 final int NPROCS = pkgState.mProcesses.size();
314                                 for (int iproc=0; iproc<NPROCS; iproc++) {
315                                     Slog.w(TAG, "      Process " + pkgState.mProcesses.keyAt(iproc)
316                                             + ": " + pkgState.mProcesses.valueAt(iproc));
317                                 }
318                                 final int NSRVS = pkgState.mServices.size();
319                                 for (int isvc=0; isvc<NSRVS; isvc++) {
320                                     Slog.w(TAG, "      Service " + pkgState.mServices.keyAt(isvc)
321                                             + ": " + pkgState.mServices.valueAt(isvc));
322 
323                                 }
324                             }
325                         }
326                     }
327                 }
328                 return false;
329             }
330         } catch (Throwable e) {
331             stats.mReadError = "caught exception: " + e;
332             Slog.e(TAG, "Error reading process statistics", e);
333             return false;
334         }
335         return true;
336     }
337 
getCommittedFiles(int minNum, boolean inclCurrent, boolean inclCheckedIn)338     private ArrayList<String> getCommittedFiles(int minNum, boolean inclCurrent,
339             boolean inclCheckedIn) {
340         File[] files = mBaseDir.listFiles();
341         if (files == null || files.length <= minNum) {
342             return null;
343         }
344         ArrayList<String> filesArray = new ArrayList<String>(files.length);
345         String currentFile = mFile.getBaseFile().getPath();
346         if (DEBUG) Slog.d(TAG, "Collecting " + files.length + " files except: " + currentFile);
347         for (int i=0; i<files.length; i++) {
348             File file = files[i];
349             String fileStr = file.getPath();
350             if (DEBUG) Slog.d(TAG, "Collecting: " + fileStr);
351             if (!inclCheckedIn && fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX)) {
352                 if (DEBUG) Slog.d(TAG, "Skipping: already checked in");
353                 continue;
354             }
355             if (!inclCurrent && fileStr.equals(currentFile)) {
356                 if (DEBUG) Slog.d(TAG, "Skipping: current stats");
357                 continue;
358             }
359             filesArray.add(fileStr);
360         }
361         Collections.sort(filesArray);
362         return filesArray;
363     }
364 
trimHistoricStatesWriteLocked()365     public void trimHistoricStatesWriteLocked() {
366         ArrayList<String> filesArray = getCommittedFiles(MAX_HISTORIC_STATES, false, true);
367         if (filesArray == null) {
368             return;
369         }
370         while (filesArray.size() > MAX_HISTORIC_STATES) {
371             String file = filesArray.remove(0);
372             Slog.i(TAG, "Pruning old procstats: " + file);
373             (new File(file)).delete();
374         }
375     }
376 
dumpFilteredProcessesCsvLocked(PrintWriter pw, String header, boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates, boolean sepProcStates, int[] procStates, long now, String reqPackage)377     boolean dumpFilteredProcessesCsvLocked(PrintWriter pw, String header,
378             boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates,
379             boolean sepProcStates, int[] procStates, long now, String reqPackage) {
380         ArrayList<ProcessState> procs = mProcessStats.collectProcessesLocked(
381                 screenStates, memStates, procStates, procStates, now, reqPackage, false);
382         if (procs.size() > 0) {
383             if (header != null) {
384                 pw.println(header);
385             }
386             DumpUtils.dumpProcessListCsv(pw, procs, sepScreenStates, screenStates,
387                     sepMemStates, memStates, sepProcStates, procStates, now);
388             return true;
389         }
390         return false;
391     }
392 
parseStateList(String[] states, int mult, String arg, boolean[] outSep, String[] outError)393     static int[] parseStateList(String[] states, int mult, String arg, boolean[] outSep,
394             String[] outError) {
395         ArrayList<Integer> res = new ArrayList<Integer>();
396         int lastPos = 0;
397         for (int i=0; i<=arg.length(); i++) {
398             char c = i < arg.length() ? arg.charAt(i) : 0;
399             if (c != ',' && c != '+' && c != ' ' && c != 0) {
400                 continue;
401             }
402             boolean isSep = c == ',';
403             if (lastPos == 0) {
404                 // We now know the type of op.
405                 outSep[0] = isSep;
406             } else if (c != 0 && outSep[0] != isSep) {
407                 outError[0] = "inconsistent separators (can't mix ',' with '+')";
408                 return null;
409             }
410             if (lastPos < (i-1)) {
411                 String str = arg.substring(lastPos, i);
412                 for (int j=0; j<states.length; j++) {
413                     if (str.equals(states[j])) {
414                         res.add(j);
415                         str = null;
416                         break;
417                     }
418                 }
419                 if (str != null) {
420                     outError[0] = "invalid word \"" + str + "\"";
421                     return null;
422                 }
423             }
424             lastPos = i + 1;
425         }
426 
427         int[] finalRes = new int[res.size()];
428         for (int i=0; i<res.size(); i++) {
429             finalRes[i] = res.get(i) * mult;
430         }
431         return finalRes;
432     }
433 
434     public byte[] getCurrentStats(List<ParcelFileDescriptor> historic) {
435         mAm.mContext.enforceCallingOrSelfPermission(
436                 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
437         Parcel current = Parcel.obtain();
438         synchronized (mAm) {
439             long now = SystemClock.uptimeMillis();
440             mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
441             mProcessStats.mTimePeriodEndUptime = now;
442             mProcessStats.writeToParcel(current, now, 0);
443         }
444         mWriteLock.lock();
445         try {
446             if (historic != null) {
447                 ArrayList<String> files = getCommittedFiles(0, false, true);
448                 if (files != null) {
449                     for (int i=files.size()-1; i>=0; i--) {
450                         try {
451                             ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
452                                     new File(files.get(i)), ParcelFileDescriptor.MODE_READ_ONLY);
453                             historic.add(pfd);
454                         } catch (IOException e) {
455                             Slog.w(TAG, "Failure opening procstat file " + files.get(i), e);
456                         }
457                     }
458                 }
459             }
460         } finally {
461             mWriteLock.unlock();
462         }
463         return current.marshall();
464     }
465 
466     public ParcelFileDescriptor getStatsOverTime(long minTime) {
467         mAm.mContext.enforceCallingOrSelfPermission(
468                 android.Manifest.permission.PACKAGE_USAGE_STATS, null);
469         Parcel current = Parcel.obtain();
470         long curTime;
471         synchronized (mAm) {
472             long now = SystemClock.uptimeMillis();
473             mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
474             mProcessStats.mTimePeriodEndUptime = now;
475             mProcessStats.writeToParcel(current, now, 0);
476             curTime = mProcessStats.mTimePeriodEndRealtime
477                     - mProcessStats.mTimePeriodStartRealtime;
478         }
479         mWriteLock.lock();
480         try {
481             if (curTime < minTime) {
482                 // Need to add in older stats to reach desired time.
483                 ArrayList<String> files = getCommittedFiles(0, false, true);
484                 if (files != null && files.size() > 0) {
485                     current.setDataPosition(0);
486                     ProcessStats stats = ProcessStats.CREATOR.createFromParcel(current);
487                     current.recycle();
488                     int i = files.size()-1;
489                     while (i >= 0 && (stats.mTimePeriodEndRealtime
490                             - stats.mTimePeriodStartRealtime) < minTime) {
491                         AtomicFile file = new AtomicFile(new File(files.get(i)));
492                         i--;
493                         ProcessStats moreStats = new ProcessStats(false);
494                         readLocked(moreStats, file);
495                         if (moreStats.mReadError == null) {
496                             stats.add(moreStats);
497                             StringBuilder sb = new StringBuilder();
498                             sb.append("Added stats: ");
499                             sb.append(moreStats.mTimePeriodStartClockStr);
500                             sb.append(", over ");
501                             TimeUtils.formatDuration(moreStats.mTimePeriodEndRealtime
502                                     - moreStats.mTimePeriodStartRealtime, sb);
503                             Slog.i(TAG, sb.toString());
504                         } else {
505                             Slog.w(TAG, "Failure reading " + files.get(i+1) + "; "
506                                     + moreStats.mReadError);
507                             continue;
508                         }
509                     }
510                     current = Parcel.obtain();
511                     stats.writeToParcel(current, 0);
512                 }
513             }
514             final byte[] outData = current.marshall();
515             current.recycle();
516             final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
517             Thread thr = new Thread("ProcessStats pipe output") {
518                 public void run() {
519                     FileOutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]);
520                     try {
521                         fout.write(outData);
522                         fout.close();
523                     } catch (IOException e) {
524                         Slog.w(TAG, "Failure writing pipe", e);
525                     }
526                 }
527             };
528             thr.start();
529             return fds[0];
530         } catch (IOException e) {
531             Slog.w(TAG, "Failed building output pipe", e);
532         } finally {
533             mWriteLock.unlock();
534         }
535         return null;
536     }
537 
538     public int getCurrentMemoryState() {
539         synchronized (mAm) {
540             return mLastMemOnlyState;
541         }
542     }
543 
544     private void dumpAggregatedStats(PrintWriter pw, long aggregateHours, long now,
545             String reqPackage, boolean isCompact, boolean dumpDetails, boolean dumpFullDetails,
546             boolean dumpAll, boolean activeOnly) {
547         ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000
548                 - (ProcessStats.COMMIT_PERIOD/2));
549         if (pfd == null) {
550             pw.println("Unable to build stats!");
551             return;
552         }
553         ProcessStats stats = new ProcessStats(false);
554         InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
555         stats.read(stream);
556         if (stats.mReadError != null) {
557             pw.print("Failure reading: "); pw.println(stats.mReadError);
558             return;
559         }
560         if (isCompact) {
561             stats.dumpCheckinLocked(pw, reqPackage);
562         } else {
563             if (dumpDetails || dumpFullDetails) {
564                 stats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, activeOnly);
565             } else {
566                 stats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
567             }
568         }
569     }
570 
571     static private void dumpHelp(PrintWriter pw) {
572         pw.println("Process stats (procstats) dump options:");
573         pw.println("    [--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]");
574         pw.println("    [--details] [--full-details] [--current] [--hours N] [--last N]");
575         pw.println("    [--max N] --active] [--commit] [--reset] [--clear] [--write] [-h]");
576         pw.println("    [--start-testing] [--stop-testing] [<package.name>]");
577         pw.println("  --checkin: perform a checkin: print and delete old committed states.");
578         pw.println("  -c: print only state in checkin format.");
579         pw.println("  --csv: output data suitable for putting in a spreadsheet.");
580         pw.println("  --csv-screen: on, off.");
581         pw.println("  --csv-mem: norm, mod, low, crit.");
582         pw.println("  --csv-proc: pers, top, fore, vis, precept, backup,");
583         pw.println("    service, home, prev, cached");
584         pw.println("  --details: dump per-package details, not just summary.");
585         pw.println("  --full-details: dump all timing and active state details.");
586         pw.println("  --current: only dump current state.");
587         pw.println("  --hours: aggregate over about N last hours.");
588         pw.println("  --last: only show the last committed stats at index N (starting at 1).");
589         pw.println("  --max: for -a, max num of historical batches to print.");
590         pw.println("  --active: only show currently active processes/services.");
591         pw.println("  --commit: commit current stats to disk and reset to start new stats.");
592         pw.println("  --reset: reset current stats, without committing.");
593         pw.println("  --clear: clear all stats; does both --reset and deletes old stats.");
594         pw.println("  --write: write current in-memory stats to disk.");
595         pw.println("  --read: replace current stats with last-written stats.");
596         pw.println("  --start-testing: clear all stats and starting high frequency pss sampling.");
597         pw.println("  --stop-testing: stop high frequency pss sampling.");
598         pw.println("  -a: print everything.");
599         pw.println("  -h: print this help text.");
600         pw.println("  <package.name>: optional name of package to filter output by.");
601     }
602 
603     @Override
604     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
605         if (mAm.checkCallingPermission(android.Manifest.permission.DUMP)
606                 != PackageManager.PERMISSION_GRANTED) {
607             pw.println("Permission Denial: can't dump procstats from from pid="
608                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
609                     + " without permission " + android.Manifest.permission.DUMP);
610             return;
611         }
612 
613         long ident = Binder.clearCallingIdentity();
614         try {
615             dumpInner(fd, pw, args);
616         } finally {
617             Binder.restoreCallingIdentity(ident);
618         }
619     }
620 
621     private void dumpInner(FileDescriptor fd, PrintWriter pw, String[] args) {
622         final long now = SystemClock.uptimeMillis();
623 
624         boolean isCheckin = false;
625         boolean isCompact = false;
626         boolean isCsv = false;
627         boolean currentOnly = false;
628         boolean dumpDetails = false;
629         boolean dumpFullDetails = false;
630         boolean dumpAll = false;
631         boolean quit = false;
632         int aggregateHours = 0;
633         int lastIndex = 0;
634         int maxNum = 2;
635         boolean activeOnly = false;
636         String reqPackage = null;
637         boolean csvSepScreenStats = false;
638         int[] csvScreenStats = new int[] { ProcessStats.ADJ_SCREEN_OFF, ProcessStats.ADJ_SCREEN_ON};
639         boolean csvSepMemStats = false;
640         int[] csvMemStats = new int[] { ProcessStats.ADJ_MEM_FACTOR_CRITICAL};
641         boolean csvSepProcStats = true;
642         int[] csvProcStats = ProcessStats.ALL_PROC_STATES;
643         if (args != null) {
644             for (int i=0; i<args.length; i++) {
645                 String arg = args[i];
646                 if ("--checkin".equals(arg)) {
647                     isCheckin = true;
648                 } else if ("-c".equals(arg)) {
649                     isCompact = true;
650                 } else if ("--csv".equals(arg)) {
651                     isCsv = true;
652                 } else if ("--csv-screen".equals(arg)) {
653                     i++;
654                     if (i >= args.length) {
655                         pw.println("Error: argument required for --csv-screen");
656                         dumpHelp(pw);
657                         return;
658                     }
659                     boolean[] sep = new boolean[1];
660                     String[] error = new String[1];
661                     csvScreenStats = parseStateList(DumpUtils.ADJ_SCREEN_NAMES_CSV,
662                             ProcessStats.ADJ_SCREEN_MOD, args[i], sep, error);
663                     if (csvScreenStats == null) {
664                         pw.println("Error in \"" + args[i] + "\": " + error[0]);
665                         dumpHelp(pw);
666                         return;
667                     }
668                     csvSepScreenStats = sep[0];
669                 } else if ("--csv-mem".equals(arg)) {
670                     i++;
671                     if (i >= args.length) {
672                         pw.println("Error: argument required for --csv-mem");
673                         dumpHelp(pw);
674                         return;
675                     }
676                     boolean[] sep = new boolean[1];
677                     String[] error = new String[1];
678                     csvMemStats = parseStateList(DumpUtils.ADJ_MEM_NAMES_CSV, 1, args[i],
679                             sep, error);
680                     if (csvMemStats == null) {
681                         pw.println("Error in \"" + args[i] + "\": " + error[0]);
682                         dumpHelp(pw);
683                         return;
684                     }
685                     csvSepMemStats = sep[0];
686                 } else if ("--csv-proc".equals(arg)) {
687                     i++;
688                     if (i >= args.length) {
689                         pw.println("Error: argument required for --csv-proc");
690                         dumpHelp(pw);
691                         return;
692                     }
693                     boolean[] sep = new boolean[1];
694                     String[] error = new String[1];
695                     csvProcStats = parseStateList(DumpUtils.STATE_NAMES_CSV, 1, args[i],
696                             sep, error);
697                     if (csvProcStats == null) {
698                         pw.println("Error in \"" + args[i] + "\": " + error[0]);
699                         dumpHelp(pw);
700                         return;
701                     }
702                     csvSepProcStats = sep[0];
703                 } else if ("--details".equals(arg)) {
704                     dumpDetails = true;
705                 } else if ("--full-details".equals(arg)) {
706                     dumpFullDetails = true;
707                 } else if ("--hours".equals(arg)) {
708                     i++;
709                     if (i >= args.length) {
710                         pw.println("Error: argument required for --hours");
711                         dumpHelp(pw);
712                         return;
713                     }
714                     try {
715                         aggregateHours = Integer.parseInt(args[i]);
716                     } catch (NumberFormatException e) {
717                         pw.println("Error: --hours argument not an int -- " + args[i]);
718                         dumpHelp(pw);
719                         return;
720                     }
721                 } else if ("--last".equals(arg)) {
722                     i++;
723                     if (i >= args.length) {
724                         pw.println("Error: argument required for --last");
725                         dumpHelp(pw);
726                         return;
727                     }
728                     try {
729                         lastIndex = Integer.parseInt(args[i]);
730                     } catch (NumberFormatException e) {
731                         pw.println("Error: --last argument not an int -- " + args[i]);
732                         dumpHelp(pw);
733                         return;
734                     }
735                 } else if ("--max".equals(arg)) {
736                     i++;
737                     if (i >= args.length) {
738                         pw.println("Error: argument required for --max");
739                         dumpHelp(pw);
740                         return;
741                     }
742                     try {
743                         maxNum = Integer.parseInt(args[i]);
744                     } catch (NumberFormatException e) {
745                         pw.println("Error: --max argument not an int -- " + args[i]);
746                         dumpHelp(pw);
747                         return;
748                     }
749                 } else if ("--active".equals(arg)) {
750                     activeOnly = true;
751                     currentOnly = true;
752                 } else if ("--current".equals(arg)) {
753                     currentOnly = true;
754                 } else if ("--commit".equals(arg)) {
755                     synchronized (mAm) {
756                         mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
757                         writeStateLocked(true, true);
758                         pw.println("Process stats committed.");
759                         quit = true;
760                     }
761                 } else if ("--reset".equals(arg)) {
762                     synchronized (mAm) {
763                         mProcessStats.resetSafely();
764                         pw.println("Process stats reset.");
765                         quit = true;
766                     }
767                 } else if ("--clear".equals(arg)) {
768                     synchronized (mAm) {
769                         mProcessStats.resetSafely();
770                         ArrayList<String> files = getCommittedFiles(0, true, true);
771                         if (files != null) {
772                             for (int fi=0; fi<files.size(); fi++) {
773                                 (new File(files.get(fi))).delete();
774                             }
775                         }
776                         pw.println("All process stats cleared.");
777                         quit = true;
778                     }
779                 } else if ("--write".equals(arg)) {
780                     synchronized (mAm) {
781                         writeStateSyncLocked();
782                         pw.println("Process stats written.");
783                         quit = true;
784                     }
785                 } else if ("--read".equals(arg)) {
786                     synchronized (mAm) {
787                         readLocked(mProcessStats, mFile);
788                         pw.println("Process stats read.");
789                         quit = true;
790                     }
791                 } else if ("--start-testing".equals(arg)) {
792                     synchronized (mAm) {
793                         mAm.setTestPssMode(true);
794                         pw.println("Started high frequency sampling.");
795                         quit = true;
796                     }
797                 } else if ("--stop-testing".equals(arg)) {
798                     synchronized (mAm) {
799                         mAm.setTestPssMode(false);
800                         pw.println("Stopped high frequency sampling.");
801                         quit = true;
802                     }
803                 } else if ("-h".equals(arg)) {
804                     dumpHelp(pw);
805                     return;
806                 } else if ("-a".equals(arg)) {
807                     dumpDetails = true;
808                     dumpAll = true;
809                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
810                     pw.println("Unknown option: " + arg);
811                     dumpHelp(pw);
812                     return;
813                 } else {
814                     // Not an option, last argument must be a package name.
815                     reqPackage = arg;
816                     // Include all details, since we know we are only going to
817                     // be dumping a smaller set of data.  In fact only the details
818                     // contain per-package data, so this is needed to be able
819                     // to dump anything at all when filtering by package.
820                     dumpDetails = true;
821                 }
822             }
823         }
824 
825         if (quit) {
826             return;
827         }
828 
829         if (isCsv) {
830             pw.print("Processes running summed over");
831             if (!csvSepScreenStats) {
832                 for (int i=0; i<csvScreenStats.length; i++) {
833                     pw.print(" ");
834                     DumpUtils.printScreenLabelCsv(pw, csvScreenStats[i]);
835                 }
836             }
837             if (!csvSepMemStats) {
838                 for (int i=0; i<csvMemStats.length; i++) {
839                     pw.print(" ");
840                     DumpUtils.printMemLabelCsv(pw, csvMemStats[i]);
841                 }
842             }
843             if (!csvSepProcStats) {
844                 for (int i=0; i<csvProcStats.length; i++) {
845                     pw.print(" ");
846                     pw.print(DumpUtils.STATE_NAMES_CSV[csvProcStats[i]]);
847                 }
848             }
849             pw.println();
850             synchronized (mAm) {
851                 dumpFilteredProcessesCsvLocked(pw, null,
852                         csvSepScreenStats, csvScreenStats, csvSepMemStats, csvMemStats,
853                         csvSepProcStats, csvProcStats, now, reqPackage);
854                 /*
855                 dumpFilteredProcessesCsvLocked(pw, "Processes running while critical mem:",
856                         false, new int[] {ADJ_SCREEN_OFF, ADJ_SCREEN_ON},
857                         true, new int[] {ADJ_MEM_FACTOR_CRITICAL},
858                         true, new int[] {STATE_PERSISTENT, STATE_TOP, STATE_FOREGROUND, STATE_VISIBLE,
859                                 STATE_PERCEPTIBLE, STATE_BACKUP, STATE_SERVICE, STATE_HOME,
860                                 STATE_PREVIOUS, STATE_CACHED},
861                         now, reqPackage);
862                 dumpFilteredProcessesCsvLocked(pw, "Processes running over all mem:",
863                         false, new int[] {ADJ_SCREEN_OFF, ADJ_SCREEN_ON},
864                         false, new int[] {ADJ_MEM_FACTOR_CRITICAL, ADJ_MEM_FACTOR_LOW,
865                                 ADJ_MEM_FACTOR_MODERATE, ADJ_MEM_FACTOR_MODERATE},
866                         true, new int[] {STATE_PERSISTENT, STATE_TOP, STATE_FOREGROUND, STATE_VISIBLE,
867                                 STATE_PERCEPTIBLE, STATE_BACKUP, STATE_SERVICE, STATE_HOME,
868                                 STATE_PREVIOUS, STATE_CACHED},
869                         now, reqPackage);
870                 */
871             }
872             return;
873         } else if (aggregateHours != 0) {
874             pw.print("AGGREGATED OVER LAST "); pw.print(aggregateHours); pw.println(" HOURS:");
875             dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact,
876                     dumpDetails, dumpFullDetails, dumpAll, activeOnly);
877             return;
878         } else if (lastIndex > 0) {
879             pw.print("LAST STATS AT INDEX "); pw.print(lastIndex); pw.println(":");
880             ArrayList<String> files = getCommittedFiles(0, false, true);
881             if (lastIndex >= files.size()) {
882                 pw.print("Only have "); pw.print(files.size()); pw.println(" data sets");
883                 return;
884             }
885             AtomicFile file = new AtomicFile(new File(files.get(lastIndex)));
886             ProcessStats processStats = new ProcessStats(false);
887             readLocked(processStats, file);
888             if (processStats.mReadError != null) {
889                 if (isCheckin || isCompact) pw.print("err,");
890                 pw.print("Failure reading "); pw.print(files.get(lastIndex));
891                 pw.print("; "); pw.println(processStats.mReadError);
892                 return;
893             }
894             String fileStr = file.getBaseFile().getPath();
895             boolean checkedIn = fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX);
896             if (isCheckin || isCompact) {
897                 // Don't really need to lock because we uniquely own this object.
898                 processStats.dumpCheckinLocked(pw, reqPackage);
899             } else {
900                 pw.print("COMMITTED STATS FROM ");
901                 pw.print(processStats.mTimePeriodStartClockStr);
902                 if (checkedIn) pw.print(" (checked in)");
903                 pw.println(":");
904                 if (dumpDetails || dumpFullDetails) {
905                     processStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
906                             activeOnly);
907                     if (dumpAll) {
908                         pw.print("  mFile="); pw.println(mFile.getBaseFile());
909                     }
910                 } else {
911                     processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
912                 }
913             }
914             return;
915         }
916 
917         boolean sepNeeded = false;
918         if (dumpAll || isCheckin) {
919             mWriteLock.lock();
920             try {
921                 ArrayList<String> files = getCommittedFiles(0, false, !isCheckin);
922                 if (files != null) {
923                     int start = isCheckin ? 0 : (files.size() - maxNum);
924                     if (start < 0) {
925                         start = 0;
926                     }
927                     for (int i=start; i<files.size(); i++) {
928                         if (DEBUG) Slog.d(TAG, "Retrieving state: " + files.get(i));
929                         try {
930                             AtomicFile file = new AtomicFile(new File(files.get(i)));
931                             ProcessStats processStats = new ProcessStats(false);
932                             readLocked(processStats, file);
933                             if (processStats.mReadError != null) {
934                                 if (isCheckin || isCompact) pw.print("err,");
935                                 pw.print("Failure reading "); pw.print(files.get(i));
936                                 pw.print("; "); pw.println(processStats.mReadError);
937                                 if (DEBUG) Slog.d(TAG, "Deleting state: " + files.get(i));
938                                 (new File(files.get(i))).delete();
939                                 continue;
940                             }
941                             String fileStr = file.getBaseFile().getPath();
942                             boolean checkedIn = fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX);
943                             if (isCheckin || isCompact) {
944                                 // Don't really need to lock because we uniquely own this object.
945                                 processStats.dumpCheckinLocked(pw, reqPackage);
946                             } else {
947                                 if (sepNeeded) {
948                                     pw.println();
949                                 } else {
950                                     sepNeeded = true;
951                                 }
952                                 pw.print("COMMITTED STATS FROM ");
953                                 pw.print(processStats.mTimePeriodStartClockStr);
954                                 if (checkedIn) pw.print(" (checked in)");
955                                 pw.println(":");
956                                 // Don't really need to lock because we uniquely own this object.
957                                 // Always dump summary here, dumping all details is just too
958                                 // much crud.
959                                 if (dumpFullDetails) {
960                                     processStats.dumpLocked(pw, reqPackage, now, false, false,
961                                             activeOnly);
962                                 } else {
963                                     processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
964                                 }
965                             }
966                             if (isCheckin) {
967                                 // Rename file suffix to mark that it has checked in.
968                                 file.getBaseFile().renameTo(new File(
969                                         fileStr + STATE_FILE_CHECKIN_SUFFIX));
970                             }
971                         } catch (Throwable e) {
972                             pw.print("**** FAILURE DUMPING STATE: "); pw.println(files.get(i));
973                             e.printStackTrace(pw);
974                         }
975                     }
976                 }
977             } finally {
978                 mWriteLock.unlock();
979             }
980         }
981         if (!isCheckin) {
982             synchronized (mAm) {
983                 if (isCompact) {
984                     mProcessStats.dumpCheckinLocked(pw, reqPackage);
985                 } else {
986                     if (sepNeeded) {
987                         pw.println();
988                     }
989                     pw.println("CURRENT STATS:");
990                     if (dumpDetails || dumpFullDetails) {
991                         mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
992                                 activeOnly);
993                         if (dumpAll) {
994                             pw.print("  mFile="); pw.println(mFile.getBaseFile());
995                         }
996                     } else {
997                         mProcessStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
998                     }
999                     sepNeeded = true;
1000                 }
1001             }
1002             if (!currentOnly) {
1003                 if (sepNeeded) {
1004                     pw.println();
1005                 }
1006                 pw.println("AGGREGATED OVER LAST 24 HOURS:");
1007                 dumpAggregatedStats(pw, 24, now, reqPackage, isCompact,
1008                         dumpDetails, dumpFullDetails, dumpAll, activeOnly);
1009                 pw.println();
1010                 pw.println("AGGREGATED OVER LAST 3 HOURS:");
1011                 dumpAggregatedStats(pw, 3, now, reqPackage, isCompact,
1012                         dumpDetails, dumpFullDetails, dumpAll, activeOnly);
1013             }
1014         }
1015     }
1016 }
1017