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.internal.os;
  
  import static com.android.internal.os.KernelCpuProcStringReader.asLongs;
  import static com.android.internal.util.Preconditions.checkNotNull;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.os.StrictMode;
  import android.os.SystemClock;
  import android.util.IntArray;
  import android.util.Slog;
  import android.util.SparseArray;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator;
  
  import java.io.BufferedReader;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.nio.CharBuffer;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  
  /**
   * Reads per-UID CPU time proc files. Concrete implementations are all nested inside.
   *
   * This class uses a throttler to reject any {@link #readDelta} or {@link #readAbsolute} call
   * within {@link #mMinTimeBetweenRead}. The throttler can be enable / disabled via a param in
   * the constructor.
   *
   * This class and its subclasses are NOT thread-safe and NOT designed to be accessed by more than
   * one caller since each caller has its own view of delta.
   *
   * @param <T> The type of CPU time for the callback.
   */
  public abstract class KernelCpuUidTimeReader<T> {
      protected static final boolean DEBUG = false;
      private static final long DEFAULT_MIN_TIME_BETWEEN_READ = 1000L; // In milliseconds
  
      final String mTag = this.getClass().getSimpleName();
      final SparseArray<T> mLastTimes = new SparseArray<>();
      final KernelCpuProcStringReader mReader;
      final boolean mThrottle;
      private long mMinTimeBetweenRead = DEFAULT_MIN_TIME_BETWEEN_READ;
      private long mLastReadTimeMs = 0;
  
      /**
       * Callback interface for processing each line of the proc file.
       *
       * @param <T> The type of CPU time for the callback function.
       */
      public interface Callback<T> {
          /**
           * @param uid  UID of the app
           * @param time Time spent. The exact data structure depends on subclass implementation.
           */
          void onUidCpuTime(int uid, T time);
      }
  
      KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
          mReader = reader;
          mThrottle = throttle;
      }
  
      /**
       * Reads the proc file, calling into the callback with a delta of time for each UID.
       *
       * @param cb The callback to invoke for each line of the proc file. If null,the data is
       *           consumed and subsequent calls to readDelta will provide a fresh delta.
       */
      public void readDelta(@Nullable Callback<T> cb) {
          if (!mThrottle) {
              readDeltaImpl(cb);
              return;
          }
          final long currTimeMs = SystemClock.elapsedRealtime();
          if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
              if (DEBUG) {
                  Slog.d(mTag, "Throttle readDelta");
              }
              return;
          }
          readDeltaImpl(cb);
          mLastReadTimeMs = currTimeMs;
      }
  
      /**
       * Reads the proc file, calling into the callback with cumulative time for each UID.
       *
       * @param cb The callback to invoke for each line of the proc file. It cannot be null.
       */
      public void readAbsolute(Callback<T> cb) {
          if (!mThrottle) {
              readAbsoluteImpl(cb);
              return;
          }
          final long currTimeMs = SystemClock.elapsedRealtime();
          if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) {
              if (DEBUG) {
                  Slog.d(mTag, "Throttle readAbsolute");
              }
              return;
          }
          readAbsoluteImpl(cb);
          mLastReadTimeMs = currTimeMs;
      }
  
      abstract void readDeltaImpl(@Nullable Callback<T> cb);
  
      abstract void readAbsoluteImpl(Callback<T> callback);
  
      /**
       * Removes the UID from internal accounting data. This method, overridden in
       * {@link KernelCpuUidUserSysTimeReader}, also removes the UID from the kernel module.
       *
       * @param uid The UID to remove.
       * @see KernelCpuUidUserSysTimeReader#removeUid(int)
       */
      public void removeUid(int uid) {
          mLastTimes.delete(uid);
      }
  
      /**
       * Removes UIDs in a given range from internal accounting data. This method, overridden in
       * {@link KernelCpuUidUserSysTimeReader}, also removes the UIDs from the kernel module.
       *
       * @param startUid the first uid to remove.
       * @param endUid   the last uid to remove.
       * @see KernelCpuUidUserSysTimeReader#removeUidsInRange(int, int)
       */
      public void removeUidsInRange(int startUid, int endUid) {
          if (endUid < startUid) {
              Slog.e(mTag, "start UID " + startUid + " > end UID " + endUid);
              return;
          }
          mLastTimes.put(startUid, null);
          mLastTimes.put(endUid, null);
          final int firstIndex = mLastTimes.indexOfKey(startUid);
          final int lastIndex = mLastTimes.indexOfKey(endUid);
          mLastTimes.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
      }
  
      /**
       * Set the minimum time in milliseconds between reads. If throttle is not enabled, this method
       * has no effect.
       *
       * @param minTimeBetweenRead The minimum time in milliseconds.
       */
      public void setThrottle(long minTimeBetweenRead) {
          if (mThrottle && minTimeBetweenRead >= 0) {
              mMinTimeBetweenRead = minTimeBetweenRead;
          }
      }
  
      /**
       * Reads /proc/uid_cputime/show_uid_stat which has the line format:
       *
       * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds
       *
       * This provides the time a UID's processes spent executing in user-space and kernel-space.
       * The file contains a monotonically increasing count of time for a single boot. This class
       * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
       * delta.
       *
       * The second parameter of the callback is a long[] with 2 elements, [user time in us, system
       * time in us].
       */
      public static class KernelCpuUidUserSysTimeReader extends KernelCpuUidTimeReader<long[]> {
          private static final String REMOVE_UID_PROC_FILE = "/proc/uid_cputime/remove_uid_range";
  
          // [uid, user_time, system_time, (maybe) power_in_milli-amp-micro_seconds]
          private final long[] mBuffer = new long[4];
          // A reusable array to hold [user_time, system_time] for the callback.
          private final long[] mUsrSysTime = new long[2];
  
          public KernelCpuUidUserSysTimeReader(boolean throttle) {
              super(KernelCpuProcStringReader.getUserSysTimeReaderInstance(), throttle);
          }
  
          @VisibleForTesting
          public KernelCpuUidUserSysTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
              super(reader, throttle);
          }
  
          @Override
          void readDeltaImpl(@Nullable Callback<long[]> cb) {
              try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                  if (iter == null) {
                      return;
                  }
                  CharBuffer buf;
                  while ((buf = iter.nextLine()) != null) {
                      if (asLongs(buf, mBuffer) < 3) {
                          Slog.wtf(mTag, "Invalid line: " + buf.toString());
                          continue;
                      }
                      final int uid = (int) mBuffer[0];
                      long[] lastTimes = mLastTimes.get(uid);
                      if (lastTimes == null) {
                          lastTimes = new long[2];
                          mLastTimes.put(uid, lastTimes);
                      }
                      final long currUsrTimeUs = mBuffer[1];
                      final long currSysTimeUs = mBuffer[2];
                      mUsrSysTime[0] = currUsrTimeUs - lastTimes[0];
                      mUsrSysTime[1] = currSysTimeUs - lastTimes[1];
  
                      if (mUsrSysTime[0] < 0 || mUsrSysTime[1] < 0) {
                          Slog.e(mTag, "Negative user/sys time delta for UID=" + uid
                                  + "\nPrev times: u=" + lastTimes[0] + " s=" + lastTimes[1]
                                  + " Curr times: u=" + currUsrTimeUs + " s=" + currSysTimeUs);
                      } else if (mUsrSysTime[0] > 0 || mUsrSysTime[1] > 0) {
                          if (cb != null) {
                              cb.onUidCpuTime(uid, mUsrSysTime);
                          }
                      }
                      lastTimes[0] = currUsrTimeUs;
                      lastTimes[1] = currSysTimeUs;
                  }
              }
          }
  
          @Override
          void readAbsoluteImpl(Callback<long[]> cb) {
              try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                  if (iter == null) {
                      return;
                  }
                  CharBuffer buf;
                  while ((buf = iter.nextLine()) != null) {
                      if (asLongs(buf, mBuffer) < 3) {
                          Slog.wtf(mTag, "Invalid line: " + buf.toString());
                          continue;
                      }
                      mUsrSysTime[0] = mBuffer[1]; // User time in microseconds
                      mUsrSysTime[1] = mBuffer[2]; // System time in microseconds
                      cb.onUidCpuTime((int) mBuffer[0], mUsrSysTime);
                  }
              }
          }
  
          @Override
          public void removeUid(int uid) {
              super.removeUid(uid);
              removeUidsFromKernelModule(uid, uid);
          }
  
          @Override
          public void removeUidsInRange(int startUid, int endUid) {
              super.removeUidsInRange(startUid, endUid);
              removeUidsFromKernelModule(startUid, endUid);
          }
  
          /**
           * Removes UIDs in a given range from the kernel module and internal accounting data. Only
           * {@link BatteryStatsImpl} and its child processes should call this, as the change on
           * Kernel is
           * visible system wide.
           *
           * @param startUid the first uid to remove
           * @param endUid   the last uid to remove
           */
          private void removeUidsFromKernelModule(int startUid, int endUid) {
              Slog.d(mTag, "Removing uids " + startUid + "-" + endUid);
              final int oldMask = StrictMode.allowThreadDiskWritesMask();
              try (FileWriter writer = new FileWriter(REMOVE_UID_PROC_FILE)) {
                  writer.write(startUid + "-" + endUid);
                  writer.flush();
              } catch (IOException e) {
                  Slog.e(mTag, "failed to remove uids " + startUid + " - " + endUid
                          + " from uid_cputime module", e);
              } finally {
                  StrictMode.setThreadPolicyMask(oldMask);
              }
          }
      }
  
      /**
       * Reads /proc/uid_time_in_state which has the format:
       *
       * uid: [freq1] [freq2] [freq3] ...
       * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
       * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
       * ...
       *
       * This provides the times a UID's processes spent executing at each different cpu frequency.
       * The file contains a monotonically increasing count of time for a single boot. This class
       * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
       * delta.
       */
      public static class KernelCpuUidFreqTimeReader extends KernelCpuUidTimeReader<long[]> {
          private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
          // We check the existence of proc file a few times (just in case it is not ready yet when we
          // start reading) and if it is not available, we simply ignore further read requests.
          private static final int MAX_ERROR_COUNT = 5;
  
          private final Path mProcFilePath;
          private long[] mBuffer;
          private long[] mCurTimes;
          private long[] mDeltaTimes;
          private long[] mCpuFreqs;
  
          private int mFreqCount = 0;
          private int mErrors = 0;
          private boolean mPerClusterTimesAvailable;
          private boolean mAllUidTimesAvailable = true;
  
          public KernelCpuUidFreqTimeReader(boolean throttle) {
              this(UID_TIMES_PROC_FILE, KernelCpuProcStringReader.getFreqTimeReaderInstance(),
                      throttle);
          }
  
          @VisibleForTesting
          public KernelCpuUidFreqTimeReader(String procFile, KernelCpuProcStringReader reader,
                  boolean throttle) {
              super(reader, throttle);
              mProcFilePath = Paths.get(procFile);
          }
  
          /**
           * @return Whether per-cluster times are available.
           */
          public boolean perClusterTimesAvailable() {
              return mPerClusterTimesAvailable;
          }
  
          /**
           * @return Whether all-UID times are available.
           */
          public boolean allUidTimesAvailable() {
              return mAllUidTimesAvailable;
          }
  
          /**
           * @return A map of all UIDs to their CPU time-in-state array in milliseconds.
           */
          public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
              return mLastTimes;
          }
  
          /**
           * Reads a list of CPU frequencies from /proc/uid_time_in_state. Uses a given PowerProfile
           * to determine if per-cluster times are available.
           *
           * @param powerProfile The PowerProfile to compare against.
           * @return A long[] of CPU frequencies in Hz.
           */
          public long[] readFreqs(@NonNull PowerProfile powerProfile) {
              checkNotNull(powerProfile);
              if (mCpuFreqs != null) {
                  // No need to read cpu freqs more than once.
                  return mCpuFreqs;
              }
              if (!mAllUidTimesAvailable) {
                  return null;
              }
              final int oldMask = StrictMode.allowThreadDiskReadsMask();
              try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
                  if (readFreqs(reader.readLine()) == null) {
                      return null;
                  }
              } catch (IOException e) {
                  if (++mErrors >= MAX_ERROR_COUNT) {
                      mAllUidTimesAvailable = false;
                  }
                  Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
                  return null;
              } finally {
                  StrictMode.setThreadPolicyMask(oldMask);
              }
              // Check if the freqs in the proc file correspond to per-cluster freqs.
              final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
              final int numClusters = powerProfile.getNumCpuClusters();
              if (numClusterFreqs.size() == numClusters) {
                  mPerClusterTimesAvailable = true;
                  for (int i = 0; i < numClusters; ++i) {
                      if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
                          mPerClusterTimesAvailable = false;
                          break;
                      }
                  }
              } else {
                  mPerClusterTimesAvailable = false;
              }
              Slog.i(mTag, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
              return mCpuFreqs;
          }
  
          private long[] readFreqs(String line) {
              if (line == null) {
                  return null;
              }
              final String[] lineArray = line.split(" ");
              if (lineArray.length <= 1) {
                  Slog.wtf(mTag, "Malformed freq line: " + line);
                  return null;
              }
              mFreqCount = lineArray.length - 1;
              mCpuFreqs = new long[mFreqCount];
              mCurTimes = new long[mFreqCount];
              mDeltaTimes = new long[mFreqCount];
              mBuffer = new long[mFreqCount + 1];
              for (int i = 0; i < mFreqCount; ++i) {
                  mCpuFreqs[i] = Long.parseLong(lineArray[i + 1], 10);
              }
              return mCpuFreqs;
          }
  
          @Override
          void readDeltaImpl(@Nullable Callback<long[]> cb) {
              try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                  if (!checkPrecondition(iter)) {
                      return;
                  }
                  CharBuffer buf;
                  while ((buf = iter.nextLine()) != null) {
                      if (asLongs(buf, mBuffer) != mBuffer.length) {
                          Slog.wtf(mTag, "Invalid line: " + buf.toString());
                          continue;
                      }
                      final int uid = (int) mBuffer[0];
                      long[] lastTimes = mLastTimes.get(uid);
                      if (lastTimes == null) {
                          lastTimes = new long[mFreqCount];
                          mLastTimes.put(uid, lastTimes);
                      }
                      copyToCurTimes();
                      boolean notify = false;
                      boolean valid = true;
                      for (int i = 0; i < mFreqCount; i++) {
                          // Unit is 10ms.
                          mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
                          if (mDeltaTimes[i] < 0) {
                              Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
                              valid = false;
                          }
                          notify |= mDeltaTimes[i] > 0;
                      }
                      if (notify && valid) {
                          System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
                          if (cb != null) {
                              cb.onUidCpuTime(uid, mDeltaTimes);
                          }
                      }
                  }
              }
          }
  
          @Override
          void readAbsoluteImpl(Callback<long[]> cb) {
              try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                  if (!checkPrecondition(iter)) {
                      return;
                  }
                  CharBuffer buf;
                  while ((buf = iter.nextLine()) != null) {
                      if (asLongs(buf, mBuffer) != mBuffer.length) {
                          Slog.wtf(mTag, "Invalid line: " + buf.toString());
                          continue;
                      }
                      copyToCurTimes();
                      cb.onUidCpuTime((int) mBuffer[0], mCurTimes);
                  }
              }
          }
  
          private void copyToCurTimes() {
              for (int i = 0; i < mFreqCount; i++) {
                  mCurTimes[i] = mBuffer[i + 1] * 10;
              }
          }
  
          private boolean checkPrecondition(ProcFileIterator iter) {
              if (iter == null || !iter.hasNextLine()) {
                  // Error logged in KernelCpuProcStringReader.
                  return false;
              }
              CharBuffer line = iter.nextLine();
              if (mCpuFreqs != null) {
                  return true;
              }
              return readFreqs(line.toString()) != null;
          }
  
          /**
           * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
           * read from the proc file.
           *
           * We need to assume that freqs in each cluster are strictly increasing.
           * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
           * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
           *
           * @return an IntArray filled with no. of freqs in each cluster.
           */
          private IntArray extractClusterInfoFromProcFileFreqs() {
              final IntArray numClusterFreqs = new IntArray();
              int freqsFound = 0;
              for (int i = 0; i < mFreqCount; ++i) {
                  freqsFound++;
                  if (i + 1 == mFreqCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
                      numClusterFreqs.add(freqsFound);
                      freqsFound = 0;
                  }
              }
              return numClusterFreqs;
          }
      }
  
      /**
       * Reads /proc/uid_concurrent_active_time and reports CPU active time to BatteryStats to
       * compute {@link PowerProfile#POWER_CPU_ACTIVE}.
       *
       * /proc/uid_concurrent_active_time has the following format:
       * cpus: n
       * uid0: time0a, time0b, ..., time0n,
       * uid1: time1a, time1b, ..., time1n,
       * uid2: time2a, time2b, ..., time2n,
       * ...
       * where n is the total number of cpus (num_possible_cpus)
       * timeXn means the CPU time that a UID X spent running concurrently with n other processes.
       *
       * The file contains a monotonically increasing count of time for a single boot. This class
       * maintains the previous results of a call to {@link #readDelta} in order to provide a
       * proper delta.
       */
      public static class KernelCpuUidActiveTimeReader extends KernelCpuUidTimeReader<Long> {
          private int mCores = 0;
          private long[] mBuffer;
  
          public KernelCpuUidActiveTimeReader(boolean throttle) {
              super(KernelCpuProcStringReader.getActiveTimeReaderInstance(), throttle);
          }
  
          @VisibleForTesting
          public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
              super(reader, throttle);
          }
  
          @Override
          void readDeltaImpl(@Nullable Callback<Long> cb) {
              try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                  if (!checkPrecondition(iter)) {
                      return;
                  }
                  CharBuffer buf;
                  while ((buf = iter.nextLine()) != null) {
                      if (asLongs(buf, mBuffer) != mBuffer.length) {
                          Slog.wtf(mTag, "Invalid line: " + buf.toString());
                          continue;
                      }
                      int uid = (int) mBuffer[0];
                      long cpuActiveTime = sumActiveTime(mBuffer);
                      if (cpuActiveTime > 0) {
                          long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
                          if (delta > 0) {
                              mLastTimes.put(uid, cpuActiveTime);
                              if (cb != null) {
                                  cb.onUidCpuTime(uid, delta);
                              }
                          } else if (delta < 0) {
                              Slog.e(mTag, "Negative delta from active time proc: " + delta);
                          }
                      }
                  }
              }
          }
  
          @Override
          void readAbsoluteImpl(Callback<Long> cb) {
              try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                  if (!checkPrecondition(iter)) {
                      return;
                  }
                  CharBuffer buf;
                  while ((buf = iter.nextLine()) != null) {
                      if (asLongs(buf, mBuffer) != mBuffer.length) {
                          Slog.wtf(mTag, "Invalid line: " + buf.toString());
                          continue;
                      }
                      long cpuActiveTime = sumActiveTime(mBuffer);
                      if (cpuActiveTime > 0) {
                          cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
                      }
                  }
              }
          }
  
          private static long sumActiveTime(long[] times) {
              // UID is stored at times[0].
              double sum = 0;
              for (int i = 1; i < times.length; i++) {
                  sum += (double) times[i] * 10 / i; // Unit is 10ms.
              }
              return (long) sum;
          }
  
          private boolean checkPrecondition(ProcFileIterator iter) {
              if (iter == null || !iter.hasNextLine()) {
                  // Error logged in KernelCpuProcStringReader.
                  return false;
              }
              CharBuffer line = iter.nextLine();
              if (mCores > 0) {
                  return true;
              }
  
              String str = line.toString();
              if (!str.startsWith("cpus:")) {
                  Slog.wtf(mTag, "Malformed uid_concurrent_active_time line: " + line);
                  return false;
              }
              int cores = Integer.parseInt(str.substring(5).trim(), 10);
              if (cores <= 0) {
                  Slog.wtf(mTag, "Malformed uid_concurrent_active_time line: " + line);
                  return false;
              }
              mCores = cores;
              mBuffer = new long[mCores + 1]; // UID is stored at mBuffer[0].
              return true;
          }
      }
  
  
      /**
       * Reads /proc/uid_concurrent_policy_time and reports CPU cluster times to BatteryStats to
       * compute cluster power. See {@link PowerProfile#getAveragePowerForCpuCluster(int)}.
       *
       * /proc/uid_concurrent_policy_time has the following format:
       * policyX: x policyY: y policyZ: z...
       * uid1, time1a, time1b, ..., time1n,
       * uid2, time2a, time2b, ..., time2n,
       * ...
       * The first line lists all policies (i.e. clusters) followed by # cores in each policy.
       * Each uid is followed by x time entries corresponding to the time it spent on clusterX
       * running concurrently with 0, 1, 2, ..., x - 1 other processes, then followed by y, z, ...
       * time entries.
       *
       * The file contains a monotonically increasing count of time for a single boot. This class
       * maintains the previous results of a call to {@link #readDelta} in order to provide a
       * proper delta.
       */
      public static class KernelCpuUidClusterTimeReader extends KernelCpuUidTimeReader<long[]> {
          private int mNumClusters;
          private int mNumCores;
          private int[] mCoresOnClusters; // # cores on each cluster.
          private long[] mBuffer; // To store data returned from ProcFileIterator.
          private long[] mCurTime;
          private long[] mDeltaTime;
  
          public KernelCpuUidClusterTimeReader(boolean throttle) {
              super(KernelCpuProcStringReader.getClusterTimeReaderInstance(), throttle);
          }
  
          @VisibleForTesting
          public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
              super(reader, throttle);
          }
  
          @Override
          void readDeltaImpl(@Nullable Callback<long[]> cb) {
              try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                  if (!checkPrecondition(iter)) {
                      return;
                  }
                  CharBuffer buf;
                  while ((buf = iter.nextLine()) != null) {
                      if (asLongs(buf, mBuffer) != mBuffer.length) {
                          Slog.wtf(mTag, "Invalid line: " + buf.toString());
                          continue;
                      }
                      int uid = (int) mBuffer[0];
                      long[] lastTimes = mLastTimes.get(uid);
                      if (lastTimes == null) {
                          lastTimes = new long[mNumClusters];
                          mLastTimes.put(uid, lastTimes);
                      }
                      sumClusterTime();
                      boolean valid = true;
                      boolean notify = false;
                      for (int i = 0; i < mNumClusters; i++) {
                          mDeltaTime[i] = mCurTime[i] - lastTimes[i];
                          if (mDeltaTime[i] < 0) {
                              Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
                              valid = false;
                          }
                          notify |= mDeltaTime[i] > 0;
                      }
                      if (notify && valid) {
                          System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
                          if (cb != null) {
                              cb.onUidCpuTime(uid, mDeltaTime);
                          }
                      }
                  }
              }
          }
  
          @Override
          void readAbsoluteImpl(Callback<long[]> cb) {
              try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                  if (!checkPrecondition(iter)) {
                      return;
                  }
                  CharBuffer buf;
                  while ((buf = iter.nextLine()) != null) {
                      if (asLongs(buf, mBuffer) != mBuffer.length) {
                          Slog.wtf(mTag, "Invalid line: " + buf.toString());
                          continue;
                      }
                      sumClusterTime();
                      cb.onUidCpuTime((int) mBuffer[0], mCurTime);
                  }
              }
          }
  
          private void sumClusterTime() {
              // UID is stored at mBuffer[0].
              int core = 1;
              for (int i = 0; i < mNumClusters; i++) {
                  double sum = 0;
                  for (int j = 1; j <= mCoresOnClusters[i]; j++) {
                      sum += (double) mBuffer[core++] * 10 / j; // Unit is 10ms.
                  }
                  mCurTime[i] = (long) sum;
              }
          }
  
          private boolean checkPrecondition(ProcFileIterator iter) {
              if (iter == null || !iter.hasNextLine()) {
                  // Error logged in KernelCpuProcStringReader.
                  return false;
              }
              CharBuffer line = iter.nextLine();
              if (mNumClusters > 0) {
                  return true;
              }
              // Parse # cores in clusters.
              String[] lineArray = line.toString().split(" ");
              if (lineArray.length % 2 != 0) {
                  Slog.wtf(mTag, "Malformed uid_concurrent_policy_time line: " + line);
                  return false;
              }
              int[] clusters = new int[lineArray.length / 2];
              int cores = 0;
              for (int i = 0; i < clusters.length; i++) {
                  if (!lineArray[i * 2].startsWith("policy")) {
                      Slog.wtf(mTag, "Malformed uid_concurrent_policy_time line: " + line);
                      return false;
                  }
                  clusters[i] = Integer.parseInt(lineArray[i * 2 + 1], 10);
                  cores += clusters[i];
              }
              mNumClusters = clusters.length;
              mNumCores = cores;
              mCoresOnClusters = clusters;
              mBuffer = new long[cores + 1];
              mCurTime = new long[mNumClusters];
              mDeltaTime = new long[mNumClusters];
              return true;
          }
      }
  
  }
  