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