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