Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2aec55a6 authored by Dianne Hackborn's avatar Dianne Hackborn
Browse files

Add more data (fg services, associations) to procstats.

- Keep track of foreground services.
- Keep track of associations between processes.

The big part of this is the second, tracking associations.
We have have procstats keeping continual track of associations
between processes, much like the "am track-associations"
command.  Currently the data kept on them is very minimal
(just the count and total duration, not separated by other
states) due to the potential number of them that there can be,
but we can look in to trying to maintain more data going
forward if it is feasible.

The way this is incorporated into the activity manager makes
it a little different than "am track-associations," with
potentially some new interesting data available.  These
associations are tied with the connection objects in the
activity manager, so they only count while the target
process is actually running (so their duration should match
with the lifecycle of the target).  They are tied to the
target package, since that is what we know all of the
information we need for rooting data in procstats (package
name, uid, and version code of that package); only the process
name and uid are available for the source of the association

Since these are tied to the connection components, it is
possible that we could even maintain data on the duration per
proc state that is flowing from that association in to the
target process.  That would be very useful, but would add
a fair amount more overhead in data being tracked.

English output of the new association data looks like:

  * com.android.providers.downloads / u0a17 / v28:
      * Prc android.process.media / u0a17 / v28:
               TOTAL: 0.45%
              Imp Bg: 0.26%
             Service: 0.18%
            Receiver: 0.01%
          (Last Act): 0.78%
            (Cached): 37% (5.2MB-5.8MB-8.2MB/3.9MB-4.4MB-6.0MB/3.9MB-7.0MB-50MB over 18)
      * Svc com.android.providers.downloads.DownloadIdleService:
        Process: android.process.media
            Running count 3 / time 0.01%
            Bound count 3 / time 0.01%
            Executing count 6 / time 0.00%
      * Svc com.android.providers.downloads.DownloadJobService:
        Process: android.process.media
            Running count 6 / time 0.21%
            Bound count 6 / time 0.21%
            Executing count 12 / time 0.00%
      * Asc com.android.providers.downloads.DownloadIdleService:
        Process: android.process.media
          <- system / 1000:
             Count 3 / time 0.01%
      * Asc com.android.providers.downloads.DownloadStorageProvider:
        Process: android.process.media
          <- com.android.documentsui / u0a10:
             Count 1 / time 0.00%
      * Asc com.android.providers.downloads.DownloadProvider:
        Process: android.process.media
          <- com.android.vending / u0a11:
             Count 39 / time 2.6%
          <- system / 1000:
             Count 3 / time 0.00%
          <- com.google.android.gms / u0a36:
             Count 8 / time 0.01%
      * Asc com.android.providers.downloads.DownloadJobService:
        Process: android.process.media
          <- system / 1000:
             Count 6 / time 0.21%

And the corresponding checkin:

pkgproc,com.android.providers.downloads,10017,28,android.process.media,0nf:717,0nb:71332,0ns:48335,0nr:3652,0nl:218034,0ne:10103500,0mf:21,0ms:614,0me:185,1ne:100236
pkgpss,com.android.providers.downloads,10017,28,android.process.media,0ne:18:5310:5950:8434:4036:4522:6140:4036:7127:51056
pkgsvc-run,com.android.providers.downloads,10017,28,.DownloadIdleService,3,0n:1849
pkgsvc-bound,com.android.providers.downloads,10017,28,.DownloadIdleService,3,0n:1794
pkgsvc-exec,com.android.providers.downloads,10017,28,.DownloadIdleService,6,0n:89
pkgsvc-run,com.android.providers.downloads,10017,28,.DownloadJobService,6,0n:58224
pkgsvc-bound,com.android.providers.downloads,10017,28,.DownloadJobService,6,0n:58154
pkgsvc-exec,com.android.providers.downloads,10017,28,.DownloadJobService,12,0n:187
pkgasc,com.android.providers.downloads,10017,28,.DownloadIdleService,system,1000,3,1790
pkgasc,com.android.providers.downloads,10017,28,.DownloadStorageProvider,com.android.documentsui,10010,1,80
pkgasc,com.android.providers.downloads,10017,28,.DownloadProvider,com.android.vending,10011,39,1067022
pkgasc,com.android.providers.downloads,10017,28,.DownloadProvider,system,1000,3,96
pkgasc,com.android.providers.downloads,10017,28,.DownloadProvider,com.google.android.gms,10036,8,1951
pkgasc,com.android.providers.downloads,10017,28,.DownloadJobService,system,1000,6,58149

