summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/BatteryStats.java91
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java276
-rw-r--r--core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java117
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java109
4 files changed, 592 insertions, 1 deletions
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b5fd116e68aa..3eea72d5819b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -203,6 +203,8 @@ public abstract class BatteryStats implements Parcelable {
private static final String APK_DATA = "apk";
private static final String PROCESS_DATA = "pr";
private static final String CPU_DATA = "cpu";
+ private static final String GLOBAL_CPU_FREQ_DATA = "gcf";
+ private static final String CPU_TIMES_AT_FREQ_DATA = "ctf";
private static final String SENSOR_DATA = "sr";
private static final String VIBRATOR_DATA = "vib";
private static final String FOREGROUND_DATA = "fg";
@@ -265,6 +267,13 @@ public abstract class BatteryStats implements Parcelable {
private final Formatter mFormatter = new Formatter(mFormatBuilder);
/**
+ * Indicates times spent by the uid at each cpu frequency in all process states.
+ *
+ * Other types might include times spent in foreground, background etc.
+ */
+ private final String UID_TIMES_TYPE_ALL = "A";
+
+ /**
* State for keeping track of counting information.
*/
public static abstract class Counter {
@@ -303,6 +312,24 @@ public abstract class BatteryStats implements Parcelable {
}
/**
+ * State for keeping track of array of long counting information.
+ */
+ public static abstract class LongCounterArray {
+ /**
+ * Returns the counts associated with this Counter for the
+ * selected type of statistics.
+ *
+ * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+ */
+ public abstract long[] getCountsLocked(int which);
+
+ /**
+ * Temporary for debugging.
+ */
+ public abstract void logState(Printer pw, String prefix);
+ }
+
+ /**
* Container class that aggregates counters for transmit, receive, and idle state of a
* radio controller.
*/
@@ -523,6 +550,9 @@ public abstract class BatteryStats implements Parcelable {
public abstract Timer getBluetoothScanBackgroundTimer();
public abstract Counter getBluetoothScanResultCounter();
+ public abstract long[] getCpuFreqTimes(int which);
+ public abstract long[] getScreenOffCpuFreqTimes(int which);
+
// Note: the following times are disjoint. They can be added together to find the
// total time a uid has had any processes running at all.
@@ -1077,6 +1107,8 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getNextMaxDailyDeadline();
+ public abstract long[] getCpuFreqs();
+
public final static class HistoryTag {
public String string;
public int uid;
@@ -3274,6 +3306,15 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ final long[] cpuFreqs = getCpuFreqs();
+ if (cpuFreqs != null) {
+ sb.setLength(0);
+ for (int i = 0; i < cpuFreqs.length; ++i) {
+ sb.append((i == 0 ? "" : ",") + cpuFreqs[i]);
+ }
+ dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString());
+ }
+
for (int iu = 0; iu < NU; iu++) {
final int uid = uidStats.keyAt(iu);
if (reqUid >= 0 && uid != reqUid) {
@@ -3506,6 +3547,27 @@ public abstract class BatteryStats implements Parcelable {
0 /* old cpu power, keep for compatibility */);
}
+ final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
+ // If total cpuFreqTimes is null, then we don't need to check for screenOffCpuFreqTimes.
+ if (cpuFreqTimeMs != null) {
+ sb.setLength(0);
+ for (int i = 0; i < cpuFreqTimeMs.length; ++i) {
+ sb.append((i == 0 ? "" : ",") + cpuFreqTimeMs[i]);
+ }
+ final long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
+ if (screenOffCpuFreqTimeMs != null) {
+ for (int i = 0; i < screenOffCpuFreqTimeMs.length; ++i) {
+ sb.append("," + screenOffCpuFreqTimeMs[i]);
+ }
+ } else {
+ for (int i = 0; i < cpuFreqTimeMs.length; ++i) {
+ sb.append(",0");
+ }
+ }
+ dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA, UID_TIMES_TYPE_ALL,
+ cpuFreqTimeMs.length, sb.toString());
+ }
+
final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
= u.getProcessStats();
for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
@@ -4373,6 +4435,16 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
+ final long[] cpuFreqs = getCpuFreqs();
+ if (cpuFreqs != null) {
+ sb.setLength(0);
+ sb.append("CPU freqs:");
+ for (int i = 0; i < cpuFreqs.length; ++i) {
+ sb.append(" " + cpuFreqs[i]);
+ }
+ pw.println(sb.toString());
+ }
+
for (int iu=0; iu<NU; iu++) {
final int uid = uidStats.keyAt(iu);
if (reqUid >= 0 && uid != reqUid && uid != Process.SYSTEM_UID) {
@@ -4835,6 +4907,25 @@ public abstract class BatteryStats implements Parcelable {
pw.println(sb.toString());
}
+ final long[] cpuFreqTimes = u.getCpuFreqTimes(which);
+ if (cpuFreqTimes != null) {
+ sb.setLength(0);
+ sb.append(" Total cpu time per freq:");
+ for (int i = 0; i < cpuFreqTimes.length; ++i) {
+ sb.append(" " + cpuFreqTimes[i]);
+ }
+ pw.println(sb.toString());
+ }
+ final long[] screenOffCpuFreqTimes = u.getScreenOffCpuFreqTimes(which);
+ if (screenOffCpuFreqTimes != null) {
+ sb.setLength(0);
+ sb.append(" Total screen-off cpu time per freq:");
+ for (int i = 0; i < screenOffCpuFreqTimes.length; ++i) {
+ sb.append(" " + screenOffCpuFreqTimes[i]);
+ }
+ pw.println(sb.toString());
+ }
+
final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
= u.getProcessStats();
for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6b185919d7f6..be4df51b53ee 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -87,6 +88,7 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
@@ -114,7 +116,7 @@ public class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 156 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 157 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -149,6 +151,8 @@ public class BatteryStatsImpl extends BatteryStats {
private final KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+ private final KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
+ new KernelUidCpuFreqTimeReader();
private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
= new KernelMemoryBandwidthStats();
@@ -570,6 +574,8 @@ public class BatteryStatsImpl extends BatteryStats {
private final NetworkStats.Entry mTmpNetworkStatsEntry = new NetworkStats.Entry();
+ private long[] mCpuFreqs;
+
private PowerProfile mPowerProfile;
/*
@@ -957,6 +963,131 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ public static class LongSamplingCounterArray extends LongCounterArray implements TimeBaseObs {
+ final TimeBase mTimeBase;
+ long[] mCounts;
+ long[] mLoadedCounts;
+ long[] mUnpluggedCounts;
+ long[] mPluggedCounts;
+
+ LongSamplingCounterArray(TimeBase timeBase, Parcel in) {
+ mTimeBase = timeBase;
+ mPluggedCounts = in.createLongArray();
+ mCounts = copyArray(mPluggedCounts, mCounts);
+ mLoadedCounts = in.createLongArray();
+ mUnpluggedCounts = in.createLongArray();
+ timeBase.add(this);
+ }
+
+ LongSamplingCounterArray(TimeBase timeBase) {
+ mTimeBase = timeBase;
+ timeBase.add(this);
+ }
+
+ public void writeToParcel(Parcel out) {
+ out.writeLongArray(mCounts);
+ out.writeLongArray(mLoadedCounts);
+ out.writeLongArray(mUnpluggedCounts);
+ }
+
+ @Override
+ public void onTimeStarted(long elapsedRealTime, long baseUptime, long baseRealtime) {
+ mUnpluggedCounts = copyArray(mPluggedCounts, mUnpluggedCounts);
+ mCounts = copyArray(mPluggedCounts, mCounts);
+ }
+
+ @Override
+ public void onTimeStopped(long elapsedRealtime, long baseUptime, long baseRealtime) {
+ mPluggedCounts = copyArray(mCounts, mPluggedCounts);
+ }
+
+ @Override
+ public long[] getCountsLocked(int which) {
+ long[] val = copyArray(mTimeBase.isRunning() ? mCounts : mPluggedCounts, null);
+ if (which == STATS_SINCE_UNPLUGGED) {
+ subtract(val, mUnpluggedCounts);
+ } else if (which != STATS_SINCE_CHARGED) {
+ subtract(val, mLoadedCounts);
+ }
+ return val;
+ }
+
+ @Override
+ public void logState(Printer pw, String prefix) {
+ pw.println(prefix + "mCounts=" + Arrays.toString(mCounts)
+ + " mLoadedCounts=" + Arrays.toString(mLoadedCounts)
+ + " mUnpluggedCounts=" + Arrays.toString(mUnpluggedCounts)
+ + " mPluggedCounts=" + Arrays.toString(mPluggedCounts));
+ }
+
+ void addCountLocked(long[] counts) {
+ if (counts == null) {
+ return;
+ }
+ if (mCounts == null) {
+ mCounts = new long[counts.length];
+ }
+ for (int i = 0; i < counts.length; ++i) {
+ mCounts[i] += counts[i];
+ }
+ }
+
+ /**
+ * Clear state of this counter.
+ */
+ void reset(boolean detachIfReset) {
+ fillArray(mCounts, 0);
+ fillArray(mLoadedCounts, 0);
+ fillArray(mPluggedCounts, 0);
+ fillArray(mUnpluggedCounts, 0);
+ if (detachIfReset) {
+ detach();
+ }
+ }
+
+ void detach() {
+ mTimeBase.remove(this);
+ }
+
+ void writeSummaryFromParcelLocked(Parcel out) {
+ out.writeLongArray(mCounts);
+ }
+
+ void readSummaryFromParcelLocked(Parcel in) {
+ mCounts = in.createLongArray();
+ mLoadedCounts = copyArray(mCounts, mLoadedCounts);
+ mUnpluggedCounts = copyArray(mCounts, mUnpluggedCounts);
+ mPluggedCounts = copyArray(mCounts, mPluggedCounts);
+ }
+
+ private void fillArray(long[] a, long val) {
+ if (a != null) {
+ Arrays.fill(a, val);
+ }
+ }
+
+ private void subtract(@NonNull long[] val, long[] toSubtract) {
+ if (toSubtract == null) {
+ return;
+ }
+ for (int i = 0; i < val.length; i++) {
+ val[i] -= toSubtract[i];
+ }
+ }
+
+ private long[] copyArray(long[] src, long[] dest) {
+ if (src == null) {
+ return null;
+ } else {
+ if (dest == null) {
+ dest = new long[src.length];
+ }
+ System.arraycopy(src, 0, dest, 0, src.length);
+ return dest;
+ }
+ }
+ }
+
public static class LongSamplingCounter extends LongCounter implements TimeBaseObs {
final TimeBase mTimeBase;
long mCount;
@@ -5483,6 +5614,9 @@ public class BatteryStatsImpl extends BatteryStats {
LongSamplingCounter mSystemCpuTime;
LongSamplingCounter[][] mCpuClusterSpeed;
+ LongSamplingCounterArray mCpuFreqTimeMs;
+ LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
+
/**
* The statistics we have collected for this uid's wake locks.
*/
@@ -5560,6 +5694,42 @@ public class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public long[] getCpuFreqTimes(int which) {
+ if (mCpuFreqTimeMs == null) {
+ return null;
+ }
+ final long[] cpuFreqTimes = mCpuFreqTimeMs.getCountsLocked(which);
+ if (cpuFreqTimes == null) {
+ return null;
+ }
+ // Return cpuFreqTimes only if atleast one of the elements in non-zero.
+ for (int i = 0; i < cpuFreqTimes.length; ++i) {
+ if (cpuFreqTimes[i] != 0) {
+ return cpuFreqTimes;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public long[] getScreenOffCpuFreqTimes(int which) {
+ if (mScreenOffCpuFreqTimeMs == null) {
+ return null;
+ }
+ final long[] cpuFreqTimes = mScreenOffCpuFreqTimeMs.getCountsLocked(which);
+ if (cpuFreqTimes == null) {
+ return null;
+ }
+ // Return cpuFreqTimes only if atleast one of the elements in non-zero.
+ for (int i = 0; i < cpuFreqTimes.length; ++i) {
+ if (cpuFreqTimes[i] != 0) {
+ return cpuFreqTimes;
+ }
+ }
+ return null;
+ }
+
+ @Override
public ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> getWakelockStats() {
return mWakelockStats.getMap();
}
@@ -6354,6 +6524,13 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ if (mCpuFreqTimeMs != null) {
+ mCpuFreqTimeMs.reset(false);
+ }
+ if (mScreenOffCpuFreqTimeMs != null) {
+ mScreenOffCpuFreqTimeMs.reset(false);
+ }
+
resetLongCounterIfNotNull(mMobileRadioApWakeupCount, false);
resetLongCounterIfNotNull(mWifiRadioApWakeupCount, false);
@@ -6523,6 +6700,13 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ if (mCpuFreqTimeMs != null) {
+ mCpuFreqTimeMs.detach();
+ }
+ if (mScreenOffCpuFreqTimeMs != null) {
+ mScreenOffCpuFreqTimeMs.detach();
+ }
+
detachLongCounterIfNotNull(mMobileRadioApWakeupCount);
detachLongCounterIfNotNull(mWifiRadioApWakeupCount);
}
@@ -6739,6 +6923,19 @@ public class BatteryStatsImpl extends BatteryStats {
out.writeInt(0);
}
+ if (mCpuFreqTimeMs != null) {
+ out.writeInt(1);
+ mCpuFreqTimeMs.writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
+ if (mScreenOffCpuFreqTimeMs != null) {
+ out.writeInt(1);
+ mScreenOffCpuFreqTimeMs.writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
+
if (mMobileRadioApWakeupCount != null) {
out.writeInt(1);
mMobileRadioApWakeupCount.writeToParcel(out);
@@ -6987,6 +7184,18 @@ public class BatteryStatsImpl extends BatteryStats {
}
if (in.readInt() != 0) {
+ mCpuFreqTimeMs = new LongSamplingCounterArray(mBsi.mOnBatteryTimeBase, in);
+ } else {
+ mCpuFreqTimeMs = null;
+ }
+ if (in.readInt() != 0) {
+ mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray(
+ mBsi.mOnBatteryScreenOffTimeBase, in);
+ } else {
+ mScreenOffCpuFreqTimeMs = null;
+ }
+
+ if (in.readInt() != 0) {
mMobileRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
} else {
mMobileRadioApWakeupCount = null;
@@ -8192,6 +8401,10 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ public long[] getCpuFreqs() {
+ return mCpuFreqs;
+ }
+
public BatteryStatsImpl(File systemDir, Handler handler, ExternalStatsSync externalSync) {
this(new SystemClocks(), systemDir, handler, externalSync, null);
}
@@ -9812,6 +10025,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
});
+ readKernelUidCpuFreqTimesLocked();
+
final long elapse = (mClocks.elapsedRealtime() - startTimeMs);
if (DEBUG_ENERGY_CPU || (elapse >= 100)) {
Slog.d(TAG, "Reading cpu stats took " + elapse + " ms");
@@ -9900,6 +10115,30 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ void readKernelUidCpuFreqTimesLocked() {
+ mKernelUidCpuFreqTimeReader.readDelta(!mOnBatteryInternal ? null :
+ new KernelUidCpuFreqTimeReader.Callback() {
+ @Override
+ public void onCpuFreqs(long[] cpuFreqs) {
+ mCpuFreqs = cpuFreqs;
+ }
+
+ @Override
+ public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
+ final Uid u = getUidStatsLocked(uid);
+ if (u.mCpuFreqTimeMs == null) {
+ u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase);
+ }
+ u.mCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
+ if (u.mScreenOffCpuFreqTimeMs == null) {
+ u.mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray(
+ mOnBatteryScreenOffTimeBase);
+ }
+ u.mScreenOffCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
+ }
+ });
+ }
+
boolean setChargingLocked(boolean charging) {
if (mCharging != charging) {
mCharging = charging;
@@ -10988,6 +11227,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ mCpuFreqs = in.createLongArray();
+
final int NU = in.readInt();
if (NU > 10000) {
throw new ParcelFormatException("File corrupt: too many uids " + NU);
@@ -11111,6 +11352,20 @@ public class BatteryStatsImpl extends BatteryStats {
}
if (in.readInt() != 0) {
+ u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase);
+ u.mCpuFreqTimeMs.readSummaryFromParcelLocked(in);
+ } else {
+ u.mCpuFreqTimeMs = null;
+ }
+ if (in.readInt() != 0) {
+ u.mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray(
+ mOnBatteryScreenOffTimeBase);
+ u.mScreenOffCpuFreqTimeMs.readSummaryFromParcelLocked(in);
+ } else {
+ u.mScreenOffCpuFreqTimeMs = null;
+ }
+
+ if (in.readInt() != 0) {
u.mMobileRadioApWakeupCount = new LongSamplingCounter(mOnBatteryTimeBase);
u.mMobileRadioApWakeupCount.readSummaryFromParcelLocked(in);
} else {
@@ -11360,6 +11615,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ out.writeLongArray(mCpuFreqs);
+
final int NU = mUidStats.size();
out.writeInt(NU);
for (int iu = 0; iu < NU; iu++) {
@@ -11504,6 +11761,19 @@ public class BatteryStatsImpl extends BatteryStats {
out.writeInt(0);
}
+ if (u.mCpuFreqTimeMs != null) {
+ out.writeInt(1);
+ u.mCpuFreqTimeMs.writeSummaryFromParcelLocked(out);
+ } else {
+ out.writeInt(0);
+ }
+ if (u.mScreenOffCpuFreqTimeMs != null) {
+ out.writeInt(1);
+ u.mScreenOffCpuFreqTimeMs.writeSummaryFromParcelLocked(out);
+ } else {
+ out.writeInt(0);
+ }
+
if (u.mMobileRadioApWakeupCount != null) {
out.writeInt(1);
u.mMobileRadioApWakeupCount.writeSummaryFromParcelLocked(out);
@@ -11794,6 +12064,8 @@ public class BatteryStatsImpl extends BatteryStats {
mFlashlightTurnedOnTimers.clear();
mCameraTurnedOnTimers.clear();
+ mCpuFreqs = in.createLongArray();
+
int numUids = in.readInt();
mUidStats.clear();
for (int i = 0; i < numUids; i++) {
@@ -11953,6 +12225,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
+ out.writeLongArray(mCpuFreqs);
+
if (inclUids) {
int size = mUidStats.size();
out.writeInt(size);
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
new file mode 100644
index 000000000000..568c8830d35b
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 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 android.annotation.Nullable;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+
+/**
+ * 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 class KernelUidCpuFreqTimeReader {
+ private static final String TAG = "KernelUidCpuFreqTimeReader";
+ private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
+
+ public interface Callback {
+ void onCpuFreqs(long[] cpuFreqs);
+ void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
+ }
+
+ private long[] mCpuFreqs;
+ private int mCpuFreqsCount;
+
+ private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
+
+ public void readDelta(@Nullable Callback callback) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
+ readDelta(reader, callback);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
+ }
+ }
+
+ @VisibleForTesting
+ public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
+ String line = reader.readLine();
+ if (line == null) {
+ return;
+ }
+ readCpuFreqs(line, callback);
+ while ((line = reader.readLine()) != null) {
+ final int index = line.indexOf(' ');
+ final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
+ readTimesForUid(uid, line.substring(index + 1, line.length()), callback);
+ }
+ }
+
+ private void readTimesForUid(int uid, String line, Callback callback) {
+ long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid);
+ if (uidTimeMs == null) {
+ uidTimeMs = new long[mCpuFreqsCount];
+ mLastUidCpuFreqTimeMs.put(uid, uidTimeMs);
+ }
+ final String[] timesStr = line.split(" ");
+ final int size = timesStr.length;
+ if (size != uidTimeMs.length) {
+ Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size
+ + " cpuFreqsCount: " + uidTimeMs.length);
+ return;
+ }
+ final long[] deltaUidTimeMs = new long[size];
+ for (int i = 0; i < size; ++i) {
+ // Times read will be in units of 10ms
+ final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10;
+ deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i];
+ uidTimeMs[i] = totalTimeMs;
+ }
+ if (callback != null) {
+ callback.onUidCpuFreqTime(uid, deltaUidTimeMs);
+ }
+ }
+
+ private void readCpuFreqs(String line, Callback callback) {
+ if (mCpuFreqs == null) {
+ final String[] freqStr = line.split(" ");
+ // First item would be "uid:" which needs to be ignored
+ mCpuFreqsCount = freqStr.length - 1;
+ mCpuFreqs = new long[mCpuFreqsCount];
+ for (int i = 0; i < mCpuFreqsCount; ++i) {
+ mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
+ }
+ }
+ if (callback != null) {
+ callback.onCpuFreqs(mCpuFreqs);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
new file mode 100644
index 000000000000..ad8221b470ae
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelUidCpuFreqTimeReaderTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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 org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.BufferedReader;
+
+/**
+ * Test class for {@link KernelUidCpuFreqTimeReader}.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.internal.os.KernelUidCpuFreqTimeReaderTest frameworks-core
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksCoreTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+ * Run: adb shell am instrument -e class com.android.internal.os.KernelUidCpuFreqTimeReaderTest -w \
+ * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelUidCpuFreqTimeReaderTest {
+ @Mock private BufferedReader mBufferedReader;
+ @Mock private KernelUidCpuFreqTimeReader.Callback mCallback;
+
+ private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mKernelUidCpuFreqTimeReader = new KernelUidCpuFreqTimeReader();
+ }
+
+ @Test
+ public void testReadDelta() throws Exception {
+ final long[] freqs = {1, 12, 123, 1234, 12345, 123456};
+ final int[] uids = {1, 22, 333, 4444, 5555};
+ final long[][] times = new long[uids.length][freqs.length];
+ for (int i = 0; i < uids.length; ++i) {
+ for (int j = 0; j < freqs.length; ++j) {
+ times[i][j] = uids[i] * freqs[j] * 10;
+ }
+ }
+ final String[] uidsTimesLines = getUidTimesLines(uids, times);
+ final String[] lines = new String[uidsTimesLines.length + 1];
+ System.arraycopy(uidsTimesLines, 0, lines, 0, uidsTimesLines.length);
+ lines[uidsTimesLines.length] = null;
+ when(mBufferedReader.readLine())
+ .thenReturn(getFreqsLine(freqs), lines);
+ mKernelUidCpuFreqTimeReader.readDelta(mBufferedReader, mCallback);
+ verify(mCallback).onCpuFreqs(freqs);
+ for (int i = 0; i < uids.length; ++i) {
+ verify(mCallback).onUidCpuFreqTime(uids[i], times[i]);
+ }
+ verifyNoMoreInteractions(mCallback);
+ }
+
+ private String getFreqsLine(long[] freqs) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("uid:");
+ for (int i = 0; i < freqs.length; ++i) {
+ sb.append(" " + freqs[i]);
+ }
+ return sb.toString();
+ }
+
+ private String[] getUidTimesLines(int[] uids, long[][] times) {
+ final String[] lines = new String[uids.length];
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < uids.length; ++i) {
+ sb.setLength(0);
+ sb.append(uids[i] + ":");
+ for (int j = 0; j < times[i].length; ++j) {
+ sb.append(" " + times[i][j] / 10);
+ }
+ lines[i] = sb.toString();
+ }
+ return lines;
+ }
+}