blob: 2638f34fe7df4e3b68f524f5a5c2fc78c4b109ab [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 android.annotation.Nullable;
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.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserHandle;
import android.power.PowerStatsInternal;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.function.pooled.PooledLambda;
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 java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.CompletableFuture;
/**
* This class provides a system service that estimates system power usage
* per subsystem (modem, wifi, gps, display, etc) and provides those power
* estimates to subscribers.
*/
public class PowerStatsService extends SystemService {
private static final String TAG = PowerStatsService.class.getSimpleName();
private static final boolean DEBUG = false;
private static final String DATA_STORAGE_SUBDIR = "powerstats";
private static final int DATA_STORAGE_VERSION = 0;
private static final String METER_FILENAME = "log.powerstats.meter." + DATA_STORAGE_VERSION;
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
private PowerStatsLogger mPowerStatsLogger;
@Nullable
private BatteryTrigger mBatteryTrigger;
@Nullable
private TimerTrigger mTimerTrigger;
@Nullable
private StatsPullAtomCallbackImpl mPullAtomCallback;
@Nullable
private PowerStatsInternal mPowerStatsInternal;
@Nullable
@GuardedBy("this")
private Looper mLooper;
@Nullable
@GuardedBy("this")
private EnergyConsumer[] mEnergyConsumers = null;
@VisibleForTesting
static class Injector {
@GuardedBy("this")
private IPowerStatsHALWrapper mPowerStatsHALWrapper;
File createDataStoragePath() {
return new File(Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM),
DATA_STORAGE_SUBDIR);
}
String createMeterFilename() {
return METER_FILENAME;
}
String createModelFilename() {
return MODEL_FILENAME;
}
String createResidencyFilename() {
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();
}
IPowerStatsHALWrapper getPowerStatsHALWrapperImpl() {
synchronized (this) {
if (mPowerStatsHALWrapper == null) {
mPowerStatsHALWrapper = PowerStatsHALWrapper.getPowerStatsHalImpl();
}
return mPowerStatsHALWrapper;
}
}
PowerStatsLogger createPowerStatsLogger(Context context, Looper looper,
File dataStoragePath, String meterFilename, String meterCacheFilename,
String modelFilename, String modelCacheFilename,
String residencyFilename, String residencyCacheFilename,
IPowerStatsHALWrapper powerStatsHALWrapper) {
return new PowerStatsLogger(context, looper, dataStoragePath,
meterFilename, meterCacheFilename,
modelFilename, modelCacheFilename,
residencyFilename, residencyCacheFilename,
powerStatsHALWrapper);
}
BatteryTrigger createBatteryTrigger(Context context, PowerStatsLogger powerStatsLogger) {
return new BatteryTrigger(context, powerStatsLogger, true /* trigger enabled */);
}
TimerTrigger createTimerTrigger(Context context, PowerStatsLogger powerStatsLogger) {
return new TimerTrigger(context, powerStatsLogger, true /* trigger enabled */);
}
StatsPullAtomCallbackImpl createStatsPullerImpl(Context context,
PowerStatsInternal powerStatsInternal) {
return new StatsPullAtomCallbackImpl(context, powerStatsInternal);
}
}
private final class BinderService extends Binder {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
if (mPowerStatsLogger == null) {
Slog.e(TAG, "PowerStats HAL is not initialized. No data available.");
} else {
if (args.length > 0 && "--proto".equals(args[0])) {
if ("model".equals(args[1])) {
mPowerStatsLogger.writeModelDataToFile(fd);
} else if ("meter".equals(args[1])) {
mPowerStatsLogger.writeMeterDataToFile(fd);
} else if ("residency".equals(args[1])) {
mPowerStatsLogger.writeResidencyDataToFile(fd);
}
} else if (args.length == 0) {
pw.println("PowerStatsService dumpsys: available PowerEntities");
PowerEntity[] powerEntity = getPowerStatsHal().getPowerEntityInfo();
PowerEntityUtils.dumpsys(powerEntity, pw);
pw.println("PowerStatsService dumpsys: available Channels");
Channel[] channel = getPowerStatsHal().getEnergyMeterInfo();
ChannelUtils.dumpsys(channel, pw);
pw.println("PowerStatsService dumpsys: available EnergyConsumers");
EnergyConsumer[] energyConsumer = getPowerStatsHal().getEnergyConsumerInfo();
EnergyConsumerUtils.dumpsys(energyConsumer, pw);
}
}
}
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
onSystemServicesReady();
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
onBootCompleted();
}
}
@Override
public void onStart() {
if (getPowerStatsHal().isInitialized()) {
mPowerStatsInternal = new LocalService();
publishLocalService(PowerStatsInternal.class, mPowerStatsInternal);
}
publishBinderService(Context.POWER_STATS_SERVICE, new BinderService());
}
private void onSystemServicesReady() {
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, getLooper(),
mDataStoragePath, mInjector.createMeterFilename(),
mInjector.createMeterCacheFilename(), mInjector.createModelFilename(),
mInjector.createModelCacheFilename(), mInjector.createResidencyFilename(),
mInjector.createResidencyCacheFilename(), getPowerStatsHal());
mBatteryTrigger = mInjector.createBatteryTrigger(mContext, mPowerStatsLogger);
mTimerTrigger = mInjector.createTimerTrigger(mContext, mPowerStatsLogger);
} else {
Slog.e(TAG, "Failed to start PowerStatsService loggers");
}
}
private IPowerStatsHALWrapper getPowerStatsHal() {
return mInjector.getPowerStatsHALWrapperImpl();
}
private Looper getLooper() {
synchronized (this) {
if (mLooper == null) {
HandlerThread thread = new HandlerThread(TAG);
thread.start();
return thread.getLooper();
}
return mLooper;
}
}
private EnergyConsumer[] getEnergyConsumerInfo() {
synchronized (this) {
if (mEnergyConsumers == null) {
mEnergyConsumers = getPowerStatsHal().getEnergyConsumerInfo();
}
return mEnergyConsumers;
}
}
public PowerStatsService(Context context) {
this(context, new Injector());
}
@VisibleForTesting
public PowerStatsService(Context context, Injector injector) {
super(context);
mContext = context;
mInjector = injector;
}
private final class LocalService extends PowerStatsInternal {
private final Handler mHandler;
LocalService() {
mHandler = new Handler(getLooper());
}
@Override
public EnergyConsumer[] getEnergyConsumerInfo() {
return getPowerStatsHal().getEnergyConsumerInfo();
}
@Override
public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumedAsync(
int[] energyConsumerIds) {
final CompletableFuture<EnergyConsumerResult[]> future = new CompletableFuture<>();
mHandler.sendMessage(
PooledLambda.obtainMessage(PowerStatsService.this::getEnergyConsumedAsync,
future, energyConsumerIds));
return future;
}
@Override
public PowerEntity[] getPowerEntityInfo() {
return getPowerStatsHal().getPowerEntityInfo();
}
@Override
public CompletableFuture<StateResidencyResult[]> getStateResidencyAsync(
int[] powerEntityIds) {
final CompletableFuture<StateResidencyResult[]> future = new CompletableFuture<>();
mHandler.sendMessage(
PooledLambda.obtainMessage(PowerStatsService.this::getStateResidencyAsync,
future, powerEntityIds));
return future;
}
@Override
public Channel[] getEnergyMeterInfo() {
return getPowerStatsHal().getEnergyMeterInfo();
}
@Override
public CompletableFuture<EnergyMeasurement[]> readEnergyMeterAsync(
int[] channelIds) {
final CompletableFuture<EnergyMeasurement[]> future = new CompletableFuture<>();
mHandler.sendMessage(
PooledLambda.obtainMessage(PowerStatsService.this::readEnergyMeterAsync,
future, channelIds));
return future;
}
}
private void getEnergyConsumedAsync(CompletableFuture<EnergyConsumerResult[]> future,
int[] energyConsumerIds) {
EnergyConsumerResult[] results = getPowerStatsHal().getEnergyConsumed(energyConsumerIds);
// STOPSHIP(253292374): Remove once missing EnergyConsumer results issue is resolved.
EnergyConsumer[] energyConsumers = getEnergyConsumerInfo();
if (energyConsumers != null) {
final int expectedLength;
if (energyConsumerIds.length == 0) {
// Empty request is a request for all available EnergyConsumers.
expectedLength = energyConsumers.length;
} else {
expectedLength = energyConsumerIds.length;
}
if (results == null || expectedLength != results.length) {
// Mismatch in requested/received energy consumer data.
StringBuilder sb = new StringBuilder();
sb.append("Requested ids:");
if (energyConsumerIds.length == 0) {
sb.append("ALL");
}
sb.append("[");
for (int i = 0; i < energyConsumerIds.length; i++) {
final int id = energyConsumerIds[i];
sb.append(id);
sb.append("(type:");
sb.append(energyConsumers[id].type);
sb.append(",ord:");
sb.append(energyConsumers[id].ordinal);
sb.append(",name:");
sb.append(energyConsumers[id].name);
sb.append(")");
if (i != expectedLength - 1) {
sb.append(", ");
}
}
sb.append("]");
sb.append(", Received result ids:");
if (results == null) {
sb.append("null");
} else {
sb.append("[");
final int resultLength = results.length;
for (int i = 0; i < resultLength; i++) {
final int id = results[i].id;
sb.append(id);
sb.append("(type:");
sb.append(energyConsumers[id].type);
sb.append(",ord:");
sb.append(energyConsumers[id].ordinal);
sb.append(",name:");
sb.append(energyConsumers[id].name);
sb.append(")");
if (i != resultLength - 1) {
sb.append(", ");
}
}
sb.append("]");
}
Slog.wtf(TAG, "Missing result from getEnergyConsumedAsync call. " + sb);
}
}
future.complete(results);
}
private void getStateResidencyAsync(CompletableFuture<StateResidencyResult[]> future,
int[] powerEntityIds) {
future.complete(getPowerStatsHal().getStateResidency(powerEntityIds));
}
private void readEnergyMeterAsync(CompletableFuture<EnergyMeasurement[]> future,
int[] channelIds) {
future.complete(getPowerStatsHal().readEnergyMeter(channelIds));
}
}