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.os.Handler; 20 import android.os.SystemClock; 21 import android.util.Slog; 22 import android.view.HapticFeedbackConstants; 23 24 import com.android.internal.annotations.GuardedBy; 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.internal.util.FrameworkStatsLog; 27 import com.android.modules.expresslog.Counter; 28 import com.android.modules.expresslog.Histogram; 29 30 import java.util.ArrayDeque; 31 import java.util.Queue; 32 33 /** Helper class for async write of atoms to {@link FrameworkStatsLog} using a given Handler. */ 34 public class VibratorFrameworkStatsLogger { 35 private static final String TAG = "VibratorFrameworkStatsLogger"; 36 37 // VibrationReported pushed atom needs to be throttled to at most one every 10ms. 38 private static final int VIBRATION_REPORTED_MIN_INTERVAL_MILLIS = 10; 39 // We accumulate events that should take 3s to write and drop excessive metrics. 40 private static final int VIBRATION_REPORTED_MAX_QUEUE_SIZE = 300; 41 // Warning about dropping entries after this amount of atoms were dropped by the throttle. 42 private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200; 43 44 // Latency between 0ms and 99ms, with 100 representing overflow latencies >= 100ms. 45 // Underflow not expected. 46 private static final Histogram sVibrationParamRequestLatencyHistogram = new Histogram( 47 "vibrator.value_vibration_param_request_latency", 48 new Histogram.UniformOptions(20, 0, 100)); 49 50 // Scales in [0, 2), with 2 representing overflow scales >= 2. 51 // Underflow expected to represent how many times scales were cleared (set to -1). 52 private static final Histogram sVibrationParamScaleHistogram = new Histogram( 53 "vibrator.value_vibration_param_scale", new Histogram.UniformOptions(20, 0, 2)); 54 55 // Scales in [0, 2), with 2 representing overflow scales >= 2. 56 // Underflow not expected. 57 private static final Histogram sAdaptiveHapticScaleHistogram = new Histogram( 58 "vibrator.value_vibration_adaptive_haptic_scale", 59 new Histogram.UniformOptions(20, 0, 2)); 60 61 private final Object mLock = new Object(); 62 private final Handler mHandler; 63 private final long mVibrationReportedLogIntervalMillis; 64 private final long mVibrationReportedQueueMaxSize; 65 private final Runnable mConsumeVibrationStatsQueueRunnable = 66 () -> writeVibrationReportedFromQueue(); 67 68 @GuardedBy("mLock") 69 private long mLastVibrationReportedLogUptime; 70 @GuardedBy("mLock") 71 private Queue<VibrationStats.StatsInfo> mVibrationStatsQueue = new ArrayDeque<>(); 72 VibratorFrameworkStatsLogger(Handler handler)73 VibratorFrameworkStatsLogger(Handler handler) { 74 this(handler, VIBRATION_REPORTED_MIN_INTERVAL_MILLIS, VIBRATION_REPORTED_MAX_QUEUE_SIZE); 75 } 76 77 @VisibleForTesting VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis, int vibrationReportedQueueMaxSize)78 VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis, 79 int vibrationReportedQueueMaxSize) { 80 mHandler = handler; 81 mVibrationReportedLogIntervalMillis = vibrationReportedLogIntervalMillis; 82 mVibrationReportedQueueMaxSize = vibrationReportedQueueMaxSize; 83 } 84 85 /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state ON. */ writeVibratorStateOnAsync(int uid, long duration)86 public void writeVibratorStateOnAsync(int uid, long duration) { 87 mHandler.post( 88 () -> FrameworkStatsLog.write_non_chained( 89 FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null, 90 FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, duration)); 91 } 92 93 /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state OFF. */ writeVibratorStateOffAsync(int uid)94 public void writeVibratorStateOffAsync(int uid) { 95 mHandler.post( 96 () -> FrameworkStatsLog.write_non_chained( 97 FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null, 98 FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF, 99 /* duration= */ 0)); 100 } 101 102 /** 103 * Writes {@link FrameworkStatsLog#VIBRATION_REPORTED} for given vibration. 104 * 105 * <p>This atom is throttled to be pushed once every 10ms, so this logger can keep a queue of 106 * {@link VibrationStats.StatsInfo} entries to slowly write to statsd. 107 */ writeVibrationReportedAsync(VibrationStats.StatsInfo metrics)108 public void writeVibrationReportedAsync(VibrationStats.StatsInfo metrics) { 109 boolean needsScheduling; 110 long scheduleDelayMs; 111 int queueSize; 112 113 synchronized (mLock) { 114 queueSize = mVibrationStatsQueue.size(); 115 needsScheduling = (queueSize == 0); 116 117 if (queueSize < mVibrationReportedQueueMaxSize) { 118 mVibrationStatsQueue.offer(metrics); 119 } 120 121 long nextLogUptime = 122 mLastVibrationReportedLogUptime + mVibrationReportedLogIntervalMillis; 123 scheduleDelayMs = Math.max(0, nextLogUptime - SystemClock.uptimeMillis()); 124 } 125 126 if ((queueSize + 1) == VIBRATION_REPORTED_WARNING_QUEUE_SIZE) { 127 Slog.w(TAG, " Approaching vibration metrics queue limit, events might be dropped."); 128 } 129 130 if (needsScheduling) { 131 mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, scheduleDelayMs); 132 } 133 } 134 135 /** Writes next {@link FrameworkStatsLog#VIBRATION_REPORTED} from the queue. */ writeVibrationReportedFromQueue()136 private void writeVibrationReportedFromQueue() { 137 boolean needsScheduling; 138 VibrationStats.StatsInfo stats; 139 140 synchronized (mLock) { 141 stats = mVibrationStatsQueue.poll(); 142 needsScheduling = !mVibrationStatsQueue.isEmpty(); 143 144 if (stats != null) { 145 mLastVibrationReportedLogUptime = SystemClock.uptimeMillis(); 146 } 147 } 148 149 if (stats == null) { 150 Slog.w(TAG, "Unexpected vibration metric flush with empty queue. Ignoring."); 151 } else { 152 stats.writeVibrationReported(); 153 } 154 155 if (needsScheduling) { 156 mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, 157 mVibrationReportedLogIntervalMillis); 158 } 159 } 160 161 /** Logs adaptive haptic scale value applied to a vibration, only if it's not 1.0. */ logVibrationAdaptiveHapticScale(int uid, float scale)162 public void logVibrationAdaptiveHapticScale(int uid, float scale) { 163 if (Float.compare(scale, 1f) != 0) { 164 sAdaptiveHapticScaleHistogram.logSampleWithUid(uid, scale); 165 } 166 } 167 168 /** Logs a vibration param scale value received by the vibrator control service. */ logVibrationParamScale(float scale)169 public void logVibrationParamScale(float scale) { 170 sVibrationParamScaleHistogram.logSample(scale); 171 } 172 173 /** Logs the latency of a successful vibration params request completed before a vibration. */ logVibrationParamRequestLatency(int uid, long latencyMs)174 public void logVibrationParamRequestLatency(int uid, long latencyMs) { 175 sVibrationParamRequestLatencyHistogram.logSampleWithUid(uid, (float) latencyMs); 176 } 177 178 /** Logs a vibration params request timed out before a vibration. */ logVibrationParamRequestTimeout(int uid)179 public void logVibrationParamRequestTimeout(int uid) { 180 Counter.logIncrementWithUid("vibrator.value_vibration_param_request_timeout", uid); 181 } 182 183 /** Logs when a response received for a vibration params request is ignored by the service. */ logVibrationParamResponseIgnored()184 public void logVibrationParamResponseIgnored() { 185 Counter.logIncrement("vibrator.value_vibration_param_response_ignored"); 186 } 187 188 /** Logs only if the haptics feedback effect is one of the KEYBOARD_ constants. */ logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect)189 public static void logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect) { 190 boolean isKeyboard; 191 switch (hapticsFeedbackEffect) { 192 case HapticFeedbackConstants.KEYBOARD_TAP: 193 case HapticFeedbackConstants.KEYBOARD_RELEASE: 194 isKeyboard = true; 195 break; 196 default: 197 isKeyboard = false; 198 break; 199 } 200 if (isKeyboard) { 201 Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid); 202 } 203 } 204 } 205