diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 8de30f8547a7..944edb0ff750 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -521,6 +521,11 @@ message IncidentProto { (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 9a7ed7cbe98f..30c427433639 100644 --- a/core/proto/android/server/powerstatsservice.proto +++ b/core/proto/android/server/powerstatsservice.proto @@ -40,6 +40,16 @@ message IncidentReportModelProto { optional PowerStatsServiceModelProto incident_report = 3055; } +/** + * 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 @@ -62,6 +72,99 @@ message PowerStatsServiceMeterProto { repeated EnergyMeasurementProto energy_measurement = 2; } +/** + * 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 diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java index c9595c2eec2b..6d9cb7522bef 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 @@ public class PowerStatsDataStorage { * 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 409cd826b6bc..4e86cd8ed7ae 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.content.Context; 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.ChannelInfoUtils; 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 @@ import java.io.IOException; * 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 @@ public final class PowerStatsLogger extends Handler { private final PowerStatsDataStorage mPowerStatsMeterStorage; private final PowerStatsDataStorage mPowerStatsModelStorage; + private final PowerStatsDataStorage mPowerStatsResidencyStorage; private final IPowerStatsHALWrapper mPowerStatsHALWrapper; @Override @@ -73,6 +78,13 @@ public final class PowerStatsLogger extends Handler { 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 @@ public final class PowerStatsLogger extends Handler { 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 ce50e5833c45..777857209de0 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 @@ public class PowerStatsService extends SystemService { 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 @@ public class PowerStatsService extends SystemService { 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 @@ public class PowerStatsService extends SystemService { 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 @@ public class PowerStatsService extends SystemService { // 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 5e23b86e0adb..ab9b3e0c41cd 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.ChannelInfo; 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 @@ public class ProtoStreamUtils { 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 @@ public class ProtoStreamUtils { } 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 stateResidencyResultList = + new ArrayList(); + 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 stateResidencyList = new ArrayList(); + + 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 @@ public class ProtoStreamUtils { 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 class ProtoStreamUtils { 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 class ProtoStreamUtils { public static EnergyMeasurement[] unpackProtoMessage(byte[] data) throws IOException { final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data)); List energyMeasurementList = new ArrayList(); - long token; while (true) { try { @@ -163,8 +320,8 @@ public class ProtoStreamUtils { 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 @@ public class ProtoStreamUtils { } } - private static EnergyMeasurement unpackProtoMessage(ProtoInputStream pis) + private static EnergyMeasurement unpackEnergyMeasurementProto(ProtoInputStream pis) throws IOException { EnergyMeasurement energyMeasurement = new EnergyMeasurement(); @@ -230,12 +387,10 @@ public class ProtoStreamUtils { 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 class ProtoStreamUtils { 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 @@ public class ProtoStreamUtils { final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data)); List energyConsumerResultList = new ArrayList(); - 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 @@ public class ProtoStreamUtils { } } - 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 08d4caacd777..9fe3c0da1b1d 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 androidx.test.InstrumentationRegistry; 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 @@ public class PowerStatsServiceTest { 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"; @@ -98,6 +104,11 @@ public class PowerStatsServiceTest { return MODEL_FILENAME; } + @Override + String createResidencyFilename() { + return RESIDENCY_FILENAME; + } + @Override IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() { return new TestPowerStatsHALWrapper(); @@ -105,10 +116,10 @@ public class PowerStatsServiceTest { @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 @@ public class PowerStatsServiceTest { 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 @@ public class PowerStatsServiceTest { 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; @@ -300,6 +312,61 @@ public class PowerStatsServiceTest { } } + @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); @@ -383,6 +450,55 @@ public class PowerStatsServiceTest { assertTrue(pssProto.energyConsumerResult.length == 0); } + @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 @@ public class PowerStatsServiceTest { // 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 76edd6341b55..97a2a402b537 100644 --- a/tools/powerstats/PowerStatsServiceProtoParser.java +++ b/tools/powerstats/PowerStatsServiceProtoParser.java @@ -90,6 +90,38 @@ public class PowerStatsServiceProtoParser { } } + 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 @@ public class PowerStatsServiceProtoParser { } 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);