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