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