1 /* 2 * Copyright 2024 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.stats.pull; 18 19 import android.annotation.NonNull; 20 import android.app.ActivityManager; 21 import android.app.StatsManager; 22 import android.app.usage.NetworkStatsManager; 23 import android.net.NetworkStats; 24 import android.os.Handler; 25 import android.os.HandlerThread; 26 import android.os.Trace; 27 import android.util.ArrayMap; 28 import android.util.Slog; 29 import android.util.SparseIntArray; 30 import android.util.StatsEvent; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.util.FrameworkStatsLog; 34 import com.android.server.selinux.RateLimiter; 35 36 import java.time.Duration; 37 import java.util.List; 38 import java.util.Map; 39 40 /** 41 * Aggregates Mobile Data Usage by process state per uid 42 */ 43 class AggregatedMobileDataStatsPuller { 44 private static final String TAG = "AggregatedMobileDataStatsPuller"; 45 46 private static final boolean DEBUG = false; 47 48 private static class UidProcState { 49 50 private final int mUid; 51 private final int mState; 52 UidProcState(int uid, int state)53 UidProcState(int uid, int state) { 54 mUid = uid; 55 mState = state; 56 } 57 58 @Override equals(Object o)59 public boolean equals(Object o) { 60 if (this == o) return true; 61 if (!(o instanceof UidProcState key)) return false; 62 return mUid == key.mUid && mState == key.mState; 63 } 64 65 @Override hashCode()66 public int hashCode() { 67 int result = mUid; 68 result = 31 * result + mState; 69 return result; 70 } 71 getUid()72 public int getUid() { 73 return mUid; 74 } 75 getState()76 public int getState() { 77 return mState; 78 } 79 80 } 81 82 private static class MobileDataStats { 83 private long mRxPackets = 0; 84 private long mTxPackets = 0; 85 private long mRxBytes = 0; 86 private long mTxBytes = 0; 87 getRxPackets()88 public long getRxPackets() { 89 return mRxPackets; 90 } 91 getTxPackets()92 public long getTxPackets() { 93 return mTxPackets; 94 } 95 getRxBytes()96 public long getRxBytes() { 97 return mRxBytes; 98 } 99 getTxBytes()100 public long getTxBytes() { 101 return mTxBytes; 102 } 103 addRxPackets(long rxPackets)104 public void addRxPackets(long rxPackets) { 105 mRxPackets += rxPackets; 106 } 107 addTxPackets(long txPackets)108 public void addTxPackets(long txPackets) { 109 mTxPackets += txPackets; 110 } 111 addRxBytes(long rxBytes)112 public void addRxBytes(long rxBytes) { 113 mRxBytes += rxBytes; 114 } 115 addTxBytes(long txBytes)116 public void addTxBytes(long txBytes) { 117 mTxBytes += txBytes; 118 } 119 isEmpty()120 public boolean isEmpty() { 121 return mRxPackets == 0 && mTxPackets == 0 && mRxBytes == 0 && mTxBytes == 0; 122 } 123 } 124 125 private final Object mLock = new Object(); 126 @GuardedBy("mLock") 127 private final Map<UidProcState, MobileDataStats> mUidStats; 128 129 // No reason to keep more dimensions than 3000. The 3000 is the hard top for the statsd metrics 130 // dimensions guardrail. It also will keep the result binder transaction size capped to 131 // approximately 220kB for 3000 atoms 132 private static final int UID_STATS_MAX_SIZE = 3000; 133 134 private final SparseIntArray mUidPreviousState; 135 136 private NetworkStats mLastMobileUidStats = new NetworkStats(0, -1); 137 138 private final NetworkStatsManager mNetworkStatsManager; 139 140 private final Handler mMobileDataStatsHandler; 141 142 private final RateLimiter mRateLimiter; 143 AggregatedMobileDataStatsPuller(@onNull NetworkStatsManager networkStatsManager)144 AggregatedMobileDataStatsPuller(@NonNull NetworkStatsManager networkStatsManager) { 145 if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 146 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-Init"); 147 } 148 149 mRateLimiter = new RateLimiter(/* window= */ Duration.ofSeconds(1)); 150 151 mUidStats = new ArrayMap<>(); 152 mUidPreviousState = new SparseIntArray(); 153 154 mNetworkStatsManager = networkStatsManager; 155 156 HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler"); 157 mMobileDataStatsHandlerThread.start(); 158 mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper()); 159 160 if (mNetworkStatsManager != null) { 161 mMobileDataStatsHandler.post( 162 () -> { 163 updateNetworkStats(mNetworkStatsManager); 164 }); 165 } 166 if (DEBUG) { 167 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 168 } 169 } 170 noteUidProcessState(int uid, int state, long unusedElapsedRealtime, long unusedUptime)171 public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime, 172 long unusedUptime) { 173 if (mRateLimiter.tryAcquire()) { 174 mMobileDataStatsHandler.post( 175 () -> { 176 noteUidProcessStateImpl(uid, state); 177 }); 178 } else { 179 synchronized (mLock) { 180 mUidPreviousState.put(uid, state); 181 } 182 } 183 } 184 pullDataBytesTransfer(List<StatsEvent> data)185 public int pullDataBytesTransfer(List<StatsEvent> data) { 186 synchronized (mLock) { 187 return pullDataBytesTransferLocked(data); 188 } 189 } 190 191 @GuardedBy("mLock") getUidStatsForPreviousStateLocked(int uid)192 private MobileDataStats getUidStatsForPreviousStateLocked(int uid) { 193 final int previousState = mUidPreviousState.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN); 194 if (DEBUG && previousState == ActivityManager.PROCESS_STATE_UNKNOWN) { 195 Slog.d(TAG, "getUidStatsForPreviousStateLocked() no prev state info for uid " 196 + uid + ". Tracking stats with ActivityManager.PROCESS_STATE_UNKNOWN"); 197 } 198 199 final UidProcState statsKey = new UidProcState(uid, previousState); 200 if (mUidStats.containsKey(statsKey)) { 201 return mUidStats.get(statsKey); 202 } 203 if (mUidStats.size() < UID_STATS_MAX_SIZE) { 204 MobileDataStats stats = new MobileDataStats(); 205 mUidStats.put(statsKey, stats); 206 return stats; 207 } 208 if (DEBUG) { 209 Slog.w(TAG, "getUidStatsForPreviousStateLocked() UID_STATS_MAX_SIZE reached"); 210 } 211 return null; 212 } 213 noteUidProcessStateImpl(int uid, int state)214 private void noteUidProcessStateImpl(int uid, int state) { 215 // noteUidProcessStateImpl can be called back to back several times while 216 // the updateNetworkStats loops over several stats for multiple uids 217 // and during the first call in a batch of proc state change event it can 218 // contain info for uid with unknown previous state yet which can happen due to a few 219 // reasons: 220 // - app was just started 221 // - app was started before the ActivityManagerService 222 // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN 223 if (mNetworkStatsManager != null) { 224 updateNetworkStats(mNetworkStatsManager); 225 } else { 226 Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager"); 227 } 228 synchronized (mLock) { 229 mUidPreviousState.put(uid, state); 230 } 231 } 232 updateNetworkStats(NetworkStatsManager networkStatsManager)233 private void updateNetworkStats(NetworkStatsManager networkStatsManager) { 234 if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 235 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats"); 236 } 237 238 final NetworkStats latestStats = networkStatsManager.getMobileUidStats(); 239 if (isEmpty(latestStats)) { 240 if (DEBUG) { 241 Slog.w(TAG, "getMobileUidStats() failed"); 242 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 243 } 244 return; 245 } 246 NetworkStats delta = latestStats.subtract(mLastMobileUidStats); 247 mLastMobileUidStats = latestStats; 248 249 if (!isEmpty(delta)) { 250 updateNetworkStatsDelta(delta); 251 } else if (DEBUG) { 252 Slog.w(TAG, "updateNetworkStats() no delta"); 253 } 254 if (DEBUG) { 255 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 256 } 257 } 258 updateNetworkStatsDelta(NetworkStats delta)259 private void updateNetworkStatsDelta(NetworkStats delta) { 260 if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 261 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStatsDelta"); 262 } 263 synchronized (mLock) { 264 for (NetworkStats.Entry entry : delta) { 265 if (entry.getRxPackets() != 0 || entry.getTxPackets() != 0) { 266 MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid()); 267 if (stats != null) { 268 stats.addTxBytes(entry.getTxBytes()); 269 stats.addRxBytes(entry.getRxBytes()); 270 stats.addTxPackets(entry.getTxPackets()); 271 stats.addRxPackets(entry.getRxPackets()); 272 } 273 } 274 } 275 } 276 if (DEBUG) { 277 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 278 } 279 } 280 281 @GuardedBy("mLock") pullDataBytesTransferLocked(List<StatsEvent> pulledData)282 private int pullDataBytesTransferLocked(List<StatsEvent> pulledData) { 283 if (DEBUG) { 284 Slog.d(TAG, "pullDataBytesTransferLocked() start"); 285 } 286 for (Map.Entry<UidProcState, MobileDataStats> uidStats : mUidStats.entrySet()) { 287 if (!uidStats.getValue().isEmpty()) { 288 MobileDataStats stats = uidStats.getValue(); 289 pulledData.add(FrameworkStatsLog.buildStatsEvent( 290 FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE, 291 uidStats.getKey().getUid(), 292 ActivityManager.processStateAmToProto(uidStats.getKey().getState()), 293 stats.getRxBytes(), 294 stats.getRxPackets(), 295 stats.getTxBytes(), 296 stats.getTxPackets())); 297 } 298 } 299 if (DEBUG) { 300 Slog.d(TAG, 301 "pullDataBytesTransferLocked() done. results count " + pulledData.size()); 302 } 303 return StatsManager.PULL_SUCCESS; 304 } 305 isEmpty(NetworkStats stats)306 private static boolean isEmpty(NetworkStats stats) { 307 for (NetworkStats.Entry entry : stats) { 308 if (entry.getRxPackets() != 0 || entry.getTxPackets() != 0) { 309 // at least one non empty entry located 310 return false; 311 } 312 } 313 return true; 314 } 315 } 316