• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.server.vibrator;
18 
19 import android.annotation.Nullable;
20 import android.os.CombinedVibration;
21 import android.os.Handler;
22 import android.os.Parcel;
23 import android.os.SystemClock;
24 import android.os.VibrationEffect;
25 import android.util.Slog;
26 import android.view.HapticFeedbackConstants;
27 
28 import com.android.internal.annotations.GuardedBy;
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.internal.util.FrameworkStatsLog;
31 import com.android.modules.expresslog.Counter;
32 import com.android.modules.expresslog.Histogram;
33 
34 import java.util.ArrayDeque;
35 import java.util.Queue;
36 
37 /** Helper class for async write of atoms to {@link FrameworkStatsLog} using a given Handler. */
38 public class VibratorFrameworkStatsLogger {
39     private static final String TAG = "VibratorFrameworkStatsLogger";
40 
41     // VibrationReported pushed atom needs to be throttled to at most one every 10ms.
42     private static final int VIBRATION_REPORTED_MIN_INTERVAL_MILLIS = 10;
43     // We accumulate events that should take 3s to write and drop excessive metrics.
44     private static final int VIBRATION_REPORTED_MAX_QUEUE_SIZE = 300;
45     // Warning about dropping entries after this amount of atoms were dropped by the throttle.
46     private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
47 
48     // Latency between 0ms and 99ms, with 100 representing overflow latencies >= 100ms.
49     // Underflow not expected.
50     private static final Histogram sVibrationParamRequestLatencyHistogram = new Histogram(
51             "vibrator.value_vibration_param_request_latency",
52             new Histogram.UniformOptions(20, 0, 100));
53 
54     // Scales in [0, 2), with 2 representing overflow scales >= 2.
55     // Underflow expected to represent how many times scales were cleared (set to -1).
56     private static final Histogram sVibrationParamScaleHistogram = new Histogram(
57             "vibrator.value_vibration_param_scale", new Histogram.UniformOptions(20, 0, 2));
58 
59     // Scales in [0, 2), with 2 representing overflow scales >= 2.
60     // Underflow not expected.
61     private static final Histogram sAdaptiveHapticScaleHistogram = new Histogram(
62             "vibrator.value_vibration_adaptive_haptic_scale",
63             new Histogram.UniformOptions(20, 0, 2));
64 
65     // Sizes in [1KB, ~4.5MB) defined by scaled buckets.
66     private static final Histogram sVibrationVendorEffectSizeHistogram = new Histogram(
67             "vibrator.value_vibration_vendor_effect_size",
68             new Histogram.ScaledRangeOptions(25, 0, 1, 1.4f));
69 
70     // Session vibration count in [0, ~840) defined by scaled buckets.
71     private static final Histogram sVibrationVendorSessionVibrationsHistogram = new Histogram(
72             "vibrator.value_vibration_vendor_session_vibrations",
73             new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
74 
75     private final Object mLock = new Object();
76     private final Handler mHandler;
77     private final long mVibrationReportedLogIntervalMillis;
78     private final long mVibrationReportedQueueMaxSize;
79     private final Runnable mConsumeVibrationStatsQueueRunnable =
80             () -> writeVibrationReportedFromQueue();
81 
82     @GuardedBy("mLock")
83     private long mLastVibrationReportedLogUptime;
84     @GuardedBy("mLock")
85     private Queue<VibrationStats.StatsInfo> mVibrationStatsQueue = new ArrayDeque<>();
86 
VibratorFrameworkStatsLogger(Handler handler)87     VibratorFrameworkStatsLogger(Handler handler) {
88         this(handler, VIBRATION_REPORTED_MIN_INTERVAL_MILLIS, VIBRATION_REPORTED_MAX_QUEUE_SIZE);
89     }
90 
91     @VisibleForTesting
VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis, int vibrationReportedQueueMaxSize)92     VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis,
93             int vibrationReportedQueueMaxSize) {
94         mHandler = handler;
95         mVibrationReportedLogIntervalMillis = vibrationReportedLogIntervalMillis;
96         mVibrationReportedQueueMaxSize = vibrationReportedQueueMaxSize;
97     }
98 
99     /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state ON. */
writeVibratorStateOnAsync(int uid, long duration)100     public void writeVibratorStateOnAsync(int uid, long duration) {
101         mHandler.post(
102                 () -> FrameworkStatsLog.write_non_chained(
103                         FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
104                         FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, duration));
105     }
106 
107     /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state OFF. */
writeVibratorStateOffAsync(int uid)108     public void writeVibratorStateOffAsync(int uid) {
109         mHandler.post(
110                 () -> FrameworkStatsLog.write_non_chained(
111                         FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
112                         FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
113                         /* duration= */ 0));
114     }
115 
116     /**
117      *  Writes {@link FrameworkStatsLog#VIBRATION_REPORTED} for given vibration.
118      *
119      *  <p>This atom is throttled to be pushed once every 10ms, so this logger can keep a queue of
120      *  {@link VibrationStats.StatsInfo} entries to slowly write to statsd.
121      */
writeVibrationReportedAsync(VibrationStats.StatsInfo metrics)122     public void writeVibrationReportedAsync(VibrationStats.StatsInfo metrics) {
123         boolean needsScheduling;
124         long scheduleDelayMs;
125         int queueSize;
126 
127         synchronized (mLock) {
128             queueSize = mVibrationStatsQueue.size();
129             needsScheduling = (queueSize == 0);
130 
131             if (queueSize < mVibrationReportedQueueMaxSize) {
132                 mVibrationStatsQueue.offer(metrics);
133             }
134 
135             long nextLogUptime =
136                     mLastVibrationReportedLogUptime + mVibrationReportedLogIntervalMillis;
137             scheduleDelayMs = Math.max(0, nextLogUptime - SystemClock.uptimeMillis());
138         }
139 
140         if ((queueSize + 1) == VIBRATION_REPORTED_WARNING_QUEUE_SIZE) {
141             Slog.w(TAG, " Approaching vibration metrics queue limit, events might be dropped.");
142         }
143 
144         if (needsScheduling) {
145             mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, scheduleDelayMs);
146         }
147     }
148 
149     /** Writes next {@link FrameworkStatsLog#VIBRATION_REPORTED} from the queue. */
writeVibrationReportedFromQueue()150     private void writeVibrationReportedFromQueue() {
151         boolean needsScheduling;
152         VibrationStats.StatsInfo stats;
153 
154         synchronized (mLock) {
155             stats = mVibrationStatsQueue.poll();
156             needsScheduling = !mVibrationStatsQueue.isEmpty();
157 
158             if (stats != null) {
159                 mLastVibrationReportedLogUptime = SystemClock.uptimeMillis();
160             }
161         }
162 
163         if (stats == null) {
164             Slog.w(TAG, "Unexpected vibration metric flush with empty queue. Ignoring.");
165         } else {
166             stats.writeVibrationReported();
167         }
168 
169         if (needsScheduling) {
170             mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable,
171                     mVibrationReportedLogIntervalMillis);
172         }
173     }
174 
175     /** Logs adaptive haptic scale value applied to a vibration, only if it's not 1.0. */
logVibrationAdaptiveHapticScale(int uid, float scale)176     public void logVibrationAdaptiveHapticScale(int uid, float scale) {
177         if (Float.compare(scale, 1f) != 0) {
178             sAdaptiveHapticScaleHistogram.logSampleWithUid(uid, scale);
179         }
180     }
181 
182     /** Logs a vibration param scale value received by the vibrator control service. */
logVibrationParamScale(float scale)183     public void logVibrationParamScale(float scale) {
184         sVibrationParamScaleHistogram.logSample(scale);
185     }
186 
187     /** Logs the latency of a successful vibration params request completed before a vibration. */
logVibrationParamRequestLatency(int uid, long latencyMs)188     public void logVibrationParamRequestLatency(int uid, long latencyMs) {
189         sVibrationParamRequestLatencyHistogram.logSampleWithUid(uid, (float) latencyMs);
190     }
191 
192     /** Logs a vibration params request timed out before a vibration. */
logVibrationParamRequestTimeout(int uid)193     public void logVibrationParamRequestTimeout(int uid) {
194         Counter.logIncrementWithUid("vibrator.value_vibration_param_request_timeout", uid);
195     }
196 
197     /** Logs when a response received for a vibration params request is ignored by the service. */
logVibrationParamResponseIgnored()198     public void logVibrationParamResponseIgnored() {
199         Counter.logIncrement("vibrator.value_vibration_param_response_ignored");
200     }
201 
202     /** Logs only if the haptics feedback effect is one of the KEYBOARD_ constants. */
logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect)203     public static void logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect) {
204         boolean isKeyboard;
205         switch (hapticsFeedbackEffect) {
206             case HapticFeedbackConstants.KEYBOARD_TAP:
207             case HapticFeedbackConstants.KEYBOARD_RELEASE:
208                 isKeyboard = true;
209                 break;
210             default:
211                 isKeyboard = false;
212                 break;
213         }
214         if (isKeyboard) {
215             Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid);
216         }
217     }
218 
219     /** Logs when a vendor vibration session successfully started. */
logVibrationVendorSessionStarted(int uid)220     public void logVibrationVendorSessionStarted(int uid) {
221         Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_started", uid);
222     }
223 
224     /**
225      * Logs when a vendor vibration session is interrupted by the platform.
226      *
227      * <p>A vendor session is interrupted if it has successfully started and its end was not
228      * requested by the vendor. This could be the vibrator service interrupting an ongoing session,
229      * the vibrator HAL triggering the session completed callback early.
230      */
logVibrationVendorSessionInterrupted(int uid)231     public void logVibrationVendorSessionInterrupted(int uid) {
232         Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_interrupted", uid);
233     }
234 
235     /** Logs the number of vibrations requested for a single vendor vibration session. */
logVibrationVendorSessionVibrations(int uid, int vibrationCount)236     public void logVibrationVendorSessionVibrations(int uid, int vibrationCount) {
237         sVibrationVendorSessionVibrationsHistogram.logSampleWithUid(uid, vibrationCount);
238     }
239 
240     /**
241      * Logs if given vibration contains at least one {@link VibrationEffect.VendorEffect}.
242      *
243      * <p>Each {@link VibrationEffect.VendorEffect} will also log the parcel data size for the
244      * {@link VibrationEffect.VendorEffect#getVendorData()} it holds.
245      */
logVibrationCountAndSizeIfVendorEffect(int uid, @Nullable CombinedVibration vibration)246     public void logVibrationCountAndSizeIfVendorEffect(int uid,
247             @Nullable CombinedVibration vibration) {
248         if (vibration == null) {
249             return;
250         }
251         boolean hasVendorEffects = logVibrationSizeOfVendorEffects(uid, vibration);
252         if (hasVendorEffects) {
253             // Increment CombinedVibration with one or more vendor effects only once.
254             Counter.logIncrementWithUid("vibrator.value_vibration_vendor_effect_requests", uid);
255         }
256     }
257 
logVibrationSizeOfVendorEffects(int uid, CombinedVibration vibration)258     private static boolean logVibrationSizeOfVendorEffects(int uid, CombinedVibration vibration) {
259         if (vibration instanceof CombinedVibration.Mono mono) {
260             if (mono.getEffect() instanceof VibrationEffect.VendorEffect effect) {
261                 logVibrationVendorEffectSize(uid, effect);
262                 return true;
263             }
264             return false;
265         }
266         if (vibration instanceof CombinedVibration.Stereo stereo) {
267             boolean hasVendorEffects = false;
268             for (int i = 0; i < stereo.getEffects().size(); i++) {
269                 if (stereo.getEffects().valueAt(i) instanceof VibrationEffect.VendorEffect effect) {
270                     logVibrationVendorEffectSize(uid, effect);
271                     hasVendorEffects = true;
272                 }
273             }
274             return hasVendorEffects;
275         }
276         if (vibration instanceof CombinedVibration.Sequential sequential) {
277             boolean hasVendorEffects = false;
278             for (int i = 0; i < sequential.getEffects().size(); i++) {
279                 hasVendorEffects |= logVibrationSizeOfVendorEffects(uid,
280                         sequential.getEffects().get(i));
281             }
282             return hasVendorEffects;
283         }
284         // Unknown combined vibration, skip metrics.
285         return false;
286     }
287 
logVibrationVendorEffectSize(int uid, VibrationEffect.VendorEffect effect)288     private static void logVibrationVendorEffectSize(int uid, VibrationEffect.VendorEffect effect) {
289         int dataSize;
290         Parcel vendorData = Parcel.obtain();
291         try {
292             // Measure data size as it'll be sent to the HAL via binder, not the serialization size.
293             // PersistableBundle creates an XML representation for the data in writeToStream, so it
294             // might be larger than the actual data that is transferred between processes.
295             effect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
296             dataSize = vendorData.dataSize();
297         } finally {
298             vendorData.recycle();
299         }
300         sVibrationVendorEffectSizeHistogram.logSampleWithUid(uid, dataSize);
301     }
302 }
303