blob: 304fe5a1c9c3ef81e421da2b652da36eab3d19d6 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.powerstats;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.Context;
import android.hardware.power.stats.Channel;
import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerAttribution;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyMeasurement;
import android.hardware.power.stats.PowerEntity;
import android.hardware.power.stats.State;
import android.hardware.power.stats.StateResidency;
import android.hardware.power.stats.StateResidencyResult;
import android.os.Looper;
import androidx.test.InstrumentationRegistry;
import com.android.server.SystemService;
import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerUtils;
import com.android.server.powerstats.ProtoStreamUtils.PowerEntityUtils;
import com.android.server.powerstats.nano.PowerEntityProto;
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.StateProto;
import com.android.server.powerstats.nano.StateResidencyProto;
import com.android.server.powerstats.nano.StateResidencyResultProto;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Random;
/**
* Tests for {@link com.android.server.powerstats.PowerStatsService}.
*
* Build/Install/Run:
* atest FrameworksServicesTests:PowerStatsServiceTest
*/
public class PowerStatsServiceTest {
private static final String TAG = PowerStatsServiceTest.class.getSimpleName();
private static final String DATA_STORAGE_SUBDIR = "powerstatstest";
private static final String METER_FILENAME = "log.powerstats.metertest.0";
private static final String MODEL_FILENAME = "log.powerstats.modeltest.0";
private static final String RESIDENCY_FILENAME = "log.powerstats.residencytest.0";
private static final String PROTO_OUTPUT_FILENAME = "powerstats.proto";
private static final String CHANNEL_NAME = "channelname";
private static final String CHANNEL_SUBSYSTEM = "channelsubsystem";
private static final String POWER_ENTITY_NAME = "powerentityinfo";
private static final String STATE_NAME = "stateinfo";
private static final String ENERGY_CONSUMER_NAME = "energyconsumer";
private static final String METER_CACHE_FILENAME = "meterCacheTest";
private static final String MODEL_CACHE_FILENAME = "modelCacheTest";
private static final String RESIDENCY_CACHE_FILENAME = "residencyCacheTest";
private static final int ENERGY_METER_COUNT = 8;
private static final int ENERGY_CONSUMER_COUNT = 2;
private static final int ENERGY_CONSUMER_ATTRIBUTION_COUNT = 5;
private static final int POWER_ENTITY_COUNT = 3;
private static final int STATE_INFO_COUNT = 5;
private static final int STATE_RESIDENCY_COUNT = 4;
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
private PowerStatsService mService;
private File mDataStorageDir;
private TimerTrigger mTimerTrigger;
private BatteryTrigger mBatteryTrigger;
private PowerStatsLogger mPowerStatsLogger;
private final PowerStatsService.Injector mInjector = new PowerStatsService.Injector() {
private TestPowerStatsHALWrapper mTestPowerStatsHALWrapper = new TestPowerStatsHALWrapper();
@Override
File createDataStoragePath() {
if (mDataStorageDir == null) {
try {
mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile();
} catch (IOException e) {
fail("Could not create temp directory.");
}
}
return mDataStorageDir;
}
@Override
String createMeterFilename() {
return METER_FILENAME;
}
@Override
String createModelFilename() {
return MODEL_FILENAME;
}
@Override
String createResidencyFilename() {
return RESIDENCY_FILENAME;
}
@Override
String createMeterCacheFilename() {
return METER_CACHE_FILENAME;
}
@Override
String createModelCacheFilename() {
return MODEL_CACHE_FILENAME;
}
@Override
String createResidencyCacheFilename() {
return RESIDENCY_CACHE_FILENAME;
}
@Override
IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() {
return mTestPowerStatsHALWrapper;
}
@Override
PowerStatsLogger createPowerStatsLogger(Context context, Looper looper,
File dataStoragePath, String meterFilename, String meterCacheFilename,
String modelFilename, String modelCacheFilename,
String residencyFilename, String residencyCacheFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
mPowerStatsLogger = new PowerStatsLogger(context, looper, dataStoragePath,
meterFilename, meterCacheFilename,
modelFilename, modelCacheFilename,
residencyFilename, residencyCacheFilename,
powerStatsHALWrapper);
return mPowerStatsLogger;
}
@Override
BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
mBatteryTrigger = new BatteryTrigger(context, powerStatsLogger,
false /* trigger enabled */);
return mBatteryTrigger;
}
@Override
TimerTrigger createTimerTrigger(Context context, PowerStatsLogger powerStatsLogger) {
mTimerTrigger = new TimerTrigger(context, powerStatsLogger,
false /* trigger enabled */);
return mTimerTrigger;
}
};
public static final class TestPowerStatsHALWrapper implements IPowerStatsHALWrapper {
@Override
public PowerEntity[] getPowerEntityInfo() {
PowerEntity[] powerEntityList = new PowerEntity[POWER_ENTITY_COUNT];
for (int i = 0; i < powerEntityList.length; i++) {
powerEntityList[i] = new PowerEntity();
powerEntityList[i].id = i;
powerEntityList[i].name = new String(POWER_ENTITY_NAME + i);
powerEntityList[i].states = new State[STATE_INFO_COUNT];
for (int j = 0; j < powerEntityList[i].states.length; j++) {
powerEntityList[i].states[j] = new State();
powerEntityList[i].states[j].id = j;
powerEntityList[i].states[j].name = new String(STATE_NAME + j);
}
}
return powerEntityList;
}
@Override
public StateResidencyResult[] getStateResidency(int[] powerEntityIds) {
StateResidencyResult[] stateResidencyResultList =
new StateResidencyResult[POWER_ENTITY_COUNT];
for (int i = 0; i < stateResidencyResultList.length; i++) {
stateResidencyResultList[i] = new StateResidencyResult();
stateResidencyResultList[i].id = i;
stateResidencyResultList[i].stateResidencyData =
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].id = j;
stateResidencyResultList[i].stateResidencyData[j].totalTimeInStateMs = j;
stateResidencyResultList[i].stateResidencyData[j].totalStateEntryCount = j;
stateResidencyResultList[i].stateResidencyData[j].lastEntryTimestampMs = j;
}
}
return stateResidencyResultList;
}
@Override
public EnergyConsumer[] getEnergyConsumerInfo() {
EnergyConsumer[] energyConsumerList = new EnergyConsumer[ENERGY_CONSUMER_COUNT];
for (int i = 0; i < energyConsumerList.length; i++) {
energyConsumerList[i] = new EnergyConsumer();
energyConsumerList[i].id = i;
energyConsumerList[i].ordinal = i;
energyConsumerList[i].type = (byte) i;
energyConsumerList[i].name = new String(ENERGY_CONSUMER_NAME + i);
}
return energyConsumerList;
}
@Override
public EnergyConsumerResult[] getEnergyConsumed(int[] energyConsumerIds) {
EnergyConsumerResult[] energyConsumedList =
new EnergyConsumerResult[ENERGY_CONSUMER_COUNT];
for (int i = 0; i < energyConsumedList.length; i++) {
energyConsumedList[i] = new EnergyConsumerResult();
energyConsumedList[i].id = i;
energyConsumedList[i].timestampMs = i;
energyConsumedList[i].energyUWs = i;
energyConsumedList[i].attribution =
new EnergyConsumerAttribution[ENERGY_CONSUMER_ATTRIBUTION_COUNT];
for (int j = 0; j < energyConsumedList[i].attribution.length; j++) {
energyConsumedList[i].attribution[j] = new EnergyConsumerAttribution();
energyConsumedList[i].attribution[j].uid = j;
energyConsumedList[i].attribution[j].energyUWs = j;
}
}
return energyConsumedList;
}
@Override
public Channel[] getEnergyMeterInfo() {
Channel[] energyMeterList = new Channel[ENERGY_METER_COUNT];
for (int i = 0; i < energyMeterList.length; i++) {
energyMeterList[i] = new Channel();
energyMeterList[i].id = i;
energyMeterList[i].name = new String(CHANNEL_NAME + i);
energyMeterList[i].subsystem = new String(CHANNEL_SUBSYSTEM + i);
}
return energyMeterList;
}
@Override
public EnergyMeasurement[] readEnergyMeter(int[] channelIds) {
EnergyMeasurement[] energyMeasurementList = new EnergyMeasurement[ENERGY_METER_COUNT];
for (int i = 0; i < energyMeasurementList.length; i++) {
energyMeasurementList[i] = new EnergyMeasurement();
energyMeasurementList[i].id = i;
energyMeasurementList[i].timestampMs = i;
energyMeasurementList[i].durationMs = i;
energyMeasurementList[i].energyUWs = i;
}
return energyMeasurementList;
}
@Override
public boolean isInitialized() {
return true;
}
}
@Before
public void setUp() {
mService = new PowerStatsService(mContext, mInjector);
}
@Test
public void testWrittenMeterDataMatchesReadIncidentReportData()
throws InterruptedException, IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Write data to on-device storage.
mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY);
// 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.writeMeterDataToFile(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 PowerStatsServiceMeterProto object.
PowerStatsServiceMeterProto pssProto = PowerStatsServiceMeterProto.parseFrom(fileContent);
// Validate the channel array matches what was written to on-device storage.
assertTrue(pssProto.channel.length == ENERGY_METER_COUNT);
for (int i = 0; i < pssProto.channel.length; i++) {
assertTrue(pssProto.channel[i].id == i);
assertTrue(pssProto.channel[i].name.equals(CHANNEL_NAME + i));
assertTrue(pssProto.channel[i].subsystem.equals(CHANNEL_SUBSYSTEM + i));
}
// Validate the energyMeasurement array matches what was written to on-device storage.
assertTrue(pssProto.energyMeasurement.length == ENERGY_METER_COUNT);
for (int i = 0; i < pssProto.energyMeasurement.length; i++) {
assertTrue(pssProto.energyMeasurement[i].id == i);
assertTrue(pssProto.energyMeasurement[i].timestampMs ==
i + mPowerStatsLogger.getStartWallTime());
assertTrue(pssProto.energyMeasurement[i].durationMs == i);
assertTrue(pssProto.energyMeasurement[i].energyUws == i);
}
}
@Test
public void testWrittenModelDataMatchesReadIncidentReportData()
throws InterruptedException, IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Write data to on-device storage.
mTimerTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY);
// 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.writeModelDataToFile(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 PowerStatsServiceModelProto object.
PowerStatsServiceModelProto pssProto = PowerStatsServiceModelProto.parseFrom(fileContent);
// Validate the energyConsumer array matches what was written to on-device storage.
assertTrue(pssProto.energyConsumer.length == ENERGY_CONSUMER_COUNT);
for (int i = 0; i < pssProto.energyConsumer.length; i++) {
assertTrue(pssProto.energyConsumer[i].id == i);
}
// Validate the energyConsumerResult array matches what was written to on-device storage.
assertTrue(pssProto.energyConsumerResult.length == ENERGY_CONSUMER_COUNT);
for (int i = 0; i < pssProto.energyConsumerResult.length; i++) {
assertTrue(pssProto.energyConsumerResult[i].id == i);
assertTrue(pssProto.energyConsumerResult[i].timestampMs ==
i + mPowerStatsLogger.getStartWallTime());
assertTrue(pssProto.energyConsumerResult[i].energyUws == i);
assertTrue(pssProto.energyConsumerResult[i].attribution.length
== ENERGY_CONSUMER_ATTRIBUTION_COUNT);
for (int j = 0; j < pssProto.energyConsumerResult[i].attribution.length; j++) {
assertTrue(pssProto.energyConsumerResult[i].attribution[j].uid == j);
assertTrue(pssProto.energyConsumerResult[i].attribution[j].energyUws == j);
}
}
}
@Test
public void testWrittenResidencyDataMatchesReadIncidentReportData()
throws InterruptedException, IOException {
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Write data to on-device storage.
mBatteryTrigger.logPowerStatsData(PowerStatsLogger.MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP);
// 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 powerEntity array matches what was written to on-device storage.
assertTrue(pssProto.powerEntity.length == POWER_ENTITY_COUNT);
for (int i = 0; i < pssProto.powerEntity.length; i++) {
PowerEntityProto powerEntity = pssProto.powerEntity[i];
assertTrue(powerEntity.id == i);
assertTrue(powerEntity.name.equals(POWER_ENTITY_NAME + i));
for (int j = 0; j < powerEntity.states.length; j++) {
StateProto state = powerEntity.states[j];
assertTrue(state.id == j);
assertTrue(state.name.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.id == i);
assertTrue(stateResidencyResult.stateResidencyData.length == STATE_RESIDENCY_COUNT);
for (int j = 0; j < stateResidencyResult.stateResidencyData.length; j++) {
StateResidencyProto stateResidency = stateResidencyResult.stateResidencyData[j];
assertTrue(stateResidency.id == j);
assertTrue(stateResidency.totalTimeInStateMs == j);
assertTrue(stateResidency.totalStateEntryCount == j);
assertTrue(stateResidency.lastEntryTimestampMs ==
j + mPowerStatsLogger.getStartWallTime());
}
}
}
@Test
public void testCorruptOnDeviceMeterStorage() 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, METER_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.writeMeterDataToFile(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 PowerStatsServiceMeterProto object.
PowerStatsServiceMeterProto pssProto = PowerStatsServiceMeterProto.parseFrom(fileContent);
// Valid channel data is written to the incident report in the call to
// mPowerStatsLogger.writeMeterDataToFile().
assertTrue(pssProto.channel.length == ENERGY_METER_COUNT);
for (int i = 0; i < pssProto.channel.length; i++) {
assertTrue(pssProto.channel[i].id == i);
assertTrue(pssProto.channel[i].name.equals(CHANNEL_NAME + i));
assertTrue(pssProto.channel[i].subsystem.equals(CHANNEL_SUBSYSTEM + i));
}
// No energyMeasurements should be written to the incident report since it
// is all corrupt (random bytes generated above).
assertTrue(pssProto.energyMeasurement.length == 0);
}
@Test
public void testCorruptOnDeviceModelStorage() 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, MODEL_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.writeModelDataToFile(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 PowerStatsServiceModelProto object.
PowerStatsServiceModelProto pssProto = PowerStatsServiceModelProto.parseFrom(fileContent);
// Valid energyConsumer data is written to the incident report in the call to
// mPowerStatsLogger.writeModelDataToFile().
assertTrue(pssProto.energyConsumer.length == ENERGY_CONSUMER_COUNT);
for (int i = 0; i < pssProto.energyConsumer.length; i++) {
assertTrue(pssProto.energyConsumer[i].id == i);
}
// No energyConsumerResults should be written to the incident report since it
// is all corrupt (random bytes generated above).
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 powerEntity data is written to the incident report in the call to
// mPowerStatsLogger.writeResidencyDataToFile().
assertTrue(pssProto.powerEntity.length == POWER_ENTITY_COUNT);
for (int i = 0; i < pssProto.powerEntity.length; i++) {
PowerEntityProto powerEntity = pssProto.powerEntity[i];
assertTrue(powerEntity.id == i);
assertTrue(powerEntity.name.equals(POWER_ENTITY_NAME + i));
for (int j = 0; j < powerEntity.states.length; j++) {
StateProto state = powerEntity.states[j];
assertTrue(state.id == j);
assertTrue(state.name.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);
// 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, METER_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.writeMeterDataToFile(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 PowerStatsServiceMeterProto object.
PowerStatsServiceMeterProto pssProto = PowerStatsServiceMeterProto.parseFrom(fileContent);
// Valid channel data is written to the incident report in the call to
// mPowerStatsLogger.writeMeterDataToFile().
assertTrue(pssProto.channel.length == ENERGY_METER_COUNT);
for (int i = 0; i < pssProto.channel.length; i++) {
assertTrue(pssProto.channel[i].id == i);
assertTrue(pssProto.channel[i].name.equals(CHANNEL_NAME + i));
assertTrue(pssProto.channel[i].subsystem.equals(CHANNEL_SUBSYSTEM + i));
}
// No energyMeasurements should be written to the incident report since the
// input buffer had only length and no data.
assertTrue(pssProto.energyMeasurement.length == 0);
}
@Test
public void testNotEnoughBytesAfterModelLengthField() 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, MODEL_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.writeModelDataToFile(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 PowerStatsServiceModelProto object.
PowerStatsServiceModelProto pssProto = PowerStatsServiceModelProto.parseFrom(fileContent);
// Valid energyConsumer data is written to the incident report in the call to
// mPowerStatsLogger.writeModelDataToFile().
assertTrue(pssProto.energyConsumer.length == ENERGY_CONSUMER_COUNT);
for (int i = 0; i < pssProto.energyConsumer.length; i++) {
assertTrue(pssProto.energyConsumer[i].id == i);
}
// No energyConsumerResults should be written to the incident report since the
// 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 powerEntity data is written to the incident report in the call to
// mPowerStatsLogger.writeResidencyDataToFile().
assertTrue(pssProto.powerEntity.length == POWER_ENTITY_COUNT);
for (int i = 0; i < pssProto.powerEntity.length; i++) {
PowerEntityProto powerEntity = pssProto.powerEntity[i];
assertTrue(powerEntity.id == i);
assertTrue(powerEntity.name.equals(POWER_ENTITY_NAME + i));
for (int j = 0; j < powerEntity.states.length; j++) {
StateProto state = powerEntity.states[j];
assertTrue(state.id == j);
assertTrue(state.name.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);
}
@Test
public void testDataStorageDeletedMeterMismatch() throws IOException {
// Create the directory where cached data will be stored.
mInjector.createDataStoragePath();
// In order to create cached data that will match the current data read by the
// PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
// returned from the Injector.
IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
// Generate random array of bytes to emulate cached meter data. Store to file.
Random rd = new Random();
byte[] bytes = new byte[100];
rd.nextBytes(bytes);
File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create cached energy consumer data and write to file.
EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create cached power entity info data and write to file.
PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create log files.
File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
meterFile.createNewFile();
modelFile.createNewFile();
residencyFile.createNewFile();
// Verify log files exist.
assertTrue(meterFile.exists());
assertTrue(modelFile.exists());
assertTrue(residencyFile.exists());
// Boot device after creating old cached data.
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Since cached meter data is just random bytes it won't match the data read from the HAL.
// This mismatch of cached and current HAL data should force a delete.
assertTrue(mService.getDeleteMeterDataOnBoot());
assertFalse(mService.getDeleteModelDataOnBoot());
assertFalse(mService.getDeleteResidencyDataOnBoot());
// Verify log files were deleted.
assertFalse(meterFile.exists());
assertTrue(modelFile.exists());
assertTrue(residencyFile.exists());
// Verify cached meter data was updated to new HAL output.
Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
byte[] bytesExpected = ChannelUtils.getProtoBytes(channels);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
onDeviceStorageFis.read(bytesActual);
assertTrue(Arrays.equals(bytesExpected, bytesActual));
}
@Test
public void testDataStorageDeletedModelMismatch() throws IOException {
// Create the directory where cached data will be stored.
mInjector.createDataStoragePath();
// In order to create cached data that will match the current data read by the
// PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
// returned from the Injector.
IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
// Create cached channel data and write to file.
Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
byte[] bytes = ChannelUtils.getProtoBytes(channels);
File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Generate random array of bytes to emulate cached energy consumer data. Store to file.
Random rd = new Random();
bytes = new byte[100];
rd.nextBytes(bytes);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create cached power entity info data and write to file.
PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create log files.
File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
meterFile.createNewFile();
modelFile.createNewFile();
residencyFile.createNewFile();
// Verify log files exist.
assertTrue(meterFile.exists());
assertTrue(modelFile.exists());
assertTrue(residencyFile.exists());
// Boot device after creating old cached data.
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Since cached energy consumer data is just random bytes it won't match the data read from
// the HAL. This mismatch of cached and current HAL data should force a delete.
assertFalse(mService.getDeleteMeterDataOnBoot());
assertTrue(mService.getDeleteModelDataOnBoot());
assertFalse(mService.getDeleteResidencyDataOnBoot());
// Verify log files were deleted.
assertTrue(meterFile.exists());
assertFalse(modelFile.exists());
assertTrue(residencyFile.exists());
// Verify cached energy consumer data was updated to new HAL output.
EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
byte[] bytesExpected = EnergyConsumerUtils.getProtoBytes(energyConsumers);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
onDeviceStorageFis.read(bytesActual);
assertTrue(Arrays.equals(bytesExpected, bytesActual));
}
@Test
public void testDataStorageDeletedResidencyMismatch() throws IOException {
// Create the directory where cached data will be stored.
mInjector.createDataStoragePath();
// In order to create cached data that will match the current data read by the
// PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
// returned from the Injector.
IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
// Create cached channel data and write to file.
Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
byte[] bytes = ChannelUtils.getProtoBytes(channels);
File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create cached energy consumer data and write to file.
EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Generate random array of bytes to emulate cached power entity info data. Store to file.
Random rd = new Random();
bytes = new byte[100];
rd.nextBytes(bytes);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create log files.
File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
meterFile.createNewFile();
modelFile.createNewFile();
residencyFile.createNewFile();
// Verify log files exist.
assertTrue(meterFile.exists());
assertTrue(modelFile.exists());
assertTrue(residencyFile.exists());
// Boot device after creating old cached data.
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Since cached power entity info data is just random bytes it won't match the data read
// from the HAL. This mismatch of cached and current HAL data should force a delete.
assertFalse(mService.getDeleteMeterDataOnBoot());
assertFalse(mService.getDeleteModelDataOnBoot());
assertTrue(mService.getDeleteResidencyDataOnBoot());
// Verify log files were deleted.
assertTrue(meterFile.exists());
assertTrue(modelFile.exists());
assertFalse(residencyFile.exists());
// Verify cached power entity data was updated to new HAL output.
PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
byte[] bytesExpected = PowerEntityUtils.getProtoBytes(powerEntityInfo);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
byte[] bytesActual = new byte[(int) onDeviceStorageFile.length()];
FileInputStream onDeviceStorageFis = new FileInputStream(onDeviceStorageFile);
onDeviceStorageFis.read(bytesActual);
assertTrue(Arrays.equals(bytesExpected, bytesActual));
}
@Test
public void testDataStorageNotDeletedNoCachedData() throws IOException {
// Create the directory where log files will be stored.
mInjector.createDataStoragePath();
// Create log files.
File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
meterFile.createNewFile();
modelFile.createNewFile();
residencyFile.createNewFile();
// Verify log files exist.
assertTrue(meterFile.exists());
assertTrue(modelFile.exists());
assertTrue(residencyFile.exists());
// This test mimics the device's first boot where there is no cached data.
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// Since there is no cached data on the first boot any log files that happen to exist
// should be deleted.
assertTrue(mService.getDeleteMeterDataOnBoot());
assertTrue(mService.getDeleteModelDataOnBoot());
assertTrue(mService.getDeleteResidencyDataOnBoot());
// Verify log files were deleted.
assertFalse(meterFile.exists());
assertFalse(modelFile.exists());
assertFalse(residencyFile.exists());
}
@Test
public void testDataStorageNotDeletedAllDataMatches() throws IOException {
// Create the directory where cached data will be stored.
mInjector.createDataStoragePath();
// In order to create cached data that will match the current data read by the
// PowerStatsService we need to write valid data from the TestPowerStatsHALWrapper that is
// returned from the Injector.
IPowerStatsHALWrapper powerStatsHALWrapper = mInjector.getPowerStatsHALWrapperImpl();
// Create cached channel data and write to file.
Channel[] channels = powerStatsHALWrapper.getEnergyMeterInfo();
byte[] bytes = ChannelUtils.getProtoBytes(channels);
File onDeviceStorageFile = new File(mDataStorageDir, mInjector.createMeterCacheFilename());
FileOutputStream onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create cached energy consumer data and write to file.
EnergyConsumer[] energyConsumers = powerStatsHALWrapper.getEnergyConsumerInfo();
bytes = EnergyConsumerUtils.getProtoBytes(energyConsumers);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createModelCacheFilename());
onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create cached power entity info data and write to file.
PowerEntity[] powerEntityInfo = powerStatsHALWrapper.getPowerEntityInfo();
bytes = PowerEntityUtils.getProtoBytes(powerEntityInfo);
onDeviceStorageFile = new File(mDataStorageDir, mInjector.createResidencyCacheFilename());
onDeviceStorageFos = new FileOutputStream(onDeviceStorageFile);
onDeviceStorageFos.write(bytes);
onDeviceStorageFos.close();
// Create log files.
File meterFile = new File(mDataStorageDir, mInjector.createMeterFilename());
File modelFile = new File(mDataStorageDir, mInjector.createModelFilename());
File residencyFile = new File(mDataStorageDir, mInjector.createResidencyFilename());
meterFile.createNewFile();
modelFile.createNewFile();
residencyFile.createNewFile();
// Verify log files exist.
assertTrue(meterFile.exists());
assertTrue(modelFile.exists());
assertTrue(residencyFile.exists());
// Boot device after creating old cached data.
mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
// All cached data created above should match current data read in PowerStatsService so we
// expect the data not to be deleted.
assertFalse(mService.getDeleteMeterDataOnBoot());
assertFalse(mService.getDeleteModelDataOnBoot());
assertFalse(mService.getDeleteResidencyDataOnBoot());
// Verify log files were not deleted.
assertTrue(meterFile.exists());
assertTrue(modelFile.exists());
assertTrue(residencyFile.exists());
}
}