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