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