Bug: 110957691
Test: manual
Change-Id: Id466b085303527e7bf7354f7f33a0fbaa768fb7b
parent 9852064d
Loading
Loading
Loading
Loading
+293 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.app.procstats;


import android.os.Parcel;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.TimeUtils;

import java.io.PrintWriter;
import java.util.Objects;

public final class AssociationState {
    private static final String TAG = "ProcessStats";
    private static final boolean DEBUG = false;

    private final String mPackage;
    private final String mProcessName;
    private final String mName;
    private final DurationsTable mDurations;

    public final class SourceState {
        public void stop() {
            mNesting--;
            if (mNesting == 0) {
                mDuration += SystemClock.uptimeMillis() - mStartTime;
                mNumActive--;
            }
        }

        int mNesting;
        int mCount;
        long mStartTime;
        long mDuration;
    }

    final static class SourceKey {
        int mUid;
        String mProcess;

        SourceKey(int uid, String process) {
            mUid = uid;
            mProcess = process;
        }

        public boolean equals(Object o) {
            if (!(o instanceof SourceKey)) {
                return false;
            }
            SourceKey s = (SourceKey) o;
            return s.mUid == mUid && Objects.equals(s.mProcess, mProcess);
        }

        @Override
        public int hashCode() {
            return Integer.hashCode(mUid) ^ (mProcess == null ? 0 : mProcess.hashCode());
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder(64);
            sb.append("SourceKey{");
            UserHandle.formatUid(sb, mUid);
            sb.append(' ');
            sb.append(mProcess);
            sb.append('}');
            return sb.toString();
        }

    }

    /**
     * All known sources for this target component...  uid -> process name -> source state.
     */
    private final ArrayMap<SourceKey, SourceState> mSources = new ArrayMap<>();

    private final SourceKey mTmpSourceKey = new SourceKey(0, null);

    private int mNumActive;

    public AssociationState(ProcessStats processStats, String pkg, String name,
            String processName) {
        mPackage = pkg;
        mName = name;
        mProcessName = processName;
        mDurations = new DurationsTable(processStats.mTableData);
    }

    public String getPackage() {
        return mPackage;
    }

    public String getProcessName() {
        return mProcessName;
    }

    public String getName() {
        return mName;
    }

    public SourceState startSource(int uid, String processName) {
        mTmpSourceKey.mUid = uid;
        mTmpSourceKey.mProcess = processName;
        SourceState src = mSources.get(mTmpSourceKey);
        if (src == null) {
            src = new SourceState();
            mSources.put(new SourceKey(uid, processName), src);
        }
        src.mNesting++;
        if (src.mNesting == 1) {
            src.mCount++;
            src.mStartTime = SystemClock.uptimeMillis();
            mNumActive++;
        }
        return src;
    }

    public void add(AssociationState other) {
        mDurations.addDurations(other.mDurations);
        for (int isrc = other.mSources.size() - 1; isrc >= 0; isrc--) {
            final SourceKey key = other.mSources.keyAt(isrc);
            final SourceState otherSrc = other.mSources.valueAt(isrc);
            SourceState mySrc = mSources.get(key);
            if (mySrc == null) {
                mySrc = new SourceState();
                mSources.put(key, mySrc);
            }
            mySrc.mCount += otherSrc.mCount;
            mySrc.mDuration += otherSrc.mDuration;
        }
    }

    public boolean isInUse() {
        return mNumActive > 0;
    }

