UsageStatsService.java revision 760ec4a095567457707abe764cf4dfda0ed84032
1/*
2 * Copyright (C) 2006-2007 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
17package com.android.server.am;
18
19import com.android.internal.app.IUsageStats;
20
21import android.content.ComponentName;
22import android.content.Context;
23import android.os.Binder;
24import android.os.IBinder;
25import com.android.internal.os.PkgUsageStats;
26import android.os.Parcel;
27import android.os.Process;
28import android.os.ServiceManager;
29import android.os.SystemClock;
30import android.util.Log;
31import java.io.File;
32import java.io.FileDescriptor;
33import java.io.FileInputStream;
34import java.io.FileNotFoundException;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.io.PrintWriter;
38import java.util.ArrayList;
39import java.util.Calendar;
40import java.util.Collections;
41import java.util.HashMap;
42import java.util.List;
43import java.util.Map;
44import java.util.Set;
45import java.util.TimeZone;
46
47/**
48 * This service collects the statistics associated with usage
49 * of various components, like when a particular package is launched or
50 * paused and aggregates events like number of time a component is launched
51 * total duration of a component launch.
52 */
53public final class UsageStatsService extends IUsageStats.Stub {
54    public static final String SERVICE_NAME = "usagestats";
55    private static final boolean localLOGV = false;
56    private static final String TAG = "UsageStats";
57
58    // Current on-disk Parcel version
59    private static final int VERSION = 1005;
60
61    private static final int CHECKIN_VERSION = 4;
62
63    private static final String FILE_PREFIX = "usage-";
64
65    private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms
66
67    private static final int MAX_NUM_FILES = 5;
68
69    private static final int NUM_LAUNCH_TIME_BINS = 10;
70    private static final int[] LAUNCH_TIME_BINS = {
71        250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000
72    };
73
74    static IUsageStats sService;
75    private Context mContext;
76    // structure used to maintain statistics since the last checkin.
77    final private Map<String, PkgUsageStatsExtended> mStats;
78    // Lock to update package stats. Methods suffixed by SLOCK should invoked with
79    // this lock held
80    final Object mStatsLock;
81    // Lock to write to file. Methods suffixed by FLOCK should invoked with
82    // this lock held.
83    final Object mFileLock;
84    // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks
85    private String mLastResumedPkg;
86    private String mLastResumedComp;
87    private boolean mIsResumed;
88    private File mFile;
89    private String mFileLeaf;
90    //private File mBackupFile;
91    private long mLastWriteElapsedTime;
92    private File mDir;
93    private Calendar mCal;
94    private int mLastWriteDay;
95
96    static class TimeStats {
97        int count;
98        int[] times = new int[NUM_LAUNCH_TIME_BINS];
99
100        TimeStats() {
101        }
102
103        void incCount() {
104            count++;
105        }
106
107        void add(int val) {
108            final int[] bins = LAUNCH_TIME_BINS;
109            for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
110                if (val < bins[i]) {
111                    times[i]++;
112                    return;
113                }
114            }
115            times[NUM_LAUNCH_TIME_BINS-1]++;
116        }
117
118        TimeStats(Parcel in) {
119            count = in.readInt();
120            final int[] localTimes = times;
121            for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
122                localTimes[i] = in.readInt();
123            }
124        }
125
126        void writeToParcel(Parcel out) {
127            out.writeInt(count);
128            final int[] localTimes = times;
129            for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
130                out.writeInt(localTimes[i]);
131            }
132        }
133    }
134
135    private class PkgUsageStatsExtended {
136        final HashMap<String, TimeStats> mLaunchTimes
137                = new HashMap<String, TimeStats>();
138        int mLaunchCount;
139        long mUsageTime;
140        long mPausedTime;
141        long mResumedTime;
142
143        PkgUsageStatsExtended() {
144            mLaunchCount = 0;
145            mUsageTime = 0;
146        }
147
148        PkgUsageStatsExtended(Parcel in) {
149            mLaunchCount = in.readInt();
150            mUsageTime = in.readLong();
151            if (localLOGV) Log.v(TAG, "Launch count: " + mLaunchCount
152                    + ", Usage time:" + mUsageTime);
153
154            final int N = in.readInt();
155            if (localLOGV) Log.v(TAG, "Reading comps: " + N);
156            for (int i=0; i<N; i++) {
157                String comp = in.readString();
158                if (localLOGV) Log.v(TAG, "Component: " + comp);
159                TimeStats times = new TimeStats(in);
160                mLaunchTimes.put(comp, times);
161            }
162        }
163
164        void updateResume(boolean launched) {
165            if (launched) {
166                mLaunchCount ++;
167            }
168            mResumedTime = SystemClock.elapsedRealtime();
169        }
170
171        void updatePause() {
172            mPausedTime =  SystemClock.elapsedRealtime();
173            mUsageTime += (mPausedTime - mResumedTime);
174        }
175
176        void addLaunchCount(String comp) {
177            TimeStats times = mLaunchTimes.get(comp);
178            if (times == null) {
179                times = new TimeStats();
180                mLaunchTimes.put(comp, times);
181            }
182            times.incCount();
183        }
184
185        void addLaunchTime(String comp, int millis) {
186            TimeStats times = mLaunchTimes.get(comp);
187            if (times == null) {
188                times = new TimeStats();
189                mLaunchTimes.put(comp, times);
190            }
191            times.add(millis);
192        }
193
194        void writeToParcel(Parcel out) {
195            out.writeInt(mLaunchCount);
196            out.writeLong(mUsageTime);
197            final int N = mLaunchTimes.size();
198            out.writeInt(N);
199            if (N > 0) {
200                for (Map.Entry<String, TimeStats> ent : mLaunchTimes.entrySet()) {
201                    out.writeString(ent.getKey());
202                    TimeStats times = ent.getValue();
203                    times.writeToParcel(out);
204                }
205            }
206        }
207
208        void clear() {
209            mLaunchTimes.clear();
210            mLaunchCount = 0;
211            mUsageTime = 0;
212        }
213    }
214
215    UsageStatsService(String dir) {
216        mStats = new HashMap<String, PkgUsageStatsExtended>();
217        mStatsLock = new Object();
218        mFileLock = new Object();
219        mDir = new File(dir);
220        mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
221
222        mDir.mkdir();
223
224        // Remove any old usage files from previous versions.
225        File parentDir = mDir.getParentFile();
226        String fList[] = parentDir.list();
227        if (fList != null) {
228            String prefix = mDir.getName() + ".";
229            int i = fList.length;
230            while (i > 0) {
231                i--;
232                if (fList[i].startsWith(prefix)) {
233                    Log.i(TAG, "Deleting old usage file: " + fList[i]);
234                    (new File(parentDir, fList[i])).delete();
235                }
236            }
237        }
238
239        // Update current stats which are binned by date
240        mFileLeaf = getCurrentDateStr(FILE_PREFIX);
241        mFile = new File(mDir, mFileLeaf);
242        readStatsFromFile();
243        mLastWriteElapsedTime = SystemClock.elapsedRealtime();
244        // mCal was set by getCurrentDateStr(), want to use that same time.
245        mLastWriteDay = mCal.get(Calendar.DAY_OF_YEAR);
246    }
247
248    /*
249     * Utility method to convert date into string.
250     */
251    private String getCurrentDateStr(String prefix) {
252        mCal.setTimeInMillis(System.currentTimeMillis());
253        StringBuilder sb = new StringBuilder();
254        if (prefix != null) {
255            sb.append(prefix);
256        }
257        sb.append(mCal.get(Calendar.YEAR));
258        int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1;
259        if (mm < 10) {
260            sb.append("0");
261        }
262        sb.append(mm);
263        int dd = mCal.get(Calendar.DAY_OF_MONTH);
264        if (dd < 10) {
265            sb.append("0");
266        }
267        sb.append(dd);
268        return sb.toString();
269    }
270
271    private Parcel getParcelForFile(File file) throws IOException {
272        FileInputStream stream = new FileInputStream(file);
273        byte[] raw = readFully(stream);
274        Parcel in = Parcel.obtain();
275        in.unmarshall(raw, 0, raw.length);
276        in.setDataPosition(0);
277        stream.close();
278        return in;
279    }
280
281    private void readStatsFromFile() {
282        File newFile = mFile;
283        synchronized (mFileLock) {
284            try {
285                if (newFile.exists()) {
286                    readStatsFLOCK(newFile);
287                } else {
288                    // Check for file limit before creating a new file
289                    checkFileLimitFLOCK();
290                    newFile.createNewFile();
291                }
292            } catch (IOException e) {
293                Log.w(TAG,"Error : " + e + " reading data from file:" + newFile);
294            }
295        }
296    }
297
298    private void readStatsFLOCK(File file) throws IOException {
299        Parcel in = getParcelForFile(file);
300        int vers = in.readInt();
301        if (vers != VERSION) {
302            Log.w(TAG, "Usage stats version changed; dropping");
303            return;
304        }
305        int N = in.readInt();
306        while (N > 0) {
307            N--;
308            String pkgName = in.readString();
309            if (pkgName == null) {
310                break;
311            }
312            if (localLOGV) Log.v(TAG, "Reading package #" + N + ": " + pkgName);
313            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
314            synchronized (mStatsLock) {
315                mStats.put(pkgName, pus);
316            }
317        }
318    }
319
320    private ArrayList<String> getUsageStatsFileListFLOCK() {
321        // Check if there are too many files in the system and delete older files
322        String fList[] = mDir.list();
323        if (fList == null) {
324            return null;
325        }
326        ArrayList<String> fileList = new ArrayList<String>();
327        for (String file : fList) {
328            if (!file.startsWith(FILE_PREFIX)) {
329                continue;
330            }
331            if (file.endsWith(".bak")) {
332                (new File(mDir, file)).delete();
333                continue;
334            }
335            fileList.add(file);
336        }
337        return fileList;
338    }
339
340    private void checkFileLimitFLOCK() {
341        // Get all usage stats output files
342        ArrayList<String> fileList = getUsageStatsFileListFLOCK();
343        if (fileList == null) {
344            // Strange but we dont have to delete any thing
345            return;
346        }
347        int count = fileList.size();
348        if (count <= MAX_NUM_FILES) {
349            return;
350        }
351        // Sort files
352        Collections.sort(fileList);
353        count -= MAX_NUM_FILES;
354        // Delete older files
355        for (int i = 0; i < count; i++) {
356            String fileName = fileList.get(i);
357            File file = new File(mDir, fileName);
358            Log.i(TAG, "Deleting usage file : " + fileName);
359            file.delete();
360        }
361    }
362
363    private void writeStatsToFile(boolean force) {
364        synchronized (mFileLock) {
365            mCal.setTimeInMillis(System.currentTimeMillis());
366            final int curDay = mCal.get(Calendar.DAY_OF_YEAR);
367            // Determine if the day changed...  note that this will be wrong
368            // if the year has changed but we are in the same day of year...
369            // we can probably live with this.
370            final boolean dayChanged =  curDay != mLastWriteDay;
371            long currElapsedTime = SystemClock.elapsedRealtime();
372            if (!force) {
373                if (((currElapsedTime-mLastWriteElapsedTime) < FILE_WRITE_INTERVAL) &&
374                        (!dayChanged)) {
375                    // wait till the next update
376                    return;
377                }
378            }
379            // Get the most recent file
380            mFileLeaf = getCurrentDateStr(FILE_PREFIX);
381            // Copy current file to back up
382            File backupFile =  new File(mFile.getPath() + ".bak");
383            mFile.renameTo(backupFile);
384            try {
385                // Write mStats to file
386                writeStatsFLOCK();
387                mLastWriteElapsedTime = currElapsedTime;
388                if (dayChanged) {
389                    mLastWriteDay = curDay;
390                    // clear stats
391                    synchronized (mStats) {
392                        mStats.clear();
393                    }
394                    mFile = new File(mDir, mFileLeaf);
395                    checkFileLimitFLOCK();
396                }
397                // Delete the backup file
398                if (backupFile != null) {
399                    backupFile.delete();
400                }
401            } catch (IOException e) {
402                Log.w(TAG, "Failed writing stats to file:" + mFile);
403                if (backupFile != null) {
404                    mFile.delete();
405                    backupFile.renameTo(mFile);
406                }
407            }
408        }
409    }
410
411    private void writeStatsFLOCK() throws IOException {
412        FileOutputStream stream = new FileOutputStream(mFile);
413        try {
414            Parcel out = Parcel.obtain();
415            writeStatsToParcelFLOCK(out);
416            stream.write(out.marshall());
417            out.recycle();
418            stream.flush();
419        } finally {
420            stream.close();
421        }
422    }
423
424    private void writeStatsToParcelFLOCK(Parcel out) {
425        synchronized (mStatsLock) {
426            out.writeInt(VERSION);
427            Set<String> keys = mStats.keySet();
428            out.writeInt(keys.size());
429            for (String key : keys) {
430                PkgUsageStatsExtended pus = mStats.get(key);
431                out.writeString(key);
432                pus.writeToParcel(out);
433            }
434        }
435    }
436
437    public void publish(Context context) {
438        mContext = context;
439        ServiceManager.addService(SERVICE_NAME, asBinder());
440    }
441
442    public void shutdown() {
443        Log.w(TAG, "Writing usage stats before shutdown...");
444        writeStatsToFile(true);
445    }
446
447    public static IUsageStats getService() {
448        if (sService != null) {
449            return sService;
450        }
451        IBinder b = ServiceManager.getService(SERVICE_NAME);
452        sService = asInterface(b);
453        return sService;
454    }
455
456    public void noteResumeComponent(ComponentName componentName) {
457        enforceCallingPermission();
458        String pkgName;
459        synchronized (mStatsLock) {
460            if ((componentName == null) ||
461                    ((pkgName = componentName.getPackageName()) == null)) {
462                return;
463            }
464
465            final boolean samePackage = pkgName.equals(mLastResumedPkg);
466            if (mIsResumed) {
467                if (samePackage) {
468                    Log.w(TAG, "Something wrong here, didn't expect "
469                            + pkgName + " to be resumed");
470                    return;
471                }
472
473                if (mLastResumedPkg != null) {
474                    // We last resumed some other package...  just pause it now
475                    // to recover.
476                    Log.w(TAG, "Unexpected resume of " + pkgName
477                            + " while already resumed in " + mLastResumedPkg);
478                    PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
479                    if (pus != null) {
480                        pus.updatePause();
481                    }
482                }
483            }
484
485            final boolean sameComp = samePackage
486                    && componentName.getClassName().equals(mLastResumedComp);
487
488            mIsResumed = true;
489            mLastResumedPkg = pkgName;
490            mLastResumedComp = componentName.getClassName();
491
492            if (localLOGV) Log.i(TAG, "started component:" + pkgName);
493            PkgUsageStatsExtended pus = mStats.get(pkgName);
494            if (pus == null) {
495                pus = new PkgUsageStatsExtended();
496                mStats.put(pkgName, pus);
497            }
498            pus.updateResume(!samePackage);
499            if (!sameComp) {
500                pus.addLaunchCount(mLastResumedComp);
501            }
502        }
503    }
504
505    public void notePauseComponent(ComponentName componentName) {
506        enforceCallingPermission();
507
508        synchronized (mStatsLock) {
509            String pkgName;
510            if ((componentName == null) ||
511                    ((pkgName = componentName.getPackageName()) == null)) {
512                return;
513            }
514            if (!mIsResumed) {
515                Log.w(TAG, "Something wrong here, didn't expect "
516                        + pkgName + " to be paused");
517                return;
518            }
519            mIsResumed = false;
520
521            if (localLOGV) Log.i(TAG, "paused component:"+pkgName);
522
523            PkgUsageStatsExtended pus = mStats.get(pkgName);
524            if (pus == null) {
525                // Weird some error here
526                Log.w(TAG, "No package stats for pkg:"+pkgName);
527                return;
528            }
529            pus.updatePause();
530        }
531
532        // Persist current data to file if needed.
533        writeStatsToFile(false);
534    }
535
536    public void noteLaunchTime(ComponentName componentName, int millis) {
537        enforceCallingPermission();
538        String pkgName;
539        if ((componentName == null) ||
540                ((pkgName = componentName.getPackageName()) == null)) {
541            return;
542        }
543
544        // Persist current data to file if needed.
545        writeStatsToFile(false);
546
547        synchronized (mStatsLock) {
548            PkgUsageStatsExtended pus = mStats.get(pkgName);
549            if (pus != null) {
550                pus.addLaunchTime(componentName.getClassName(), millis);
551            }
552        }
553    }
554
555    public void enforceCallingPermission() {
556        if (Binder.getCallingPid() == Process.myPid()) {
557            return;
558        }
559        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
560                Binder.getCallingPid(), Binder.getCallingUid(), null);
561    }
562
563    public PkgUsageStats getPkgUsageStats(ComponentName componentName) {
564        mContext.enforceCallingOrSelfPermission(
565                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
566        String pkgName;
567        if ((componentName == null) ||
568                ((pkgName = componentName.getPackageName()) == null)) {
569            return null;
570        }
571        synchronized (mStatsLock) {
572            PkgUsageStatsExtended pus = mStats.get(pkgName);
573            if (pus == null) {
574               return null;
575            }
576            return new PkgUsageStats(pkgName, pus.mLaunchCount, pus.mUsageTime);
577        }
578    }
579
580    public PkgUsageStats[] getAllPkgUsageStats() {
581        mContext.enforceCallingOrSelfPermission(
582                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
583        synchronized (mStatsLock) {
584            Set<String> keys = mStats.keySet();
585            int size = keys.size();
586            if (size <= 0) {
587                return null;
588            }
589            PkgUsageStats retArr[] = new PkgUsageStats[size];
590            int i = 0;
591            for (String key: keys) {
592                PkgUsageStatsExtended pus = mStats.get(key);
593                retArr[i] = new PkgUsageStats(key, pus.mLaunchCount, pus.mUsageTime);
594                i++;
595            }
596            return retArr;
597        }
598    }
599
600    static byte[] readFully(FileInputStream stream) throws java.io.IOException {
601        int pos = 0;
602        int avail = stream.available();
603        byte[] data = new byte[avail];
604        while (true) {
605            int amt = stream.read(data, pos, data.length-pos);
606            if (amt <= 0) {
607                return data;
608            }
609            pos += amt;
610            avail = stream.available();
611            if (avail > data.length-pos) {
612                byte[] newData = new byte[pos+avail];
613                System.arraycopy(data, 0, newData, 0, pos);
614                data = newData;
615            }
616        }
617    }
618
619    private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
620            boolean deleteAfterPrint) {
621        List<String> fileList = getUsageStatsFileListFLOCK();
622        if (fileList == null) {
623            return;
624        }
625        Collections.sort(fileList);
626        for (String file : fileList) {
627            if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) {
628                // In this mode we don't print the current day's stats, since
629                // they are incomplete.
630                continue;
631            }
632            File dFile = new File(mDir, file);
633            String dateStr = file.substring(FILE_PREFIX.length());
634            try {
635                Parcel in = getParcelForFile(dFile);
636                collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput);
637                if (deleteAfterPrint) {
638                    // Delete old file after collecting info only for checkin requests
639                    dFile.delete();
640                }
641            } catch (FileNotFoundException e) {
642                Log.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file);
643                return;
644            } catch (IOException e) {
645                Log.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file);
646            }
647        }
648    }
649
650    private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
651            String date, boolean isCompactOutput) {
652        StringBuilder sb = new StringBuilder(512);
653        if (isCompactOutput) {
654            sb.append("D:");
655            sb.append(CHECKIN_VERSION);
656            sb.append(',');
657        } else {
658            sb.append("Date: ");
659        }
660
661        sb.append(date);
662
663        int vers = in.readInt();
664        if (vers != VERSION) {
665            sb.append(" (old data version)");
666            pw.println(sb.toString());
667            return;
668        }
669
670        pw.println(sb.toString());
671        int N = in.readInt();
672
673        while (N > 0) {
674            N--;
675            String pkgName = in.readString();
676            if (pkgName == null) {
677                break;
678            }
679            sb.setLength(0);
680            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
681            if (isCompactOutput) {
682                sb.append("P:");
683                sb.append(pkgName);
684                sb.append(',');
685                sb.append(pus.mLaunchCount);
686                sb.append(',');
687                sb.append(pus.mUsageTime);
688                sb.append('\n');
689                final int NC = pus.mLaunchTimes.size();
690                if (NC > 0) {
691                    for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
692                        sb.append("A:");
693                        sb.append(ent.getKey());
694                        TimeStats times = ent.getValue();
695                        sb.append(',');
696                        sb.append(times.count);
697                        for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
698                            sb.append(",");
699                            sb.append(times.times[i]);
700                        }
701                        sb.append('\n');
702                    }
703                }
704
705            } else {
706                sb.append("  ");
707                sb.append(pkgName);
708                sb.append(": ");
709                sb.append(pus.mLaunchCount);
710                sb.append(" times, ");
711                sb.append(pus.mUsageTime);
712                sb.append(" ms");
713                sb.append('\n');
714                final int NC = pus.mLaunchTimes.size();
715                if (NC > 0) {
716                    for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) {
717                        sb.append("    ");
718                        sb.append(ent.getKey());
719                        TimeStats times = ent.getValue();
720                        sb.append(": ");
721                        sb.append(times.count);
722                        sb.append(" starts");
723                        int lastBin = 0;
724                        for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
725                            if (times.times[i] != 0) {
726                                sb.append(", ");
727                                sb.append(lastBin);
728                                sb.append('-');
729                                sb.append(LAUNCH_TIME_BINS[i]);
730                                sb.append("ms=");
731                                sb.append(times.times[i]);
732                            }
733                            lastBin = LAUNCH_TIME_BINS[i];
734                        }
735                        if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) {
736                            sb.append(", ");
737                            sb.append(">=");
738                            sb.append(lastBin);
739                            sb.append("ms=");
740                            sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]);
741                        }
742                        sb.append('\n');
743                    }
744                }
745            }
746
747            pw.write(sb.toString());
748        }
749    }
750
751    /**
752     * Searches array of arguments for the specified string
753     * @param args array of argument strings
754     * @param value value to search for
755     * @return true if the value is contained in the array
756     */
757    private static boolean scanArgs(String[] args, String value) {
758        if (args != null) {
759            for (String arg : args) {
760                if (value.equals(arg)) {
761                    return true;
762                }
763            }
764        }
765        return false;
766    }
767
768    @Override
769    /*
770     * The data persisted to file is parsed and the stats are computed.
771     */
772    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
773        final boolean isCheckinRequest = scanArgs(args, "--checkin");
774        final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
775        final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
776
777        // Make sure the current stats are written to the file.  This
778        // doesn't need to be done if we are deleting files after printing,
779        // since it that case we won't print the current stats.
780        if (!deleteAfterPrint) {
781            writeStatsToFile(true);
782        }
783
784        synchronized (mFileLock) {
785            collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint);
786        }
787    }
788
789}
790