blob: ef0079e0c01fcdd07fdf209ac87128c45580bf8a [file] [log] [blame]
/*
* 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);
}
}
}