Merge "Limit the number of UidBatteryConsumers included in the BatteryUsageStats atom" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ab31594c96
@@ -75,6 +75,8 @@ public final class BatteryUsageStats implements Parcelable {
|
|||||||
|
|
||||||
public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT = 2;
|
public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT = 2;
|
||||||
|
|
||||||
|
private static final int STATSD_PULL_ATOM_MAX_BYTES = 45000;
|
||||||
|
|
||||||
private final int mDischargePercentage;
|
private final int mDischargePercentage;
|
||||||
private final double mBatteryCapacityMah;
|
private final double mBatteryCapacityMah;
|
||||||
private final long mStatsStartTimestampMs;
|
private final long mStatsStartTimestampMs;
|
||||||
@@ -369,30 +371,60 @@ public final class BatteryUsageStats implements Parcelable {
|
|||||||
|
|
||||||
/** Returns a proto (as used for atoms.proto) corresponding to this BatteryUsageStats. */
|
/** Returns a proto (as used for atoms.proto) corresponding to this BatteryUsageStats. */
|
||||||
public byte[] getStatsProto() {
|
public byte[] getStatsProto() {
|
||||||
|
// ProtoOutputStream.getRawSize() returns the buffer size before compaction.
|
||||||
|
// BatteryUsageStats contains a lot of integers, so compaction of integers to
|
||||||
|
// varint reduces the size of the proto buffer by as much as 50%.
|
||||||
|
int maxRawSize = (int) (STATSD_PULL_ATOM_MAX_BYTES * 1.75);
|
||||||
|
// Limit the number of attempts in order to prevent an infinite loop
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
final ProtoOutputStream proto = new ProtoOutputStream();
|
||||||
|
writeStatsProto(proto, maxRawSize);
|
||||||
|
|
||||||
|
final int rawSize = proto.getRawSize();
|
||||||
|
final byte[] protoOutput = proto.getBytes();
|
||||||
|
|
||||||
|
if (protoOutput.length <= STATSD_PULL_ATOM_MAX_BYTES) {
|
||||||
|
return protoOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust maxRawSize proportionately and try again.
|
||||||
|
maxRawSize =
|
||||||
|
(int) ((long) STATSD_PULL_ATOM_MAX_BYTES * rawSize / protoOutput.length - 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: if we have failed to generate a proto smaller than STATSD_PULL_ATOM_MAX_BYTES,
|
||||||
|
// just generate a proto with the _rawSize_ of STATSD_PULL_ATOM_MAX_BYTES, which is
|
||||||
|
// guaranteed to produce a compacted proto (significantly) smaller than
|
||||||
|
// STATSD_PULL_ATOM_MAX_BYTES.
|
||||||
|
final ProtoOutputStream proto = new ProtoOutputStream();
|
||||||
|
writeStatsProto(proto, STATSD_PULL_ATOM_MAX_BYTES);
|
||||||
|
return proto.getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private void writeStatsProto(ProtoOutputStream proto, int maxRawSize) {
|
||||||
final BatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
|
final BatteryConsumer deviceBatteryConsumer = getAggregateBatteryConsumer(
|
||||||
AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
|
AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
|
||||||
|
|
||||||
final ProtoOutputStream proto = new ProtoOutputStream();
|
|
||||||
proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, getStatsStartTimestamp());
|
proto.write(BatteryUsageStatsAtomsProto.SESSION_START_MILLIS, getStatsStartTimestamp());
|
||||||
proto.write(BatteryUsageStatsAtomsProto.SESSION_END_MILLIS, getStatsEndTimestamp());
|
proto.write(BatteryUsageStatsAtomsProto.SESSION_END_MILLIS, getStatsEndTimestamp());
|
||||||
proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, getStatsDuration());
|
proto.write(BatteryUsageStatsAtomsProto.SESSION_DURATION_MILLIS, getStatsDuration());
|
||||||
deviceBatteryConsumer.writeStatsProto(proto,
|
|
||||||
BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
|
|
||||||
writeUidBatteryConsumersProto(proto);
|
|
||||||
proto.write(BatteryUsageStatsAtomsProto.SESSION_DISCHARGE_PERCENTAGE,
|
proto.write(BatteryUsageStatsAtomsProto.SESSION_DISCHARGE_PERCENTAGE,
|
||||||
getDischargePercentage());
|
getDischargePercentage());
|
||||||
return proto.getBytes();
|
deviceBatteryConsumer.writeStatsProto(proto,
|
||||||
|
BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
|
||||||
|
writeUidBatteryConsumersProto(proto, maxRawSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the UidBatteryConsumers data, held by this BatteryUsageStats, to the proto (as used
|
* Writes the UidBatteryConsumers data, held by this BatteryUsageStats, to the proto (as used
|
||||||
* for atoms.proto).
|
* for atoms.proto).
|
||||||
*/
|
*/
|
||||||
private void writeUidBatteryConsumersProto(ProtoOutputStream proto) {
|
private void writeUidBatteryConsumersProto(ProtoOutputStream proto, int maxRawSize) {
|
||||||
final List<UidBatteryConsumer> consumers = getUidBatteryConsumers();
|
final List<UidBatteryConsumer> consumers = getUidBatteryConsumers();
|
||||||
|
// Order consumers by descending weight (a combination of consumed power and usage time)
|
||||||
|
consumers.sort(Comparator.comparingDouble(this::getUidBatteryConsumerWeight).reversed());
|
||||||
|
|
||||||
// TODO(b/189225426): Sort the list by power consumption. If during the for,
|
|
||||||
// proto.getRawSize() > 45kb, truncate the remainder of the list.
|
|
||||||
final int size = consumers.size();
|
final int size = consumers.size();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
final UidBatteryConsumer consumer = consumers.get(i);
|
final UidBatteryConsumer consumer = consumers.get(i);
|
||||||
@@ -420,9 +452,35 @@ public final class BatteryUsageStats implements Parcelable {
|
|||||||
BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_BACKGROUND_MILLIS,
|
BatteryUsageStatsAtomsProto.UidBatteryConsumer.TIME_IN_BACKGROUND_MILLIS,
|
||||||
bgMs);
|
bgMs);
|
||||||
proto.end(token);
|
proto.end(token);
|
||||||
|
|
||||||
|
if (proto.getRawSize() >= maxRawSize) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final double WEIGHT_CONSUMED_POWER = 1;
|
||||||
|
// Weight one hour in foreground the same as 100 mAh of power drain
|
||||||
|
private static final double WEIGHT_FOREGROUND_STATE = 100.0 / (1 * 60 * 60 * 1000);
|
||||||
|
// Weight one hour in background the same as 300 mAh of power drain
|
||||||
|
private static final double WEIGHT_BACKGROUND_STATE = 300.0 / (1 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the weight associated with a UidBatteryConsumer, which is used for sorting.
|
||||||
|
* We want applications with the largest consumed power as well as applications
|
||||||
|
* with the highest usage time to be included in the statsd atom.
|
||||||
|
*/
|
||||||
|
private double getUidBatteryConsumerWeight(UidBatteryConsumer uidBatteryConsumer) {
|
||||||
|
final double consumedPower = uidBatteryConsumer.getConsumedPower();
|
||||||
|
final long timeInForeground =
|
||||||
|
uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND);
|
||||||
|
final long timeInBackground =
|
||||||
|
uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND);
|
||||||
|
return consumedPower * WEIGHT_CONSUMED_POWER
|
||||||
|
+ timeInForeground * WEIGHT_FOREGROUND_STATE
|
||||||
|
+ timeInBackground * WEIGHT_BACKGROUND_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prints the stats in a human-readable format.
|
* Prints the stats in a human-readable format.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ android_test {
|
|||||||
static_libs: [
|
static_libs: [
|
||||||
"androidx.test.rules",
|
"androidx.test.rules",
|
||||||
"junit",
|
"junit",
|
||||||
|
"mockito-target-minus-junit4",
|
||||||
"platform-test-annotations",
|
"platform-test-annotations",
|
||||||
"platformprotosnano",
|
"platformprotosnano",
|
||||||
"statsdprotolite",
|
"statsdprotolite",
|
||||||
|
"truth-prebuilt",
|
||||||
],
|
],
|
||||||
|
|
||||||
libs: ["android.test.runner"],
|
libs: ["android.test.runner"],
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ package com.android.internal.os;
|
|||||||
|
|
||||||
import static android.os.BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE;
|
import static android.os.BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.os.BatteryConsumer;
|
import android.os.BatteryConsumer;
|
||||||
import android.os.BatteryUsageStats;
|
import android.os.BatteryUsageStats;
|
||||||
@@ -37,7 +41,6 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public class BatteryUsageStatsPulledTest {
|
public class BatteryUsageStatsPulledTest {
|
||||||
|
|
||||||
@@ -225,4 +228,75 @@ public class BatteryUsageStatsPulledTest {
|
|||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLargeAtomTruncated() {
|
||||||
|
final BatteryUsageStats.Builder builder =
|
||||||
|
new BatteryUsageStats.Builder(new String[0]);
|
||||||
|
// If not truncated, this BatteryUsageStats object would generate a proto buffer
|
||||||
|
// larger than 70 Kb
|
||||||
|
for (int i = 0; i < 20000; i++) {
|
||||||
|
BatteryStatsImpl.Uid mockUid = mock(BatteryStatsImpl.Uid.class);
|
||||||
|
when(mockUid.getUid()).thenReturn(i);
|
||||||
|
builder.getOrCreateUidBatteryConsumerBuilder(mockUid)
|
||||||
|
.setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 1 * 60 * 1000)
|
||||||
|
.setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 2 * 60 * 1000)
|
||||||
|
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 30)
|
||||||
|
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a UID with much larger battery footprint
|
||||||
|
final int largeConsumerUid = 20001;
|
||||||
|
BatteryStatsImpl.Uid largeConsumerMockUid = mock(BatteryStatsImpl.Uid.class);
|
||||||
|
when(largeConsumerMockUid.getUid()).thenReturn(largeConsumerUid);
|
||||||
|
builder.getOrCreateUidBatteryConsumerBuilder(largeConsumerMockUid)
|
||||||
|
.setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 10 * 60 * 1000)
|
||||||
|
.setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 20 * 60 * 1000)
|
||||||
|
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
|
||||||
|
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 400);
|
||||||
|
|
||||||
|
// Add a UID with much larger usage duration
|
||||||
|
final int highUsageUid = 20002;
|
||||||
|
BatteryStatsImpl.Uid highUsageMockUid = mock(BatteryStatsImpl.Uid.class);
|
||||||
|
when(highUsageMockUid.getUid()).thenReturn(highUsageUid);
|
||||||
|
builder.getOrCreateUidBatteryConsumerBuilder(highUsageMockUid)
|
||||||
|
.setTimeInStateMs(android.os.UidBatteryConsumer.STATE_FOREGROUND, 60 * 60 * 1000)
|
||||||
|
.setTimeInStateMs(android.os.UidBatteryConsumer.STATE_BACKGROUND, 120 * 60 * 1000)
|
||||||
|
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, 3)
|
||||||
|
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 4);
|
||||||
|
|
||||||
|
BatteryUsageStats batteryUsageStats = builder.build();
|
||||||
|
final byte[] bytes = batteryUsageStats.getStatsProto();
|
||||||
|
assertThat(bytes.length).isGreaterThan(40000);
|
||||||
|
assertThat(bytes.length).isLessThan(50000);
|
||||||
|
|
||||||
|
BatteryUsageStatsAtomsProto proto;
|
||||||
|
try {
|
||||||
|
proto = BatteryUsageStatsAtomsProto.parseFrom(bytes);
|
||||||
|
} catch (InvalidProtocolBufferNanoException e) {
|
||||||
|
fail("Invalid proto: " + e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean largeConsumerIncluded = false;
|
||||||
|
boolean highUsageAppIncluded = false;
|
||||||
|
for (int i = 0; i < proto.uidBatteryConsumers.length; i++) {
|
||||||
|
if (proto.uidBatteryConsumers[i].uid == largeConsumerUid) {
|
||||||
|
largeConsumerIncluded = true;
|
||||||
|
BatteryUsageStatsAtomsProto.BatteryConsumerData consumerData =
|
||||||
|
proto.uidBatteryConsumers[i].batteryConsumerData;
|
||||||
|
assertThat(consumerData.totalConsumedPowerDeciCoulombs / 36)
|
||||||
|
.isEqualTo(300 + 400);
|
||||||
|
} else if (proto.uidBatteryConsumers[i].uid == highUsageUid) {
|
||||||
|
highUsageAppIncluded = true;
|
||||||
|
BatteryUsageStatsAtomsProto.BatteryConsumerData consumerData =
|
||||||
|
proto.uidBatteryConsumers[i].batteryConsumerData;
|
||||||
|
assertThat(consumerData.totalConsumedPowerDeciCoulombs / 36)
|
||||||
|
.isEqualTo(3 + 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(largeConsumerIncluded).isTrue();
|
||||||
|
assertThat(highUsageAppIncluded).isTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user