Add state residency logging to power stats
Test: atest FrameworksServicesTests:PowerStatsServiceTest
Bug: 175724197
Change-Id: I4ba7af3b9a895ecdc9f8c16cd371e5582458d212
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 8de30f8..944edb0 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -521,6 +521,11 @@
(section).args = "power_stats --proto model"
];
+ optional com.android.server.powerstats.PowerStatsServiceResidencyProto powerstats_residency = 3056 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "power_stats --proto residency"
+ ];
+
// Dumps in text format (on userdebug and eng builds only): 4000 ~ 4999
optional android.util.TextDumpProto textdump_wifi = 4000 [
(section).type = SECTION_TEXT_DUMPSYS,
diff --git a/core/proto/android/server/powerstatsservice.proto b/core/proto/android/server/powerstatsservice.proto
index 9a7ed7c..30c4274 100644
--- a/core/proto/android/server/powerstatsservice.proto
+++ b/core/proto/android/server/powerstatsservice.proto
@@ -41,6 +41,16 @@
}
/**
+ * IncidentReportResidencyProto is used only in the parsing tool located
+ * in frameworks/base/tools which is used to parse this data out of
+ * incident reports.
+ */
+message IncidentReportResidencyProto {
+ /** Section number matches that in incident.proto */
+ optional PowerStatsServiceResidencyProto incident_report = 3056;
+}
+
+/**
* EnergyConsumer (model) data is exposed by the PowerStats HAL. This data
* represents modeled energy consumption estimates and is provided per
* subsystem. The default subsystems are defined in EnergyConsumerId.aidl.
@@ -63,6 +73,99 @@
}
/**
+ * A PowerEntity is defined as a platform subsystem, peripheral, or power domain
+ * that impacts the total device power consumption. PowerEntityInfo is
+ * information related to each power entity. Each PowerEntity may reside in one
+ * of multiple states. It may also transition from one state to another.
+ * StateResidency is defined as an accumulation of time that a PowerEntity
+ * resided in each of its possible states, the number of times that each state
+ * was entered, and a timestamp corresponding to the last time that state was
+ * entered.
+ */
+message PowerStatsServiceResidencyProto {
+ repeated PowerEntityInfoProto power_entity_info = 1;
+ repeated StateResidencyResultProto state_residency_result = 2;
+}
+
+/**
+ * Information about the possible states for a particular PowerEntity.
+ */
+message StateInfoProto {
+ /**
+ * Unique (for a given PowerEntityInfo) ID of this StateInfo
+ */
+ optional int32 state_id = 1;
+ /**
+ * Unique (for a given PowerEntityInfo) name of the state. Vendor/device specific.
+ * Opaque to framework
+ */
+ optional string state_name = 2;
+}
+
+/**
+ * A PowerEntity is defined as a platform subsystem, peripheral, or power domain
+ * that impacts the total device power consumption. PowerEntityInfo is
+ * information about a PowerEntity. It includes an array of information about
+ * each possible state of the PowerEntity.
+ */
+message PowerEntityInfoProto {
+ /**
+ * Unique ID of this PowerEntityInfo
+ */
+ optional int32 power_entity_id = 1;
+ /**
+ * Unique name of the PowerEntity. Vendor/device specific. Opaque to framework
+ */
+ optional string power_entity_name = 2;
+ /**
+ * List of states that the PowerEntity may reside in
+ */
+ repeated StateInfoProto states = 3;
+}
+
+/**
+ * StateResidency is defined as an accumulation of time that a PowerEntity
+ * resided in each of its possible states, the number of times that each state
+ * was entered, and a timestamp corresponding to the last time that state was
+ * entered. Data is accumulated starting at device boot.
+ */
+message StateResidencyProto {
+ /**
+ * ID of the state associated with this residency
+ */
+ optional int32 state_id = 1;
+ /**
+ * Total time in milliseconds that the corresponding PowerEntity resided
+ * in this state since boot
+ */
+ optional int64 total_time_in_state_ms = 2;
+ /**
+ * Total number of times that the state was entered since boot
+ */
+ optional int64 total_state_entry_count = 3;
+ /**
+ * Last time this state was entered. Time in milliseconds since boot
+ */
+ optional int64 last_entry_timestamp_ms = 4;
+}
+
+/**
+ * A StateResidencyResult is an array of StateResidencies for a particular
+ * PowerEntity. The StateResidencyResult can be matched to its corresponding
+ * PowerEntityInfo through the power_entity_id field.
+ */
+message StateResidencyResultProto {
+ /**
+ * ID of the PowerEntity associated with this result
+ */
+ optional int32 power_entity_id = 1;
+ /**
+ * Residency for each state in the PowerEntity's state space
+ */
+ repeated StateResidencyProto state_residency_data = 2;
+}
+
+/**
* Energy consumer ID:
* A list of default subsystems for which energy consumption estimates
* may be provided (hardware dependent).
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index c9595c2..6d9cb75 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -218,7 +218,7 @@
* array and written to on-device storage.
*/
public void write(byte[] data) {
- if (data.length > 0) {
+ if (data != null && data.length > 0) {
mLock.lock();
long currentTimeMillis = System.currentTimeMillis();
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
index 409cd82..4e86cd8 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java
@@ -20,6 +20,8 @@
import android.hardware.power.stats.ChannelInfo;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyMeasurement;
+import android.hardware.power.stats.PowerEntityInfo;
+import android.hardware.power.stats.StateResidencyResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -32,6 +34,8 @@
import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerIdUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyMeasurementUtils;
+import com.android.server.powerstats.ProtoStreamUtils.PowerEntityInfoUtils;
+import com.android.server.powerstats.ProtoStreamUtils.StateResidencyResultUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -42,8 +46,8 @@
* PowerStatsLogger is responsible for logging model and meter energy data to on-device storage.
* Messages are sent to its message handler to request that energy data be logged, at which time it
* queries the PowerStats HAL and logs the data to on-device storage. The on-device storage is
- * dumped to file by calling writeModelDataToFile or writeMeterDataToFile with a file descriptor
- * that points to the output file.
+ * dumped to file by calling writeModelDataToFile, writeMeterDataToFile, or writeResidencyDataToFile
+ * with a file descriptor that points to the output file.
*/
public final class PowerStatsLogger extends Handler {
private static final String TAG = PowerStatsLogger.class.getSimpleName();
@@ -52,6 +56,7 @@
private final PowerStatsDataStorage mPowerStatsMeterStorage;
private final PowerStatsDataStorage mPowerStatsModelStorage;
+ private final PowerStatsDataStorage mPowerStatsResidencyStorage;
private final IPowerStatsHALWrapper mPowerStatsHALWrapper;
@Override
@@ -73,6 +78,13 @@
mPowerStatsModelStorage.write(
EnergyConsumerResultUtils.getProtoBytes(energyConsumerResults));
if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResults);
+
+ // Log state residency data.
+ StateResidencyResult[] stateResidencyResults =
+ mPowerStatsHALWrapper.getStateResidency(new int[0]);
+ mPowerStatsResidencyStorage.write(
+ StateResidencyResultUtils.getProtoBytes(stateResidencyResults));
+ if (DEBUG) StateResidencyResultUtils.print(stateResidencyResults);
break;
}
}
@@ -159,13 +171,57 @@
pos.flush();
}
+ /**
+ * Writes residency data stored in PowerStatsDataStorage to a file descriptor.
+ *
+ * @param fd FileDescriptor where residency data stored in PowerStatsDataStorage is written.
+ * Data is written in protobuf format as defined by powerstatsservice.proto.
+ */
+ public void writeResidencyDataToFile(FileDescriptor fd) {
+ if (DEBUG) Slog.d(TAG, "Writing residency data to file");
+
+ final ProtoOutputStream pos = new ProtoOutputStream(fd);
+
+ try {
+ PowerEntityInfo[] powerEntityInfo = mPowerStatsHALWrapper.getPowerEntityInfo();
+ PowerEntityInfoUtils.packProtoMessage(powerEntityInfo, pos);
+ if (DEBUG) PowerEntityInfoUtils.print(powerEntityInfo);
+
+ mPowerStatsResidencyStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
+ @Override
+ public void onReadDataElement(byte[] data) {
+ try {
+ final ProtoInputStream pis =
+ new ProtoInputStream(new ByteArrayInputStream(data));
+ // TODO(b/166535853): ProtoOutputStream doesn't provide a method to write
+ // a byte array that already contains a serialized proto, so I have to
+ // deserialize, then re-serialize. This is computationally inefficient.
+ StateResidencyResult[] stateResidencyResult =
+ StateResidencyResultUtils.unpackProtoMessage(data);
+ StateResidencyResultUtils.packProtoMessage(stateResidencyResult, pos);
+ if (DEBUG) StateResidencyResultUtils.print(stateResidencyResult);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write residency data to incident report.");
+ }
+ }
+ });
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write residency data to incident report.");
+ }
+
+ pos.flush();
+ }
+
public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename,
- String modelFilename, IPowerStatsHALWrapper powerStatsHALWrapper) {
+ String modelFilename, String residencyFilename,
+ IPowerStatsHALWrapper powerStatsHALWrapper) {
super(Looper.getMainLooper());
mPowerStatsHALWrapper = powerStatsHALWrapper;
mPowerStatsMeterStorage = new PowerStatsDataStorage(context, dataStoragePath,
meterFilename);
mPowerStatsModelStorage = new PowerStatsDataStorage(context, dataStoragePath,
modelFilename);
+ mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, dataStoragePath,
+ residencyFilename);
}
}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java
index ce50e583..7778572 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsService.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java
@@ -49,6 +49,8 @@
private static final int DATA_STORAGE_VERSION = 0;
private static final String METER_FILENAME = "log.powerstats.meter." + DATA_STORAGE_VERSION;
private static final String MODEL_FILENAME = "log.powerstats.model." + DATA_STORAGE_VERSION;
+ private static final String RESIDENCY_FILENAME =
+ "log.powerstats.residency." + DATA_STORAGE_VERSION;
private final Injector mInjector;
@@ -76,15 +78,19 @@
return MODEL_FILENAME;
}
+ String createResidencyFilename() {
+ return RESIDENCY_FILENAME;
+ }
+
IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() {
return PowerStatsHALWrapper.getPowerStatsHalImpl();
}
PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
- String meterFilename, String modelFilename,
+ String meterFilename, String modelFilename, String residencyFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
return new PowerStatsLogger(context, dataStoragePath, meterFilename,
- modelFilename, powerStatsHALWrapper);
+ modelFilename, residencyFilename, powerStatsHALWrapper);
}
BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
@@ -109,6 +115,8 @@
mPowerStatsLogger.writeModelDataToFile(fd);
} else if ("meter".equals(args[1])) {
mPowerStatsLogger.writeMeterDataToFile(fd);
+ } else if ("residency".equals(args[1])) {
+ mPowerStatsLogger.writeResidencyDataToFile(fd);
}
} else if (args.length == 0) {
pw.println("PowerStatsService dumpsys: available PowerEntityInfos");
@@ -148,7 +156,8 @@
// Only start logger and triggers if initialization is successful.
mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext,
mInjector.createDataStoragePath(), mInjector.createMeterFilename(),
- mInjector.createModelFilename(), mPowerStatsHALWrapper);
+ mInjector.createModelFilename(), mInjector.createResidencyFilename(),
+ mPowerStatsHALWrapper);
mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
} else {
diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
index 5e23b86..ab9b3e0 100644
--- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
+++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java
@@ -20,6 +20,8 @@
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyMeasurement;
import android.hardware.power.stats.PowerEntityInfo;
+import android.hardware.power.stats.StateInfo;
+import android.hardware.power.stats.StateResidency;
import android.hardware.power.stats.StateResidencyResult;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
@@ -45,6 +47,29 @@
private static final String TAG = ProtoStreamUtils.class.getSimpleName();
static class PowerEntityInfoUtils {
+ public static void packProtoMessage(PowerEntityInfo[] powerEntityInfo,
+ ProtoOutputStream pos) {
+ if (powerEntityInfo == null) return;
+
+ for (int i = 0; i < powerEntityInfo.length; i++) {
+ long peiToken = pos.start(PowerStatsServiceResidencyProto.POWER_ENTITY_INFO);
+ pos.write(PowerEntityInfoProto.POWER_ENTITY_ID, powerEntityInfo[i].powerEntityId);
+ pos.write(PowerEntityInfoProto.POWER_ENTITY_NAME,
+ powerEntityInfo[i].powerEntityName);
+ if (powerEntityInfo[i].states != null) {
+ final int statesLength = powerEntityInfo[i].states.length;
+ for (int j = 0; j < statesLength; j++) {
+ final StateInfo state = powerEntityInfo[i].states[j];
+ long siToken = pos.start(PowerEntityInfoProto.STATES);
+ pos.write(StateInfoProto.STATE_ID, state.stateId);
+ pos.write(StateInfoProto.STATE_NAME, state.stateName);
+ pos.end(siToken);
+ }
+ }
+ pos.end(peiToken);
+ }
+ }
+
public static void print(PowerEntityInfo[] powerEntityInfo) {
if (powerEntityInfo == null) return;
@@ -77,6 +102,144 @@
}
static class StateResidencyResultUtils {
+ public static byte[] getProtoBytes(StateResidencyResult[] stateResidencyResult) {
+ ProtoOutputStream pos = new ProtoOutputStream();
+ packProtoMessage(stateResidencyResult, pos);
+ return pos.getBytes();
+ }
+
+ public static void packProtoMessage(StateResidencyResult[] stateResidencyResult,
+ ProtoOutputStream pos) {
+ if (stateResidencyResult == null) return;
+
+ for (int i = 0; i < stateResidencyResult.length; i++) {
+ final int stateLength = stateResidencyResult[i].stateResidencyData.length;
+ long srrToken = pos.start(PowerStatsServiceResidencyProto.STATE_RESIDENCY_RESULT);
+ pos.write(StateResidencyResultProto.POWER_ENTITY_ID,
+ stateResidencyResult[i].powerEntityId);
+ for (int j = 0; j < stateLength; j++) {
+ final StateResidency stateResidencyData =
+ stateResidencyResult[i].stateResidencyData[j];
+ long srdToken = pos.start(StateResidencyResultProto.STATE_RESIDENCY_DATA);
+ pos.write(StateResidencyProto.STATE_ID, stateResidencyData.stateId);
+ pos.write(StateResidencyProto.TOTAL_TIME_IN_STATE_MS,
+ stateResidencyData.totalTimeInStateMs);
+ pos.write(StateResidencyProto.TOTAL_STATE_ENTRY_COUNT,
+ stateResidencyData.totalStateEntryCount);
+ pos.write(StateResidencyProto.LAST_ENTRY_TIMESTAMP_MS,
+ stateResidencyData.lastEntryTimestampMs);
+ pos.end(srdToken);
+ }
+ pos.end(srrToken);
+ }
+ }
+
+ public static StateResidencyResult[] unpackProtoMessage(byte[] data) throws IOException {
+ final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data));
+ List<StateResidencyResult> stateResidencyResultList =
+ new ArrayList<StateResidencyResult>();
+ while (true) {
+ try {
+ int nextField = pis.nextField();
+ StateResidencyResult stateResidencyResult = new StateResidencyResult();
+
+ if (nextField == (int) PowerStatsServiceResidencyProto.STATE_RESIDENCY_RESULT) {
+ long token =
+ pis.start(PowerStatsServiceResidencyProto.STATE_RESIDENCY_RESULT);
+ stateResidencyResultList.add(unpackStateResidencyResultProto(pis));
+ pis.end(token);
+ } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) {
+ return stateResidencyResultList.toArray(
+ new StateResidencyResult[stateResidencyResultList.size()]);
+ } else {
+ Slog.e(TAG, "Unhandled field in PowerStatsServiceResidencyProto: "
+ + ProtoUtils.currentFieldToString(pis));
+ }
+ } catch (WireTypeMismatchException wtme) {
+ Slog.e(TAG, "Wire Type mismatch in PowerStatsServiceResidencyProto: "
+ + ProtoUtils.currentFieldToString(pis));
+ }
+ }
+ }
+
+ private static StateResidencyResult unpackStateResidencyResultProto(ProtoInputStream pis)
+ throws IOException {
+ StateResidencyResult stateResidencyResult = new StateResidencyResult();
+ List<StateResidency> stateResidencyList = new ArrayList<StateResidency>();
+
+ while (true) {
+ try {
+ switch (pis.nextField()) {
+ case (int) StateResidencyResultProto.POWER_ENTITY_ID:
+ stateResidencyResult.powerEntityId =
+ pis.readInt(StateResidencyResultProto.POWER_ENTITY_ID);
+ break;
+
+ case (int) StateResidencyResultProto.STATE_RESIDENCY_DATA:
+ long token = pis.start(StateResidencyResultProto.STATE_RESIDENCY_DATA);
+ stateResidencyList.add(unpackStateResidencyProto(pis));
+ pis.end(token);
+ break;
+
+ case ProtoInputStream.NO_MORE_FIELDS:
+ stateResidencyResult.stateResidencyData = stateResidencyList.toArray(
+ new StateResidency[stateResidencyList.size()]);
+ return stateResidencyResult;
+
+ default:
+ Slog.e(TAG, "Unhandled field in StateResidencyResultProto: "
+ + ProtoUtils.currentFieldToString(pis));
+ break;
+ }
+ } catch (WireTypeMismatchException wtme) {
+ Slog.e(TAG, "Wire Type mismatch in StateResidencyResultProto: "
+ + ProtoUtils.currentFieldToString(pis));
+ }
+ }
+ }
+
+ private static StateResidency unpackStateResidencyProto(ProtoInputStream pis)
+ throws IOException {
+ StateResidency stateResidency = new StateResidency();
+
+ while (true) {
+ try {
+ switch (pis.nextField()) {
+ case (int) StateResidencyProto.STATE_ID:
+ stateResidency.stateId = pis.readInt(StateResidencyProto.STATE_ID);
+ break;
+
+ case (int) StateResidencyProto.TOTAL_TIME_IN_STATE_MS:
+ stateResidency.totalTimeInStateMs =
+ pis.readLong(StateResidencyProto.TOTAL_TIME_IN_STATE_MS);
+ break;
+
+ case (int) StateResidencyProto.TOTAL_STATE_ENTRY_COUNT:
+ stateResidency.totalStateEntryCount =
+ pis.readLong(StateResidencyProto.TOTAL_STATE_ENTRY_COUNT);
+ break;
+
+ case (int) StateResidencyProto.LAST_ENTRY_TIMESTAMP_MS:
+ stateResidency.lastEntryTimestampMs =
+ pis.readLong(StateResidencyProto.LAST_ENTRY_TIMESTAMP_MS);
+ break;
+
+ case ProtoInputStream.NO_MORE_FIELDS:
+ return stateResidency;
+
+ default:
+ Slog.e(TAG, "Unhandled field in StateResidencyProto: "
+ + ProtoUtils.currentFieldToString(pis));
+ break;
+
+ }
+ } catch (WireTypeMismatchException wtme) {
+ Slog.e(TAG, "Wire Type mismatch in StateResidencyProto: "
+ + ProtoUtils.currentFieldToString(pis));
+ }
+ }
+ }
+
public static void print(StateResidencyResult[] stateResidencyResult) {
if (stateResidencyResult == null) return;
@@ -98,17 +261,14 @@
static class ChannelInfoUtils {
public static void packProtoMessage(ChannelInfo[] channelInfo, ProtoOutputStream pos) {
- long token;
-
if (channelInfo == null) return;
for (int i = 0; i < channelInfo.length; i++) {
- token = pos.start(PowerStatsServiceMeterProto.CHANNEL_INFO);
+ long token = pos.start(PowerStatsServiceMeterProto.CHANNEL_INFO);
pos.write(ChannelInfoProto.CHANNEL_ID, channelInfo[i].channelId);
pos.write(ChannelInfoProto.CHANNEL_NAME, channelInfo[i].channelName);
pos.end(token);
}
-
}
public static void print(ChannelInfo[] channelInfo) {
@@ -139,12 +299,10 @@
public static void packProtoMessage(EnergyMeasurement[] energyMeasurement,
ProtoOutputStream pos) {
- long token;
-
if (energyMeasurement == null) return;
for (int i = 0; i < energyMeasurement.length; i++) {
- token = pos.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT);
+ long token = pos.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT);
pos.write(EnergyMeasurementProto.CHANNEL_ID, energyMeasurement[i].channelId);
pos.write(EnergyMeasurementProto.TIMESTAMP_MS, energyMeasurement[i].timestampMs);
pos.write(EnergyMeasurementProto.ENERGY_UWS, energyMeasurement[i].energyUWs);
@@ -155,7 +313,6 @@
public static EnergyMeasurement[] unpackProtoMessage(byte[] data) throws IOException {
final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data));
List<EnergyMeasurement> energyMeasurementList = new ArrayList<EnergyMeasurement>();
- long token;
while (true) {
try {
@@ -163,8 +320,8 @@
EnergyMeasurement energyMeasurement = new EnergyMeasurement();
if (nextField == (int) PowerStatsServiceMeterProto.ENERGY_MEASUREMENT) {
- token = pis.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT);
- energyMeasurementList.add(unpackProtoMessage(pis));
+ long token = pis.start(PowerStatsServiceMeterProto.ENERGY_MEASUREMENT);
+ energyMeasurementList.add(unpackEnergyMeasurementProto(pis));
pis.end(token);
} else if (nextField == ProtoInputStream.NO_MORE_FIELDS) {
return energyMeasurementList.toArray(
@@ -180,7 +337,7 @@
}
}
- private static EnergyMeasurement unpackProtoMessage(ProtoInputStream pis)
+ private static EnergyMeasurement unpackEnergyMeasurementProto(ProtoInputStream pis)
throws IOException {
EnergyMeasurement energyMeasurement = new EnergyMeasurement();
@@ -230,12 +387,10 @@
static class EnergyConsumerIdUtils {
public static void packProtoMessage(int[] energyConsumerId, ProtoOutputStream pos) {
- long token;
-
if (energyConsumerId == null) return;
for (int i = 0; i < energyConsumerId.length; i++) {
- token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_ID);
+ long token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_ID);
pos.write(EnergyConsumerIdProto.ENERGY_CONSUMER_ID, energyConsumerId[i]);
pos.end(token);
}
@@ -267,12 +422,10 @@
public static void packProtoMessage(EnergyConsumerResult[] energyConsumerResult,
ProtoOutputStream pos) {
- long token;
-
if (energyConsumerResult == null) return;
for (int i = 0; i < energyConsumerResult.length; i++) {
- token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
+ long token = pos.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
pos.write(EnergyConsumerResultProto.ENERGY_CONSUMER_ID,
energyConsumerResult[i].energyConsumerId);
pos.write(EnergyConsumerResultProto.TIMESTAMP_MS,
@@ -286,16 +439,14 @@
final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data));
List<EnergyConsumerResult> energyConsumerResultList =
new ArrayList<EnergyConsumerResult>();
- long token;
-
while (true) {
try {
int nextField = pis.nextField();
EnergyConsumerResult energyConsumerResult = new EnergyConsumerResult();
if (nextField == (int) PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT) {
- token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
- energyConsumerResultList.add(unpackProtoMessage(pis));
+ long token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER_RESULT);
+ energyConsumerResultList.add(unpackEnergyConsumerResultProto(pis));
pis.end(token);
} else if (nextField == ProtoInputStream.NO_MORE_FIELDS) {
return energyConsumerResultList.toArray(
@@ -311,7 +462,7 @@
}
}
- private static EnergyConsumerResult unpackProtoMessage(ProtoInputStream pis)
+ private static EnergyConsumerResult unpackEnergyConsumerResultProto(ProtoInputStream pis)
throws IOException {
EnergyConsumerResult energyConsumerResult = new EnergyConsumerResult();
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
index 08d4caa..9fe3c0d 100644
--- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
@@ -32,8 +32,13 @@
import com.android.server.SystemService;
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
+import com.android.server.powerstats.nano.PowerEntityInfoProto;
import com.android.server.powerstats.nano.PowerStatsServiceMeterProto;
import com.android.server.powerstats.nano.PowerStatsServiceModelProto;
+import com.android.server.powerstats.nano.PowerStatsServiceResidencyProto;
+import com.android.server.powerstats.nano.StateInfoProto;
+import com.android.server.powerstats.nano.StateResidencyProto;
+import com.android.server.powerstats.nano.StateResidencyResultProto;
import org.junit.Before;
import org.junit.Test;
@@ -58,6 +63,7 @@
private static final String DATA_STORAGE_SUBDIR = "powerstatstest";
private static final String METER_FILENAME = "metertest";
private static final String MODEL_FILENAME = "modeltest";
+ private static final String RESIDENCY_FILENAME = "residencytest";
private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto";
private static final String CHANNEL_NAME = "channelname";
private static final String POWER_ENTITY_NAME = "powerentityinfo";
@@ -99,16 +105,21 @@
}
@Override
+ String createResidencyFilename() {
+ return RESIDENCY_FILENAME;
+ }
+
+ @Override
IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() {
return new TestPowerStatsHALWrapper();
}
@Override
PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath,
- String meterFilename, String modelFilename,
+ String meterFilename, String modelFilename, String residencyFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, meterFilename,
- modelFilename, powerStatsHALWrapper);
+ modelFilename, residencyFilename, powerStatsHALWrapper);
return mPowerStatsLogger;
}
@@ -137,7 +148,7 @@
for (int j = 0; j < powerEntityInfoList[i].states.length; j++) {
powerEntityInfoList[i].states[j] = new StateInfo();
powerEntityInfoList[i].states[j].stateId = j;
- powerEntityInfoList[i].states[j].stateName = new String(STATE_NAME + i);
+ powerEntityInfoList[i].states[j].stateName = new String(STATE_NAME + j);
}
}
return powerEntityInfoList;
@@ -154,6 +165,7 @@
new StateResidency[STATE_RESIDENCY_COUNT];
for (int j = 0; j < stateResidencyResultList[i].stateResidencyData.length; j++) {
stateResidencyResultList[i].stateResidencyData[j] = new StateResidency();
+ stateResidencyResultList[i].stateResidencyData[j].stateId = j;
stateResidencyResultList[i].stateResidencyData[j].totalTimeInStateMs = j;
stateResidencyResultList[i].stateResidencyData[j].totalStateEntryCount = j;
stateResidencyResultList[i].stateResidencyData[j].lastEntryTimestampMs = j;
@@ -301,6 +313,61 @@
}
@Test
+ public void testWrittenResidencyDataMatchesReadIncidentReportData()
+ throws InterruptedException, IOException {
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // Write data to on-device storage.
+ mTimerTrigger.logPowerStatsData();
+
+ // The above call puts a message on a handler. Wait for
+ // it to be processed.
+ Thread.sleep(100);
+
+ // Write on-device storage to an incident report.
+ File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME);
+ FileOutputStream fos = new FileOutputStream(incidentReport);
+ mPowerStatsLogger.writeResidencyDataToFile(fos.getFD());
+
+ // Read the incident report in to a byte array.
+ FileInputStream fis = new FileInputStream(incidentReport);
+ byte[] fileContent = new byte[(int) incidentReport.length()];
+ fis.read(fileContent);
+
+ // Parse the incident data into a PowerStatsServiceResidencyProto object.
+ PowerStatsServiceResidencyProto pssProto =
+ PowerStatsServiceResidencyProto.parseFrom(fileContent);
+
+ // Validate the powerEntityInfo array matches what was written to on-device storage.
+ assertTrue(pssProto.powerEntityInfo.length == POWER_ENTITY_COUNT);
+ for (int i = 0; i < pssProto.powerEntityInfo.length; i++) {
+ PowerEntityInfoProto powerEntityInfo = pssProto.powerEntityInfo[i];
+ assertTrue(powerEntityInfo.powerEntityId == i);
+ assertTrue(powerEntityInfo.powerEntityName.equals(POWER_ENTITY_NAME + i));
+ for (int j = 0; j < powerEntityInfo.states.length; j++) {
+ StateInfoProto stateInfo = powerEntityInfo.states[j];
+ assertTrue(stateInfo.stateId == j);
+ assertTrue(stateInfo.stateName.equals(STATE_NAME + j));
+ }
+ }
+
+ // Validate the stateResidencyResult array matches what was written to on-device storage.
+ assertTrue(pssProto.stateResidencyResult.length == POWER_ENTITY_COUNT);
+ for (int i = 0; i < pssProto.stateResidencyResult.length; i++) {
+ StateResidencyResultProto stateResidencyResult = pssProto.stateResidencyResult[i];
+ assertTrue(stateResidencyResult.powerEntityId == i);
+ assertTrue(stateResidencyResult.stateResidencyData.length == STATE_RESIDENCY_COUNT);
+ for (int j = 0; j < stateResidencyResult.stateResidencyData.length; j++) {
+ StateResidencyProto stateResidency = stateResidencyResult.stateResidencyData[j];
+ assertTrue(stateResidency.stateId == j);
+ assertTrue(stateResidency.totalTimeInStateMs == j);
+ assertTrue(stateResidency.totalStateEntryCount == j);
+ assertTrue(stateResidency.lastEntryTimestampMs == j);
+ }
+ }
+ }
+
+ @Test
public void testCorruptOnDeviceMeterStorage() throws IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -384,6 +451,55 @@
}
@Test
+ public void testCorruptOnDeviceResidencyStorage() throws IOException {
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // Generate random array of bytes to emulate corrupt data.
+ Random rd = new Random();
+ byte[] bytes = new byte[100];
+ rd.nextBytes(bytes);
+
+ // Store corrupt data in on-device storage. Add fake timestamp to filename
+ // to match format expected by FileRotator.
+ File onDeviceStorageFile = new File(mDataStorageDir, RESIDENCY_FILENAME + ".1234-2234");
+ FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(bytes);
+ onDeviceStorageFos.close();
+
+ // Write on-device storage to an incident report.
+ File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME);
+ FileOutputStream incidentReportFos = new FileOutputStream(incidentReport);
+ mPowerStatsLogger.writeResidencyDataToFile(incidentReportFos.getFD());
+
+ // Read the incident report in to a byte array.
+ FileInputStream fis = new FileInputStream(incidentReport);
+ byte[] fileContent = new byte[(int) incidentReport.length()];
+ fis.read(fileContent);
+
+ // Parse the incident data into a PowerStatsServiceResidencyProto object.
+ PowerStatsServiceResidencyProto pssProto =
+ PowerStatsServiceResidencyProto.parseFrom(fileContent);
+
+ // Valid powerEntityInfo data is written to the incident report in the call to
+ // mPowerStatsLogger.writeResidencyDataToFile().
+ assertTrue(pssProto.powerEntityInfo.length == POWER_ENTITY_COUNT);
+ for (int i = 0; i < pssProto.powerEntityInfo.length; i++) {
+ PowerEntityInfoProto powerEntityInfo = pssProto.powerEntityInfo[i];
+ assertTrue(powerEntityInfo.powerEntityId == i);
+ assertTrue(powerEntityInfo.powerEntityName.equals(POWER_ENTITY_NAME + i));
+ for (int j = 0; j < powerEntityInfo.states.length; j++) {
+ StateInfoProto stateInfo = powerEntityInfo.states[j];
+ assertTrue(stateInfo.stateId == j);
+ assertTrue(stateInfo.stateName.equals(STATE_NAME + j));
+ }
+ }
+
+ // No stateResidencyResults should be written to the incident report since it
+ // is all corrupt (random bytes generated above).
+ assertTrue(pssProto.stateResidencyResult.length == 0);
+ }
+
+ @Test
public void testNotEnoughBytesAfterMeterLengthField() throws IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -467,4 +583,54 @@
// input buffer had only length and no data.
assertTrue(pssProto.energyConsumerResult.length == 0);
}
+
+ @Test
+ public void testNotEnoughBytesAfterResidencyLengthField() throws IOException {
+ mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+
+ // Create corrupt data.
+ // Length field is correct, but there is no data following the length.
+ ByteArrayOutputStream data = new ByteArrayOutputStream();
+ data.write(ByteBuffer.allocate(4).putInt(50).array());
+ byte[] test = data.toByteArray();
+
+ // Store corrupt data in on-device storage. Add fake timestamp to filename
+ // to match format expected by FileRotator.
+ File onDeviceStorageFile = new File(mDataStorageDir, RESIDENCY_FILENAME + ".1234-2234");
+ FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
+ onDeviceStorageFos.write(data.toByteArray());
+ onDeviceStorageFos.close();
+
+ // Write on-device storage to an incident report.
+ File incidentReport = new File(mDataStorageDir, PROTO_OUTPUT_FILENAME);
+ FileOutputStream incidentReportFos = new FileOutputStream(incidentReport);
+ mPowerStatsLogger.writeResidencyDataToFile(incidentReportFos.getFD());
+
+ // Read the incident report in to a byte array.
+ FileInputStream fis = new FileInputStream(incidentReport);
+ byte[] fileContent = new byte[(int) incidentReport.length()];
+ fis.read(fileContent);
+
+ // Parse the incident data into a PowerStatsServiceResidencyProto object.
+ PowerStatsServiceResidencyProto pssProto =
+ PowerStatsServiceResidencyProto.parseFrom(fileContent);
+
+ // Valid powerEntityInfo data is written to the incident report in the call to
+ // mPowerStatsLogger.writeResidencyDataToFile().
+ assertTrue(pssProto.powerEntityInfo.length == POWER_ENTITY_COUNT);
+ for (int i = 0; i < pssProto.powerEntityInfo.length; i++) {
+ PowerEntityInfoProto powerEntityInfo = pssProto.powerEntityInfo[i];
+ assertTrue(powerEntityInfo.powerEntityId == i);
+ assertTrue(powerEntityInfo.powerEntityName.equals(POWER_ENTITY_NAME + i));
+ for (int j = 0; j < powerEntityInfo.states.length; j++) {
+ StateInfoProto stateInfo = powerEntityInfo.states[j];
+ assertTrue(stateInfo.stateId == j);
+ assertTrue(stateInfo.stateName.equals(STATE_NAME + j));
+ }
+ }
+
+ // No stateResidencyResults should be written to the incident report since the
+ // input buffer had only length and no data.
+ assertTrue(pssProto.stateResidencyResult.length == 0);
+ }
}
diff --git a/tools/powerstats/PowerStatsServiceProtoParser.java b/tools/powerstats/PowerStatsServiceProtoParser.java
index 76edd63..97a2a40 100644
--- a/tools/powerstats/PowerStatsServiceProtoParser.java
+++ b/tools/powerstats/PowerStatsServiceProtoParser.java
@@ -90,6 +90,38 @@
}
}
+ private static void printPowerEntityInfo(PowerStatsServiceResidencyProto proto) {
+ String csvHeader = new String();
+ for (int i = 0; i < proto.getPowerEntityInfoCount(); i++) {
+ PowerEntityInfoProto powerEntityInfo = proto.getPowerEntityInfo(i);
+ csvHeader += powerEntityInfo.getPowerEntityId() + ","
+ + powerEntityInfo.getPowerEntityName() + ",";
+ for (int j = 0; j < powerEntityInfo.getStatesCount(); j++) {
+ StateInfoProto stateInfo = powerEntityInfo.getStates(j);
+ csvHeader += stateInfo.getStateId() + "," + stateInfo.getStateName() + ",";
+ }
+ }
+ System.out.println(csvHeader);
+ }
+
+ private static void printStateResidencyResult(PowerStatsServiceResidencyProto proto) {
+ for (int i = 0; i < proto.getStateResidencyResultCount(); i++) {
+ String csvRow = new String();
+
+ StateResidencyResultProto stateResidencyResult = proto.getStateResidencyResult(i);
+ csvRow += stateResidencyResult.getPowerEntityId() + ",";
+
+ for (int j = 0; j < stateResidencyResult.getStateResidencyDataCount(); j++) {
+ StateResidencyProto stateResidency = stateResidencyResult.getStateResidencyData(j);
+ csvRow += stateResidency.getStateId() + ","
+ + stateResidency.getTotalTimeInStateMs() + ","
+ + stateResidency.getTotalStateEntryCount() + ","
+ + stateResidency.getLastEntryTimestampMs() + ",";
+ }
+ System.out.println(csvRow);
+ }
+ }
+
private static void generateCsvFile(String pathToIncidentReport) {
try {
// Print power meter data.
@@ -115,6 +147,21 @@
} else {
System.out.println("Model incident report not found. Exiting.");
}
+
+ // Print state residency data.
+ IncidentReportResidencyProto irResidencyProto =
+ IncidentReportResidencyProto.parseFrom(
+ new FileInputStream(pathToIncidentReport));
+
+ if (irResidencyProto.hasIncidentReport()) {
+ PowerStatsServiceResidencyProto pssResidencyProto =
+ irResidencyProto.getIncidentReport();
+ printPowerEntityInfo(pssResidencyProto);
+ printStateResidencyResult(pssResidencyProto);
+ } else {
+ System.out.println("Residency incident report not found. Exiting.");
+ }
+
} catch (IOException e) {
System.out.println("Unable to open incident report file: " + pathToIncidentReport);
System.out.println(e);