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