blob: 9e0aa32a0b9efe4833d3d701d28a22179d4716aa [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.am;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.power.stats.EnergyConsumer;
import android.hardware.power.stats.EnergyConsumerAttribution;
import android.hardware.power.stats.EnergyConsumerResult;
import android.hardware.power.stats.EnergyConsumerType;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
/**
* Keeps snapshots of data from previously pulled EnergyConsumerResults.
*/
@VisibleForTesting
public class MeasuredEnergySnapshot {
private static final String TAG = "MeasuredEnergySnapshot";
public static final long UNAVAILABLE = -1L;
/** Map of {@link EnergyConsumer#id} to its corresponding {@link EnergyConsumer}. */
private final SparseArray<EnergyConsumer> mEnergyConsumers;
/** Number of ordinals for {@link EnergyConsumerType#OTHER}. */
private final int mNumOtherOrdinals;
/**
* Energy snapshots, mapping {@link EnergyConsumer#id} to energy (UJ) from the last time
* each {@link EnergyConsumer} was updated.
*
* Note that the snapshots for different ids may have been taken at different times.
* Note that energies for all existing ids are stored here, including each ordinal of type
* {@link EnergyConsumerType#OTHER} (tracking their total energy usage).
*
* If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}).
*/
private final SparseLongArray mMeasuredEnergySnapshots;
/**
* Energy snapshots <b>per uid</b> from the last time each {@link EnergyConsumer} of type
* {@link EnergyConsumerType#OTHER} was updated.
* It maps each OTHER {@link EnergyConsumer#id} to a SparseLongArray, which itself maps each
* uid to an energy (UJ). That is,
* mAttributionSnapshots.get(consumerId).get(uid) = energy used by uid for this consumer.
*
* If an id is not present yet, it is treated as uninitialized (i.e. each uid is unavailable).
* If an id is present but a uid is not present, that uid's energy is 0.
*/
private final SparseArray<SparseLongArray> mAttributionSnapshots;
/**
* Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
* exist and what their details are.
*/
MeasuredEnergySnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
mEnergyConsumers = idToConsumerMap;
mMeasuredEnergySnapshots = new SparseLongArray(mEnergyConsumers.size());
mNumOtherOrdinals = calculateNumOtherOrdinals(idToConsumerMap);
mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
}
/**
* Returns the number of ordinals for {@link EnergyConsumerType#OTHER}, i.e. the number of
* custom energy buckets supported by the device.
*/
public int getNumOtherOrdinals() {
return mNumOtherOrdinals;
}
/** Class for returning measured energy delta data. */
static class MeasuredEnergyDeltaData {
/** The energyUJ for {@link EnergyConsumerType#DISPLAY}. */
public long displayEnergyUJ = UNAVAILABLE;
/** Map of {@link EnergyConsumerType#OTHER} ordinals to their total energyUJ. */
public @Nullable long[] otherTotalEnergyUJ = null;
/** Map of {@link EnergyConsumerType#OTHER} ordinals to their {uid->energyUJ} maps. */
public @Nullable SparseLongArray[] otherUidEnergiesUJ = null;
}
/**
* Update with the some freshly measured energies and return the difference (delta)
* between the previously stored values and the passed-in values.
*
* @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s.
* Consumers that are not present are ignored (they are *not* treated as 0).
*
* @return a MeasuredEnergyDeltaData, containing maps from the updated consumers to
* their corresponding energy deltas.
* Fields with no interesting data (consumers not present in ecrs or with no energy
* difference) will generally be left as their default values.
* otherTotalEnergyUJ and otherUidEnergiesUJ are always either both null or both of
* length {@link #getNumOtherOrdinals()}.
* Returns null, if ecrs is null or empty.
*/
public @Nullable MeasuredEnergyDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs) {
if (ecrs == null || ecrs.length == 0) {
return null;
}
final MeasuredEnergyDeltaData output = new MeasuredEnergyDeltaData();
for (final EnergyConsumerResult ecr : ecrs) {
// Extract the new energy data for the current consumer.
final int consumerId = ecr.id;
final long newEnergyUJ = ecr.energyUWs;
final EnergyConsumerAttribution[] newAttributions = ecr.attribution;
// Look up the static information about this consumer.
final EnergyConsumer consumer = mEnergyConsumers.get(consumerId, null);
if (consumer == null) {
Slog.e(TAG, "updateAndGetDelta given invalid consumerId " + consumerId);
continue;
}
final int type = consumer.type;
final int ordinal = consumer.ordinal;
// Look up, and update, the old energy information about this consumer.
final long oldEnergyUJ = mMeasuredEnergySnapshots.get(consumerId, UNAVAILABLE);
mMeasuredEnergySnapshots.put(consumerId, newEnergyUJ);
final SparseLongArray otherUidEnergies
= updateAndGetDeltaForTypeOther(consumer, newAttributions);
// Everything is fully done being updated. We now calculate the delta for returning.
// NB: Since sum(attribution.energyUWs)<=energyUWs we assume that if deltaEnergy==0
// there's no attribution either. Technically that isn't enforced at the HAL, but we
// can't really trust data like that anyway.
if (oldEnergyUJ < 0) continue; // Generally happens only on initialization.
if (newEnergyUJ == oldEnergyUJ) continue;
final long deltaUJ = newEnergyUJ - oldEnergyUJ;
if (deltaUJ < 0) {
Slog.e(TAG, "EnergyConsumer " + consumer.name + ": new energy (" + newEnergyUJ
+ ") < old energy (" + oldEnergyUJ + "). Skipping. ");
continue;
}
switch (type) {
case EnergyConsumerType.DISPLAY:
output.displayEnergyUJ = deltaUJ;
break;
case EnergyConsumerType.OTHER:
if (output.otherTotalEnergyUJ == null) {
output.otherTotalEnergyUJ = new long[getNumOtherOrdinals()];
output.otherUidEnergiesUJ = new SparseLongArray[getNumOtherOrdinals()];
}
output.otherTotalEnergyUJ[ordinal] = deltaUJ;
output.otherUidEnergiesUJ[ordinal] = otherUidEnergies;
break;
default:
Slog.w(TAG, "Ignoring consumer " + consumer.name + " of unknown type " + type);
}
}
return output;
}
/**
* For a consumer of type {@link EnergyConsumerType#OTHER}, updates
* {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
* difference (delta) between the previously stored values and the passed-in values.
*
* @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
* @param newAttributions Record of uids and their new energyUJ values.
* Any uid not present is treated as having energy 0.
* If null or empty, all uids are treated as having energy 0.
* @return A map (in the sense of {@link MeasuredEnergyDeltaData#otherUidEnergiesUJ} for this
* consumer) of uid -> energyDelta, with all uids that have a non-zero energyDelta.
* Returns null if no delta available to calculate.
*/
private @Nullable SparseLongArray updateAndGetDeltaForTypeOther(
@NonNull EnergyConsumer consumerInfo,
@Nullable EnergyConsumerAttribution[] newAttributions) {
if (consumerInfo.type != EnergyConsumerType.OTHER) {
return null;
}
if (newAttributions == null) {
// Treat null as empty (i.e. all uids have 0 energy).
newAttributions = new EnergyConsumerAttribution[0];
}
// SparseLongArray mapping uid -> energyUJ (for this particular consumerId)
SparseLongArray uidOldEnergyMap = mAttributionSnapshots.get(consumerInfo.id, null);
// If uidOldEnergyMap wasn't present, each uid was UNAVAILABLE, so update data and return.
if (uidOldEnergyMap == null) {
uidOldEnergyMap = new SparseLongArray(newAttributions.length);
mAttributionSnapshots.put(consumerInfo.id, uidOldEnergyMap);
for (EnergyConsumerAttribution newAttribution : newAttributions) {
uidOldEnergyMap.put(newAttribution.uid, newAttribution.energyUWs);
}
return null;
}
// Map uid -> energyDelta. No initial capacity since many deltas might be 0.
final SparseLongArray uidEnergyDeltas = new SparseLongArray();
for (EnergyConsumerAttribution newAttribution : newAttributions) {
final int uid = newAttribution.uid;
final long newEnergyUJ = newAttribution.energyUWs;
// uidOldEnergyMap was present. So any particular uid that wasn't present, had 0 energy.
final long oldEnergyUJ = uidOldEnergyMap.get(uid, 0L);
uidOldEnergyMap.put(uid, newEnergyUJ);
// Everything is fully done being updated. We now calculate the delta for returning.
if (oldEnergyUJ < 0) continue;
if (newEnergyUJ == oldEnergyUJ) continue;
final long deltaUJ = newEnergyUJ - oldEnergyUJ;
if (deltaUJ < 0) {
Slog.e(TAG, "EnergyConsumer " + consumerInfo.name + ": new energy (" + newEnergyUJ
+ ") but old energy (" + oldEnergyUJ + "). Skipping. ");
continue;
}
uidEnergyDeltas.put(uid, deltaUJ);
}
return uidEnergyDeltas;
}
/** Dump debug data. */
public void dump(PrintWriter pw) {
pw.println("Measured energy snapshot");
pw.println("List of EnergyConsumers:");
for (int i = 0; i < mEnergyConsumers.size(); i++) {
final int id = mEnergyConsumers.keyAt(i);
final EnergyConsumer consumer = mEnergyConsumers.valueAt(i);
pw.println(String.format(" Consumer %d is {id=%d, ordinal=%d, type=%d, name=%s}", id,
consumer.id, consumer.ordinal, consumer.type, consumer.name));
}
pw.println("Map of consumerIds to energy (in microjoules):");
for (int i = 0; i < mMeasuredEnergySnapshots.size(); i++) {
final int id = mMeasuredEnergySnapshots.keyAt(i);
final long energyUJ = mMeasuredEnergySnapshots.valueAt(i);
pw.println(String.format(" Consumer %d has energy %d uJ}", id, energyUJ));
}
pw.println("List of the " + mNumOtherOrdinals + " OTHER EnergyConsumers:");
pw.println(" " + mAttributionSnapshots);
pw.println();
}
/** Determines the number of ordinals for {@link EnergyConsumerType#OTHER}. */
private static int calculateNumOtherOrdinals(SparseArray<EnergyConsumer> idToConsumer) {
if (idToConsumer == null) return 0;
int numOtherOrdinals = 0;
final int size = idToConsumer.size();
for (int idx = 0; idx < size; idx++) {
final EnergyConsumer consumer = idToConsumer.valueAt(idx);
if (consumer.type == EnergyConsumerType.OTHER) numOtherOrdinals++;
}
return numOtherOrdinals;
}
}