• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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