    public void resetSafely(long now) {
        mDurations.resetTable();
        if (!isInUse()) {
            mSources.clear();
        } else {
            // We have some active sources...  clear out everything but those.
            for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
                SourceState src = mSources.valueAt(isrc);
                if (src.mNesting > 0) {
                    src.mCount = 1;
                    src.mStartTime = now;
                    src.mDuration = 0;
                } else {
                    mSources.removeAt(isrc);
                }
            }
        }
    }

    public void writeToParcel(ProcessStats stats, Parcel out, long now) {
        mDurations.writeToParcel(out);
        final int NSRC = mSources.size();
        out.writeInt(NSRC);
        for (int isrc = 0; isrc < NSRC; isrc++) {
            final SourceKey key = mSources.keyAt(isrc);
            final SourceState src = mSources.valueAt(isrc);
            out.writeInt(key.mUid);
            stats.writeCommonString(out, key.mProcess);
            out.writeInt(src.mCount);
            out.writeLong(src.mDuration);
        }
    }

    public String readFromParcel(ProcessStats stats, Parcel in, int parcelVersion) {
        if (!mDurations.readFromParcel(in)) {
            return "Duration table corrupt";
        }
        final int NSRC = in.readInt();
        if (NSRC < 0 || NSRC > 100000) {
            return "Association with bad src count: " + NSRC;
        }
        for (int isrc = 0; isrc < NSRC; isrc++) {
            final int uid = in.readInt();
            final String procName = stats.readCommonString(in, parcelVersion);
            final SourceKey key = new SourceKey(uid, procName);
            final SourceState src = new SourceState();
            src.mCount = in.readInt();
            src.mDuration = in.readLong();
            mSources.put(key, src);
        }
        return null;
    }

    public void commitStateTime(long now) {
        if (isInUse()) {
            for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
                SourceState src = mSources.valueAt(isrc);
                if (src.mNesting > 0) {
                    src.mDuration += now - src.mStartTime;
                    src.mStartTime = now;
                }
            }
        }
    }

    public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix,
            long now, long totalTime, boolean dumpSummary, boolean dumpAll) {
        if (dumpAll) {
            pw.print(prefix);
            pw.print("mNumActive=");
            pw.println(mNumActive);
        }
        final int NSRC = mSources.size();
        for (int isrc = 0; isrc < NSRC; isrc++) {
            final SourceKey key = mSources.keyAt(isrc);
            final SourceState src = mSources.valueAt(isrc);
            pw.print(prefixInner);
            pw.print("<- ");
            pw.print(key.mProcess);
            pw.print(" / ");
            UserHandle.formatUid(pw, key.mUid);
            pw.println(":");
            pw.print(prefixInner);
            pw.print("   Count ");
            pw.print(src.mCount);
            long duration = src.mDuration;
            if (src.mNesting > 0) {
                duration += now - src.mStartTime;
            }
            if (dumpAll) {
                pw.print(" / Duration ");
                TimeUtils.formatDuration(duration, pw);
                pw.print(" / ");
            } else {
                pw.print(" / time ");
            }
            DumpUtils.printPercent(pw, (double)duration/(double)totalTime);
            if (src.mNesting > 0) {
                pw.print(" (running)");
            }
            pw.println();
        }
    }

    public void dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers,
            String associationName, long now) {
        final int NSRC = mSources.size();
        for (int isrc = 0; isrc < NSRC; isrc++) {
            final SourceKey key = mSources.keyAt(isrc);
            final SourceState src = mSources.valueAt(isrc);
            pw.print("pkgasc");
            pw.print(",");
            pw.print(pkgName);
            pw.print(",");
            pw.print(uid);
            pw.print(",");
            pw.print(vers);
            pw.print(",");
            pw.print(associationName);
            pw.print(",");
            pw.print(key.mProcess);
            pw.print(",");
            pw.print(key.mUid);
            pw.print(",");
            pw.print(src.mCount);
            long duration = src.mDuration;
            if (src.mNesting > 0) {
                duration += now - src.mStartTime;
            }
            pw.print(",");
            pw.print(duration);
            pw.println();
        }
    }

    public String toString() {
        return "AssociationState{" + Integer.toHexString(System.identityHashCode(this))
                + " " + mName + " pkg=" + mPackage + " proc="
                + Integer.toHexString(System.identityHashCode(this)) + "}";
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -362,12 +362,13 @@ public final class DumpUtils {
        }
    }

    public static void dumpProcessSummaryLocked(PrintWriter pw, String prefix,
    public static void dumpProcessSummaryLocked(PrintWriter pw, String prefix, String header,
            ArrayList<ProcessState> procs, int[] screenStates, int[] memStates, int[] procStates,
            long now, long totalTime) {
        for (int i=procs.size()-1; i>=0; i--) {
            final ProcessState proc = procs.get(i);
            proc.dumpSummary(pw, prefix, screenStates, memStates, procStates, now, totalTime);
            proc.dumpSummary(pw, prefix, header, screenStates, memStates, procStates, now,
                    totalTime);
        }
    }

+9 −6
Original line number Diff line number Diff line
@@ -580,7 +580,7 @@ public final class ProcessState {
        ProcessStateHolder holder = pkgList.valueAt(index);
        ProcessState proc = holder.state;
        if (mDead && proc.mCommonProcess != proc) {
            // Somehow we are contining to use a process state that is dead, because
            // Somehow we are continuing to use a process state that is dead, because
            // it was not being told it was active during the last commit.  We can recover
            // from this by generating a fresh new state, but this is bad because we
            // are losing whatever data we had in the old process state.
@@ -600,17 +600,17 @@ public final class ProcessState {
                        + pkgList.keyAt(index) + "/" + proc.mUid
                        + " for multi-proc " + proc.mName);
            }
            PackageState pkg = vpkg.get(proc.mVersion);
            if (pkg == null) {
            PackageState expkg = vpkg.get(proc.mVersion);
            if (expkg == null) {
                throw new IllegalStateException("No existing package "
                        + pkgList.keyAt(index) + "/" + proc.mUid
                        + " for multi-proc " + proc.mName + " version " + proc.mVersion);
            }
            String savedName = proc.mName;
            proc = pkg.mProcesses.get(proc.mName);
            proc = expkg.mProcesses.get(proc.mName);
            if (proc == null) {
                throw new IllegalStateException("Didn't create per-package process "
                        + savedName + " in pkg " + pkg.mPackageName + "/" + pkg.mUid);
                        + savedName + " in pkg " + expkg.mPackageName + "/" + expkg.mUid);
            }
            holder.state = proc;
        }
@@ -769,11 +769,14 @@ public final class ProcessState {
        return totalTime;
    }

    public void dumpSummary(PrintWriter pw, String prefix,
    public void dumpSummary(PrintWriter pw, String prefix, String header,
            int[] screenStates, int[] memStates, int[] procStates,
            long now, long totalTime) {
        pw.print(prefix);
        pw.print("* ");
        if (header != null) {
            pw.print(header);
        }
        pw.print(mName);
        pw.print(" / ");
        UserHandle.formatUid(pw, mUid);
+158 −30

File changed.

Preview size limit exceeded, changes collapsed.

+81 −24
Original line number Diff line number Diff line
@@ -18,30 +18,13 @@ package com.android.internal.app.procstats;


import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;

import com.android.internal.app.procstats.ProcessStats;
import static com.android.internal.app.procstats.ProcessStats.STATE_NOTHING;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Objects;

public final class ServiceState {
    private static final String TAG = "ProcessStats";
@@ -51,7 +34,8 @@ public final class ServiceState {
    public static final int SERVICE_STARTED = 1;
    public static final int SERVICE_BOUND = 2;
    public static final int SERVICE_EXEC = 3;
    public static final int SERVICE_COUNT = 4;
    public static final int SERVICE_FOREGROUND = 4;
    public static final int SERVICE_COUNT = 5;

    private final String mPackage;
    private final String mProcessName;
@@ -79,6 +63,10 @@ public final class ServiceState {
    private int mExecState = STATE_NOTHING;
    private long mExecStartTime;

    private int mForegroundCount;
    private int mForegroundState = STATE_NOTHING;
    private long mForegroundStartTime;

    public ServiceState(ProcessStats processStats, String pkg, String name,
            String processName, ProcessState proc) {
        mPackage = pkg;
@@ -121,6 +109,9 @@ public final class ServiceState {
            if (mExecState != ProcessStats.STATE_NOTHING) {
                setExecuting(true, memFactor, now);
            }
            if (mForegroundState != ProcessStats.STATE_NOTHING) {
                setForeground(true, memFactor, now);
            }
        }
    }

@@ -133,7 +124,8 @@ public final class ServiceState {
                // There was already an old owner, reset this object for its
                // new owner.
                mOwner = newOwner;
                if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING) {
                if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING
                        || mForegroundState != STATE_NOTHING) {
                    long now = SystemClock.uptimeMillis();
                    if (mStarted) {
                        if (DEBUG) Slog.d(TAG, "Service has new owner " + newOwner
@@ -153,6 +145,12 @@ public final class ServiceState {
                                + mPackage + " service=" + mName + " proc=" + mProc);
                        setExecuting(false, 0, now);
                    }
                    if (mForegroundState != STATE_NOTHING) {
                        if (DEBUG) Slog.d(TAG, "Service has new owner " + newOwner
                                + " from " + mOwner + " while foreground: pkg="
                                + mPackage + " service=" + mName + " proc=" + mProc);
                        setForeground(false, 0, now);
                    }
                }
            }
        }
@@ -161,7 +159,8 @@ public final class ServiceState {
    public void clearCurrentOwner(Object owner, boolean silently) {
        if (mOwner == owner) {
            mProc.decActiveServices(mName);
            if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING) {
            if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING
                    || mForegroundState != STATE_NOTHING) {
                long now = SystemClock.uptimeMillis();
                if (mStarted) {
                    if (!silently) {
@@ -187,6 +186,14 @@ public final class ServiceState {
                    }
                    setExecuting(false, 0, now);
                }
                if (mForegroundState != STATE_NOTHING) {
                    if (!silently) {
                        Slog.wtfStack(TAG, "Service owner " + owner
                                + " cleared while foreground: pkg=" + mPackage + " service="
                                + mName + " proc=" + mProc);
                    }
                    setForeground(false, 0, now);
                }
            }
            mOwner = null;
        }
@@ -206,6 +213,7 @@ public final class ServiceState {
        mStartedCount += other.mStartedCount;
        mBoundCount += other.mBoundCount;
        mExecCount += other.mExecCount;
        mForegroundCount += other.mForegroundCount;
    }

    public void resetSafely(long now) {
@@ -214,7 +222,9 @@ public final class ServiceState {
        mStartedCount = mStartedState != STATE_NOTHING ? 1 : 0;
        mBoundCount = mBoundState != STATE_NOTHING ? 1 : 0;
        mExecCount = mExecState != STATE_NOTHING ? 1 : 0;
        mRunStartTime = mStartedStartTime = mBoundStartTime = mExecStartTime = now;
        mForegroundCount = mForegroundState != STATE_NOTHING ? 1 : 0;
        mRunStartTime = mStartedStartTime = mBoundStartTime = mExecStartTime =
                mForegroundStartTime = now;
    }

    public void writeToParcel(Parcel out, long now) {
@@ -223,6 +233,7 @@ public final class ServiceState {
        out.writeInt(mStartedCount);
        out.writeInt(mBoundCount);
        out.writeInt(mExecCount);
        out.writeInt(mForegroundCount);
    }

    public boolean readFromParcel(Parcel in) {
@@ -233,6 +244,7 @@ public final class ServiceState {
        mStartedCount = in.readInt();
        mBoundCount = in.readInt();
        mExecCount = in.readInt();
        mForegroundCount = in.readInt();
        return true;
    }

@@ -257,11 +269,17 @@ public final class ServiceState {
                    now - mExecStartTime);
            mExecStartTime = now;
        }
        if (mForegroundState != STATE_NOTHING) {
            mDurations.addDuration(SERVICE_FOREGROUND + (mForegroundState*SERVICE_COUNT),
                    now - mForegroundStartTime);
            mForegroundStartTime = now;
        }
    }

    private void updateRunning(int memFactor, long now) {
        final int state = (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING
                || mExecState != STATE_NOTHING) ? memFactor : STATE_NOTHING;
                || mExecState != STATE_NOTHING || mForegroundState != STATE_NOTHING)
                ? memFactor : STATE_NOTHING;
        if (mRunState != state) {
            if (mRunState != STATE_NOTHING) {
                mDurations.addDuration(SERVICE_RUN + (mRunState*SERVICE_COUNT),
@@ -348,6 +366,24 @@ public final class ServiceState {
        }
    }

    public void setForeground(boolean foreground, int memFactor, long now) {
        if (mOwner == null) {
            Slog.wtf(TAG, "Foregrounding service " + this + " without owner");
        }
        final int state = foreground ? memFactor : STATE_NOTHING;
        if (mForegroundState != state) {
            if (mForegroundState != STATE_NOTHING) {
                mDurations.addDuration(SERVICE_FOREGROUND + (mForegroundState*SERVICE_COUNT),
                        now - mForegroundStartTime);
            } else if (foreground) {
                mForegroundCount++;
            }
            mForegroundState = state;
            mForegroundStartTime = now;
            updateRunning(memFactor, now);
        }
    }

    public long getDuration(int opType, int curState, long startTime, int memFactor,
            long now) {
        int state = opType + (memFactor*SERVICE_COUNT);
@@ -366,6 +402,9 @@ public final class ServiceState {
        dumpStats(pw, prefix, prefixInner, headerPrefix, "Started",
                mStartedCount, ServiceState.SERVICE_STARTED, mStartedState,
                mStartedStartTime, now, totalTime, !dumpSummary || dumpAll);
        dumpStats(pw, prefix, prefixInner, headerPrefix, "Foreground",
                mForegroundCount, ServiceState.SERVICE_FOREGROUND, mForegroundState,
                mForegroundStartTime, now, totalTime, !dumpSummary || dumpAll);
        dumpStats(pw, prefix, prefixInner, headerPrefix, "Bound",
                mBoundCount, ServiceState.SERVICE_BOUND, mBoundState,
                mBoundStartTime, now, totalTime, !dumpSummary || dumpAll);
@@ -393,11 +432,19 @@ public final class ServiceState {
                pw.print(" op count "); pw.print(count); pw.println(":");
                dumpTime(pw, prefixInner, serviceType, state, startTime, now);
            } else {
                long myTime = dumpTime(null, null, serviceType, state, startTime, now);
                long myTime = dumpTimeInternal(null, null, serviceType, state, startTime, now,
                        true);
                pw.print(prefix); pw.print(headerPrefix); pw.print(header);
                pw.print(" count "); pw.print(count);
                pw.print(" / time ");
                boolean isRunning = myTime < 0;
                if (isRunning) {
                    myTime = -myTime;
                }
                DumpUtils.printPercent(pw, (double)myTime/(double)totalTime);
                if (isRunning) {
                    pw.print(" (running)");
                }
                pw.println();
            }
        }
@@ -405,8 +452,14 @@ public final class ServiceState {

    public long dumpTime(PrintWriter pw, String prefix,
            int serviceType, int curState, long curStartTime, long now) {
        return dumpTimeInternal(pw, prefix, serviceType, curState, curStartTime, now, false);
    }

    long dumpTimeInternal(PrintWriter pw, String prefix,
            int serviceType, int curState, long curStartTime, long now, boolean negativeIfRunning) {
        long totalTime = 0;
        int printedScreen = -1;
        boolean isRunning = false;
        for (int iscreen=0; iscreen<ProcessStats.ADJ_COUNT; iscreen+=ProcessStats.ADJ_SCREEN_MOD) {
            int printedMem = -1;
            for (int imem=0; imem<ProcessStats.ADJ_MEM_FACTOR_COUNT; imem++) {
@@ -415,6 +468,7 @@ public final class ServiceState {
                String running = "";
                if (curState == state && pw != null) {
                    running = " (running)";
                    isRunning = true;
                }
                if (time != 0) {
                    if (pw != null) {
@@ -438,7 +492,7 @@ public final class ServiceState {
            TimeUtils.formatDuration(totalTime, pw);
            pw.println();
        }
        return totalTime;
        return (isRunning && negativeIfRunning) ? -totalTime : totalTime;
    }

    public void dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers,
@@ -447,6 +501,9 @@ public final class ServiceState {
                ServiceState.SERVICE_RUN, mRunCount, mRunState, mRunStartTime, now);
        dumpTimeCheckin(pw, "pkgsvc-start", pkgName, uid, vers, serviceName,
                ServiceState.SERVICE_STARTED, mStartedCount, mStartedState, mStartedStartTime, now);
        dumpTimeCheckin(pw, "pkgsvc-fg", pkgName, uid, vers, serviceName,
                ServiceState.SERVICE_FOREGROUND, mForegroundCount, mForegroundState,
                mForegroundStartTime, now);
        dumpTimeCheckin(pw, "pkgsvc-bound", pkgName, uid, vers, serviceName,
                ServiceState.SERVICE_BOUND, mBoundCount, mBoundState, mBoundStartTime, now);
        dumpTimeCheckin(pw, "pkgsvc-exec", pkgName, uid, vers, serviceName,
Loading