From 05347491d69a290cdd57cf83be33bb0c4959c697 Mon Sep 17 00:00:00 2001 From: Mat Bevilacqua Date: Tue, 9 Feb 2021 08:34:12 -0800 Subject: [PATCH] Delete data storage on changes to HAL data Bug: 178504428 Test: atest FrameworksServicesTests:PowerStatsServiceTest Change-Id: Ifa40687a3712dd0b7d505c64a544ac064e4147b3 --- .../powerstats/PowerStatsDataStorage.java | 39 +- .../server/powerstats/PowerStatsLogger.java | 111 +++++- .../server/powerstats/PowerStatsService.java | 50 ++- .../server/powerstats/ProtoStreamUtils.java | 84 ++++ .../powerstats/PowerStatsServiceTest.java | 363 +++++++++++++++++- 5 files changed, 609 insertions(+), 38 deletions(-) diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java index 6d9cb7522bef6..2a95416747a6e 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java @@ -47,7 +47,8 @@ public class PowerStatsDataStorage { private static final long DELETE_AGE_MILLIS = 48 * MILLISECONDS_PER_HOUR; private final ReentrantLock mLock = new ReentrantLock(); - private File mDataStorageDir; + private final File mDataStorageDir; + private final String mDataStorageFilename; private final FileRotator mFileRotator; private static class DataElement { @@ -168,6 +169,7 @@ public class PowerStatsDataStorage { public PowerStatsDataStorage(Context context, File dataStoragePath, String dataStorageFilename) { mDataStorageDir = dataStoragePath; + mDataStorageFilename = dataStorageFilename; if (!mDataStorageDir.exists() && !mDataStorageDir.mkdirs()) { Slog.wtf(TAG, "mDataStorageDir does not exist: " + mDataStorageDir.getPath()); @@ -177,33 +179,35 @@ public class PowerStatsDataStorage { // filename, so any files that don't match the current version number can be deleted. File[] files = mDataStorageDir.listFiles(); for (int i = 0; i < files.length; i++) { - // Meter and model files are stored in the same directory. + // Meter, model, and residency files are stored in the same directory. // // The format of filenames on disk is: // log.powerstats.meter.version.timestamp // log.powerstats.model.version.timestamp + // log.powerstats.residency.version.timestamp // // The format of dataStorageFilenames is: // log.powerstats.meter.version // log.powerstats.model.version + // log.powerstats.residency.version // - // A PowerStatsDataStorage object is created for meter and model data. Strip off - // the version and check that the current file we're checking starts with the stem - // (log.powerstats.meter or log.powerstats.model). If the stem matches and the - // version number is different, delete the old file. - int versionDot = dataStorageFilename.lastIndexOf('.'); - String beforeVersionDot = dataStorageFilename.substring(0, versionDot); + // A PowerStatsDataStorage object is created for meter, model, and residency data. + // Strip off the version and check that the current file we're checking starts with + // the stem (log.powerstats.meter, log.powerstats.model, log.powerstats.residency). + // If the stem matches and the version number is different, delete the old file. + int versionDot = mDataStorageFilename.lastIndexOf('.'); + String beforeVersionDot = mDataStorageFilename.substring(0, versionDot); // Check that the stems match. if (files[i].getName().startsWith(beforeVersionDot)) { // Check that the version number matches. If not, delete the old file. - if (!files[i].getName().startsWith(dataStorageFilename)) { + if (!files[i].getName().startsWith(mDataStorageFilename)) { files[i].delete(); } } } mFileRotator = new FileRotator(mDataStorageDir, - dataStorageFilename, + mDataStorageFilename, ROTATE_AGE_MILLIS, DELETE_AGE_MILLIS); } @@ -242,4 +246,19 @@ public class PowerStatsDataStorage { public void read(DataElementReadCallback callback) throws IOException { mFileRotator.readMatching(new DataReader(callback), Long.MIN_VALUE, Long.MAX_VALUE); } + + /** + * Deletes all stored log data. + */ + public void deleteLogs() { + File[] files = mDataStorageDir.listFiles(); + for (int i = 0; i < files.length; i++) { + int versionDot = mDataStorageFilename.lastIndexOf('.'); + String beforeVersionDot = mDataStorageFilename.substring(0, versionDot); + // Check that the stems before the version match. + if (files[i].getName().startsWith(beforeVersionDot)) { + files[i].delete(); + } + } + } } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java index 37fc5a0f68fa8..c4f29ea692182 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsLogger.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsLogger.java @@ -26,6 +26,7 @@ import android.hardware.power.stats.StateResidencyResult; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.util.AtomicFile; import android.util.Slog; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; @@ -41,14 +42,17 @@ import com.android.server.powerstats.ProtoStreamUtils.StateResidencyResultUtils; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.util.Arrays; /** - * 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, writeMeterDataToFile, or writeResidencyDataToFile - * with a file descriptor that points to the output file. + * PowerStatsLogger is responsible for logging model, meter, and residency 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, 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(); @@ -61,6 +65,10 @@ public final class PowerStatsLogger extends Handler { private final PowerStatsDataStorage mPowerStatsModelStorage; private final PowerStatsDataStorage mPowerStatsResidencyStorage; private final IPowerStatsHALWrapper mPowerStatsHALWrapper; + private File mDataStoragePath; + private boolean mDeleteMeterDataOnBoot; + private boolean mDeleteModelDataOnBoot; + private boolean mDeleteResidencyDataOnBoot; @Override public void handleMessage(Message msg) { @@ -230,16 +238,99 @@ public final class PowerStatsLogger extends Handler { pos.flush(); } - public PowerStatsLogger(Context context, File dataStoragePath, String meterFilename, - String modelFilename, String residencyFilename, + private boolean dataChanged(String cachedFilename, byte[] dataCurrent) { + boolean dataChanged = false; + + if (mDataStoragePath.exists() || mDataStoragePath.mkdirs()) { + final File cachedFile = new File(mDataStoragePath, cachedFilename); + + if (cachedFile.exists()) { + // Get the byte array for the cached data. + final byte[] dataCached = new byte[(int) cachedFile.length()]; + + // Get the cached data from file. + try { + final FileInputStream fis = new FileInputStream(cachedFile.getPath()); + fis.read(dataCached); + } catch (IOException e) { + Slog.e(TAG, "Failed to read cached data from file"); + } + + // If the cached and current data are different, delete the data store. + dataChanged = !Arrays.equals(dataCached, dataCurrent); + } else { + // Either the cached file was somehow deleted, or this is the first + // boot of the device and we're creating the file for the first time. + // In either case, delete the log files. + dataChanged = true; + } + } + + return dataChanged; + } + + private void updateCacheFile(String cacheFilename, byte[] data) { + try { + final AtomicFile atomicCachedFile = + new AtomicFile(new File(mDataStoragePath, cacheFilename)); + final FileOutputStream fos = atomicCachedFile.startWrite(); + fos.write(data); + atomicCachedFile.finishWrite(fos); + } catch (IOException e) { + Slog.e(TAG, "Failed to write current data to cached file"); + } + } + + public boolean getDeleteMeterDataOnBoot() { + return mDeleteMeterDataOnBoot; + } + + public boolean getDeleteModelDataOnBoot() { + return mDeleteModelDataOnBoot; + } + + public boolean getDeleteResidencyDataOnBoot() { + return mDeleteResidencyDataOnBoot; + } + + public PowerStatsLogger(Context context, File dataStoragePath, + String meterFilename, String meterCacheFilename, + String modelFilename, String modelCacheFilename, + String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { super(Looper.getMainLooper()); mPowerStatsHALWrapper = powerStatsHALWrapper; - mPowerStatsMeterStorage = new PowerStatsDataStorage(context, dataStoragePath, + mDataStoragePath = dataStoragePath; + + mPowerStatsMeterStorage = new PowerStatsDataStorage(context, mDataStoragePath, meterFilename); - mPowerStatsModelStorage = new PowerStatsDataStorage(context, dataStoragePath, + mPowerStatsModelStorage = new PowerStatsDataStorage(context, mDataStoragePath, modelFilename); - mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, dataStoragePath, + mPowerStatsResidencyStorage = new PowerStatsDataStorage(context, mDataStoragePath, residencyFilename); + + final Channel[] channels = mPowerStatsHALWrapper.getEnergyMeterInfo(); + final byte[] channelBytes = ChannelUtils.getProtoBytes(channels); + mDeleteMeterDataOnBoot = dataChanged(meterCacheFilename, channelBytes); + if (mDeleteMeterDataOnBoot) { + mPowerStatsMeterStorage.deleteLogs(); + updateCacheFile(meterCacheFilename, channelBytes); + } + + final EnergyConsumer[] energyConsumers = mPowerStatsHALWrapper.getEnergyConsumerInfo(); + final byte[] energyConsumerBytes = EnergyConsumerUtils.getProtoBytes(energyConsumers); + mDeleteModelDataOnBoot = dataChanged(modelCacheFilename, energyConsumerBytes); + if (mDeleteModelDataOnBoot) { + mPowerStatsModelStorage.deleteLogs(); + updateCacheFile(modelCacheFilename, energyConsumerBytes); + } + + final PowerEntity[] powerEntities = mPowerStatsHALWrapper.getPowerEntityInfo(); + final byte[] powerEntityBytes = PowerEntityUtils.getProtoBytes(powerEntities); + mDeleteResidencyDataOnBoot = dataChanged(residencyCacheFilename, powerEntityBytes); + if (mDeleteResidencyDataOnBoot) { + mPowerStatsResidencyStorage.deleteLogs(); + updateCacheFile(residencyCacheFilename, powerEntityBytes); + } } } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index ea41980c02b1b..cb59a5db15def 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -61,8 +61,12 @@ public class PowerStatsService extends SystemService { 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 static final String METER_CACHE_FILENAME = "meterCache"; + private static final String MODEL_CACHE_FILENAME = "modelCache"; + private static final String RESIDENCY_CACHE_FILENAME = "residencyCache"; private final Injector mInjector; + private File mDataStoragePath; private Context mContext; @Nullable @@ -96,6 +100,18 @@ public class PowerStatsService extends SystemService { return RESIDENCY_FILENAME; } + String createMeterCacheFilename() { + return METER_CACHE_FILENAME; + } + + String createModelCacheFilename() { + return MODEL_CACHE_FILENAME; + } + + String createResidencyCacheFilename() { + return RESIDENCY_CACHE_FILENAME; + } + IPowerStatsHALWrapper createPowerStatsHALWrapperImpl() { return PowerStatsHALWrapper.getPowerStatsHalImpl(); } @@ -110,10 +126,15 @@ public class PowerStatsService extends SystemService { } PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String modelFilename, String residencyFilename, + String meterFilename, String meterCacheFilename, + String modelFilename, String modelCacheFilename, + String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - return new PowerStatsLogger(context, dataStoragePath, meterFilename, - modelFilename, residencyFilename, powerStatsHALWrapper); + return new PowerStatsLogger(context, dataStoragePath, + meterFilename, meterCacheFilename, + modelFilename, modelCacheFilename, + residencyFilename, residencyCacheFilename, + powerStatsHALWrapper); } BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) { @@ -192,14 +213,31 @@ public class PowerStatsService extends SystemService { } } + @VisibleForTesting + public boolean getDeleteMeterDataOnBoot() { + return mPowerStatsLogger.getDeleteMeterDataOnBoot(); + } + + @VisibleForTesting + public boolean getDeleteModelDataOnBoot() { + return mPowerStatsLogger.getDeleteModelDataOnBoot(); + } + + @VisibleForTesting + public boolean getDeleteResidencyDataOnBoot() { + return mPowerStatsLogger.getDeleteResidencyDataOnBoot(); + } + private void onBootCompleted() { if (getPowerStatsHal().isInitialized()) { if (DEBUG) Slog.d(TAG, "Starting PowerStatsService loggers"); + mDataStoragePath = mInjector.createDataStoragePath(); // Only start logger and triggers if initialization is successful. - mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, - mInjector.createDataStoragePath(), mInjector.createMeterFilename(), - mInjector.createModelFilename(), mInjector.createResidencyFilename(), + mPowerStatsLogger = mInjector.createPowerStatsLogger(mContext, mDataStoragePath, + mInjector.createMeterFilename(), mInjector.createMeterCacheFilename(), + mInjector.createModelFilename(), mInjector.createModelCacheFilename(), + mInjector.createResidencyFilename(), mInjector.createResidencyCacheFilename(), getPowerStatsHal()); mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger); mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger); diff --git a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java index bd003d3ac2dd2..11b22a574476e 100644 --- a/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java +++ b/services/core/java/com/android/server/powerstats/ProtoStreamUtils.java @@ -49,6 +49,12 @@ public class ProtoStreamUtils { private static final String TAG = ProtoStreamUtils.class.getSimpleName(); static class PowerEntityUtils { + public static byte[] getProtoBytes(PowerEntity[] powerEntity) { + ProtoOutputStream pos = new ProtoOutputStream(); + packProtoMessage(powerEntity, pos); + return pos.getBytes(); + } + public static void packProtoMessage(PowerEntity[] powerEntity, ProtoOutputStream pos) { if (powerEntity == null) return; @@ -260,6 +266,12 @@ public class ProtoStreamUtils { } static class ChannelUtils { + public static byte[] getProtoBytes(Channel[] channel) { + ProtoOutputStream pos = new ProtoOutputStream(); + packProtoMessage(channel, pos); + return pos.getBytes(); + } + public static void packProtoMessage(Channel[] channel, ProtoOutputStream pos) { if (channel == null) return; @@ -396,6 +408,12 @@ public class ProtoStreamUtils { } static class EnergyConsumerUtils { + public static byte[] getProtoBytes(EnergyConsumer[] energyConsumer) { + ProtoOutputStream pos = new ProtoOutputStream(); + packProtoMessage(energyConsumer, pos); + return pos.getBytes(); + } + public static void packProtoMessage(EnergyConsumer[] energyConsumer, ProtoOutputStream pos) { if (energyConsumer == null) return; @@ -410,6 +428,72 @@ public class ProtoStreamUtils { } } + public static EnergyConsumer[] unpackProtoMessage(byte[] data) throws IOException { + final ProtoInputStream pis = new ProtoInputStream(new ByteArrayInputStream(data)); + List energyConsumerList = new ArrayList(); + + while (true) { + try { + int nextField = pis.nextField(); + EnergyConsumer energyConsumer = new EnergyConsumer(); + + if (nextField == (int) PowerStatsServiceModelProto.ENERGY_CONSUMER) { + long token = pis.start(PowerStatsServiceModelProto.ENERGY_CONSUMER); + energyConsumerList.add(unpackEnergyConsumerProto(pis)); + pis.end(token); + } else if (nextField == ProtoInputStream.NO_MORE_FIELDS) { + return energyConsumerList.toArray( + new EnergyConsumer[energyConsumerList.size()]); + } else { + Slog.e(TAG, "Unhandled field in proto: " + + ProtoUtils.currentFieldToString(pis)); + } + } catch (WireTypeMismatchException wtme) { + Slog.e(TAG, "Wire Type mismatch in proto: " + + ProtoUtils.currentFieldToString(pis)); + } + } + } + + private static EnergyConsumer unpackEnergyConsumerProto(ProtoInputStream pis) + throws IOException { + final EnergyConsumer energyConsumer = new EnergyConsumer(); + + while (true) { + try { + switch (pis.nextField()) { + case (int) EnergyConsumerProto.ID: + energyConsumer.id = pis.readInt(EnergyConsumerProto.ID); + break; + + case (int) EnergyConsumerProto.ORDINAL: + energyConsumer.ordinal = pis.readInt(EnergyConsumerProto.ORDINAL); + break; + + case (int) EnergyConsumerProto.TYPE: + energyConsumer.type = (byte) pis.readInt(EnergyConsumerProto.TYPE); + break; + + case (int) EnergyConsumerProto.NAME: + energyConsumer.name = pis.readString(EnergyConsumerProto.NAME); + break; + + case ProtoInputStream.NO_MORE_FIELDS: + return energyConsumer; + + default: + Slog.e(TAG, "Unhandled field in EnergyConsumerProto: " + + ProtoUtils.currentFieldToString(pis)); + break; + + } + } catch (WireTypeMismatchException wtme) { + Slog.e(TAG, "Wire Type mismatch in EnergyConsumerProto: " + + ProtoUtils.currentFieldToString(pis)); + } + } + } + public static void print(EnergyConsumer[] energyConsumer) { if (energyConsumer == null) return; 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 03e60afe44896..ddbe81c81d6da 100644 --- a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.powerstats; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -34,6 +35,9 @@ 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; @@ -52,6 +56,7 @@ 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; /** @@ -63,15 +68,18 @@ import java.util.Random; 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 = "metertest"; - private static final String MODEL_FILENAME = "modeltest"; - private static final String RESIDENCY_FILENAME = "residencytest"; + 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; @@ -90,12 +98,12 @@ public class PowerStatsServiceTest { private TestPowerStatsHALWrapper mTestPowerStatsHALWrapper = new TestPowerStatsHALWrapper(); @Override File createDataStoragePath() { - mDataStorageDir = null; - - try { - mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile(); - } catch (IOException e) { - fail("Could not create temp directory."); + if (mDataStorageDir == null) { + try { + mDataStorageDir = Files.createTempDirectory(DATA_STORAGE_SUBDIR).toFile(); + } catch (IOException e) { + fail("Could not create temp directory."); + } } return mDataStorageDir; @@ -116,6 +124,21 @@ public class PowerStatsServiceTest { 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; @@ -123,10 +146,15 @@ public class PowerStatsServiceTest { @Override PowerStatsLogger createPowerStatsLogger(Context context, File dataStoragePath, - String meterFilename, String modelFilename, String residencyFilename, + String meterFilename, String meterCacheFilename, + String modelFilename, String modelCacheFilename, + String residencyFilename, String residencyCacheFilename, IPowerStatsHALWrapper powerStatsHALWrapper) { - mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, meterFilename, - modelFilename, residencyFilename, powerStatsHALWrapper); + mPowerStatsLogger = new PowerStatsLogger(context, dataStoragePath, + meterFilename, meterCacheFilename, + modelFilename, modelCacheFilename, + residencyFilename, residencyCacheFilename, + powerStatsHALWrapper); return mPowerStatsLogger; } @@ -665,4 +693,315 @@ public class PowerStatsServiceTest { // 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()); + } }