Add state residency logging to power stats

Test: atest FrameworksServicesTests:PowerStatsServiceTest
Bug: 175724197

Change-Id: I4ba7af3b9a895ecdc9f8c16cd371e5582458d212
This commit is contained in:
Mat Bevilacqua 2020-12-22 09:57:01 -08:00
parent ff001a1523
commit 723784a055
8 changed files with 569 additions and 32 deletions

View File

@ -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,

View File

@ -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

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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<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 @@ 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<EnergyMeasurement> energyMeasurementList = new ArrayList<EnergyMeasurement>();
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<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 @@ public class ProtoStreamUtils {
}
}
private static EnergyConsumerResult unpackProtoMessage(ProtoInputStream pis)
private static EnergyConsumerResult unpackEnergyConsumerResultProto(ProtoInputStream pis)
throws IOException {
EnergyConsumerResult energyConsumerResult = new EnergyConsumerResult();

View File

@ -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);
}
}

View File

@ -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);