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 23 import com.android.internal.annotations.GuardedBy; 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.internal.util.FrameworkStatsLog; 26 27 import java.util.ArrayDeque; 28 import java.util.Queue; 29 30 /** Helper class for async write of atoms to {@link FrameworkStatsLog} using a given Handler. */ 31 public class VibratorFrameworkStatsLogger { 32 private static final String TAG = "VibratorFrameworkStatsLogger"; 33 34 // VibrationReported pushed atom needs to be throttled to at most one every 10ms. 35 private static final int VIBRATION_REPORTED_MIN_INTERVAL_MILLIS = 10; 36 // We accumulate events that should take 3s to write and drop excessive metrics. 37 private static final int VIBRATION_REPORTED_MAX_QUEUE_SIZE = 300; 38 // Warning about dropping entries after this amount of atoms were dropped by the throttle. 39 private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200; 40 41 private final Object mLock = new Object(); 42 private final Handler mHandler; 43 private final long mVibrationReportedLogIntervalMillis; 44 private final long mVibrationReportedQueueMaxSize; 45 private final Runnable mConsumeVibrationStatsQueueRunnable = 46 () -> writeVibrationReportedFromQueue(); 47 48 @GuardedBy("mLock") 49 private long mLastVibrationReportedLogUptime; 50 @GuardedBy("mLock") 51 private Queue<VibrationStats.StatsInfo> mVibrationStatsQueue = new ArrayDeque<>(); 52 VibratorFrameworkStatsLogger(Handler handler)53 VibratorFrameworkStatsLogger(Handler handler) { 54 this(handler, VIBRATION_REPORTED_MIN_INTERVAL_MILLIS, VIBRATION_REPORTED_MAX_QUEUE_SIZE); 55 } 56 57 @VisibleForTesting VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis, int vibrationReportedQueueMaxSize)58 VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis, 59 int vibrationReportedQueueMaxSize) { 60 mHandler = handler; 61 mVibrationReportedLogIntervalMillis = vibrationReportedLogIntervalMillis; 62 mVibrationReportedQueueMaxSize = vibrationReportedQueueMaxSize; 63 } 64 65 /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state ON. */ writeVibratorStateOnAsync(int uid, long duration)66 public void writeVibratorStateOnAsync(int uid, long duration) { 67 mHandler.post( 68 () -> FrameworkStatsLog.write_non_chained( 69 FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null, 70 FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, duration)); 71 } 72 73 /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state OFF. */ writeVibratorStateOffAsync(int uid)74 public void writeVibratorStateOffAsync(int uid) { 75 mHandler.post( 76 () -> FrameworkStatsLog.write_non_chained( 77 FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null, 78 FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF, 79 /* duration= */ 0)); 80 } 81 82 /** 83 * Writes {@link FrameworkStatsLog#VIBRATION_REPORTED} for given vibration. 84 * 85 * <p>This atom is throttled to be pushed once every 10ms, so this logger can keep a queue of 86 * {@link VibrationStats.StatsInfo} entries to slowly write to statsd. 87 */ writeVibrationReportedAsync(VibrationStats.StatsInfo metrics)88 public void writeVibrationReportedAsync(VibrationStats.StatsInfo metrics) { 89 boolean needsScheduling; 90 long scheduleDelayMs; 91 int queueSize; 92 93 synchronized (mLock) { 94 queueSize = mVibrationStatsQueue.size(); 95 needsScheduling = (queueSize == 0); 96 97 if (queueSize < mVibrationReportedQueueMaxSize) { 98 mVibrationStatsQueue.offer(metrics); 99 } 100 101 long nextLogUptime = 102 mLastVibrationReportedLogUptime + mVibrationReportedLogIntervalMillis; 103 scheduleDelayMs = Math.max(0, nextLogUptime - SystemClock.uptimeMillis()); 104 } 105 106 if ((queueSize + 1) == VIBRATION_REPORTED_WARNING_QUEUE_SIZE) { 107 Slog.w(TAG, " Approaching vibration metrics queue limit, events might be dropped."); 108 } 109 110 if (needsScheduling) { 111 mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, scheduleDelayMs); 112 } 113 } 114 115 /** Writes next {@link FrameworkStatsLog#VIBRATION_REPORTED} from the queue. */ writeVibrationReportedFromQueue()116 private void writeVibrationReportedFromQueue() { 117 boolean needsScheduling; 118 VibrationStats.StatsInfo stats; 119 120 synchronized (mLock) { 121 stats = mVibrationStatsQueue.poll(); 122 needsScheduling = !mVibrationStatsQueue.isEmpty(); 123 124 if (stats != null) { 125 mLastVibrationReportedLogUptime = SystemClock.uptimeMillis(); 126 } 127 } 128 129 if (stats == null) { 130 Slog.w(TAG, "Unexpected vibration metric flush with empty queue. Ignoring."); 131 } else { 132 stats.writeVibrationReported(); 133 } 134 135 if (needsScheduling) { 136 mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, 137 mVibrationReportedLogIntervalMillis); 138 } 139 } 140 } 141