All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.powerstats.PowerStatsLogger Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.powerstats;

import static java.lang.System.currentTimeMillis;

import android.content.Context;
import android.hardware.power.stats.Channel;
import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyMeasurement;
import android.hardware.power.stats.PowerEntity;
import android.hardware.power.stats.StateResidencyResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.VisibleForTesting;

import com.android.server.powerstats.PowerStatsHALWrapper.IPowerStatsHALWrapper;
import com.android.server.powerstats.ProtoStreamUtils.ChannelUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerResultUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyConsumerUtils;
import com.android.server.powerstats.ProtoStreamUtils.EnergyMeasurementUtils;
import com.android.server.powerstats.ProtoStreamUtils.PowerEntityUtils;
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, 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();
    private static final boolean DEBUG = false;
    protected static final int MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP = 0;
    protected static final int MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY = 1;
    protected static final int MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY = 2;

    // TODO(b/181240441): Add a listener to update the Wall clock baseline when changed
    private final long mStartWallTime;
    private final PowerStatsDataStorage mPowerStatsMeterStorage;
    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) {
        switch (msg.what) {
            case MSG_LOG_TO_DATA_STORAGE_HIGH_FREQUENCY:
                if (DEBUG) Slog.d(TAG, "Logging to data storage on high frequency timer");

                // Log power meter data.
                EnergyMeasurement[] energyMeasurements =
                    mPowerStatsHALWrapper.readEnergyMeter(new int[0]);
                EnergyMeasurementUtils.adjustTimeSinceBootToEpoch(energyMeasurements,
                        mStartWallTime);
                mPowerStatsMeterStorage.write(
                        EnergyMeasurementUtils.getProtoBytes(energyMeasurements));
                if (DEBUG) EnergyMeasurementUtils.print(energyMeasurements);

                // Log power model data without attribution data.
                EnergyConsumerResult[] ecrNoAttribution =
                    mPowerStatsHALWrapper.getEnergyConsumed(new int[0]);
                EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrNoAttribution,
                        mStartWallTime);
                mPowerStatsModelStorage.write(
                        EnergyConsumerResultUtils.getProtoBytes(ecrNoAttribution, false));
                if (DEBUG) EnergyConsumerResultUtils.print(ecrNoAttribution);
                break;

            case MSG_LOG_TO_DATA_STORAGE_LOW_FREQUENCY:
                if (DEBUG) Slog.d(TAG, "Logging to data storage on low frequency timer");

                // Log power model data with attribution data.
                EnergyConsumerResult[] ecrAttribution =
                    mPowerStatsHALWrapper.getEnergyConsumed(new int[0]);
                EnergyConsumerResultUtils.adjustTimeSinceBootToEpoch(ecrAttribution,
                        mStartWallTime);
                mPowerStatsModelStorage.write(
                        EnergyConsumerResultUtils.getProtoBytes(ecrAttribution, true));
                if (DEBUG) EnergyConsumerResultUtils.print(ecrAttribution);
                break;

            case MSG_LOG_TO_DATA_STORAGE_BATTERY_DROP:
                if (DEBUG) Slog.d(TAG, "Logging to data storage on battery drop");

                // Log state residency data.
                StateResidencyResult[] stateResidencyResults =
                    mPowerStatsHALWrapper.getStateResidency(new int[0]);
                StateResidencyResultUtils.adjustTimeSinceBootToEpoch(stateResidencyResults,
                        mStartWallTime);
                mPowerStatsResidencyStorage.write(
                        StateResidencyResultUtils.getProtoBytes(stateResidencyResults));
                if (DEBUG) StateResidencyResultUtils.print(stateResidencyResults);
                break;
        }
    }

    /**
     * Writes meter data stored in PowerStatsDataStorage to a file descriptor.
     *
     * @param fd FileDescriptor where meter data stored in PowerStatsDataStorage is written.  Data
     *           is written in protobuf format as defined by powerstatsservice.proto.
     */
    public void writeMeterDataToFile(FileDescriptor fd) {
        if (DEBUG) Slog.d(TAG, "Writing meter data to file");

        final ProtoOutputStream pos = new ProtoOutputStream(fd);

        try {
            Channel[] channel = mPowerStatsHALWrapper.getEnergyMeterInfo();
            ChannelUtils.packProtoMessage(channel, pos);
            if (DEBUG) ChannelUtils.print(channel);

            mPowerStatsMeterStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
                @Override
                public void onReadDataElement(byte[] data) {
                    try {
                        final ProtoInputStream pis =
                                new ProtoInputStream(new ByteArrayInputStream(data));
                        // TODO(b/166535853): ProtoOutputStream doesn't provide a method to write
                        // a byte array that already contains a serialized proto, so I have to
                        // deserialize, then re-serialize.  This is computationally inefficient.
                        EnergyMeasurement[] energyMeasurement =
                            EnergyMeasurementUtils.unpackProtoMessage(data);
                        EnergyMeasurementUtils.packProtoMessage(energyMeasurement, pos);
                        if (DEBUG) EnergyMeasurementUtils.print(energyMeasurement);
                    } catch (IOException e) {
                        Slog.e(TAG, "Failed to write energy meter data to incident report.");
                    }
                }
            });
        } catch (IOException e) {
            Slog.e(TAG, "Failed to write energy meter info to incident report.");
        }

        pos.flush();
    }

    /**
     * Writes model data stored in PowerStatsDataStorage to a file descriptor.
     *
     * @param fd FileDescriptor where model data stored in PowerStatsDataStorage is written.  Data
     *           is written in protobuf format as defined by powerstatsservice.proto.
     */
    public void writeModelDataToFile(FileDescriptor fd) {
        if (DEBUG) Slog.d(TAG, "Writing model data to file");

        final ProtoOutputStream pos = new ProtoOutputStream(fd);

        try {
            EnergyConsumer[] energyConsumer = mPowerStatsHALWrapper.getEnergyConsumerInfo();
            EnergyConsumerUtils.packProtoMessage(energyConsumer, pos);
            if (DEBUG) EnergyConsumerUtils.print(energyConsumer);

            mPowerStatsModelStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
                @Override
                public void onReadDataElement(byte[] data) {
                    try {
                        final ProtoInputStream pis =
                                new ProtoInputStream(new ByteArrayInputStream(data));
                        // TODO(b/166535853): ProtoOutputStream doesn't provide a method to write
                        // a byte array that already contains a serialized proto, so I have to
                        // deserialize, then re-serialize.  This is computationally inefficient.
                        EnergyConsumerResult[] energyConsumerResult =
                            EnergyConsumerResultUtils.unpackProtoMessage(data);
                        EnergyConsumerResultUtils.packProtoMessage(energyConsumerResult, pos, true);
                        if (DEBUG) EnergyConsumerResultUtils.print(energyConsumerResult);
                    } catch (IOException e) {
                        Slog.e(TAG, "Failed to write energy model data to incident report.");
                    }
                }
            });
        } catch (IOException e) {
            Slog.e(TAG, "Failed to write energy model info to incident report.");
        }

        pos.flush();
    }

    /**
     * Writes residency data stored in PowerStatsDataStorage to a file descriptor.
     *
     * @param fd FileDescriptor where residency data stored in PowerStatsDataStorage is written.
     *           Data is written in protobuf format as defined by powerstatsservice.proto.
     */
    public void writeResidencyDataToFile(FileDescriptor fd) {
        if (DEBUG) Slog.d(TAG, "Writing residency data to file");

        final ProtoOutputStream pos = new ProtoOutputStream(fd);

        try {
            PowerEntity[] powerEntity = mPowerStatsHALWrapper.getPowerEntityInfo();
            PowerEntityUtils.packProtoMessage(powerEntity, pos);
            if (DEBUG) PowerEntityUtils.print(powerEntity);

            mPowerStatsResidencyStorage.read(new PowerStatsDataStorage.DataElementReadCallback() {
                @Override
                public void onReadDataElement(byte[] data) {
                    try {
                        final ProtoInputStream pis =
                                new ProtoInputStream(new ByteArrayInputStream(data));
                        // TODO(b/166535853): ProtoOutputStream doesn't provide a method to write
                        // a byte array that already contains a serialized proto, so I have to
                        // deserialize, then re-serialize.  This is computationally inefficient.
                        StateResidencyResult[] stateResidencyResult =
                            StateResidencyResultUtils.unpackProtoMessage(data);
                        StateResidencyResultUtils.packProtoMessage(stateResidencyResult, pos);
                        if (DEBUG) StateResidencyResultUtils.print(stateResidencyResult);
                    } catch (IOException e) {
                        Slog.e(TAG, "Failed to write residency data to incident report.");
                    }
                }
            });
        } catch (IOException e) {
            Slog.e(TAG, "Failed to write residency data to incident report.");
        }

        pos.flush();
    }

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

    @VisibleForTesting
    public long getStartWallTime() {
        return mStartWallTime;
    }

    public PowerStatsLogger(Context context, File dataStoragePath,
            String meterFilename, String meterCacheFilename,
            String modelFilename, String modelCacheFilename,
            String residencyFilename, String residencyCacheFilename,
            IPowerStatsHALWrapper powerStatsHALWrapper) {
        super(Looper.getMainLooper());
        mStartWallTime = currentTimeMillis() - SystemClock.elapsedRealtime();
        if (DEBUG) Slog.d(TAG, "mStartWallTime: " + mStartWallTime);
        mPowerStatsHALWrapper = powerStatsHALWrapper;
        mDataStoragePath = dataStoragePath;

        mPowerStatsMeterStorage = new PowerStatsDataStorage(context, mDataStoragePath,
            meterFilename);
        mPowerStatsModelStorage = new PowerStatsDataStorage(context, mDataStoragePath,
            modelFilename);
        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);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy