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) { 146 if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 147 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, 148 TAG + "-AggregatedMobileDataStatsPullerInit"); 149 } 150 } 151 152 mRateLimiter = new RateLimiter(/* window= */ Duration.ofSeconds(1)); 153 154 mUidStats = new ArrayMap<>(); 155 mUidPreviousState = new SparseIntArray(); 156 157 mNetworkStatsManager = networkStatsManager; 158 159 HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler"); 160 mMobileDataStatsHandlerThread.start(); 161 mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper()); 162 163 if (mNetworkStatsManager != null) { 164 mMobileDataStatsHandler.post( 165 () -> { 166 updateNetworkStats(mNetworkStatsManager); 167 }); 168 } 169 if (DEBUG) { 170 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 171 } 172 } 173 noteUidProcessState(int uid, int state, long unusedElapsedRealtime, long unusedUptime)174 public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime, 175 long unusedUptime) { 176 mMobileDataStatsHandler.post( 177 () -> { 178 noteUidProcessStateImpl(uid, state); 179 }); 180 } 181 pullDataBytesTransfer(List<StatsEvent> data)182 public int pullDataBytesTransfer(List<StatsEvent> data) { 183 synchronized (mLock) { 184 return pullDataBytesTransferLocked(data); 185 } 186 } 187 188 @GuardedBy("mLock") getUidStatsForPreviousStateLocked(int uid)189 private MobileDataStats getUidStatsForPreviousStateLocked(int uid) { 190 final int previousState = mUidPreviousState.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN); 191 if (DEBUG && previousState == ActivityManager.PROCESS_STATE_UNKNOWN) { 192 Slog.d(TAG, "getUidStatsForPreviousStateLocked() no prev state info for uid " 193 + uid + ". Tracking stats with ActivityManager.PROCESS_STATE_UNKNOWN"); 194 } 195 196 final UidProcState statsKey = new UidProcState(uid, previousState); 197 if (mUidStats.containsKey(statsKey)) { 198 return mUidStats.get(statsKey); 199 } 200 if (mUidStats.size() < UID_STATS_MAX_SIZE) { 201 MobileDataStats stats = new MobileDataStats(); 202 mUidStats.put(statsKey, stats); 203 return stats; 204 } 205 if (DEBUG) { 206 Slog.w(TAG, "getUidStatsForPreviousStateLocked() UID_STATS_MAX_SIZE reached"); 207 } 208 return null; 209 } 210 noteUidProcessStateImpl(int uid, int state)211 private void noteUidProcessStateImpl(int uid, int state) { 212 if (mRateLimiter.tryAcquire()) { 213 // noteUidProcessStateImpl can be called back to back several times while 214 // the updateNetworkStats loops over several stats for multiple uids 215 // and during the first call in a batch of proc state change event it can 216 // contain info for uid with unknown previous state yet which can happen due to a few 217 // reasons: 218 // - app was just started 219 // - app was started before the ActivityManagerService 220 // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN 221 if (mNetworkStatsManager != null) { 222 updateNetworkStats(mNetworkStatsManager); 223 } else { 224 Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager"); 225 } 226 } 227 mUidPreviousState.put(uid, state); 228 } 229 updateNetworkStats(NetworkStatsManager networkStatsManager)230 private void updateNetworkStats(NetworkStatsManager networkStatsManager) { 231 if (DEBUG) { 232 if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { 233 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats"); 234 } 235 } 236 237 final NetworkStats latestStats = networkStatsManager.getMobileUidStats(); 238 if (isEmpty(latestStats)) { 239 if (DEBUG) { 240 Slog.w(TAG, "getMobileUidStats() failed"); 241 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 242 } 243 return; 244 } 245 NetworkStats delta = latestStats.subtract(mLastMobileUidStats); 246 mLastMobileUidStats = latestStats; 247 248 if (!isEmpty(delta)) { 249 updateNetworkStatsDelta(delta); 250 } else if (DEBUG) { 251 Slog.w(TAG, "updateNetworkStats() no delta"); 252 } 253 if (DEBUG) { 254 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 255 } 256 } 257 updateNetworkStatsDelta(NetworkStats delta)258 private void updateNetworkStatsDelta(NetworkStats delta) { 259 synchronized (mLock) { 260 for (NetworkStats.Entry entry : delta) { 261 if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) { 262 continue; 263 } 264 MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid()); 265 if (stats != null) { 266 stats.addTxBytes(entry.getTxBytes()); 267 stats.addRxBytes(entry.getRxBytes()); 268 stats.addTxPackets(entry.getTxPackets()); 269 stats.addRxPackets(entry.getRxPackets()); 270 } 271 } 272 } 273 } 274 275 @GuardedBy("mLock") pullDataBytesTransferLocked(List<StatsEvent> pulledData)276 private int pullDataBytesTransferLocked(List<StatsEvent> pulledData) { 277 if (DEBUG) { 278 Slog.d(TAG, "pullDataBytesTransferLocked() start"); 279 } 280 for (Map.Entry<UidProcState, MobileDataStats> uidStats : mUidStats.entrySet()) { 281 if (!uidStats.getValue().isEmpty()) { 282 MobileDataStats stats = uidStats.getValue(); 283 pulledData.add(FrameworkStatsLog.buildStatsEvent( 284 FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE, 285 uidStats.getKey().getUid(), 286 ActivityManager.processStateAmToProto(uidStats.getKey().getState()), 287 stats.getRxBytes(), 288 stats.getRxPackets(), 289 stats.getTxBytes(), 290 stats.getTxPackets())); 291 } 292 } 293 if (DEBUG) { 294 Slog.d(TAG, 295 "pullDataBytesTransferLocked() done. results count " + pulledData.size()); 296 } 297 return StatsManager.PULL_SUCCESS; 298 } 299 isEmpty(NetworkStats stats)300 private static boolean isEmpty(NetworkStats stats) { 301 long totalRxPackets = 0; 302 long totalTxPackets = 0; 303 for (NetworkStats.Entry entry : stats) { 304 if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) { 305 continue; 306 } 307 totalRxPackets += entry.getRxPackets(); 308 totalTxPackets += entry.getTxPackets(); 309 // at least one non empty entry located 310 break; 311 } 312 final long totalPackets = totalRxPackets + totalTxPackets; 313 return totalPackets == 0; 314 } 315 } 316