1 /*
2  * Copyright (C) 2023 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.telephony.statslib;
18 
19 import android.content.Context;
20 import android.os.Handler;
21 import android.os.HandlerThread;
22 import android.os.Looper;
23 import android.os.Message;
24 import android.util.Log;
25 import android.util.StatsEvent;
26 import android.util.StatsLog;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 /** Statslib class */
31 public class StatsLib {
32 
33     private static final String LOG_TAG = StatsLib.class.getSimpleName();
34     private static final boolean DBG = true;
35     private static final int DEFAULT_FREQUENCY_WRITING_PUSHED_ATOM_IN_MILLS = 50;
36 
37     private final StatsLibPulledAtomCallback mStatsLibPulledAtomCallback;
38     private final WritePushedAtomHandler mHandler;
39 
40     /** Default constructor. */
StatsLib(Context context)41     public StatsLib(Context context) {
42         mStatsLibPulledAtomCallback = new StatsLibPulledAtomCallback(context);
43         log("created StatsLib.");
44         HandlerThread handlerThread = new HandlerThread("StatsLibStorage");
45         handlerThread.start();
46         mHandler = new WritePushedAtomHandler(handlerThread.getLooper());
47     }
48 
49     @VisibleForTesting
StatsLib(StatsLibPulledAtomCallback cb)50     protected StatsLib(StatsLibPulledAtomCallback cb) {
51         mStatsLibPulledAtomCallback = cb;
52         log("created StatsLib.");
53         HandlerThread handlerThread = new HandlerThread("StatsLibStorage");
54         handlerThread.start();
55         mHandler = new WritePushedAtomHandler(handlerThread.getLooper());
56     }
57 
58     /**
59      * Registers the target atom to be pulled.
60      *
61      * @param statsId the pulled atom tag to register to take data from.
62      */
registerPulledAtomCallback(int statsId)63     public void registerPulledAtomCallback(int statsId) {
64         mStatsLibPulledAtomCallback.registerAtom(statsId);
65     }
66 
67     /**
68      * Registers the target atom to be pulled.
69      *
70      * @param statsId The tag of the atom for this puller callback.
71      * @param callback callback to be registered.
72      */
registerPulledAtomCallback(int statsId, PulledCallback callback)73     public void registerPulledAtomCallback(int statsId, PulledCallback callback) {
74         mStatsLibPulledAtomCallback.registerAtom(statsId, callback);
75     }
76 
77     /**
78      * checks whether stats id was already registered or not.
79      *
80      * @param statsId The tag of the atom for this puller callback.
81      * @return true already registered.
82      */
isRegisteredPulledAtomCallback(int statsId)83     public boolean isRegisteredPulledAtomCallback(int statsId) {
84         return mStatsLibPulledAtomCallback.isRegisteredAtom(statsId);
85     }
86 
87     /**
88      * Unregisters the target atom being pulled.
89      *
90      * @param statsId The tag of the atom to remove callback and tag
91      */
unregisterPulledAtomCallback(int statsId)92     public void unregisterPulledAtomCallback(int statsId) {
93         if (isRegisteredPulledAtomCallback(statsId)) {
94             mStatsLibPulledAtomCallback.unregisterAtom(statsId);
95         }
96     }
97 
98     /**
99      * Write the pushed atoms
100      *
101      * @param pushed AtomsPushed
102      */
write(AtomsPushed pushed)103     public void write(AtomsPushed pushed) {
104         if (pushed == null) {
105             loge("writePushedAtoms: pushed is null");
106             return;
107         }
108         mHandler.sendMessage(Message.obtain(mHandler, 0, pushed));
109     }
110 
onWritePushedAtom(AtomsPushed pushed)111     protected void onWritePushedAtom(AtomsPushed pushed) {
112         final StatsEvent.Builder builder = StatsEvent.newBuilder();
113         builder.setAtomId(pushed.getStatsId());
114         pushed.build(builder);
115         builder.usePooledBuffer();
116         StatsLog.write(builder.build());
117         log("writePushedAtoms: pushed=" + pushed);
118 
119         append(pushed);
120     }
121 
122     private class WritePushedAtomHandler extends Handler {
123         /**
124          * Use the provided {@link Looper} instead of the default one.
125          *
126          * @param looper The looper, must not be null.
127          */
WritePushedAtomHandler(Looper looper)128         WritePushedAtomHandler(Looper looper) {
129             super(looper);
130         }
131 
132         @Override
handleMessage(Message message)133         public void handleMessage(Message message) {
134             try {
135                 AtomsPushed pushed = (AtomsPushed) message.obj;
136                 onWritePushedAtom(pushed);
137                 /* Atom logging frequency should not exceed once per 10 milliseconds (i.e.
138                  * consecutive atom calls should be at least 10 milliseconds apart). This ensures
139                  * that our logging socket is not spammed so that the socket does not drop data. If
140                  * your logging line might trigger frequently, we suggest putting a guardrail to
141                  * check that at least 1 second has passed since the last atom push.
142                  */
143                 Thread.sleep(DEFAULT_FREQUENCY_WRITING_PUSHED_ATOM_IN_MILLS);
144             } catch (InterruptedException | ClassCastException e) {
145                 loge("WritePushedAtomHandler, e:" + e);
146             }
147         }
148     }
149 
150     /**
151      * Append the Pushed atoms
152      *
153      * @param pushed AtomsPushed
154      */
append(AtomsPushed pushed)155     private void append(AtomsPushed pushed) {
156         StatsLibStorage storage = getStorage();
157         if (storage == null) {
158             loge("appendPushedAtoms: storage is null");
159             return;
160         }
161         storage.appendPushedAtoms(pushed);
162         log("appendPushedAtoms: pushed=" + pushed);
163     }
164 
165     /**
166      * Append the Pulled atoms
167      *
168      * @param pulled AtomsPulled
169      */
append(AtomsPulled pulled)170     public void append(AtomsPulled pulled) {
171         if (pulled == null) {
172             return;
173         }
174         StatsLibStorage storage = getStorage();
175         if (storage == null) {
176             loge("appendPulledAtoms: storage is null");
177             return;
178         }
179         if (!isRegisteredPulledAtomCallback(pulled.getStatsId())) {
180             registerPulledAtomCallback(pulled.getStatsId());
181         }
182         storage.appendPulledAtoms(pulled);
183         log("appendPulledAtoms: pulled=" + pulled);
184     }
185 
getStorage()186     private StatsLibStorage getStorage() {
187         if (mStatsLibPulledAtomCallback == null) {
188             return null;
189         }
190         return mStatsLibPulledAtomCallback.getStatsLibStorage();
191     }
192 
log(String s)193     private void log(String s) {
194         if (DBG) Log.d(LOG_TAG, s);
195     }
196 
loge(String s)197     private void loge(String s) {
198         Log.e(LOG_TAG, s);
199     }
200 }
201