• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.net;
18 
19 import static android.net.NetworkStats.TAG_NONE;
20 import static android.net.TrafficStats.KB_IN_BYTES;
21 import static android.net.TrafficStats.MB_IN_BYTES;
22 import static com.android.internal.util.Preconditions.checkNotNull;
23 
24 import android.net.NetworkStats;
25 import android.net.NetworkStats.NonMonotonicObserver;
26 import android.net.NetworkStatsHistory;
27 import android.net.NetworkTemplate;
28 import android.net.TrafficStats;
29 import android.os.DropBoxManager;
30 import android.util.Log;
31 import android.util.MathUtils;
32 import android.util.Slog;
33 
34 import com.android.internal.util.FileRotator;
35 import com.android.internal.util.IndentingPrintWriter;
36 import com.google.android.collect.Sets;
37 
38 import java.io.ByteArrayOutputStream;
39 import java.io.DataOutputStream;
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.OutputStream;
44 import java.lang.ref.WeakReference;
45 import java.util.Arrays;
46 import java.util.HashSet;
47 import java.util.Map;
48 
49 import libcore.io.IoUtils;
50 
51 /**
52  * Logic to record deltas between periodic {@link NetworkStats} snapshots into
53  * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
54  * Keeps pending changes in memory until they pass a specific threshold, in
55  * bytes. Uses {@link FileRotator} for persistence logic.
56  * <p>
57  * Not inherently thread safe.
58  */
59 public class NetworkStatsRecorder {
60     private static final String TAG = "NetworkStatsRecorder";
61     private static final boolean LOGD = false;
62     private static final boolean LOGV = false;
63 
64     private static final String TAG_NETSTATS_DUMP = "netstats_dump";
65 
66     /** Dump before deleting in {@link #recoverFromWtf()}. */
67     private static final boolean DUMP_BEFORE_DELETE = true;
68 
69     private final FileRotator mRotator;
70     private final NonMonotonicObserver<String> mObserver;
71     private final DropBoxManager mDropBox;
72     private final String mCookie;
73 
74     private final long mBucketDuration;
75     private final boolean mOnlyTags;
76 
77     private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
78     private NetworkStats mLastSnapshot;
79 
80     private final NetworkStatsCollection mPending;
81     private final NetworkStatsCollection mSinceBoot;
82 
83     private final CombiningRewriter mPendingRewriter;
84 
85     private WeakReference<NetworkStatsCollection> mComplete;
86 
NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer, DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags)87     public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
88             DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
89         mRotator = checkNotNull(rotator, "missing FileRotator");
90         mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
91         mDropBox = checkNotNull(dropBox, "missing DropBoxManager");
92         mCookie = cookie;
93 
94         mBucketDuration = bucketDuration;
95         mOnlyTags = onlyTags;
96 
97         mPending = new NetworkStatsCollection(bucketDuration);
98         mSinceBoot = new NetworkStatsCollection(bucketDuration);
99 
100         mPendingRewriter = new CombiningRewriter(mPending);
101     }
102 
setPersistThreshold(long thresholdBytes)103     public void setPersistThreshold(long thresholdBytes) {
104         if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
105         mPersistThresholdBytes = MathUtils.constrain(
106                 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
107     }
108 
resetLocked()109     public void resetLocked() {
110         mLastSnapshot = null;
111         mPending.reset();
112         mSinceBoot.reset();
113         mComplete.clear();
114     }
115 
getTotalSinceBootLocked(NetworkTemplate template)116     public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
117         return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
118     }
119 
120     /**
121      * Load complete history represented by {@link FileRotator}. Caches
122      * internally as a {@link WeakReference}, and updated with future
123      * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
124      * as reference is valid.
125      */
getOrLoadCompleteLocked()126     public NetworkStatsCollection getOrLoadCompleteLocked() {
127         NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
128         if (complete == null) {
129             if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie);
130             try {
131                 complete = new NetworkStatsCollection(mBucketDuration);
132                 mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE);
133                 complete.recordCollection(mPending);
134                 mComplete = new WeakReference<NetworkStatsCollection>(complete);
135             } catch (IOException e) {
136                 Log.wtf(TAG, "problem completely reading network stats", e);
137                 recoverFromWtf();
138             }
139         }
140         return complete;
141     }
142 
143     /**
144      * Record any delta that occurred since last {@link NetworkStats} snapshot,
145      * using the given {@link Map} to identify network interfaces. First
146      * snapshot is considered bootstrap, and is not counted as delta.
147      */
recordSnapshotLocked(NetworkStats snapshot, Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis)148     public void recordSnapshotLocked(NetworkStats snapshot,
149             Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
150         final HashSet<String> unknownIfaces = Sets.newHashSet();
151 
152         // skip recording when snapshot missing
153         if (snapshot == null) return;
154 
155         // assume first snapshot is bootstrap and don't record
156         if (mLastSnapshot == null) {
157             mLastSnapshot = snapshot;
158             return;
159         }
160 
161         final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
162 
163         final NetworkStats delta = NetworkStats.subtract(
164                 snapshot, mLastSnapshot, mObserver, mCookie);
165         final long end = currentTimeMillis;
166         final long start = end - delta.getElapsedRealtime();
167 
168         NetworkStats.Entry entry = null;
169         for (int i = 0; i < delta.size(); i++) {
170             entry = delta.getValues(i, entry);
171             final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
172             if (ident == null) {
173                 unknownIfaces.add(entry.iface);
174                 continue;
175             }
176 
177             // skip when no delta occurred
178             if (entry.isEmpty()) continue;
179 
180             // only record tag data when requested
181             if ((entry.tag == TAG_NONE) != mOnlyTags) {
182                 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
183 
184                 // also record against boot stats when present
185                 if (mSinceBoot != null) {
186                     mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
187                 }
188 
189                 // also record against complete dataset when present
190                 if (complete != null) {
191                     complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
192                 }
193             }
194         }
195 
196         mLastSnapshot = snapshot;
197 
198         if (LOGV && unknownIfaces.size() > 0) {
199             Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
200         }
201     }
202 
203     /**
204      * Consider persisting any pending deltas, if they are beyond
205      * {@link #mPersistThresholdBytes}.
206      */
maybePersistLocked(long currentTimeMillis)207     public void maybePersistLocked(long currentTimeMillis) {
208         final long pendingBytes = mPending.getTotalBytes();
209         if (pendingBytes >= mPersistThresholdBytes) {
210             forcePersistLocked(currentTimeMillis);
211         } else {
212             mRotator.maybeRotate(currentTimeMillis);
213         }
214     }
215 
216     /**
217      * Force persisting any pending deltas.
218      */
forcePersistLocked(long currentTimeMillis)219     public void forcePersistLocked(long currentTimeMillis) {
220         if (mPending.isDirty()) {
221             if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
222             try {
223                 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
224                 mRotator.maybeRotate(currentTimeMillis);
225                 mPending.reset();
226             } catch (IOException e) {
227                 Log.wtf(TAG, "problem persisting pending stats", e);
228                 recoverFromWtf();
229             }
230         }
231     }
232 
233     /**
234      * Remove the given UID from all {@link FileRotator} history, migrating it
235      * to {@link TrafficStats#UID_REMOVED}.
236      */
removeUidsLocked(int[] uids)237     public void removeUidsLocked(int[] uids) {
238         try {
239             // Rewrite all persisted data to migrate UID stats
240             mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
241         } catch (IOException e) {
242             Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
243             recoverFromWtf();
244         }
245 
246         // Remove any pending stats
247         mPending.removeUids(uids);
248         mSinceBoot.removeUids(uids);
249 
250         // Clear UID from current stats snapshot
251         if (mLastSnapshot != null) {
252             mLastSnapshot = mLastSnapshot.withoutUids(uids);
253         }
254 
255         final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
256         if (complete != null) {
257             complete.removeUids(uids);
258         }
259     }
260 
261     /**
262      * Rewriter that will combine current {@link NetworkStatsCollection} values
263      * with anything read from disk, and write combined set to disk. Clears the
264      * original {@link NetworkStatsCollection} when finished writing.
265      */
266     private static class CombiningRewriter implements FileRotator.Rewriter {
267         private final NetworkStatsCollection mCollection;
268 
CombiningRewriter(NetworkStatsCollection collection)269         public CombiningRewriter(NetworkStatsCollection collection) {
270             mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
271         }
272 
273         @Override
reset()274         public void reset() {
275             // ignored
276         }
277 
278         @Override
read(InputStream in)279         public void read(InputStream in) throws IOException {
280             mCollection.read(in);
281         }
282 
283         @Override
shouldWrite()284         public boolean shouldWrite() {
285             return true;
286         }
287 
288         @Override
write(OutputStream out)289         public void write(OutputStream out) throws IOException {
290             mCollection.write(new DataOutputStream(out));
291             mCollection.reset();
292         }
293     }
294 
295     /**
296      * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
297      * the requested UID, only writing data back when modified.
298      */
299     public static class RemoveUidRewriter implements FileRotator.Rewriter {
300         private final NetworkStatsCollection mTemp;
301         private final int[] mUids;
302 
RemoveUidRewriter(long bucketDuration, int[] uids)303         public RemoveUidRewriter(long bucketDuration, int[] uids) {
304             mTemp = new NetworkStatsCollection(bucketDuration);
305             mUids = uids;
306         }
307 
308         @Override
reset()309         public void reset() {
310             mTemp.reset();
311         }
312 
313         @Override
read(InputStream in)314         public void read(InputStream in) throws IOException {
315             mTemp.read(in);
316             mTemp.clearDirty();
317             mTemp.removeUids(mUids);
318         }
319 
320         @Override
shouldWrite()321         public boolean shouldWrite() {
322             return mTemp.isDirty();
323         }
324 
325         @Override
write(OutputStream out)326         public void write(OutputStream out) throws IOException {
327             mTemp.write(new DataOutputStream(out));
328         }
329     }
330 
importLegacyNetworkLocked(File file)331     public void importLegacyNetworkLocked(File file) throws IOException {
332         // legacy file still exists; start empty to avoid double importing
333         mRotator.deleteAll();
334 
335         final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
336         collection.readLegacyNetwork(file);
337 
338         final long startMillis = collection.getStartMillis();
339         final long endMillis = collection.getEndMillis();
340 
341         if (!collection.isEmpty()) {
342             // process legacy data, creating active file at starting time, then
343             // using end time to possibly trigger rotation.
344             mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
345             mRotator.maybeRotate(endMillis);
346         }
347     }
348 
importLegacyUidLocked(File file)349     public void importLegacyUidLocked(File file) throws IOException {
350         // legacy file still exists; start empty to avoid double importing
351         mRotator.deleteAll();
352 
353         final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
354         collection.readLegacyUid(file, mOnlyTags);
355 
356         final long startMillis = collection.getStartMillis();
357         final long endMillis = collection.getEndMillis();
358 
359         if (!collection.isEmpty()) {
360             // process legacy data, creating active file at starting time, then
361             // using end time to possibly trigger rotation.
362             mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
363             mRotator.maybeRotate(endMillis);
364         }
365     }
366 
dumpLocked(IndentingPrintWriter pw, boolean fullHistory)367     public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
368         pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
369         if (fullHistory) {
370             pw.println("Complete history:");
371             getOrLoadCompleteLocked().dump(pw);
372         } else {
373             pw.println("History since boot:");
374             mSinceBoot.dump(pw);
375         }
376     }
377 
378     /**
379      * Recover from {@link FileRotator} failure by dumping state to
380      * {@link DropBoxManager} and deleting contents.
381      */
recoverFromWtf()382     private void recoverFromWtf() {
383         if (DUMP_BEFORE_DELETE) {
384             final ByteArrayOutputStream os = new ByteArrayOutputStream();
385             try {
386                 mRotator.dumpAll(os);
387             } catch (IOException e) {
388                 // ignore partial contents
389                 os.reset();
390             } finally {
391                 IoUtils.closeQuietly(os);
392             }
393             mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
394         }
395 
396         mRotator.deleteAll();
397     }
398 }
399