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