Delete data storage on changes to HAL data
Bug: 178504428 Test: atest FrameworksServicesTests:PowerStatsServiceTest Change-Id: Ifa40687a3712dd0b7d505c64a544ac064e4147b3
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user