Merge "Add sampling to FGS atom" into sc-dev
This commit is contained in:
@@ -5933,6 +5933,10 @@ public final class ActiveServices {
|
||||
* @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state.
|
||||
*/
|
||||
private void logForegroundServiceStateChanged(ServiceRecord r, int state, int durationMs) {
|
||||
if (!ActivityManagerUtils.shouldSamplePackageForAtom(
|
||||
r.packageName, mAm.mConstants.mDefaultFgsAtomSampleRate)) {
|
||||
return;
|
||||
}
|
||||
FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
|
||||
r.appInfo.uid,
|
||||
r.shortInstanceName,
|
||||
|
||||
@@ -97,6 +97,7 @@ final class ActivityManagerConstants extends ContentObserver {
|
||||
static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration";
|
||||
static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration";
|
||||
static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout";
|
||||
static final String KEY_FGS_ATOM_SAMPLE_RATE = "fgs_atom_sample_rate";
|
||||
|
||||
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
|
||||
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
|
||||
@@ -137,6 +138,7 @@ final class ActivityManagerConstants extends ContentObserver {
|
||||
private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000;
|
||||
private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000;
|
||||
private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000;
|
||||
private static final float DEFAULT_FGS_ATOM_SAMPLE_RATE = 1; // 100 %
|
||||
|
||||
// Flag stored in the DeviceConfig API.
|
||||
/**
|
||||
@@ -430,6 +432,13 @@ final class ActivityManagerConstants extends ContentObserver {
|
||||
*/
|
||||
volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS;
|
||||
|
||||
/**
|
||||
* Sample rate for the FGS westworld atom.
|
||||
*
|
||||
* If the value is 0.1, 10% of the installed packages would be sampled.
|
||||
*/
|
||||
volatile float mDefaultFgsAtomSampleRate = DEFAULT_FGS_ATOM_SAMPLE_RATE;
|
||||
|
||||
private final ActivityManagerService mService;
|
||||
private ContentResolver mResolver;
|
||||
private final KeyValueListParser mParser = new KeyValueListParser(',');
|
||||
@@ -629,6 +638,9 @@ final class ActivityManagerConstants extends ContentObserver {
|
||||
case KEY_FGS_START_FOREGROUND_TIMEOUT:
|
||||
updateFgsStartForegroundTimeout();
|
||||
break;
|
||||
case KEY_FGS_ATOM_SAMPLE_RATE:
|
||||
updateFgsAtomSamplePercent();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -933,6 +945,13 @@ final class ActivityManagerConstants extends ContentObserver {
|
||||
DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
private void updateFgsAtomSamplePercent() {
|
||||
mDefaultFgsAtomSampleRate = DeviceConfig.getFloat(
|
||||
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
||||
KEY_FGS_ATOM_SAMPLE_RATE,
|
||||
DEFAULT_FGS_ATOM_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
private void updateImperceptibleKillExemptions() {
|
||||
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear();
|
||||
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
|
||||
@@ -1145,6 +1164,8 @@ final class ActivityManagerConstants extends ContentObserver {
|
||||
pw.println(mFlagFgsStartRestrictionEnabled);
|
||||
pw.print(" "); pw.print(KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK);
|
||||
pw.print("="); pw.println(mFgsStartRestrictionCheckCallerTargetSdk);
|
||||
pw.print(" "); pw.print(KEY_FGS_ATOM_SAMPLE_RATE);
|
||||
pw.print("="); pw.println(mDefaultFgsAtomSampleRate);
|
||||
|
||||
pw.println();
|
||||
if (mOverrideMaxCachedProcesses >= 0) {
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.app.ActivityThread;
|
||||
import android.provider.Settings;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* To store random utility methods...
|
||||
*/
|
||||
public class ActivityManagerUtils {
|
||||
private ActivityManagerUtils() {
|
||||
}
|
||||
|
||||
private static Integer sAndroidIdHash;
|
||||
|
||||
@GuardedBy("sHashCache")
|
||||
private static final ArrayMap<String, Integer> sHashCache = new ArrayMap<>();
|
||||
|
||||
private static String sInjectedAndroidId;
|
||||
|
||||
/** Used by the unit tests to inject an android ID. Do not set in the prod code. */
|
||||
@VisibleForTesting
|
||||
static void injectAndroidIdForTest(String androidId) {
|
||||
sInjectedAndroidId = androidId;
|
||||
sAndroidIdHash = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hash between [0, MAX_VALUE] generated from the android ID.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static int getAndroidIdHash() {
|
||||
// No synchronization is required. Double-initialization is fine here.
|
||||
if (sAndroidIdHash == null) {
|
||||
final String androidId = Settings.Secure.getString(
|
||||
ActivityThread.currentApplication().getContentResolver(),
|
||||
Settings.Secure.ANDROID_ID);
|
||||
sAndroidIdHash = getUnsignedHashUnCached(
|
||||
sInjectedAndroidId != null ? sInjectedAndroidId : androidId);
|
||||
}
|
||||
return sAndroidIdHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hash between [0, MAX_VALUE] generated from a package name, using a cache.
|
||||
*
|
||||
* Because all the results are cached, do not use it for dynamically generated strings.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static int getUnsignedHashCached(String s) {
|
||||
synchronized (sHashCache) {
|
||||
final Integer cached = sHashCache.get(s);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
final int hash = getUnsignedHashUnCached(s);
|
||||
sHashCache.put(s.intern(), hash);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hash between [0, MAX_VALUE] generated from a package name.
|
||||
*/
|
||||
private static int getUnsignedHashUnCached(String s) {
|
||||
try {
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.update(s.getBytes());
|
||||
return unsignedIntFromBytes(digest.digest());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static int unsignedIntFromBytes(byte[] longEnoughBytes) {
|
||||
return (extractByte(longEnoughBytes, 0)
|
||||
| extractByte(longEnoughBytes, 1)
|
||||
| extractByte(longEnoughBytes, 2)
|
||||
| extractByte(longEnoughBytes, 3))
|
||||
& 0x7FFF_FFFF;
|
||||
}
|
||||
|
||||
private static int extractByte(byte[] bytes, int index) {
|
||||
return (((int) bytes[index]) & 0xFF) << (index * 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether a package should be logged, using a random value based on the ANDROID_ID,
|
||||
* with a given sampling rate.
|
||||
*/
|
||||
public static boolean shouldSamplePackageForAtom(String packageName, float rate) {
|
||||
if (rate <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (rate >= 1) {
|
||||
return true;
|
||||
}
|
||||
final int hash = getUnsignedHashCached(packageName) ^ getAndroidIdHash();
|
||||
|
||||
return (((double) hash) / Integer.MAX_VALUE) <= rate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
@SmallTest
|
||||
public class ActivityManagerUtilsTest {
|
||||
@Test
|
||||
public void getAndroidIdHash() {
|
||||
// getAndroidIdHash() essentially returns a random a value. Just make sure it's
|
||||
// non-negative.
|
||||
assertThat(ActivityManagerUtils.getAndroidIdHash()).isAtLeast(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUnsignedHashCached() {
|
||||
assertThat(ActivityManagerUtils.getUnsignedHashCached("x")).isEqualTo(
|
||||
ActivityManagerUtils.getUnsignedHashCached("x"));
|
||||
|
||||
assertThat(ActivityManagerUtils.getUnsignedHashCached("x")).isNotEqualTo(
|
||||
ActivityManagerUtils.getUnsignedHashCached("y"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSamplePackage_sampleNone() {
|
||||
final int numTests = 100000;
|
||||
for (int i = 0; i < numTests; i++) {
|
||||
assertThat(ActivityManagerUtils.shouldSamplePackageForAtom("" + i, 0))
|
||||
.isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSamplePackage_sampleAll() {
|
||||
final int numTests = 100000;
|
||||
|
||||
for (int i = 0; i < numTests; i++) {
|
||||
assertThat(ActivityManagerUtils.shouldSamplePackageForAtom("" + i, 1))
|
||||
.isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure, with the same android ID, an expected rate of the packages are selected.
|
||||
*/
|
||||
@Test
|
||||
public void shouldSamplePackage_sampleSome_fixedAndroidId() {
|
||||
checkShouldSamplePackage_fixedAndroidId(0.1f);
|
||||
checkShouldSamplePackage_fixedAndroidId(0.5f);
|
||||
checkShouldSamplePackage_fixedAndroidId(0.9f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure, the same package is selected on an expected rate of the devices.
|
||||
*/
|
||||
@Test
|
||||
public void shouldSamplePackage_sampleSome_fixedPackage() {
|
||||
checkShouldSamplePackage_fixedPackage(0.1f);
|
||||
checkShouldSamplePackage_fixedPackage(0.5f);
|
||||
checkShouldSamplePackage_fixedPackage(0.9f);
|
||||
}
|
||||
|
||||
private void checkShouldSamplePackage_fixedPackage(float sampleRate) {
|
||||
checkShouldSamplePackage(sampleRate, sampleRate, true, false);
|
||||
}
|
||||
|
||||
private void checkShouldSamplePackage_fixedAndroidId(float sampleRate) {
|
||||
checkShouldSamplePackage(sampleRate, sampleRate, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSheckShouldSamplePackage() {
|
||||
// Just make sure checkShouldSamplePackage is actually working...
|
||||
try {
|
||||
checkShouldSamplePackage(0.3f, 0.6f, false, true);
|
||||
fail();
|
||||
} catch (AssertionError expected) {
|
||||
}
|
||||
try {
|
||||
checkShouldSamplePackage(0.6f, 0.3f, true, false);
|
||||
fail();
|
||||
} catch (AssertionError expected) {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkShouldSamplePackage(float inputSampleRate, float expectedRate,
|
||||
boolean fixedPackage, boolean fixedAndroidId) {
|
||||
final int numTests = 100000;
|
||||
|
||||
try {
|
||||
int numSampled = 0;
|
||||
for (int i = 0; i < numTests; i++) {
|
||||
final String pkg = fixedPackage ? "fixed-package" : "" + i;
|
||||
ActivityManagerUtils.injectAndroidIdForTest(
|
||||
fixedAndroidId ? "fixed-android-id" : "" + i);
|
||||
|
||||
if (ActivityManagerUtils.shouldSamplePackageForAtom(pkg, inputSampleRate)) {
|
||||
numSampled++;
|
||||
}
|
||||
assertThat(ActivityManagerUtils.getUnsignedHashCached(pkg)).isEqualTo(
|
||||
ActivityManagerUtils.getUnsignedHashCached(pkg));
|
||||
}
|
||||
final double actualSampleRate = ((double) numSampled) / numTests;
|
||||
|
||||
assertThat(actualSampleRate).isWithin(0.05).of(expectedRate);
|
||||
} finally {
|
||||
ActivityManagerUtils.injectAndroidIdForTest(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user