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