1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.util; 18 19 import static com.android.internal.util.LatencyTracker.ActionProperties.ENABLE_SUFFIX; 20 import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX; 21 import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX; 22 23 import android.os.ConditionVariable; 24 import android.provider.DeviceConfig; 25 import android.util.Log; 26 import android.util.SparseArray; 27 28 import com.android.internal.annotations.GuardedBy; 29 30 import com.google.common.collect.ImmutableMap; 31 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Locale; 37 import java.util.Map; 38 import java.util.concurrent.Callable; 39 import java.util.concurrent.atomic.AtomicReference; 40 41 public final class FakeLatencyTracker extends LatencyTracker { 42 43 private static final String TAG = "FakeLatencyTracker"; 44 45 private final Object mLock = new Object(); 46 @GuardedBy("mLock") 47 private final Map<Integer, List<FrameworkStatsLogEvent>> mLatenciesLogged; 48 @GuardedBy("mLock") 49 private final List<String> mPerfettoTraceNamesTriggered; 50 private final AtomicReference<SparseArray<ActionProperties>> mLastPropertiesUpdate = 51 new AtomicReference<>(); 52 private final AtomicReference<Callable<Boolean>> mShouldClosePropertiesUpdatedCallable = 53 new AtomicReference<>(); 54 private final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable(); 55 create()56 public static FakeLatencyTracker create() throws Exception { 57 Log.i(TAG, "create"); 58 disableForAllActions(); 59 Log.i(TAG, "done disabling all actions"); 60 FakeLatencyTracker fakeLatencyTracker = new FakeLatencyTracker(); 61 Log.i(TAG, "done creating tracker object"); 62 fakeLatencyTracker.startListeningForLatencyTrackerConfigChanges(); 63 // always return the fake in the disabled state and let the client control the desired state 64 fakeLatencyTracker.waitForGlobalEnabledState(false); 65 fakeLatencyTracker.waitForAllPropertiesEnableState(false); 66 return fakeLatencyTracker; 67 } 68 FakeLatencyTracker()69 FakeLatencyTracker() { 70 super(); 71 mLatenciesLogged = new HashMap<>(); 72 mPerfettoTraceNamesTriggered = new ArrayList<>(); 73 } 74 disableForAllActions()75 private static void disableForAllActions() throws DeviceConfig.BadConfigException { 76 Map<String, String> properties = new HashMap<>(); 77 properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "false"); 78 for (int action : STATSD_ACTION) { 79 Log.d(TAG, "disabling action=" + action + ", property=" + getNameOfAction( 80 action).toLowerCase(Locale.ROOT) + ENABLE_SUFFIX); 81 properties.put(getNameOfAction(action).toLowerCase(Locale.ROOT) + ENABLE_SUFFIX, 82 "false"); 83 } 84 85 DeviceConfig.setProperties( 86 new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, properties)); 87 } 88 forceEnabled(int action, int traceThresholdMillis)89 public void forceEnabled(int action, int traceThresholdMillis) 90 throws Exception { 91 String actionName = getNameOfAction(STATSD_ACTION[action]).toLowerCase(Locale.ROOT); 92 String actionEnableProperty = actionName + ENABLE_SUFFIX; 93 String actionSampleProperty = actionName + SAMPLE_INTERVAL_SUFFIX; 94 String actionTraceProperty = actionName + TRACE_THRESHOLD_SUFFIX; 95 Log.i(TAG, "setting property=" + actionTraceProperty + ", value=" + traceThresholdMillis); 96 Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true"); 97 98 Map<String, String> properties = new HashMap<>(ImmutableMap.of( 99 actionEnableProperty, "true", 100 // Fake forces to sample every event 101 actionSampleProperty, String.valueOf(1), 102 actionTraceProperty, String.valueOf(traceThresholdMillis) 103 )); 104 DeviceConfig.setProperties( 105 new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, properties)); 106 waitForMatchingActionProperties( 107 new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */, 108 traceThresholdMillis)); 109 } 110 getEventsWrittenToFrameworkStats(@ction int action)111 public List<FrameworkStatsLogEvent> getEventsWrittenToFrameworkStats(@Action int action) { 112 synchronized (mLock) { 113 Log.i(TAG, "getEventsWrittenToFrameworkStats: mLatenciesLogged=" + mLatenciesLogged); 114 return mLatenciesLogged.getOrDefault(action, Collections.emptyList()); 115 } 116 } 117 getTriggeredPerfettoTraceNames()118 public List<String> getTriggeredPerfettoTraceNames() { 119 synchronized (mLock) { 120 return mPerfettoTraceNamesTriggered; 121 } 122 } 123 clearEvents()124 public void clearEvents() { 125 synchronized (mLock) { 126 mLatenciesLogged.clear(); 127 mPerfettoTraceNamesTriggered.clear(); 128 } 129 } 130 131 @Override onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties)132 public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) { 133 Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties); 134 135 mLastPropertiesUpdate.set(actionProperties); 136 Callable<Boolean> shouldClosePropertiesUpdated = 137 mShouldClosePropertiesUpdatedCallable.get(); 138 if (shouldClosePropertiesUpdated != null) { 139 try { 140 boolean result = shouldClosePropertiesUpdated.call(); 141 Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result=" + result); 142 if (result) { 143 mShouldClosePropertiesUpdatedCallable.set(null); 144 mDeviceConfigPropertiesUpdated.open(); 145 } 146 } catch (Exception e) { 147 Log.e(TAG, "exception when calling callable", e); 148 throw new RuntimeException(e); 149 } 150 } else { 151 Log.i(TAG, "no conditional callable set, opening condition"); 152 mDeviceConfigPropertiesUpdated.open(); 153 } 154 } 155 156 @Override onTriggerPerfetto(String triggerName)157 public void onTriggerPerfetto(String triggerName) { 158 synchronized (mLock) { 159 mPerfettoTraceNamesTriggered.add(triggerName); 160 } 161 } 162 163 @Override onLogToFrameworkStats(FrameworkStatsLogEvent event)164 public void onLogToFrameworkStats(FrameworkStatsLogEvent event) { 165 synchronized (mLock) { 166 Log.i(TAG, "onLogToFrameworkStats: event=" + event); 167 List<FrameworkStatsLogEvent> eventList = mLatenciesLogged.getOrDefault(event.action, 168 new ArrayList<>()); 169 eventList.add(event); 170 mLatenciesLogged.put(event.action, eventList); 171 } 172 } 173 waitForAllPropertiesEnableState(boolean enabledState)174 public void waitForAllPropertiesEnableState(boolean enabledState) throws Exception { 175 Log.i(TAG, "waitForAllPropertiesEnableState: enabledState=" + enabledState); 176 // Update the callable to only close the properties updated condition when all the 177 // desired properties have been updated. The DeviceConfig callbacks may happen multiple 178 // times so testing the resulting updates is required. 179 waitForPropertiesCondition(() -> { 180 Log.i(TAG, "verifying if last properties update has all properties enable=" 181 + enabledState); 182 SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); 183 if (newProperties != null) { 184 for (int i = 0; i < newProperties.size(); i++) { 185 if (newProperties.get(i).isEnabled() != enabledState) { 186 return false; 187 } 188 } 189 } 190 return true; 191 }); 192 } 193 waitForMatchingActionProperties(ActionProperties actionProperties)194 public void waitForMatchingActionProperties(ActionProperties actionProperties) 195 throws Exception { 196 Log.i(TAG, "waitForMatchingActionProperties: actionProperties=" + actionProperties); 197 // Update the callable to only close the properties updated condition when all the 198 // desired properties have been updated. The DeviceConfig callbacks may happen multiple 199 // times so testing the resulting updates is required. 200 waitForPropertiesCondition(() -> { 201 Log.i(TAG, "verifying if last properties update contains matching property =" 202 + actionProperties); 203 SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); 204 if (newProperties != null) { 205 if (newProperties.size() > 0) { 206 return newProperties.get(actionProperties.getAction()).equals( 207 actionProperties); 208 } 209 } 210 return false; 211 }); 212 } 213 waitForActionEnabledState(int action, boolean enabledState)214 public void waitForActionEnabledState(int action, boolean enabledState) throws Exception { 215 Log.i(TAG, "waitForActionEnabledState:" 216 + " action=" + action + ", enabledState=" + enabledState); 217 // Update the callable to only close the properties updated condition when all the 218 // desired properties have been updated. The DeviceConfig callbacks may happen multiple 219 // times so testing the resulting updates is required. 220 waitForPropertiesCondition(() -> { 221 Log.i(TAG, "verifying if last properties update contains action=" + action 222 + ", enabledState=" + enabledState); 223 SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); 224 if (newProperties != null) { 225 if (newProperties.size() > 0) { 226 return newProperties.get(action).isEnabled() == enabledState; 227 } 228 } 229 return false; 230 }); 231 } 232 waitForGlobalEnabledState(boolean enabledState)233 public void waitForGlobalEnabledState(boolean enabledState) throws Exception { 234 Log.i(TAG, "waitForGlobalEnabledState: enabledState=" + enabledState); 235 // Update the callable to only close the properties updated condition when all the 236 // desired properties have been updated. The DeviceConfig callbacks may happen multiple 237 // times so testing the resulting updates is required. 238 waitForPropertiesCondition(() -> { 239 //noinspection deprecation 240 return isEnabled() == enabledState; 241 }); 242 } 243 waitForPropertiesCondition(Callable<Boolean> shouldClosePropertiesUpdatedCallable)244 public void waitForPropertiesCondition(Callable<Boolean> shouldClosePropertiesUpdatedCallable) 245 throws Exception { 246 mShouldClosePropertiesUpdatedCallable.set(shouldClosePropertiesUpdatedCallable); 247 mDeviceConfigPropertiesUpdated.close(); 248 if (!shouldClosePropertiesUpdatedCallable.call()) { 249 Log.i(TAG, "waiting for mDeviceConfigPropertiesUpdated condition"); 250 mDeviceConfigPropertiesUpdated.block(); 251 } 252 Log.i(TAG, "waitForPropertiesCondition: returning"); 253 } 254 } 255