• Home
  • 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 && 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