content: /*
   * 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.server.am;
  
  import android.os.PowerManagerInternal;
  import android.os.Process;
  import android.os.SystemClock;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.os.BackgroundThread;
  import com.android.internal.os.ProcessCpuTracker;
  import com.android.internal.util.RingBuffer;
  import com.android.internal.util.function.pooled.PooledLambda;
  
  import java.io.PrintWriter;
  
  public class OomAdjProfiler {
      // Disable profiling for Q. Re-enable once b/130635979 is fixed.
      private static final boolean PROFILING_DISABLED = true;
  
      @GuardedBy("this")
      private boolean mOnBattery;
      @GuardedBy("this")
      private boolean mScreenOff;
  
      @GuardedBy("this")
      private long mOomAdjStartTimeMs;
      @GuardedBy("this")
      private boolean mOomAdjStarted;
  
      @GuardedBy("this")
      private CpuTimes mOomAdjRunTime = new CpuTimes();
      @GuardedBy("this")
      private CpuTimes mSystemServerCpuTime = new CpuTimes();
  
      @GuardedBy("this")
      private long mLastSystemServerCpuTimeMs;
      @GuardedBy("this")
      private boolean mSystemServerCpuTimeUpdateScheduled;
      private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(false);
  
      @GuardedBy("this")
      final RingBuffer<CpuTimes> mOomAdjRunTimesHist = new RingBuffer<>(CpuTimes.class, 10);
      @GuardedBy("this")
      final RingBuffer<CpuTimes> mSystemServerCpuTimesHist = new RingBuffer<>(CpuTimes.class, 10);
  
      void batteryPowerChanged(boolean onBattery) {
          if (PROFILING_DISABLED) {
              return;
          }
          synchronized (this) {
              scheduleSystemServerCpuTimeUpdate();
              mOnBattery = onBattery;
          }
      }
  
      void onWakefulnessChanged(int wakefulness) {
          if (PROFILING_DISABLED) {
              return;
          }
          synchronized (this) {
              scheduleSystemServerCpuTimeUpdate();
              mScreenOff = wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE;
          }
      }
  
      void oomAdjStarted() {
          if (PROFILING_DISABLED) {
              return;
          }
          synchronized (this) {
              mOomAdjStartTimeMs = SystemClock.currentThreadTimeMillis();
              mOomAdjStarted = true;
          }
      }
  
      void oomAdjEnded() {
          if (PROFILING_DISABLED) {
              return;
          }
          synchronized (this) {
              if (!mOomAdjStarted) {
                  return;
              }
              mOomAdjRunTime.addCpuTimeMs(SystemClock.currentThreadTimeMillis() - mOomAdjStartTimeMs);
          }
      }
  
      private void scheduleSystemServerCpuTimeUpdate() {
          if (PROFILING_DISABLED) {
              return;
          }
          synchronized (this) {
              if (mSystemServerCpuTimeUpdateScheduled) {
                  return;
              }
              mSystemServerCpuTimeUpdateScheduled = true;
              BackgroundThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                      OomAdjProfiler::updateSystemServerCpuTime,
                      this, mOnBattery, mScreenOff));
          }
      }
  
      private void updateSystemServerCpuTime(boolean onBattery, boolean screenOff) {
          if (PROFILING_DISABLED) {
              return;
          }
          final long cpuTimeMs = mProcessCpuTracker.getCpuTimeForPid(Process.myPid());
          synchronized (this) {
              mSystemServerCpuTime.addCpuTimeMs(
                      cpuTimeMs - mLastSystemServerCpuTimeMs, onBattery, screenOff);
              mLastSystemServerCpuTimeMs = cpuTimeMs;
              mSystemServerCpuTimeUpdateScheduled = false;
              notifyAll();
          }
      }
  
      void reset() {
          synchronized (this) {
              if (mSystemServerCpuTime.isEmpty()) {
                  return;
              }
              mOomAdjRunTimesHist.append(mOomAdjRunTime);
              mSystemServerCpuTimesHist.append(mSystemServerCpuTime);
              mOomAdjRunTime = new CpuTimes();
              mSystemServerCpuTime = new CpuTimes();
          }
      }
  
      void dump(PrintWriter pw) {
          if (PROFILING_DISABLED) {
              return;
          }
          synchronized (this) {
              if (mSystemServerCpuTimeUpdateScheduled) {
                  while (mSystemServerCpuTimeUpdateScheduled) {
                      try {
                          wait();
                      } catch (InterruptedException e) {
                          Thread.currentThread().interrupt();
                      }
                  }
              } else {
                  updateSystemServerCpuTime(mOnBattery, mScreenOff);
              }
  
              pw.println("System server and oomAdj runtimes (ms) in recent battery sessions "
                      + "(most recent first):");
              if (!mSystemServerCpuTime.isEmpty()) {
                  pw.print("  ");
                  pw.print("system_server=");
                  pw.print(mSystemServerCpuTime);
                  pw.print("  ");
                  pw.print("oom_adj=");
                  pw.println(mOomAdjRunTime);
              }
              final CpuTimes[] systemServerCpuTimes = mSystemServerCpuTimesHist.toArray();
              final CpuTimes[] oomAdjRunTimes = mOomAdjRunTimesHist.toArray();
              for (int i = oomAdjRunTimes.length - 1; i >= 0; --i) {
                  pw.print("  ");
                  pw.print("system_server=");
                  pw.print(systemServerCpuTimes[i]);
                  pw.print("  ");
                  pw.print("oom_adj=");
                  pw.println(oomAdjRunTimes[i]);
              }
          }
      }
  
      private class CpuTimes {
          private long mOnBatteryTimeMs;
          private long mOnBatteryScreenOffTimeMs;
  
          public void addCpuTimeMs(long cpuTimeMs) {
              addCpuTimeMs(cpuTimeMs, mOnBattery, mScreenOff);
          }
  
          public void addCpuTimeMs(long cpuTimeMs, boolean onBattery, boolean screenOff) {
              if (onBattery) {
                  mOnBatteryTimeMs += cpuTimeMs;
                  if (screenOff) {
                      mOnBatteryScreenOffTimeMs += cpuTimeMs;
                  }
              }
          }
  
          public boolean isEmpty() {
              return mOnBatteryTimeMs == 0 && mOnBatteryScreenOffTimeMs == 0;
          }
  
          public String toString() {
              return "[" + mOnBatteryTimeMs + "," + mOnBatteryScreenOffTimeMs + "]";
          }
      }
  }
  