Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 05347491 authored by Mat Bevilacqua's avatar Mat Bevilacqua
Browse files

Delete data storage on changes to HAL data

Bug: 178504428
Test: atest FrameworksServicesTests:PowerStatsServiceTest

Change-Id: Ifa40687a3712dd0b7d505c64a544ac064e4147b3
parent ed2ba5d6
Loading
Loading
Loading
Loading
+29 −10
Original line number Diff line number Diff line
@@ -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();
            }
        }
    }
}
+101 −10
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+44 −6
Original line number Diff line number Diff line
@@ -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);
+84 −0
Original line number Diff line number Diff line
@@ -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;

+351 −12

File changed.

Preview size limit exceeded, changes collapsed.