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.NonNull; 20 import android.annotation.Nullable; 21 import android.os.SystemClock; 22 import android.os.vibrator.PrebakedSegment; 23 import android.os.vibrator.PrimitiveSegment; 24 import android.os.vibrator.RampSegment; 25 import android.util.Slog; 26 import android.util.SparseBooleanArray; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.util.FrameworkStatsLog; 30 31 /** Holds basic stats about the vibration playback and interaction with the vibrator HAL. */ 32 final class VibrationStats { 33 static final String TAG = "VibrationStats"; 34 35 // Milestone timestamps, using SystemClock.uptimeMillis(), for calculations. 36 // - Create: time a vibration object was created, which is closer to when the service receives a 37 // vibrate request. 38 // - Start: time a vibration started to play, which is closer to the time that the 39 // VibrationEffect started playing the very first segment. 40 // - End: time a vibration ended, even if it never started to play. This can be as soon as the 41 // vibrator HAL reports it has finished the last command, or before it has even started 42 // when the vibration is ignored or cancelled. 43 // Create and end times set by VibratorManagerService only, guarded by its lock. 44 // Start times set by VibrationThread only (single-threaded). 45 private long mCreateUptimeMillis; 46 private long mStartUptimeMillis; 47 private long mEndUptimeMillis; 48 49 // Milestone timestamps, using unix epoch time, only to be used for debugging purposes and 50 // to correlate with other system events. Any duration calculations should be done with the 51 // {create/start/end}UptimeMillis counterparts so as not to be affected by discontinuities 52 // created by RTC adjustments. 53 // Set together with the *UptimeMillis counterparts. 54 private long mCreateTimeDebug; 55 private long mStartTimeDebug; 56 private long mEndTimeDebug; 57 58 // Vibration interruption tracking. 59 // Set by VibratorManagerService only, guarded by its lock. 60 private int mEndedByUid; 61 private int mEndedByUsage; 62 private int mInterruptedUsage; 63 64 // All following counters are set by VibrationThread only (single-threaded): 65 // Counts how many times the VibrationEffect was repeated. 66 private int mRepeatCount; 67 // Total duration, in milliseconds, the vibrator was active with non-zero amplitude. 68 private int mVibratorOnTotalDurationMillis; 69 // Total number of primitives used in compositions. 70 private int mVibrationCompositionTotalSize; 71 private int mVibrationPwleTotalSize; 72 // Counts how many times each IVibrator method was triggered by this vibration. 73 private int mVibratorOnCount; 74 private int mVibratorOffCount; 75 private int mVibratorSetAmplitudeCount; 76 private int mVibratorSetExternalControlCount; 77 private int mVibratorPerformCount; 78 private int mVibratorComposeCount; 79 private int mVibratorComposePwleCount; 80 81 // Ids of vibration effects and primitives used by this vibration, with support flag. 82 // Set by VibrationThread only (single-threaded). 83 private SparseBooleanArray mVibratorEffectsUsed = new SparseBooleanArray(); 84 private SparseBooleanArray mVibratorPrimitivesUsed = new SparseBooleanArray(); 85 VibrationStats()86 VibrationStats() { 87 mCreateUptimeMillis = SystemClock.uptimeMillis(); 88 mCreateTimeDebug = System.currentTimeMillis(); 89 // Set invalid UID and VibrationAttributes.USAGE values to indicate fields are unset. 90 mEndedByUid = -1; 91 mEndedByUsage = -1; 92 mInterruptedUsage = -1; 93 } 94 getCreateUptimeMillis()95 long getCreateUptimeMillis() { 96 return mCreateUptimeMillis; 97 } 98 getStartUptimeMillis()99 long getStartUptimeMillis() { 100 return mStartUptimeMillis; 101 } 102 getEndUptimeMillis()103 long getEndUptimeMillis() { 104 return mEndUptimeMillis; 105 } 106 getCreateTimeDebug()107 long getCreateTimeDebug() { 108 return mCreateTimeDebug; 109 } 110 getStartTimeDebug()111 long getStartTimeDebug() { 112 return mStartTimeDebug; 113 } 114 getEndTimeDebug()115 long getEndTimeDebug() { 116 return mEndTimeDebug; 117 } 118 119 /** 120 * Duration calculated for debugging purposes, between the creation of a vibration and the 121 * end time being reported, or -1 if the vibration has not ended. 122 */ getDurationDebug()123 long getDurationDebug() { 124 return hasEnded() ? (mEndUptimeMillis - mCreateUptimeMillis) : -1; 125 } 126 127 /** Return true if vibration reported it has ended. */ hasEnded()128 boolean hasEnded() { 129 return mEndUptimeMillis > 0; 130 } 131 132 /** Return true if vibration reported it has started triggering the vibrator. */ hasStarted()133 boolean hasStarted() { 134 return mStartUptimeMillis > 0; 135 } 136 137 /** 138 * Set the current system time as this vibration start time, for debugging purposes. 139 * 140 * <p>This indicates the vibration has started to interact with the vibrator HAL and the 141 * device may start vibrating after this point. 142 * 143 * <p>This method will only accept given value if the start timestamp was never set. 144 */ reportStarted()145 void reportStarted() { 146 if (hasEnded() || (mStartUptimeMillis != 0)) { 147 // Vibration already started or ended, keep first time set and ignore this one. 148 return; 149 } 150 mStartUptimeMillis = SystemClock.uptimeMillis(); 151 mStartTimeDebug = System.currentTimeMillis(); 152 } 153 154 /** 155 * Set status and end cause for this vibration to end, and the current system time as this 156 * vibration end time, for debugging purposes. 157 * 158 * <p>This might be triggered before {@link #reportStarted()}, which indicates this 159 * vibration was cancelled or ignored before it started triggering the vibrator. 160 * 161 * @return true if the status was accepted. This method will only accept given values if 162 * the end timestamp was never set. 163 */ reportEnded(@ullable Vibration.CallerInfo endedBy)164 boolean reportEnded(@Nullable Vibration.CallerInfo endedBy) { 165 if (hasEnded()) { 166 // Vibration already ended, keep first ending stats set and ignore this one. 167 return false; 168 } 169 if (endedBy != null) { 170 mEndedByUid = endedBy.uid; 171 mEndedByUsage = endedBy.attrs.getUsage(); 172 } 173 mEndUptimeMillis = SystemClock.uptimeMillis(); 174 mEndTimeDebug = System.currentTimeMillis(); 175 176 return true; 177 } 178 179 /** 180 * Report this vibration has interrupted another vibration. 181 * 182 * <p>This method will only accept the first value as the one that was interrupted by this 183 * vibration, and will ignore all successive calls. 184 */ reportInterruptedAnotherVibration(@onNull Vibration.CallerInfo callerInfo)185 void reportInterruptedAnotherVibration(@NonNull Vibration.CallerInfo callerInfo) { 186 if (mInterruptedUsage < 0) { 187 mInterruptedUsage = callerInfo.attrs.getUsage(); 188 } 189 } 190 191 /** Report the vibration has looped a few more times. */ reportRepetition(int loops)192 void reportRepetition(int loops) { 193 mRepeatCount += loops; 194 } 195 196 /** Report a call to vibrator method to turn on for given duration. */ reportVibratorOn(long halResult)197 void reportVibratorOn(long halResult) { 198 mVibratorOnCount++; 199 200 if (halResult > 0) { 201 // If HAL result is positive then it represents the actual duration it will be ON. 202 mVibratorOnTotalDurationMillis += (int) halResult; 203 } 204 } 205 206 /** Report a call to vibrator method to turn off. */ reportVibratorOff()207 void reportVibratorOff() { 208 mVibratorOffCount++; 209 } 210 211 /** Report a call to vibrator method to change the vibration amplitude. */ reportSetAmplitude()212 void reportSetAmplitude() { 213 mVibratorSetAmplitudeCount++; 214 } 215 216 /** Report a call to vibrator method to trigger a vibration effect. */ reportPerformEffect(long halResult, PrebakedSegment prebaked)217 void reportPerformEffect(long halResult, PrebakedSegment prebaked) { 218 mVibratorPerformCount++; 219 220 if (halResult > 0) { 221 // If HAL result is positive then it represents the actual duration of the vibration. 222 mVibratorEffectsUsed.put(prebaked.getEffectId(), true); 223 mVibratorOnTotalDurationMillis += (int) halResult; 224 } else { 225 // Effect unsupported or request failed. 226 mVibratorEffectsUsed.put(prebaked.getEffectId(), false); 227 } 228 } 229 230 /** Report a call to vibrator method to trigger a vibration as a composition of primitives. */ reportComposePrimitives(long halResult, PrimitiveSegment[] primitives)231 void reportComposePrimitives(long halResult, PrimitiveSegment[] primitives) { 232 mVibratorComposeCount++; 233 mVibrationCompositionTotalSize += primitives.length; 234 235 if (halResult > 0) { 236 // If HAL result is positive then it represents the actual duration of the vibration. 237 // Remove the requested delays to update the total time the vibrator was ON. 238 for (PrimitiveSegment primitive : primitives) { 239 halResult -= primitive.getDelay(); 240 mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), true); 241 } 242 if (halResult > 0) { 243 mVibratorOnTotalDurationMillis += (int) halResult; 244 } 245 } else { 246 // One or more primitives were unsupported, or request failed. 247 for (PrimitiveSegment primitive : primitives) { 248 mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), false); 249 } 250 } 251 } 252 253 /** Report a call to vibrator method to trigger a vibration as a PWLE. */ reportComposePwle(long halResult, RampSegment[] segments)254 void reportComposePwle(long halResult, RampSegment[] segments) { 255 mVibratorComposePwleCount++; 256 mVibrationPwleTotalSize += segments.length; 257 258 if (halResult > 0) { 259 // If HAL result is positive then it represents the actual duration of the vibration. 260 // Remove the zero-amplitude segments to update the total time the vibrator was ON. 261 for (RampSegment ramp : segments) { 262 if ((ramp.getStartAmplitude() == 0) && (ramp.getEndAmplitude() == 0)) { 263 halResult -= ramp.getDuration(); 264 } 265 } 266 if (halResult > 0) { 267 mVibratorOnTotalDurationMillis += (int) halResult; 268 } 269 } 270 } 271 272 /** 273 * Increment the stats for total number of times the {@code setExternalControl} method was 274 * triggered in the vibrator HAL. 275 */ reportSetExternalControl()276 void reportSetExternalControl() { 277 mVibratorSetExternalControlCount++; 278 } 279 280 /** 281 * Immutable metrics about this vibration, to be kept in memory until it can be pushed through 282 * {@link com.android.internal.util.FrameworkStatsLog} as a 283 * {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}. 284 */ 285 static final class StatsInfo { 286 public final int uid; 287 public final int vibrationType; 288 public final int usage; 289 public final int status; 290 public final boolean endedBySameUid; 291 public final int endedByUsage; 292 public final int interruptedUsage; 293 public final int repeatCount; 294 public final int totalDurationMillis; 295 public final int vibratorOnMillis; 296 public final int startLatencyMillis; 297 public final int endLatencyMillis; 298 public final int halComposeCount; 299 public final int halComposePwleCount; 300 public final int halOnCount; 301 public final int halOffCount; 302 public final int halPerformCount; 303 public final int halSetAmplitudeCount; 304 public final int halSetExternalControlCount; 305 public final int halCompositionSize; 306 public final int halPwleSize; 307 public final int[] halSupportedCompositionPrimitivesUsed; 308 public final int[] halSupportedEffectsUsed; 309 public final int[] halUnsupportedCompositionPrimitivesUsed; 310 public final int[] halUnsupportedEffectsUsed; 311 private boolean mIsWritten; 312 StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status, VibrationStats stats, long completionUptimeMillis)313 StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status, 314 VibrationStats stats, long completionUptimeMillis) { 315 this.uid = uid; 316 this.vibrationType = vibrationType; 317 this.usage = usage; 318 this.status = status.getProtoEnumValue(); 319 endedBySameUid = (uid == stats.mEndedByUid); 320 endedByUsage = stats.mEndedByUsage; 321 interruptedUsage = stats.mInterruptedUsage; 322 repeatCount = stats.mRepeatCount; 323 324 // This duration goes from the time this object was created until the time it was 325 // completed. We can use latencies to detect the times between first and last 326 // interaction with vibrator. 327 totalDurationMillis = 328 (int) Math.max(0, completionUptimeMillis - stats.mCreateUptimeMillis); 329 vibratorOnMillis = stats.mVibratorOnTotalDurationMillis; 330 331 if (stats.hasStarted()) { 332 // We only measure latencies for vibrations that actually triggered the vibrator. 333 startLatencyMillis = 334 (int) Math.max(0, stats.mStartUptimeMillis - stats.mCreateUptimeMillis); 335 endLatencyMillis = 336 (int) Math.max(0, completionUptimeMillis - stats.mEndUptimeMillis); 337 } else { 338 startLatencyMillis = endLatencyMillis = 0; 339 } 340 341 halComposeCount = stats.mVibratorComposeCount; 342 halComposePwleCount = stats.mVibratorComposePwleCount; 343 halOnCount = stats.mVibratorOnCount; 344 halOffCount = stats.mVibratorOffCount; 345 halPerformCount = stats.mVibratorPerformCount; 346 halSetAmplitudeCount = stats.mVibratorSetAmplitudeCount; 347 halSetExternalControlCount = stats.mVibratorSetExternalControlCount; 348 halCompositionSize = stats.mVibrationCompositionTotalSize; 349 halPwleSize = stats.mVibrationPwleTotalSize; 350 halSupportedCompositionPrimitivesUsed = 351 filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ true); 352 halSupportedEffectsUsed = 353 filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ true); 354 halUnsupportedCompositionPrimitivesUsed = 355 filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ false); 356 halUnsupportedEffectsUsed = 357 filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ false); 358 } 359 360 @VisibleForTesting isWritten()361 boolean isWritten() { 362 return mIsWritten; 363 } 364 writeVibrationReported()365 void writeVibrationReported() { 366 if (mIsWritten) { 367 Slog.wtf(TAG, "Writing same vibration stats multiple times for uid=" + uid); 368 } 369 mIsWritten = true; 370 // Mapping from this MetricInfo representation and the atom proto VibrationReported. 371 FrameworkStatsLog.write_non_chained( 372 FrameworkStatsLog.VIBRATION_REPORTED, 373 uid, null, vibrationType, usage, status, endedBySameUid, endedByUsage, 374 interruptedUsage, repeatCount, totalDurationMillis, vibratorOnMillis, 375 startLatencyMillis, endLatencyMillis, halComposeCount, halComposePwleCount, 376 halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount, 377 halSetExternalControlCount, halSupportedCompositionPrimitivesUsed, 378 halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed, 379 halUnsupportedEffectsUsed, halCompositionSize, halPwleSize); 380 } 381 filteredKeys(SparseBooleanArray supportArray, boolean supported)382 private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) { 383 int count = 0; 384 for (int i = 0; i < supportArray.size(); i++) { 385 if (supportArray.valueAt(i) == supported) count++; 386 } 387 if (count == 0) { 388 return null; 389 } 390 int pos = 0; 391 int[] res = new int[count]; 392 for (int i = 0; i < supportArray.size(); i++) { 393 if (supportArray.valueAt(i) == supported) { 394 res[pos++] = supportArray.keyAt(i); 395 } 396 } 397 return res; 398 } 399 } 400 } 401