content: /*
   * Copyright (C) 2013 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.service.procstats.PackageServiceOperationStatsProto;
  import android.service.procstats.PackageServiceStatsProto;
  import android.service.procstats.ProcessStatsEnums;
  import android.util.Slog;
  import android.util.TimeUtils;
  import android.util.proto.ProtoOutputStream;
  
  import static com.android.internal.app.procstats.ProcessStats.STATE_NOTHING;
  
  import java.io.PrintWriter;
  
  public final class ServiceState {
      private static final String TAG = "ProcessStats";
      private static final boolean DEBUG = false;
  
      public static final int SERVICE_RUN = 0;
      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_FOREGROUND = 4;
      public static final int SERVICE_COUNT = 5;
  
      private final String mPackage;
      private final String mProcessName;
      private final String mName;
      private final DurationsTable mDurations;
  
      private ProcessState mProc;
      private Object mOwner;
  
      private int mRunCount;
      private int mRunState = STATE_NOTHING;
      private long mRunStartTime;
  
      private boolean mStarted;
      private boolean mRestarting;
      private int mStartedCount;
      private int mStartedState = STATE_NOTHING;
      private long mStartedStartTime;
  
      private int mBoundCount;
      private int mBoundState = STATE_NOTHING;
      private long mBoundStartTime;
  
      private int mExecCount;
      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;
          mName = name;
          mProcessName = processName;
          mProc = proc;
          mDurations = new DurationsTable(processStats.mTableData);
      }
  
      public String getPackage() {
          return mPackage;
      }
      
      public String getProcessName() {
          return mProcessName;
      }
  
      public String getName() {
          return mName;
      }
  
      public ProcessState getProcess() {
          return mProc;
      }
  
      public void setProcess(ProcessState proc) {
          mProc = proc;
      }
  
      public void setMemFactor(int memFactor, long now) {
          if (isRestarting()) {
              setRestarting(true, memFactor, now);
          } else if (isInUse()) {
              if (mStartedState != ProcessStats.STATE_NOTHING) {
                  setStarted(true, memFactor, now);
              }
              if (mBoundState != ProcessStats.STATE_NOTHING) {
                  setBound(true, memFactor, now);
              }
              if (mExecState != ProcessStats.STATE_NOTHING) {
                  setExecuting(true, memFactor, now);
              }
              if (mForegroundState != ProcessStats.STATE_NOTHING) {
                  setForeground(true, memFactor, now);
              }
          }
      }
  
      public void applyNewOwner(Object newOwner) {
          if (mOwner != newOwner) {
              if (mOwner == null) {
                  mOwner = newOwner;
                  mProc.incActiveServices(mName);
              } else {
                  // There was already an old owner, reset this object for its
                  // new owner.
                  mOwner = newOwner;
                  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
                                  + " from " + mOwner + " while started: pkg="
                                  + mPackage + " service=" + mName + " proc=" + mProc);
                          setStarted(false, 0, now);
                      }
                      if (mBoundState != STATE_NOTHING) {
                          if (DEBUG) Slog.d(TAG, "Service has new owner " + newOwner
                                  + " from " + mOwner + " while bound: pkg="
                                  + mPackage + " service=" + mName + " proc=" + mProc);
                          setBound(false, 0, now);
                      }
                      if (mExecState != STATE_NOTHING) {
                          if (DEBUG) Slog.d(TAG, "Service has new owner " + newOwner
                                  + " from " + mOwner + " while executing: pkg="
                                  + 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);
                      }
                  }
              }
          }
      }
  
      public void clearCurrentOwner(Object owner, boolean silently) {
          if (mOwner == owner) {
              mProc.decActiveServices(mName);
              if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING
                      || mForegroundState != STATE_NOTHING) {
                  long now = SystemClock.uptimeMillis();
                  if (mStarted) {
                      if (!silently) {
                          Slog.wtfStack(TAG, "Service owner " + owner
                                  + " cleared while started: pkg=" + mPackage + " service="
                                  + mName + " proc=" + mProc);
                      }
                      setStarted(false, 0, now);
                  }
                  if (mBoundState != STATE_NOTHING) {
                      if (!silently) {
                          Slog.wtfStack(TAG, "Service owner " + owner
                                  + " cleared while bound: pkg=" + mPackage + " service="
                                  + mName + " proc=" + mProc);
                      }
                      setBound(false, 0, now);
                  }
                  if (mExecState != STATE_NOTHING) {
                      if (!silently) {
                          Slog.wtfStack(TAG, "Service owner " + owner
                                  + " cleared while exec: pkg=" + mPackage + " service="
                                  + mName + " proc=" + mProc);
                      }
                      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;
          }
      }
  
      public boolean isInUse() {
          return mOwner != null || mRestarting;
      }
  
      public boolean isRestarting() {
          return mRestarting;
      }
  
      public void add(ServiceState other) {
          mDurations.addDurations(other.mDurations);
          mRunCount += other.mRunCount;
          mStartedCount += other.mStartedCount;
          mBoundCount += other.mBoundCount;
          mExecCount += other.mExecCount;
          mForegroundCount += other.mForegroundCount;
      }
  
      public void resetSafely(long now) {
          mDurations.resetTable();
          mRunCount = mRunState != STATE_NOTHING ? 1 : 0;
          mStartedCount = mStartedState != STATE_NOTHING ? 1 : 0;
          mBoundCount = mBoundState != STATE_NOTHING ? 1 : 0;
          mExecCount = mExecState != STATE_NOTHING ? 1 : 0;
          mForegroundCount = mForegroundState != STATE_NOTHING ? 1 : 0;
          mRunStartTime = mStartedStartTime = mBoundStartTime = mExecStartTime =
                  mForegroundStartTime = now;
      }
  
      public void writeToParcel(Parcel out, long now) {
          mDurations.writeToParcel(out);
          out.writeInt(mRunCount);
          out.writeInt(mStartedCount);
          out.writeInt(mBoundCount);
          out.writeInt(mExecCount);
          out.writeInt(mForegroundCount);
      }
  
      public boolean readFromParcel(Parcel in) {
          if (!mDurations.readFromParcel(in)) {
              return false;
          }
          mRunCount = in.readInt();
          mStartedCount = in.readInt();
          mBoundCount = in.readInt();
          mExecCount = in.readInt();
          mForegroundCount = in.readInt();
          return true;
      }
  
      public void commitStateTime(long now) {
          if (mRunState != STATE_NOTHING) {
              mDurations.addDuration(SERVICE_RUN + (mRunState*SERVICE_COUNT),
                      now - mRunStartTime);
              mRunStartTime = now;
          }
          if (mStartedState != STATE_NOTHING) {
              mDurations.addDuration(SERVICE_STARTED + (mStartedState*SERVICE_COUNT),
                      now - mStartedStartTime);
              mStartedStartTime = now;
          }
          if (mBoundState != STATE_NOTHING) {
              mDurations.addDuration(SERVICE_BOUND + (mBoundState*SERVICE_COUNT),
                      now - mBoundStartTime);
              mBoundStartTime = now;
          }
          if (mExecState != STATE_NOTHING) {
              mDurations.addDuration(SERVICE_EXEC + (mExecState*SERVICE_COUNT),
                      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 || mForegroundState != STATE_NOTHING)
                  ? memFactor : STATE_NOTHING;
          if (mRunState != state) {
              if (mRunState != STATE_NOTHING) {
                  mDurations.addDuration(SERVICE_RUN + (mRunState*SERVICE_COUNT),
                          now - mRunStartTime);
              } else if (state != STATE_NOTHING) {
                  mRunCount++;
              }
              mRunState = state;
              mRunStartTime = now;
          }
      }
  
      public void setStarted(boolean started, int memFactor, long now) {
          if (mOwner == null) {
              Slog.wtf(TAG, "Starting service " + this + " without owner");
          }
          mStarted = started;
          updateStartedState(memFactor, now);
      }
  
      public void setRestarting(boolean restarting, int memFactor, long now) {
          mRestarting = restarting;
          updateStartedState(memFactor, now);
      }
  
      public void updateStartedState(int memFactor, long now) {
          final boolean wasStarted = mStartedState != STATE_NOTHING;
          final boolean started = mStarted || mRestarting;
          final int state = started ? memFactor : STATE_NOTHING;
          if (mStartedState != state) {
              if (mStartedState != STATE_NOTHING) {
                  mDurations.addDuration(SERVICE_STARTED + (mStartedState*SERVICE_COUNT),
                          now - mStartedStartTime);
              } else if (started) {
                  mStartedCount++;
              }
              mStartedState = state;
              mStartedStartTime = now;
              mProc = mProc.pullFixedProc(mPackage);
              if (wasStarted != started) {
                  if (started) {
                      mProc.incStartedServices(memFactor, now, mName);
                  } else {
                      mProc.decStartedServices(memFactor, now, mName);
                  }
              }
              updateRunning(memFactor, now);
          }
      }
  
      public void setBound(boolean bound, int memFactor, long now) {
          if (mOwner == null) {
              Slog.wtf(TAG, "Binding service " + this + " without owner");
          }
          final int state = bound ? memFactor : STATE_NOTHING;
          if (mBoundState != state) {
              if (mBoundState != STATE_NOTHING) {
                  mDurations.addDuration(SERVICE_BOUND + (mBoundState*SERVICE_COUNT),
                          now - mBoundStartTime);
              } else if (bound) {
                  mBoundCount++;
              }
              mBoundState = state;
              mBoundStartTime = now;
              updateRunning(memFactor, now);
          }
      }
  
      public void setExecuting(boolean executing, int memFactor, long now) {
          if (mOwner == null) {
              Slog.wtf(TAG, "Executing service " + this + " without owner");
          }
          final int state = executing ? memFactor : STATE_NOTHING;
          if (mExecState != state) {
              if (mExecState != STATE_NOTHING) {
                  mDurations.addDuration(SERVICE_EXEC + (mExecState*SERVICE_COUNT),
                          now - mExecStartTime);
              } else if (executing) {
                  mExecCount++;
              }
              mExecState = state;
              mExecStartTime = now;
              updateRunning(memFactor, now);
          }
      }
  
      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);
          long time = mDurations.getValueForId((byte)state);
          if (curState == memFactor) {
              time += now - startTime;
          }
          return time;
      }
  
      public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix,
              long now, long totalTime, boolean dumpSummary, boolean dumpAll) {
          dumpStats(pw, prefix, prefixInner, headerPrefix, "Running",
                  mRunCount, ServiceState.SERVICE_RUN, mRunState,
                  mRunStartTime, now, totalTime, !dumpSummary || dumpAll);
          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);
          dumpStats(pw, prefix, prefixInner, headerPrefix, "Executing",
                  mExecCount, ServiceState.SERVICE_EXEC, mExecState,
                  mExecStartTime, now, totalTime, !dumpSummary || dumpAll);
          if (dumpAll) {
              if (mOwner != null) {
                  pw.print("        mOwner="); pw.println(mOwner);
              }
              if (mStarted || mRestarting) {
                  pw.print("        mStarted="); pw.print(mStarted);
                  pw.print(" mRestarting="); pw.println(mRestarting);
              }
          }
      }
  
      private void dumpStats(PrintWriter pw, String prefix, String prefixInner,
              String headerPrefix, String header,
              int count, int serviceType, int state, long startTime, long now, long totalTime,
              boolean dumpAll) {
          if (count != 0) {
              if (dumpAll) {
                  pw.print(prefix); pw.print(header);
                  pw.print(" op count "); pw.print(count); pw.println(":");
                  dumpTime(pw, prefixInner, serviceType, state, startTime, now);
              } else {
                  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();
              }
          }
      }
  
      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++) {
                  int state = imem+iscreen;
                  long time = getDuration(serviceType, curState, curStartTime, state, now);
                  String running = "";
                  if (curState == state && pw != null) {
                      running = " (running)";
                      isRunning = true;
                  }
                  if (time != 0) {
                      if (pw != null) {
                          pw.print(prefix);
                          DumpUtils.printScreenLabel(pw, printedScreen != iscreen
                                  ? iscreen : STATE_NOTHING);
                          printedScreen = iscreen;
                          DumpUtils.printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING,
                                  (char)0);
                          printedMem = imem;
                          pw.print(": ");
                          TimeUtils.formatDuration(time, pw); pw.println(running);
                      }
                      totalTime += time;
                  }
              }
          }
          if (totalTime != 0 && pw != null) {
              pw.print(prefix);
              pw.print("    TOTAL: ");
              TimeUtils.formatDuration(totalTime, pw);
              pw.println();
          }
          return (isRunning && negativeIfRunning) ? -totalTime : totalTime;
      }
  
      public void dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers,
              String serviceName, long now) {
          dumpTimeCheckin(pw, "pkgsvc-run", pkgName, uid, vers, serviceName,
                  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,
                  ServiceState.SERVICE_EXEC, mExecCount, mExecState, mExecStartTime, now);
      }
  
      private void dumpTimeCheckin(PrintWriter pw, String label, String packageName,
              int uid, long vers, String serviceName, int serviceType, int opCount,
              int curState, long curStartTime, long now) {
          if (opCount <= 0) {
              return;
          }
          pw.print(label);
          pw.print(",");
          pw.print(packageName);
          pw.print(",");
          pw.print(uid);
          pw.print(",");
          pw.print(vers);
          pw.print(",");
          pw.print(serviceName);
          pw.print(",");
          pw.print(opCount);
          boolean didCurState = false;
          final int N = mDurations.getKeyCount();
          for (int i=0; i<N; i++) {
              final int key = mDurations.getKeyAt(i);
              long time = mDurations.getValue(key);
              int type = SparseMappingTable.getIdFromKey(key);
              int memFactor = type / ServiceState.SERVICE_COUNT;
              type %= ServiceState.SERVICE_COUNT;
              if (type != serviceType) {
                  continue;
              }
              if (curState == memFactor) {
                  didCurState = true;
                  time += now - curStartTime;
              }
              DumpUtils.printAdjTagAndValue(pw, memFactor, time);
          }
          if (!didCurState && curState != STATE_NOTHING) {
              DumpUtils.printAdjTagAndValue(pw, curState, now - curStartTime);
          }
          pw.println();
      }
  
      public void writeToProto(ProtoOutputStream proto, long fieldId, long now) {
          final long token = proto.start(fieldId);
          proto.write(PackageServiceStatsProto.SERVICE_NAME, mName);
  
          writeTypeToProto(proto, PackageServiceStatsProto.OPERATION_STATS,
                  ProcessStatsEnums.SERVICE_OPERATION_STATE_RUNNING,
                  ServiceState.SERVICE_RUN, mRunCount, mRunState, mRunStartTime, now);
  
          writeTypeToProto(proto, PackageServiceStatsProto.OPERATION_STATS,
                  ProcessStatsEnums.SERVICE_OPERATION_STATE_STARTED,
                  ServiceState.SERVICE_STARTED, mStartedCount, mStartedState, mStartedStartTime, now);
  
          writeTypeToProto(proto, PackageServiceStatsProto.OPERATION_STATS,
                  ProcessStatsEnums.SERVICE_OPERATION_STATE_FOREGROUND,
                  ServiceState.SERVICE_FOREGROUND, mForegroundCount, mForegroundState,
                  mForegroundStartTime, now);
  
          writeTypeToProto(proto, PackageServiceStatsProto.OPERATION_STATS,
                  ProcessStatsEnums.SERVICE_OPERATION_STATE_BOUND,
                  ServiceState.SERVICE_BOUND, mBoundCount, mBoundState, mBoundStartTime, now);
  
          writeTypeToProto(proto, PackageServiceStatsProto.OPERATION_STATS,
                  ProcessStatsEnums.SERVICE_OPERATION_STATE_EXECUTING,
                  ServiceState.SERVICE_EXEC, mExecCount, mExecState, mExecStartTime, now);
  
          proto.end(token);
      }
  
      /**
       * write the metrics to proto for each operation type.
       */
      public void writeTypeToProto(ProtoOutputStream proto, long fieldId, int opType, int serviceType,
              int opCount, int curState, long curStartTime, long now) {
          if (opCount <= 0) {
              return;
          }
          final long token = proto.start(fieldId);
  
          proto.write(PackageServiceOperationStatsProto.OPERATION, opType);
          proto.write(PackageServiceOperationStatsProto.COUNT, opCount);
  
          boolean didCurState = false;
          final int N = mDurations.getKeyCount();
          for (int i=0; i<N; i++) {
              final int key = mDurations.getKeyAt(i);
              long time = mDurations.getValue(key);
              int type = SparseMappingTable.getIdFromKey(key);
              int memFactor = type / ServiceState.SERVICE_COUNT;
              type %= ServiceState.SERVICE_COUNT;
              if (type != serviceType) {
                  continue;
              }
              if (curState == memFactor) {
                  didCurState = true;
                  time += now - curStartTime;
              }
              final long stateToken = proto.start(PackageServiceOperationStatsProto.STATE_STATS);
              DumpUtils.printProcStateAdjTagProto(proto,
                      PackageServiceOperationStatsProto.StateStats.SCREEN_STATE,
                      PackageServiceOperationStatsProto.StateStats.MEMORY_STATE,
                      type);
              proto.write(PackageServiceOperationStatsProto.StateStats.DURATION_MS, time);
              proto.end(stateToken);
          }
          if (!didCurState && curState != STATE_NOTHING) {
              final long stateToken = proto.start(PackageServiceOperationStatsProto.STATE_STATS);
              DumpUtils.printProcStateAdjTagProto(proto,
                      PackageServiceOperationStatsProto.StateStats.SCREEN_STATE,
                      PackageServiceOperationStatsProto.StateStats.MEMORY_STATE,
                      curState);
              proto.write(PackageServiceOperationStatsProto.StateStats.DURATION_MS,
                      now - curStartTime);
              proto.end(stateToken);
          }
  
          proto.end(token);
      }
  
      public String toString() {
          return "ServiceState{" + Integer.toHexString(System.identityHashCode(this))
                  + " " + mName + " pkg=" + mPackage + " proc="
                  + Integer.toHexString(System.identityHashCode(mProc)) + "}";
      }
  }
  