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