• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util.Log;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.ObjectInputStream;
30 import java.io.ObjectOutputStream;
31 import java.io.Serializable;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.concurrent.ConcurrentHashMap;
35 
36 /** Stores and aggregates metrics for pulled atoms. */
37 class StatsLibStorage {
38 
39     private static final String LOG_TAG = StatsLibStorage.class.getSimpleName();
40     private static final boolean DBG = true;
41     private static final boolean TEST_DBG = true;
42 
43     private static final String STORAGE_FILE =
44             StatsLibStorage.class.getSimpleName() + "_persist_ID.pb";
45     private final Handler mHandler;
46 
47     private final Context mContext;
48     private final ConcurrentHashMap<Integer, List<AtomsPushed>> mPushed;
49     private final ConcurrentHashMap<Integer, ConcurrentHashMap<String, AtomsPulled>> mPulled;
50 
51     /** Constructor of StatsLibStorage */
StatsLibStorage(Context context)52     StatsLibStorage(Context context) {
53         mContext = context;
54         mPushed = new ConcurrentHashMap<>();
55         mPulled = new ConcurrentHashMap<>();
56         HandlerThread handlerThread = new HandlerThread("StatsLibStorage");
57         handlerThread.start();
58         mHandler = new Handler(handlerThread.getLooper());
59         log("created StatsLibStorage.");
60     }
61 
62     @VisibleForTesting
StatsLibStorage( Context context, ConcurrentHashMap<Integer, List<AtomsPushed>> pushedMap, ConcurrentHashMap<Integer, ConcurrentHashMap<String, AtomsPulled>> pulledMap, Handler testHandler)63     protected StatsLibStorage(
64             Context context,
65             ConcurrentHashMap<Integer, List<AtomsPushed>> pushedMap,
66             ConcurrentHashMap<Integer, ConcurrentHashMap<String, AtomsPulled>> pulledMap,
67             Handler testHandler) {
68         mContext = context;
69         mPushed = pushedMap;
70         mPulled = pulledMap;
71         mHandler = testHandler;
72         log("created StatsLibStorage for testing");
73     }
74 
log(String s)75     private void log(String s) {
76         if (DBG) Log.d(LOG_TAG, s);
77     }
78 
loge(String s)79     private void loge(String s) {
80         Log.e(LOG_TAG, s);
81     }
82 
logt(String s)83     private void logt(String s) {
84         if (TEST_DBG) Log.d(LOG_TAG, s);
85     }
86 
87     /** Initialize storage for the given statsId. */
init(int statsId)88     void init(int statsId) {
89         List<AtomsPushed> pushed = mPushed.get(statsId);
90         if (pushed != null) {
91             pushed.clear();
92         }
93         ConcurrentHashMap<String, AtomsPulled> pulled = mPulled.get(statsId);
94         if (pulled != null) {
95             pulled.clear();
96         }
97     }
98 
99     /**
100      * Append the Pushed Atoms
101      *
102      * @param info AtomsInfoBase
103      */
appendPushedAtoms(AtomsPushed info)104     void appendPushedAtoms(AtomsPushed info) {
105         List<AtomsPushed> atoms =
106                 mPushed.computeIfAbsent(info.getStatsId(), k -> new ArrayList<>());
107         atoms.add(info.copy());
108         logt("appendPushedAtoms, AtomsPushed:" + info);
109     }
110 
111     /** Returns the array of the stored atoms and deletes them all. */
popPushedAtoms(int statsId)112     AtomsPushed[] popPushedAtoms(int statsId) {
113         List<AtomsPushed> atoms = mPushed.computeIfAbsent(statsId, k -> new ArrayList<>());
114         AtomsPushed[] infos = atoms.toArray(new AtomsPushed[0]);
115         logt("popPushedAtoms, AtomsPushed:" + atoms);
116         atoms.clear();
117         return infos;
118     }
119 
120     /**
121      * Append the Pulled Atoms
122      *
123      * @param info AtomsInfoBase
124      */
appendPulledAtoms(AtomsPulled info)125     void appendPulledAtoms(AtomsPulled info) {
126         ConcurrentHashMap<String, AtomsPulled> atoms =
127                 mPulled.computeIfAbsent(info.getStatsId(), k -> new ConcurrentHashMap<>());
128         AtomsPulled alreadyExistPulled = atoms.get(info.getDimension());
129         if (alreadyExistPulled == null) {
130             atoms.put(info.getDimension(), info.copy());
131             logt("appendPulledAtoms, AtomsPulled:" + info);
132         } else {
133             alreadyExistPulled.accumulate(info);
134             logt("appendPulledAtoms, alreadyExistPulled:" + alreadyExistPulled);
135         }
136         if (isSerializable(info)) {
137             saveToFile(info.getStatsId());
138         }
139     }
140 
141     /** Returns the array of the stored atoms and deletes them all. */
popPulledAtoms(int statsId)142     AtomsPulled[] popPulledAtoms(int statsId) {
143         ConcurrentHashMap<String, AtomsPulled> atoms = mPulled.get(statsId);
144         if (atoms == null) {
145             return null;
146         }
147         AtomsPulled[] infos = atoms.values().toArray(new AtomsPulled[0]);
148         logt("popPulledAtoms, atoms:" + atoms);
149         atoms.clear();
150         saveToFile(statsId);
151         return infos;
152     }
153 
154     /** save serializable pulled atoms to a backup file for given statsId. */
saveToFile(int statsId)155     void saveToFile(int statsId) {
156         mHandler.post(() -> saveToFileImmediately(statsId));
157     }
158 
159     /** save atoms to file run as thread */
saveToFileImmediately(int statsId)160     private void saveToFileImmediately(int statsId) {
161         FileOutputStream fileOutputStream = null;
162         ObjectOutputStream objectOutputStream = null;
163         List<AtomsPulled> toFileList = getSerializablePulledAtoms(statsId);
164         try {
165             // TODO b/265727262 if possible, Requires changes to repositories that do not require
166             //  selinux rule.
167             String filename = getFileName(statsId);
168             fileOutputStream = mContext.openFileOutput(filename, Context.MODE_PRIVATE);
169             objectOutputStream = new ObjectOutputStream(fileOutputStream);
170             objectOutputStream.writeObject(toFileList);
171             objectOutputStream.flush();
172             logt("saveToFileImmediately, " + filename + " saved, toFileList:" + toFileList);
173         } catch (IOException e) {
174             loge("cannot save atoms, e:" + e);
175         } finally {
176             if (fileOutputStream != null) {
177                 try {
178                     fileOutputStream.close();
179                 } catch (IOException e) {
180                     loge("exception in saveToFile filestream close, e:" + e);
181                 }
182             }
183             if (objectOutputStream != null) {
184                 try {
185                     objectOutputStream.close();
186                 } catch (IOException e) {
187                     loge("exception in saveToFile object stream close, e:" + e);
188                 }
189             }
190         }
191     }
192 
isSerializable(AtomsPulled info)193     private boolean isSerializable(AtomsPulled info) {
194         return info instanceof Serializable;
195     }
196 
getFileName(int statsId)197     private String getFileName(int statsId) {
198         return (STORAGE_FILE).replace("ID", Integer.toString(statsId));
199     }
200 
getSerializablePulledAtoms(int statsId)201     private List<AtomsPulled> getSerializablePulledAtoms(int statsId) {
202         ConcurrentHashMap<String, AtomsPulled> pulls =
203                 mPulled.computeIfAbsent(statsId, k -> new ConcurrentHashMap<>());
204         List<AtomsPulled> serializable = new ArrayList<>();
205         for (AtomsPulled p : pulls.values()) {
206             if (isSerializable(p)) {
207                 serializable.add(p);
208             }
209         }
210         return serializable;
211     }
212 
setSerializablePulledAtoms( int statsId, List<AtomsPulled> serializablePulledAtoms)213     private void setSerializablePulledAtoms(
214             int statsId, List<AtomsPulled> serializablePulledAtoms) {
215         if (serializablePulledAtoms == null || serializablePulledAtoms.isEmpty()) {
216             return;
217         }
218         ConcurrentHashMap<String, AtomsPulled> atoms =
219                 mPulled.computeIfAbsent(statsId, k -> new ConcurrentHashMap<>());
220         for (AtomsPulled info : serializablePulledAtoms) {
221             AtomsPulled alreadyExistPulled = atoms.get(info.getDimension());
222             if (alreadyExistPulled == null) {
223                 atoms.put(info.getDimension(), info.copy());
224             } else {
225                 alreadyExistPulled.accumulate(info);
226             }
227         }
228     }
229 
230 
231     /** save serializable pulled atoms to a backup file for given statsId. */
loadFromFile(int statsId)232     void loadFromFile(int statsId) {
233         mHandler.post(() -> loadFromFileImmediately(statsId));
234     }
235 
236     /** load atoms from file run as thread */
loadFromFileImmediately(int statsId)237     private void loadFromFileImmediately(int statsId) {
238         FileInputStream fileInputStream = null;
239         ObjectInputStream objectInputStream = null;
240         List<AtomsPulled> fromFileList;
241         try {
242             // TODO b/265727262 if possible, Requires changes to repositories that do not require
243             //  selinux rule.
244             String filename = getFileName(statsId);
245             fileInputStream = mContext.openFileInput(filename);
246             objectInputStream = new ObjectInputStream(fileInputStream);
247             fromFileList = (ArrayList<AtomsPulled>) objectInputStream.readObject();
248             logt("loadFromFile, " + filename + " loaded, fromFileList:" + fromFileList);
249             setSerializablePulledAtoms(statsId, fromFileList);
250         } catch (IOException e) {
251             loge("IOException cannot load atoms, e:" + e);
252         } catch (ClassNotFoundException e) {
253             loge("ClassNotFoundException cannot load atoms, e:" + e);
254         } finally {
255             if (fileInputStream != null) {
256                 try {
257                     fileInputStream.close();
258                 } catch (IOException e) {
259                     loge("exception in loadFromFile filestream close, e:" + e);
260                 }
261             }
262             if (objectInputStream != null) {
263                 try {
264                     objectInputStream.close();
265                 } catch (IOException e) {
266                     loge("exception in loadFromFile object stream close, e:" + e);
267                 }
268             }
269         }
270     }
271 }
272