add LatencyTracter per action device properties

Each action LatencyTracker can log is controlled by gobal enable,
tracing threshold, and sampling rate DeviceConfig properties. This
change adds the ability to control properties per action.

By setting an action specific parameter, it will override the
associated global parameter.

New parameters added:
- <action name lowercase>_enable
- <action name lowercase>_sample_interval
- <action name lowercase>_trace_threshold

Bug: 260609037
Test: atest LatencyTrackerTest
Change-Id: I258a078074ca82a8837246c7b16407ab21ba1edf
Merged-In: I258a078074ca82a8837246c7b16407ab21ba1edf
This commit is contained in:
Nicholas Ambur
2022-12-15 06:17:18 +00:00
committed by Charles Chen
parent 980f4706f1
commit ffe101e992
2 changed files with 222 additions and 23 deletions

View File

@@ -37,12 +37,17 @@ import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPOR
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_UDFPS_ILLUMINATE;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__UNKNOWN_ACTION; import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__UNKNOWN_ACTION;
import static com.android.internal.util.LatencyTracker.ActionProperties.ENABLE_SUFFIX;
import static com.android.internal.util.LatencyTracker.ActionProperties.LEGACY_TRACE_THRESHOLD_SUFFIX;
import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX;
import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.os.ConditionVariable;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.Trace; import android.os.Trace;
import android.provider.DeviceConfig; import android.provider.DeviceConfig;
@@ -58,6 +63,7 @@ import com.android.internal.os.BackgroundThread;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -261,11 +267,11 @@ public class LatencyTracker {
@GuardedBy("mLock") @GuardedBy("mLock")
private final SparseArray<Session> mSessions = new SparseArray<>(); private final SparseArray<Session> mSessions = new SparseArray<>();
@GuardedBy("mLock") @GuardedBy("mLock")
private final int[] mTraceThresholdPerAction = new int[ACTIONS_ALL.length]; private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>();
@GuardedBy("mLock")
private int mSamplingInterval;
@GuardedBy("mLock") @GuardedBy("mLock")
private boolean mEnabled; private boolean mEnabled;
@VisibleForTesting
public final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable();
public static LatencyTracker getInstance(Context context) { public static LatencyTracker getInstance(Context context) {
if (sLatencyTracker == null) { if (sLatencyTracker == null) {
@@ -278,9 +284,9 @@ public class LatencyTracker {
return sLatencyTracker; return sLatencyTracker;
} }
private LatencyTracker() { @VisibleForTesting
public LatencyTracker() {
mEnabled = DEFAULT_ENABLED; mEnabled = DEFAULT_ENABLED;
mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
// Post initialization to the background in case we're running on the main thread. // Post initialization to the background in case we're running on the main thread.
BackgroundThread.getHandler().post(() -> this.updateProperties( BackgroundThread.getHandler().post(() -> this.updateProperties(
@@ -291,14 +297,24 @@ public class LatencyTracker {
private void updateProperties(DeviceConfig.Properties properties) { private void updateProperties(DeviceConfig.Properties properties) {
synchronized (mLock) { synchronized (mLock) {
mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY, int samplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
DEFAULT_SAMPLING_INTERVAL); DEFAULT_SAMPLING_INTERVAL);
mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED); mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
for (int action : ACTIONS_ALL) { for (int action : ACTIONS_ALL) {
mTraceThresholdPerAction[action] = String actionName = getNameOfAction(STATSD_ACTION[action]).toLowerCase(Locale.ROOT);
properties.getInt(getNameOfAction(STATSD_ACTION[action]), -1); int legacyActionTraceThreshold = properties.getInt(
actionName + LEGACY_TRACE_THRESHOLD_SUFFIX, -1);
mActionPropertiesMap.put(action, new ActionProperties(action,
properties.getBoolean(actionName + ENABLE_SUFFIX, mEnabled),
properties.getInt(actionName + SAMPLE_INTERVAL_SUFFIX, samplingInterval),
properties.getInt(actionName + TRACE_THRESHOLD_SUFFIX,
legacyActionTraceThreshold)));
}
if (DEBUG) {
Log.d(TAG, "updated action properties: " + mActionPropertiesMap);
} }
} }
mDeviceConfigPropertiesUpdated.open();
} }
/** /**
@@ -369,16 +385,38 @@ public class LatencyTracker {
return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]); return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]);
} }
/**
* @deprecated Use {@link #isEnabled(Context, int)}
*/
@Deprecated
public static boolean isEnabled(Context ctx) { public static boolean isEnabled(Context ctx) {
return getInstance(ctx).isEnabled(); return getInstance(ctx).isEnabled();
} }
/**
* @deprecated Used {@link #isEnabled(int)}
*/
@Deprecated
public boolean isEnabled() { public boolean isEnabled() {
synchronized (mLock) { synchronized (mLock) {
return mEnabled; return mEnabled;
} }
} }
public static boolean isEnabled(Context ctx, int action) {
return getInstance(ctx).isEnabled(action);
}
public boolean isEnabled(int action) {
synchronized (mLock) {
ActionProperties actionProperties = mActionPropertiesMap.get(action);
if (actionProperties != null) {
return actionProperties.isEnabled();
}
return false;
}
}
/** /**
* Notifies that an action is starting. <s>This needs to be called from the main thread.</s> * Notifies that an action is starting. <s>This needs to be called from the main thread.</s>
* *
@@ -468,8 +506,14 @@ public class LatencyTracker {
boolean shouldSample; boolean shouldSample;
int traceThreshold; int traceThreshold;
synchronized (mLock) { synchronized (mLock) {
shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0; ActionProperties actionProperties = mActionPropertiesMap.get(action);
traceThreshold = mTraceThresholdPerAction[action]; if (actionProperties == null) {
return;
}
int nextRandNum = ThreadLocalRandom.current().nextInt(
actionProperties.getSamplingInterval());
shouldSample = nextRandNum == 0;
traceThreshold = actionProperties.getTraceThreshold();
} }
if (traceThreshold > 0 && duration >= traceThreshold) { if (traceThreshold > 0 && duration >= traceThreshold) {
@@ -549,4 +593,59 @@ public class LatencyTracker {
return (int) (mEndRtc - mStartRtc); return (int) (mEndRtc - mStartRtc);
} }
} }
@VisibleForTesting
static class ActionProperties {
static final String ENABLE_SUFFIX = "_enable";
static final String SAMPLE_INTERVAL_SUFFIX = "_sample_interval";
// TODO: migrate all usages of the legacy trace theshold property
static final String LEGACY_TRACE_THRESHOLD_SUFFIX = "";
static final String TRACE_THRESHOLD_SUFFIX = "_trace_threshold";
@Action
private final int mAction;
private final boolean mEnabled;
private final int mSamplingInterval;
private final int mTraceThreshold;
ActionProperties(
@Action int action,
boolean enabled,
int samplingInterval,
int traceThreshold) {
this.mAction = action;
com.android.internal.util.AnnotationValidations.validate(
Action.class, null, mAction);
this.mEnabled = enabled;
this.mSamplingInterval = samplingInterval;
this.mTraceThreshold = traceThreshold;
}
@Action
int getAction() {
return mAction;
}
boolean isEnabled() {
return mEnabled;
}
int getSamplingInterval() {
return mSamplingInterval;
}
int getTraceThreshold() {
return mTraceThreshold;
}
@Override
public String toString() {
return "ActionProperties{"
+ " mAction=" + mAction
+ ", mEnabled=" + mEnabled
+ ", mSamplingInterval=" + mSamplingInterval
+ ", mTraceThreshold=" + mTraceThreshold
+ "}";
}
}
} }

View File

@@ -23,36 +23,53 @@ import static com.android.internal.util.LatencyTracker.STATSD_ACTION;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth.assertWithMessage;
import android.provider.DeviceConfig;
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.google.common.truth.Expect; import com.google.common.truth.Expect;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@SmallTest @SmallTest
@RunWith(AndroidJUnit4.class)
public class LatencyTrackerTest { public class LatencyTrackerTest {
private static final String TAG = LatencyTrackerTest.class.getSimpleName();
private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__"; private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__";
private static final String ACTION_ENABLE_SUFFIX = "_enable";
private static final Duration TEST_TIMEOUT = Duration.ofMillis(500);
@Rule @Rule
public final Expect mExpect = Expect.create(); public final Expect mExpect = Expect.create();
@Before
public void setUp() {
DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
LatencyTracker.SETTINGS_ENABLED_KEY);
getAllActions().forEach(action -> {
DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
action.getName().toLowerCase() + ACTION_ENABLE_SUFFIX);
});
}
@Test @Test
public void testCujsMapToEnumsCorrectly() { public void testCujsMapToEnumsCorrectly() {
List<Field> actions = Arrays.stream(LatencyTracker.class.getDeclaredFields()) List<Field> actions = getAllActions();
.filter(f -> f.getName().startsWith("ACTION_")
&& Modifier.isStatic(f.getModifiers())
&& f.getType() == int.class)
.collect(Collectors.toList());
Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields()) Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
.filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX) .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
&& Modifier.isStatic(f.getModifiers()) && Modifier.isStatic(f.getModifiers())
@@ -84,13 +101,7 @@ public class LatencyTrackerTest {
@Test @Test
public void testCujTypeEnumCorrectlyDefined() throws Exception { public void testCujTypeEnumCorrectlyDefined() throws Exception {
List<Field> cujEnumFields = List<Field> cujEnumFields = getAllActions();
Arrays.stream(LatencyTracker.class.getDeclaredFields())
.filter(field -> field.getName().startsWith("ACTION_")
&& Modifier.isStatic(field.getModifiers())
&& field.getType() == int.class)
.collect(Collectors.toList());
HashSet<Integer> allValues = new HashSet<>(); HashSet<Integer> allValues = new HashSet<>();
for (Field field : cujEnumFields) { for (Field field : cujEnumFields) {
int fieldValue = field.getInt(null); int fieldValue = field.getInt(null);
@@ -106,6 +117,95 @@ public class LatencyTrackerTest {
} }
} }
@Test
public void testIsEnabled_globalEnabled() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
LatencyTracker latencyTracker = new LatencyTracker();
waitForLatencyTrackerToUpdateProperties(latencyTracker);
assertThat(latencyTracker.isEnabled()).isTrue();
}
@Test
public void testIsEnabled_globalDisabled() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
LatencyTracker latencyTracker = new LatencyTracker();
waitForLatencyTrackerToUpdateProperties(latencyTracker);
assertThat(latencyTracker.isEnabled()).isFalse();
}
@Test
public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet() {
LatencyTracker latencyTracker = new LatencyTracker();
// using a single test action, but this applies to all actions
int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY + ", value=true");
latencyTracker.mDeviceConfigPropertiesUpdated.close();
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
waitForLatencyTrackerToUpdateProperties(latencyTracker);
assertThat(
latencyTracker.isEnabled(action)).isTrue();
Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY
+ ", value=false");
latencyTracker.mDeviceConfigPropertiesUpdated.close();
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
waitForLatencyTrackerToUpdateProperties(latencyTracker);
assertThat(latencyTracker.isEnabled(action)).isFalse();
}
@Test
public void testIsEnabledAction_actionPropertyOverridesGlobalProperty()
throws DeviceConfig.BadConfigException {
LatencyTracker latencyTracker = new LatencyTracker();
// using a single test action, but this applies to all actions
int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
String actionEnableProperty = "action_show_voice_interaction" + ACTION_ENABLE_SUFFIX;
Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true");
latencyTracker.mDeviceConfigPropertiesUpdated.close();
Map<String, String> properties = new HashMap<String, String>() {{
put(LatencyTracker.SETTINGS_ENABLED_KEY, "false");
put(actionEnableProperty, "true");
}};
DeviceConfig.setProperties(
new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
properties));
waitForLatencyTrackerToUpdateProperties(latencyTracker);
assertThat(latencyTracker.isEnabled(action)).isTrue();
latencyTracker.mDeviceConfigPropertiesUpdated.close();
Log.i(TAG, "setting property=" + actionEnableProperty + ", value=false");
properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "true");
properties.put(actionEnableProperty, "false");
DeviceConfig.setProperties(
new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
properties));
waitForLatencyTrackerToUpdateProperties(latencyTracker);
assertThat(latencyTracker.isEnabled(action)).isFalse();
}
private void waitForLatencyTrackerToUpdateProperties(LatencyTracker latencyTracker) {
try {
Thread.sleep(TEST_TIMEOUT.toMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
assertThat(latencyTracker.mDeviceConfigPropertiesUpdated.block(
TEST_TIMEOUT.toMillis())).isTrue();
}
private List<Field> getAllActions() {
return Arrays.stream(LatencyTracker.class.getDeclaredFields())
.filter(field -> field.getName().startsWith("ACTION_")
&& Modifier.isStatic(field.getModifiers())
&& field.getType() == int.class)
.collect(Collectors.toList());
}
private int getIntFieldChecked(Field field) { private int getIntFieldChecked(Field field) {
try { try {
return field.getInt(null); return field.getInt(null);