1 /* 2 * Copyright (C) 2018 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.internal.app.procstats; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.os.SystemClock; 24 import android.os.UserHandle; 25 import android.service.procstats.PackageAssociationProcessStatsProto; 26 import android.service.procstats.PackageAssociationSourceProcessStatsProto; 27 import android.util.ArrayMap; 28 import android.util.Pair; 29 import android.util.Slog; 30 import android.util.TimeUtils; 31 import android.util.proto.ProtoOutputStream; 32 33 import java.io.PrintWriter; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.Objects; 38 39 public final class AssociationState { 40 private static final String TAG = "ProcessStats"; 41 private static final boolean DEBUG = false; 42 43 private static final boolean VALIDATE_TIMES = false; 44 45 private final ProcessStats mProcessStats; 46 private final ProcessStats.PackageState mPackageState; 47 private final String mProcessName; 48 private final String mName; 49 50 private int mTotalNesting; 51 private long mTotalStartUptime; 52 private int mTotalCount; 53 private long mTotalDuration; 54 private int mTotalActiveNesting; 55 private long mTotalActiveStartUptime; 56 private int mTotalActiveCount; 57 private long mTotalActiveDuration; 58 59 /** 60 * The state of the source process of an association. 61 */ 62 @SuppressWarnings("ParcelableCreator") 63 public static final class SourceState implements Parcelable { 64 private @NonNull final ProcessStats mProcessStats; 65 private @Nullable final AssociationState mAssociationState; 66 private @Nullable final ProcessState mTargetProcess; 67 private @Nullable SourceState mCommonSourceState; 68 final SourceKey mKey; 69 int mProcStateSeq = -1; 70 int mProcState = ProcessStats.STATE_NOTHING; 71 boolean mInTrackingList; 72 int mNesting; 73 int mCount; 74 long mStartUptime; 75 long mDuration; 76 long mTrackingUptime; 77 int mActiveNesting; 78 int mActiveCount; 79 int mActiveProcState = ProcessStats.STATE_NOTHING; 80 long mActiveStartUptime; 81 long mActiveDuration; 82 DurationsTable mActiveDurations; 83 SourceState(@onNull ProcessStats processStats, @Nullable AssociationState associationState, @NonNull ProcessState targetProcess, SourceKey key)84 SourceState(@NonNull ProcessStats processStats, @Nullable AssociationState associationState, 85 @NonNull ProcessState targetProcess, SourceKey key) { 86 mProcessStats = processStats; 87 mAssociationState = associationState; 88 mTargetProcess = targetProcess; 89 mKey = key; 90 } 91 92 @Nullable getAssociationState()93 public AssociationState getAssociationState() { 94 return mAssociationState; 95 } 96 getProcessName()97 public String getProcessName() { 98 return mKey.mProcess; 99 } 100 getUid()101 public int getUid() { 102 return mKey.mUid; 103 } 104 105 @Nullable getCommonSourceState(boolean createIfNeeded)106 private SourceState getCommonSourceState(boolean createIfNeeded) { 107 if (mCommonSourceState == null && createIfNeeded) { 108 mCommonSourceState = mTargetProcess.getOrCreateSourceState(mKey); 109 } 110 return mCommonSourceState; 111 } 112 trackProcState(int procState, int seq, long now)113 public void trackProcState(int procState, int seq, long now) { 114 final int processState = procState; 115 procState = ProcessState.PROCESS_STATE_TO_STATE[procState]; 116 if (seq != mProcStateSeq) { 117 mProcStateSeq = seq; 118 mProcState = procState; 119 } else if (procState < mProcState) { 120 mProcState = procState; 121 } 122 if (procState < ProcessStats.STATE_HOME) { 123 // If the proc state has become better than cached, then we want to 124 // start tracking it to count when it is actually active. If it drops 125 // down to cached, we will clean it up when we later evaluate all currently 126 // tracked associations in ProcessStats.updateTrackingAssociationsLocked(). 127 if (!mInTrackingList) { 128 mInTrackingList = true; 129 mTrackingUptime = now; 130 if (mAssociationState != null) { 131 mProcessStats.mTrackingAssociations.add(this); 132 } 133 } 134 } 135 if (mAssociationState != null) { 136 final SourceState commonSource = getCommonSourceState(true); 137 if (commonSource != null) { 138 commonSource.trackProcState(processState, seq, now); 139 } 140 } 141 } 142 start()143 long start() { 144 final long now = start(-1); 145 if (mAssociationState != null) { 146 final SourceState commonSource = getCommonSourceState(true); 147 if (commonSource != null) { 148 commonSource.start(now); 149 } 150 } 151 return now; 152 } 153 start(long now)154 long start(long now) { 155 mNesting++; 156 if (mNesting == 1) { 157 if (now < 0) { 158 now = SystemClock.uptimeMillis(); 159 } 160 mCount++; 161 mStartUptime = now; 162 } 163 return now; 164 } 165 stop()166 public void stop() { 167 final long now = stop(-1); 168 if (mAssociationState != null) { 169 final SourceState commonSource = getCommonSourceState(false); 170 if (commonSource != null) { 171 commonSource.stop(now); 172 } 173 } 174 } 175 stop(long now)176 long stop(long now) { 177 mNesting--; 178 if (mNesting == 0) { 179 if (now < 0) { 180 now = SystemClock.uptimeMillis(); 181 } 182 mDuration += now - mStartUptime; 183 stopTracking(now); 184 } 185 return now; 186 } 187 startActive(long now)188 void startActive(long now) { 189 boolean startActive = false; 190 if (mInTrackingList) { 191 if (mActiveStartUptime == 0) { 192 mActiveStartUptime = now; 193 mActiveNesting++; 194 mActiveCount++; 195 startActive = true; 196 if (mAssociationState != null) { 197 mAssociationState.mTotalActiveNesting++; 198 if (mAssociationState.mTotalActiveNesting == 1) { 199 mAssociationState.mTotalActiveCount++; 200 mAssociationState.mTotalActiveStartUptime = now; 201 } 202 } 203 } else if (mAssociationState == null) { 204 mActiveNesting++; 205 } 206 if (mActiveProcState != mProcState) { 207 if (mActiveProcState != ProcessStats.STATE_NOTHING) { 208 // Currently active proc state changed, need to store the duration 209 // so far and switch tracking to the new proc state. 210 final long addedDuration = mActiveDuration + now - mActiveStartUptime; 211 mActiveStartUptime = now; 212 if (mAssociationState != null) { 213 startActive = true; 214 } 215 if (addedDuration != 0) { 216 if (mActiveDurations == null) { 217 makeDurations(); 218 } 219 mActiveDurations.addDuration(mActiveProcState, addedDuration); 220 mActiveDuration = 0; 221 } 222 } 223 mActiveProcState = mProcState; 224 } 225 } else if (mAssociationState != null) { 226 Slog.wtf(TAG, "startActive while not tracking: " + this); 227 } 228 if (mAssociationState != null) { 229 final SourceState commonSource = getCommonSourceState(true); 230 if (commonSource != null && startActive) { 231 commonSource.startActive(now); 232 } 233 } 234 } 235 stopActive(long now)236 void stopActive(long now) { 237 boolean stopActive = false; 238 if (mActiveStartUptime != 0) { 239 if (!mInTrackingList && mAssociationState != null) { 240 Slog.wtf(TAG, "stopActive while not tracking: " + this); 241 } 242 mActiveNesting--; 243 final long addedDuration = now - mActiveStartUptime; 244 mActiveStartUptime = mAssociationState != null || mActiveNesting == 0 ? 0 : now; 245 stopActive = mActiveStartUptime == 0; 246 if (mActiveDurations != null) { 247 mActiveDurations.addDuration(mActiveProcState, addedDuration); 248 } else { 249 mActiveDuration += addedDuration; 250 } 251 if (mAssociationState != null) { 252 mAssociationState.mTotalActiveNesting--; 253 if (mAssociationState.mTotalActiveNesting == 0) { 254 mAssociationState.mTotalActiveDuration += now 255 - mAssociationState.mTotalActiveStartUptime; 256 mAssociationState.mTotalActiveStartUptime = 0; 257 if (VALIDATE_TIMES) { 258 if (mActiveDuration > mAssociationState.mTotalActiveDuration) { 259 RuntimeException ex = new RuntimeException(); 260 Slog.w(TAG, "Source act duration " + mActiveDurations 261 + " exceeds total " + mAssociationState.mTotalActiveDuration 262 + " in procstate " + mActiveProcState + " in source " 263 + mKey.mProcess + " to assoc " 264 + mAssociationState.mName, ex); 265 } 266 267 } 268 } 269 } 270 } 271 272 if (mAssociationState != null) { 273 final SourceState commonSource = getCommonSourceState(false); 274 if (commonSource != null && stopActive) { 275 commonSource.stopActive(now); 276 } 277 } 278 } 279 stopActiveIfNecessary(int curSeq, long now)280 boolean stopActiveIfNecessary(int curSeq, long now) { 281 if (mProcStateSeq != curSeq || mProcState >= ProcessStats.STATE_HOME) { 282 // If this association did not get touched the last time we computed 283 // process states, or its state ended up down in cached, then we no 284 // longer have a reason to track it at all. 285 stopActive(now); 286 stopTrackingProcState(); 287 return true; 288 } 289 return false; 290 } 291 stopTrackingProcState()292 private void stopTrackingProcState() { 293 mInTrackingList = false; 294 mProcState = ProcessStats.STATE_NOTHING; 295 if (mAssociationState != null) { 296 final SourceState commonSource = getCommonSourceState(false); 297 if (commonSource != null) { 298 commonSource.stopTrackingProcState(); 299 } 300 } 301 } 302 isInUse()303 boolean isInUse() { 304 return mNesting > 0; 305 } 306 resetSafely(long now)307 void resetSafely(long now) { 308 if (isInUse()) { 309 mCount = 1; 310 mStartUptime = now; 311 mDuration = 0; 312 if (mActiveStartUptime > 0) { 313 mActiveCount = 1; 314 mActiveStartUptime = now; 315 } else { 316 mActiveCount = 0; 317 } 318 mActiveDuration = 0; 319 mActiveDurations = null; 320 } 321 // We're actually resetting the common sources in process state already, 322 // resetting it here too in case they're out of sync. 323 if (mAssociationState != null) { 324 final SourceState commonSource = getCommonSourceState(false); 325 if (commonSource != null) { 326 commonSource.resetSafely(now); 327 mCommonSourceState = null; 328 } 329 } 330 } 331 commitStateTime(long nowUptime)332 void commitStateTime(long nowUptime) { 333 if (mNesting > 0) { 334 mDuration += nowUptime - mStartUptime; 335 mStartUptime = nowUptime; 336 } 337 if (mActiveStartUptime > 0) { 338 final long addedDuration = nowUptime - mActiveStartUptime; 339 mActiveStartUptime = nowUptime; 340 if (mActiveDurations != null) { 341 mActiveDurations.addDuration(mActiveProcState, addedDuration); 342 } else { 343 mActiveDuration += addedDuration; 344 } 345 } 346 } 347 makeDurations()348 void makeDurations() { 349 mActiveDurations = new DurationsTable(mProcessStats.mTableData); 350 } 351 stopTracking(long now)352 private void stopTracking(long now) { 353 if (mAssociationState != null) { 354 mAssociationState.mTotalNesting--; 355 if (mAssociationState.mTotalNesting == 0) { 356 mAssociationState.mTotalDuration += now 357 - mAssociationState.mTotalStartUptime; 358 } 359 } 360 stopActive(now); 361 if (mInTrackingList) { 362 mInTrackingList = false; 363 mProcState = ProcessStats.STATE_NOTHING; 364 if (mAssociationState != null) { 365 // Do a manual search for where to remove, since these objects will typically 366 // be towards the end of the array. 367 final ArrayList<SourceState> list = mProcessStats.mTrackingAssociations; 368 for (int i = list.size() - 1; i >= 0; i--) { 369 if (list.get(i) == this) { 370 list.remove(i); 371 return; 372 } 373 } 374 Slog.wtf(TAG, "Stop tracking didn't find in tracking list: " + this); 375 } 376 } 377 } 378 add(SourceState otherSrc)379 void add(SourceState otherSrc) { 380 mCount += otherSrc.mCount; 381 mDuration += otherSrc.mDuration; 382 mActiveCount += otherSrc.mActiveCount; 383 if (otherSrc.mActiveDuration != 0 || otherSrc.mActiveDurations != null) { 384 // Only need to do anything if the other one has some duration data. 385 if (mActiveDurations != null) { 386 // If the target already has multiple durations, just add in whatever 387 // we have in the other. 388 if (otherSrc.mActiveDurations != null) { 389 mActiveDurations.addDurations(otherSrc.mActiveDurations); 390 } else { 391 mActiveDurations.addDuration(otherSrc.mActiveProcState, 392 otherSrc.mActiveDuration); 393 } 394 } else if (otherSrc.mActiveDurations != null) { 395 // The other one has multiple durations, but we don't. Expand to 396 // multiple durations and copy over. 397 makeDurations(); 398 mActiveDurations.addDurations(otherSrc.mActiveDurations); 399 if (mActiveDuration != 0) { 400 mActiveDurations.addDuration(mActiveProcState, mActiveDuration); 401 mActiveDuration = 0; 402 mActiveProcState = ProcessStats.STATE_NOTHING; 403 } 404 } else if (mActiveDuration != 0) { 405 // Both have a single inline duration... we can either add them together, 406 // or need to expand to multiple durations. 407 if (mActiveProcState == otherSrc.mActiveProcState) { 408 mActiveDuration += otherSrc.mActiveDuration; 409 } else { 410 // The two have durations with different proc states, need to turn 411 // in to multiple durations. 412 makeDurations(); 413 mActiveDurations.addDuration(mActiveProcState, mActiveDuration); 414 mActiveDurations.addDuration(otherSrc.mActiveProcState, 415 otherSrc.mActiveDuration); 416 mActiveDuration = 0; 417 mActiveProcState = ProcessStats.STATE_NOTHING; 418 } 419 } else { 420 // The other one has a duration, and we know the target doesn't. Copy over. 421 mActiveProcState = otherSrc.mActiveProcState; 422 mActiveDuration = otherSrc.mActiveDuration; 423 } 424 } 425 } 426 427 @Override writeToParcel(Parcel out, int flags)428 public void writeToParcel(Parcel out, int flags) { 429 out.writeInt(mCount); 430 out.writeLong(mDuration); 431 out.writeInt(mActiveCount); 432 if (mActiveDurations != null) { 433 out.writeInt(1); 434 mActiveDurations.writeToParcel(out); 435 } else { 436 out.writeInt(0); 437 out.writeInt(mActiveProcState); 438 out.writeLong(mActiveDuration); 439 } 440 } 441 442 @Override describeContents()443 public int describeContents() { 444 return 0; 445 } 446 readFromParcel(Parcel in)447 String readFromParcel(Parcel in) { 448 mCount = in.readInt(); 449 mDuration = in.readLong(); 450 mActiveCount = in.readInt(); 451 if (in.readInt() != 0) { 452 makeDurations(); 453 if (!mActiveDurations.readFromParcel(in)) { 454 return "Duration table corrupt: " + mKey + " <- " + toString(); 455 } 456 } else { 457 mActiveProcState = in.readInt(); 458 mActiveDuration = in.readLong(); 459 } 460 return null; 461 } 462 463 @Override toString()464 public String toString() { 465 StringBuilder sb = new StringBuilder(64); 466 sb.append("SourceState{").append(Integer.toHexString(System.identityHashCode(this))) 467 .append(" ").append(mKey.mProcess).append("/").append(mKey.mUid); 468 if (mProcState != ProcessStats.STATE_NOTHING) { 469 sb.append(" ").append(DumpUtils.STATE_NAMES[mProcState]).append(" #") 470 .append(mProcStateSeq); 471 } 472 sb.append("}"); 473 return sb.toString(); 474 } 475 } 476 477 static final class SourceDumpContainer { 478 public final SourceState mState; 479 public long mTotalTime; 480 public long mActiveTime; 481 SourceDumpContainer(SourceState state)482 public SourceDumpContainer(SourceState state) { 483 mState = state; 484 } 485 } 486 487 public static final class SourceKey { 488 /** 489 * UID, consider this final. Not final just to avoid a temporary object during lookup. 490 */ 491 int mUid; 492 493 /** 494 * Process name, consider this final. Not final just to avoid a temporary object during 495 * lookup. 496 */ 497 String mProcess; 498 499 /** 500 * Optional package name, or null; consider this final. Not final just to avoid a 501 * temporary object during lookup. 502 */ 503 @Nullable String mPackage; 504 SourceKey(int uid, String process, String pkg)505 SourceKey(int uid, String process, String pkg) { 506 mUid = uid; 507 mProcess = process; 508 mPackage = pkg; 509 } 510 SourceKey(ProcessStats stats, Parcel in, int parcelVersion)511 SourceKey(ProcessStats stats, Parcel in, int parcelVersion) { 512 mUid = in.readInt(); 513 mProcess = stats.readCommonString(in, parcelVersion); 514 mPackage = stats.readCommonString(in, parcelVersion); 515 } 516 writeToParcel(ProcessStats stats, Parcel out)517 void writeToParcel(ProcessStats stats, Parcel out) { 518 out.writeInt(mUid); 519 stats.writeCommonString(out, mProcess); 520 stats.writeCommonString(out, mPackage); 521 } 522 equals(Object o)523 public boolean equals(Object o) { 524 if (!(o instanceof SourceKey)) { 525 return false; 526 } 527 SourceKey s = (SourceKey) o; 528 return s.mUid == mUid && Objects.equals(s.mProcess, mProcess) 529 && Objects.equals(s.mPackage, mPackage); 530 } 531 532 @Override hashCode()533 public int hashCode() { 534 return Integer.hashCode(mUid) ^ (mProcess == null ? 0 : mProcess.hashCode()) 535 ^ (mPackage == null ? 0 : (mPackage.hashCode() * 33)); 536 } 537 538 @Override toString()539 public String toString() { 540 StringBuilder sb = new StringBuilder(64); 541 sb.append("SourceKey{"); 542 UserHandle.formatUid(sb, mUid); 543 sb.append(' '); 544 sb.append(mProcess); 545 sb.append(' '); 546 sb.append(mPackage); 547 sb.append('}'); 548 return sb.toString(); 549 } 550 } 551 552 /** 553 * All known sources for this target component... uid -> process name -> source state. 554 */ 555 final ArrayMap<SourceKey, SourceState> mSources = new ArrayMap<>(); 556 557 private static final SourceKey sTmpSourceKey = new SourceKey(0, null, null); 558 559 private ProcessState mProc; 560 AssociationState(ProcessStats processStats, ProcessStats.PackageState packageState, String name, String processName, ProcessState proc)561 public AssociationState(ProcessStats processStats, ProcessStats.PackageState packageState, 562 String name, String processName, ProcessState proc) { 563 mProcessStats = processStats; 564 mPackageState = packageState; 565 mName = name; 566 mProcessName = processName; 567 mProc = proc; 568 } 569 getUid()570 public int getUid() { 571 return mPackageState.mUid; 572 } 573 getPackage()574 public String getPackage() { 575 return mPackageState.mPackageName; 576 } 577 getProcessName()578 public String getProcessName() { 579 return mProcessName; 580 } 581 getName()582 public String getName() { 583 return mName; 584 } 585 getProcess()586 public ProcessState getProcess() { 587 return mProc; 588 } 589 setProcess(ProcessState proc)590 public void setProcess(ProcessState proc) { 591 mProc = proc; 592 } 593 getTotalDuration(long now)594 public long getTotalDuration(long now) { 595 return mTotalDuration 596 + (mTotalNesting > 0 ? (now - mTotalStartUptime) : 0); 597 } 598 getActiveDuration(long now)599 public long getActiveDuration(long now) { 600 return mTotalActiveDuration 601 + (mTotalActiveNesting > 0 ? (now - mTotalActiveStartUptime) : 0); 602 } 603 startSource(int uid, String processName, String packageName)604 public SourceState startSource(int uid, String processName, String packageName) { 605 SourceState src; 606 synchronized (sTmpSourceKey) { 607 sTmpSourceKey.mUid = uid; 608 sTmpSourceKey.mProcess = processName; 609 sTmpSourceKey.mPackage = packageName; 610 src = mSources.get(sTmpSourceKey); 611 } 612 if (src == null) { 613 SourceKey key = new SourceKey(uid, processName, packageName); 614 src = new SourceState(mProcessStats, this, mProc, key); 615 mSources.put(key, src); 616 } 617 final long now = src.start(); 618 if (now > 0) { 619 mTotalNesting++; 620 if (mTotalNesting == 1) { 621 mTotalCount++; 622 mTotalStartUptime = now; 623 } 624 } 625 return src; 626 } 627 add(AssociationState other)628 public void add(AssociationState other) { 629 mTotalCount += other.mTotalCount; 630 final long origDuration = mTotalDuration; 631 mTotalDuration += other.mTotalDuration; 632 mTotalActiveCount += other.mTotalActiveCount; 633 mTotalActiveDuration += other.mTotalActiveDuration; 634 for (int isrc = other.mSources.size() - 1; isrc >= 0; isrc--) { 635 final SourceKey key = other.mSources.keyAt(isrc); 636 final SourceState otherSrc = other.mSources.valueAt(isrc); 637 SourceState mySrc = mSources.get(key); 638 boolean newSrc = false; 639 if (mySrc == null) { 640 mySrc = new SourceState(mProcessStats, this, mProc, key); 641 mSources.put(key, mySrc); 642 newSrc = true; 643 } 644 if (VALIDATE_TIMES) { 645 Slog.w(TAG, "Adding tot duration " + mySrc.mDuration + "+" 646 + otherSrc.mDuration 647 + (newSrc ? " (new)" : " (old)") + " (total " 648 + origDuration + "+" + other.mTotalDuration + ") in source " 649 + mySrc.mKey.mProcess + " to assoc " + mName); 650 if ((mySrc.mDuration + otherSrc.mDuration) > mTotalDuration) { 651 RuntimeException ex = new RuntimeException(); 652 Slog.w(TAG, "Source tot duration " + mySrc.mDuration + "+" 653 + otherSrc.mDuration 654 + (newSrc ? " (new)" : " (old)") + " exceeds total " 655 + origDuration + "+" + other.mTotalDuration + " in source " 656 + mySrc.mKey.mProcess + " to assoc " + mName, ex); 657 } 658 if (mySrc.mActiveDurations == null && otherSrc.mActiveDurations == null) { 659 Slog.w(TAG, "Adding act duration " + mySrc.mActiveDuration 660 + "+" + otherSrc.mActiveDuration 661 + (newSrc ? " (new)" : " (old)") + " (total " 662 + origDuration + "+" + other.mTotalDuration + ") in source " 663 + mySrc.mKey.mProcess + " to assoc " + mName); 664 if ((mySrc.mActiveDuration + otherSrc.mActiveDuration) > mTotalDuration) { 665 RuntimeException ex = new RuntimeException(); 666 Slog.w(TAG, "Source act duration " + mySrc.mActiveDuration + "+" 667 + otherSrc.mActiveDuration 668 + (newSrc ? " (new)" : " (old)") + " exceeds total " 669 + origDuration + "+" + other.mTotalDuration + " in source " 670 + mySrc.mKey.mProcess + " to assoc " + mName, ex); 671 } 672 } 673 } 674 mySrc.add(otherSrc); 675 } 676 } 677 isInUse()678 public boolean isInUse() { 679 return mTotalNesting > 0; 680 } 681 resetSafely(long now)682 public void resetSafely(long now) { 683 if (!isInUse()) { 684 mSources.clear(); 685 mTotalCount = mTotalActiveCount = 0; 686 } else { 687 // We have some active sources... clear out everything but those. 688 for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) { 689 SourceState src = mSources.valueAt(isrc); 690 if (src.isInUse()) { 691 src.resetSafely(now); 692 } else { 693 mSources.removeAt(isrc); 694 } 695 } 696 mTotalCount = 1; 697 mTotalStartUptime = now; 698 if (mTotalActiveNesting > 0) { 699 mTotalActiveCount = 1; 700 mTotalActiveStartUptime = now; 701 } else { 702 mTotalActiveCount = 0; 703 } 704 } 705 mTotalDuration = mTotalActiveDuration = 0; 706 } 707 writeToParcel(ProcessStats stats, Parcel out, long nowUptime)708 public void writeToParcel(ProcessStats stats, Parcel out, long nowUptime) { 709 out.writeInt(mTotalCount); 710 out.writeLong(mTotalDuration); 711 out.writeInt(mTotalActiveCount); 712 out.writeLong(mTotalActiveDuration); 713 final int NSRC = mSources.size(); 714 out.writeInt(NSRC); 715 for (int isrc = 0; isrc < NSRC; isrc++) { 716 final SourceKey key = mSources.keyAt(isrc); 717 final SourceState src = mSources.valueAt(isrc); 718 key.writeToParcel(stats, out); 719 src.writeToParcel(out, 0); 720 } 721 } 722 723 /** 724 * Returns non-null if all else fine, else a String that describes the error that 725 * caused it to fail. 726 */ readFromParcel(ProcessStats stats, Parcel in, int parcelVersion)727 public String readFromParcel(ProcessStats stats, Parcel in, int parcelVersion) { 728 mTotalCount = in.readInt(); 729 mTotalDuration = in.readLong(); 730 mTotalActiveCount = in.readInt(); 731 mTotalActiveDuration = in.readLong(); 732 final int NSRC = in.readInt(); 733 if (NSRC < 0 || NSRC > 100000) { 734 return "Association with bad src count: " + NSRC; 735 } 736 for (int isrc = 0; isrc < NSRC; isrc++) { 737 final SourceKey key = new SourceKey(stats, in, parcelVersion); 738 final SourceState src = new SourceState(mProcessStats, this, mProc, key); 739 final String errMsg = src.readFromParcel(in); 740 if (errMsg != null) { 741 return errMsg; 742 } 743 if (VALIDATE_TIMES) { 744 if (src.mDuration > mTotalDuration) { 745 RuntimeException ex = new RuntimeException(); 746 Slog.w(TAG, "Reading tot duration " + src.mDuration 747 + " exceeds total " + mTotalDuration + " in source " 748 + src.mKey.mProcess + " to assoc " + mName, ex); 749 } 750 if (src.mActiveDurations == null && src.mActiveDuration > mTotalDuration) { 751 RuntimeException ex = new RuntimeException(); 752 Slog.w(TAG, "Reading act duration " + src.mActiveDuration 753 + " exceeds total " + mTotalDuration + " in source " 754 + src.mKey.mProcess + " to assoc " + mName, ex); 755 } 756 } 757 mSources.put(key, src); 758 } 759 return null; 760 } 761 commitStateTime(long nowUptime)762 public void commitStateTime(long nowUptime) { 763 if (isInUse()) { 764 for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) { 765 SourceState src = mSources.valueAt(isrc); 766 src.commitStateTime(nowUptime); 767 } 768 if (mTotalNesting > 0) { 769 mTotalDuration += nowUptime - mTotalStartUptime; 770 mTotalStartUptime = nowUptime; 771 } 772 if (mTotalActiveNesting > 0) { 773 mTotalActiveDuration += nowUptime - mTotalActiveStartUptime; 774 mTotalActiveStartUptime = nowUptime; 775 } 776 } 777 } 778 hasProcessOrPackage(String procName)779 public boolean hasProcessOrPackage(String procName) { 780 if (mProcessName.equals(procName)) { 781 return true; 782 } 783 final int NSRC = mSources.size(); 784 for (int isrc = 0; isrc < NSRC; isrc++) { 785 final SourceKey key = mSources.keyAt(isrc); 786 if (procName.equals(key.mProcess) || procName.equals(key.mPackage)) { 787 return true; 788 } 789 } 790 return false; 791 } 792 793 static final Comparator<Pair<SourceKey, SourceDumpContainer>> ASSOCIATION_COMPARATOR = 794 (o1, o2) -> { 795 if (o1.second.mActiveTime != o2.second.mActiveTime) { 796 return o1.second.mActiveTime > o2.second.mActiveTime ? -1 : 1; 797 } 798 if (o1.second.mTotalTime != o2.second.mTotalTime) { 799 return o1.second.mTotalTime > o2.second.mTotalTime ? -1 : 1; 800 } 801 if (o1.first.mUid != o2.first.mUid) { 802 return o1.first.mUid < o2.first.mUid ? -1 : 1; 803 } 804 if (o1.first.mProcess != o2.first.mProcess) { 805 int diff = o1.first.mProcess.compareTo(o2.first.mProcess); 806 if (diff != 0) { 807 return diff; 808 } 809 } 810 return 0; 811 }; 812 createSortedAssociations(long now, long totalTime, ArrayMap<SourceKey, SourceState> inSources)813 static ArrayList<Pair<SourceKey, SourceDumpContainer>> createSortedAssociations(long now, 814 long totalTime, ArrayMap<SourceKey, SourceState> inSources) { 815 final int numOfSources = inSources.size(); 816 ArrayList<Pair<SourceKey, SourceDumpContainer>> sources = new ArrayList<>(numOfSources); 817 for (int isrc = 0; isrc < numOfSources; isrc++) { 818 final SourceState src = inSources.valueAt(isrc); 819 final SourceDumpContainer cont = new SourceDumpContainer(src); 820 long duration = src.mDuration; 821 if (src.mNesting > 0) { 822 duration += now - src.mStartUptime; 823 } 824 cont.mTotalTime = duration; 825 cont.mActiveTime = dumpTime(null, null, src, totalTime, now, false, false); 826 if (cont.mActiveTime < 0) { 827 cont.mActiveTime = -cont.mActiveTime; 828 } 829 sources.add(new Pair<>(inSources.keyAt(isrc), cont)); 830 } 831 Collections.sort(sources, ASSOCIATION_COMPARATOR); 832 return sources; 833 } 834 dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix, ArrayList<Pair<SourceKey, SourceDumpContainer>> sources, long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll)835 public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix, 836 ArrayList<Pair<SourceKey, SourceDumpContainer>> sources, long now, long totalTime, 837 String reqPackage, boolean dumpDetails, boolean dumpAll) { 838 final String prefixInnerInner = prefixInner + " "; 839 long totalDuration = mTotalActiveDuration; 840 if (mTotalActiveNesting > 0) { 841 totalDuration += now - mTotalActiveStartUptime; 842 } 843 if (totalDuration > 0 || mTotalActiveCount != 0) { 844 pw.print(prefix); 845 pw.print("Active count "); 846 pw.print(mTotalActiveCount); 847 if (dumpAll) { 848 pw.print(": "); 849 TimeUtils.formatDuration(totalDuration, pw); 850 pw.print(" / "); 851 } else { 852 pw.print(": time "); 853 } 854 DumpUtils.printPercent(pw, (double) totalDuration / (double) totalTime); 855 pw.println(); 856 } 857 if (dumpAll && mTotalActiveNesting != 0) { 858 pw.print(prefix); 859 pw.print("mTotalActiveNesting="); 860 pw.print(mTotalActiveNesting); 861 pw.print(" mTotalActiveStartUptime="); 862 TimeUtils.formatDuration(mTotalActiveStartUptime, now, pw); 863 pw.println(); 864 } 865 totalDuration = mTotalDuration; 866 if (mTotalNesting > 0) { 867 totalDuration += now - mTotalStartUptime; 868 } 869 if (totalDuration > 0 || mTotalCount != 0) { 870 pw.print(prefix); 871 pw.print("Total count "); 872 pw.print(mTotalCount); 873 if (dumpAll) { 874 pw.print(": "); 875 TimeUtils.formatDuration(totalDuration, pw); 876 pw.print(" / "); 877 } else { 878 pw.print(": time "); 879 } 880 DumpUtils.printPercent(pw, (double) totalDuration / (double) totalTime); 881 pw.println(); 882 } 883 if (dumpAll && mTotalNesting != 0) { 884 pw.print(prefix); 885 pw.print("mTotalNesting="); 886 pw.print(mTotalNesting); 887 pw.print(" mTotalStartUptime="); 888 TimeUtils.formatDuration(mTotalStartUptime, now, pw); 889 pw.println(); 890 } 891 892 dumpSources(pw, prefix, prefixInner, prefixInnerInner, sources, now, totalTime, 893 reqPackage, dumpDetails, dumpAll); 894 } 895 dumpSources(PrintWriter pw, String prefix, String prefixInner, String prefixInnerInner, ArrayList<Pair<SourceKey, SourceDumpContainer>> sources, long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll)896 static void dumpSources(PrintWriter pw, String prefix, String prefixInner, 897 String prefixInnerInner, ArrayList<Pair<SourceKey, SourceDumpContainer>> sources, 898 long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll) { 899 final int NSRC = sources.size(); 900 for (int isrc = 0; isrc < NSRC; isrc++) { 901 final SourceKey key = sources.get(isrc).first; 902 final SourceDumpContainer cont = sources.get(isrc).second; 903 final SourceState src = cont.mState; 904 pw.print(prefix); 905 pw.print("<- "); 906 pw.print(key.mProcess); 907 pw.print("/"); 908 UserHandle.formatUid(pw, key.mUid); 909 if (key.mPackage != null) { 910 pw.print(" ("); 911 pw.print(key.mPackage); 912 pw.print(")"); 913 } 914 // If we are skipping this one, we still print the first line just to give 915 // context for the others (so it is clear the total times for the overall 916 // association come from other sources whose times are not shown). 917 if (reqPackage != null && !reqPackage.equals(key.mProcess) 918 && !reqPackage.equals(key.mPackage)) { 919 pw.println(); 920 continue; 921 } 922 pw.println(":"); 923 if (src.mActiveCount != 0 || src.mActiveDurations != null || src.mActiveDuration != 0 924 || src.mActiveStartUptime != 0) { 925 pw.print(prefixInner); 926 pw.print(" Active count "); 927 pw.print(src.mActiveCount); 928 if (dumpDetails) { 929 if (dumpAll) { 930 if (src.mActiveDurations != null) { 931 pw.print(" (multi-state)"); 932 } else if (src.mActiveProcState >= ProcessStats.STATE_PERSISTENT) { 933 pw.print(" ("); 934 pw.print(DumpUtils.STATE_NAMES[src.mActiveProcState]); 935 pw.print(")"); 936 } else { 937 pw.print(" (*UNKNOWN STATE*)"); 938 } 939 } 940 if (dumpAll) { 941 pw.print(": "); 942 TimeUtils.formatDuration(cont.mActiveTime, pw); 943 pw.print(" / "); 944 } else { 945 pw.print(": time "); 946 } 947 DumpUtils.printPercent(pw, (double) cont.mActiveTime / (double) totalTime); 948 if (src.mActiveStartUptime != 0) { 949 pw.print(" (running)"); 950 } 951 pw.println(); 952 if (src.mActiveDurations != null) { 953 dumpTime(pw, prefixInnerInner, src, totalTime, now, dumpDetails, dumpAll); 954 } 955 } else { 956 pw.print(": "); 957 dumpActiveDurationSummary(pw, src, totalTime, now, dumpAll); 958 } 959 } 960 pw.print(prefixInner); 961 pw.print(" Total count "); 962 pw.print(src.mCount); 963 if (dumpAll) { 964 pw.print(": "); 965 TimeUtils.formatDuration(cont.mTotalTime, pw); 966 pw.print(" / "); 967 } else { 968 pw.print(": time "); 969 } 970 DumpUtils.printPercent(pw, (double) cont.mTotalTime / (double) totalTime); 971 if (src.mNesting > 0) { 972 pw.print(" (running"); 973 if (dumpAll) { 974 pw.print(" nest="); 975 pw.print(src.mNesting); 976 } 977 if (src.mProcState != ProcessStats.STATE_NOTHING) { 978 pw.print(" / "); 979 pw.print(DumpUtils.STATE_NAMES[src.mProcState]); 980 pw.print(" #"); 981 pw.print(src.mProcStateSeq); 982 } 983 pw.print(")"); 984 } 985 pw.println(); 986 if (dumpAll) { 987 if (src.mInTrackingList) { 988 pw.print(prefixInner); 989 pw.print(" mInTrackingList="); 990 pw.println(src.mInTrackingList); 991 } 992 if (src.mProcState != ProcessStats.STATE_NOTHING) { 993 pw.print(prefixInner); 994 pw.print(" mProcState="); 995 pw.print(DumpUtils.STATE_NAMES[src.mProcState]); 996 pw.print(" mProcStateSeq="); 997 pw.println(src.mProcStateSeq); 998 } 999 } 1000 } 1001 } 1002 dumpActiveDurationSummary(PrintWriter pw, final SourceState src, long totalTime, long now, boolean dumpAll)1003 static void dumpActiveDurationSummary(PrintWriter pw, final SourceState src, long totalTime, 1004 long now, boolean dumpAll) { 1005 long duration = dumpTime(null, null, src, totalTime, now, false, false); 1006 final boolean isRunning = duration < 0; 1007 if (isRunning) { 1008 duration = -duration; 1009 } 1010 if (dumpAll) { 1011 TimeUtils.formatDuration(duration, pw); 1012 pw.print(" / "); 1013 } else { 1014 pw.print("time "); 1015 } 1016 DumpUtils.printPercent(pw, (double) duration / (double) totalTime); 1017 if (src.mActiveStartUptime > 0) { 1018 pw.print(" (running)"); 1019 } 1020 pw.println(); 1021 } 1022 dumpTime(PrintWriter pw, String prefix, final SourceState src, long overallTime, long now, boolean dumpDetails, boolean dumpAll)1023 static long dumpTime(PrintWriter pw, String prefix, final SourceState src, long overallTime, 1024 long now, boolean dumpDetails, boolean dumpAll) { 1025 long totalTime = 0; 1026 boolean isRunning = false; 1027 for (int iprocstate = 0; iprocstate < ProcessStats.STATE_COUNT; iprocstate++) { 1028 long time; 1029 if (src.mActiveDurations != null) { 1030 time = src.mActiveDurations.getValueForId((byte) iprocstate); 1031 } else { 1032 time = src.mActiveProcState == iprocstate ? src.mActiveDuration : 0; 1033 } 1034 final String running; 1035 if (src.mActiveStartUptime != 0 && src.mActiveProcState == iprocstate) { 1036 running = " (running)"; 1037 isRunning = true; 1038 time += now - src.mActiveStartUptime; 1039 } else { 1040 running = null; 1041 } 1042 if (time != 0) { 1043 if (pw != null) { 1044 pw.print(prefix); 1045 pw.print(DumpUtils.STATE_LABELS[iprocstate]); 1046 pw.print(": "); 1047 if (dumpAll) { 1048 TimeUtils.formatDuration(time, pw); 1049 pw.print(" / "); 1050 } else { 1051 pw.print("time "); 1052 } 1053 DumpUtils.printPercent(pw, (double) time / (double) overallTime); 1054 if (running != null) { 1055 pw.print(running); 1056 } 1057 pw.println(); 1058 } 1059 totalTime += time; 1060 } 1061 } 1062 return isRunning ? -totalTime : totalTime; 1063 } 1064 dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers, String associationName, long now)1065 public void dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers, 1066 String associationName, long now) { 1067 final int NSRC = mSources.size(); 1068 for (int isrc = 0; isrc < NSRC; isrc++) { 1069 final SourceKey key = mSources.keyAt(isrc); 1070 final SourceState src = mSources.valueAt(isrc); 1071 pw.print("pkgasc"); 1072 pw.print(","); 1073 pw.print(pkgName); 1074 pw.print(","); 1075 pw.print(uid); 1076 pw.print(","); 1077 pw.print(vers); 1078 pw.print(","); 1079 pw.print(associationName); 1080 pw.print(","); 1081 pw.print(key.mProcess); 1082 pw.print(","); 1083 pw.print(key.mUid); 1084 pw.print(","); 1085 pw.print(src.mCount); 1086 long duration = src.mDuration; 1087 if (src.mNesting > 0) { 1088 duration += now - src.mStartUptime; 1089 } 1090 pw.print(","); 1091 pw.print(duration); 1092 pw.print(","); 1093 pw.print(src.mActiveCount); 1094 final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0; 1095 if (src.mActiveDurations != null) { 1096 final int N = src.mActiveDurations.getKeyCount(); 1097 for (int i=0; i<N; i++) { 1098 final int dkey = src.mActiveDurations.getKeyAt(i); 1099 duration = src.mActiveDurations.getValue(dkey); 1100 if (dkey == src.mActiveProcState) { 1101 duration += timeNow; 1102 } 1103 final int procState = SparseMappingTable.getIdFromKey(dkey); 1104 pw.print(","); 1105 DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS, procState, 1); 1106 pw.print(':'); 1107 pw.print(duration); 1108 } 1109 } else { 1110 duration = src.mActiveDuration + timeNow; 1111 if (duration != 0) { 1112 pw.print(","); 1113 DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS, src.mActiveProcState, 1); 1114 pw.print(':'); 1115 pw.print(duration); 1116 } 1117 } 1118 pw.println(); 1119 } 1120 } 1121 dumpDebug(ProtoOutputStream proto, long fieldId, long now)1122 public void dumpDebug(ProtoOutputStream proto, long fieldId, long now) { 1123 final long token = proto.start(fieldId); 1124 1125 proto.write(PackageAssociationProcessStatsProto.COMPONENT_NAME, mName); 1126 1127 proto.write(PackageAssociationProcessStatsProto.TOTAL_COUNT, mTotalCount); 1128 proto.write(PackageAssociationProcessStatsProto.TOTAL_DURATION_MS, getTotalDuration(now)); 1129 if (mTotalActiveCount != 0) { 1130 proto.write(PackageAssociationProcessStatsProto.ACTIVE_COUNT, mTotalActiveCount); 1131 proto.write(PackageAssociationProcessStatsProto.ACTIVE_DURATION_MS, 1132 getActiveDuration(now)); 1133 } 1134 1135 final int NSRC = mSources.size(); 1136 for (int isrc = 0; isrc < NSRC; isrc++) { 1137 final SourceKey key = mSources.keyAt(isrc); 1138 final SourceState src = mSources.valueAt(isrc); 1139 final long sourceToken = proto.start(PackageAssociationProcessStatsProto.SOURCES); 1140 proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_NAME, key.mProcess); 1141 proto.write(PackageAssociationSourceProcessStatsProto.PACKAGE_NAME, key.mPackage); 1142 proto.write(PackageAssociationSourceProcessStatsProto.PROCESS_UID, key.mUid); 1143 proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_COUNT, src.mCount); 1144 long duration = src.mDuration; 1145 if (src.mNesting > 0) { 1146 duration += now - src.mStartUptime; 1147 } 1148 proto.write(PackageAssociationSourceProcessStatsProto.TOTAL_DURATION_MS, duration); 1149 if (src.mActiveCount != 0) { 1150 proto.write(PackageAssociationSourceProcessStatsProto.ACTIVE_COUNT, 1151 src.mActiveCount); 1152 } 1153 final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0; 1154 if (src.mActiveDurations != null) { 1155 final int N = src.mActiveDurations.getKeyCount(); 1156 for (int i=0; i<N; i++) { 1157 final int dkey = src.mActiveDurations.getKeyAt(i); 1158 duration = src.mActiveDurations.getValue(dkey); 1159 if (dkey == src.mActiveProcState) { 1160 duration += timeNow; 1161 } 1162 final int procState = SparseMappingTable.getIdFromKey(dkey); 1163 final long stateToken = proto.start( 1164 PackageAssociationSourceProcessStatsProto.ACTIVE_STATE_STATS); 1165 DumpUtils.printProto(proto, 1166 PackageAssociationSourceProcessStatsProto.StateStats.PROCESS_STATE, 1167 DumpUtils.STATE_PROTO_ENUMS, procState, 1); 1168 proto.write(PackageAssociationSourceProcessStatsProto.StateStats.DURATION_MS, 1169 duration); 1170 proto.end(stateToken); 1171 } 1172 } else { 1173 duration = src.mActiveDuration + timeNow; 1174 if (duration != 0) { 1175 final long stateToken = proto.start( 1176 PackageAssociationSourceProcessStatsProto.ACTIVE_STATE_STATS); 1177 DumpUtils.printProto(proto, 1178 PackageAssociationSourceProcessStatsProto.StateStats.PROCESS_STATE, 1179 DumpUtils.STATE_PROTO_ENUMS, src.mActiveProcState, 1); 1180 proto.write(PackageAssociationSourceProcessStatsProto.StateStats.DURATION_MS, 1181 duration); 1182 proto.end(stateToken); 1183 } 1184 } 1185 proto.end(sourceToken); 1186 } 1187 1188 proto.end(token); 1189 } 1190 toString()1191 public String toString() { 1192 return "AssociationState{" + Integer.toHexString(System.identityHashCode(this)) 1193 + " " + mName + " pkg=" + mPackageState.mPackageName + " proc=" 1194 + Integer.toHexString(System.identityHashCode(mProc)) + "}"; 1195 } 1196 } 1197