Merge "Delete data storage on changes to HAL data" into sc-dev

This commit is contained in:
Mat Bevilacqua
2021-02-17 20:25:25 +00:00
committed by Android (Google) Code Review
5 changed files with 609 additions and 38 deletions

View File

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

View File

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

View File

@@ -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
@@ -98,6 +102,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();
}
@@ -112,10 +128,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) {
@@ -187,14 +208,31 @@ public class PowerStatsService extends SystemService {
mPullAtomCallback = mInjector.createStatsPullerImpl(mContext, mPowerStatsInternal);
}
@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);

View File

@@ -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<EnergyConsumer> energyConsumerList = new ArrayList<EnergyConsumer>();
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;

View File

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