• 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 android.text.format.DateUtils.YEAR_IN_MILLIS;
23 
24 import android.annotation.NonNull;
25 import android.net.NetworkIdentitySet;
26 import android.net.NetworkStats;
27 import android.net.NetworkStats.NonMonotonicObserver;
28 import android.net.NetworkStatsAccess;
29 import android.net.NetworkStatsCollection;
30 import android.net.NetworkStatsHistory;
31 import android.net.NetworkTemplate;
32 import android.net.TrafficStats;
33 import android.os.Binder;
34 import android.os.DropBoxManager;
35 import android.service.NetworkStatsRecorderProto;
36 import android.util.IndentingPrintWriter;
37 import android.util.Log;
38 import android.util.proto.ProtoOutputStream;
39 
40 import com.android.internal.util.FileRotator;
41 import com.android.net.module.util.NetworkStatsUtils;
42 
43 import libcore.io.IoUtils;
44 
45 import java.io.ByteArrayOutputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.OutputStream;
49 import java.io.PrintWriter;
50 import java.lang.ref.WeakReference;
51 import java.util.Arrays;
52 import java.util.HashSet;
53 import java.util.Map;
54 import java.util.Objects;
55 
56 /**
57  * Logic to record deltas between periodic {@link NetworkStats} snapshots into
58  * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
59  * Keeps pending changes in memory until they pass a specific threshold, in
60  * bytes. Uses {@link FileRotator} for persistence logic if present.
61  * <p>
62  * Not inherently thread safe.
63  */
64 public class NetworkStatsRecorder {
65     private static final String TAG = "NetworkStatsRecorder";
66     private static final boolean LOGD = false;
67     private static final boolean LOGV = false;
68 
69     private static final String TAG_NETSTATS_DUMP = "netstats_dump";
70 
71     /** Dump before deleting in {@link #recoverAndDeleteData()}. */
72     private static final boolean DUMP_BEFORE_DELETE = true;
73 
74     private final FileRotator mRotator;
75     private final NonMonotonicObserver<String> mObserver;
76     private final DropBoxManager mDropBox;
77     private final String mCookie;
78 
79     private final long mBucketDuration;
80     private final boolean mOnlyTags;
81     private final boolean mWipeOnError;
82 
83     private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
84     private NetworkStats mLastSnapshot;
85 
86     private final NetworkStatsCollection mPending;
87     private final NetworkStatsCollection mSinceBoot;
88 
89     private final CombiningRewriter mPendingRewriter;
90 
91     private WeakReference<NetworkStatsCollection> mComplete;
92 
93     /**
94      * Non-persisted recorder, with only one bucket. Used by {@link NetworkStatsObservers}.
95      */
NetworkStatsRecorder()96     public NetworkStatsRecorder() {
97         mRotator = null;
98         mObserver = null;
99         mDropBox = null;
100         mCookie = null;
101 
102         // set the bucket big enough to have all data in one bucket, but allow some
103         // slack to avoid overflow
104         mBucketDuration = YEAR_IN_MILLIS;
105         mOnlyTags = false;
106         mWipeOnError = true;
107 
108         mPending = null;
109         mSinceBoot = new NetworkStatsCollection(mBucketDuration);
110 
111         mPendingRewriter = null;
112     }
113 
114     /**
115      * Persisted recorder.
116      */
NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer, DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags, boolean wipeOnError)117     public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
118             DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags,
119             boolean wipeOnError) {
120         mRotator = Objects.requireNonNull(rotator, "missing FileRotator");
121         mObserver = Objects.requireNonNull(observer, "missing NonMonotonicObserver");
122         mDropBox = Objects.requireNonNull(dropBox, "missing DropBoxManager");
123         mCookie = cookie;
124 
125         mBucketDuration = bucketDuration;
126         mOnlyTags = onlyTags;
127         mWipeOnError = wipeOnError;
128 
129         mPending = new NetworkStatsCollection(bucketDuration);
130         mSinceBoot = new NetworkStatsCollection(bucketDuration);
131 
132         mPendingRewriter = new CombiningRewriter(mPending);
133     }
134 
setPersistThreshold(long thresholdBytes)135     public void setPersistThreshold(long thresholdBytes) {
136         if (LOGV) Log.v(TAG, "setPersistThreshold() with " + thresholdBytes);
137         mPersistThresholdBytes = NetworkStatsUtils.constrain(
138                 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
139     }
140 
resetLocked()141     public void resetLocked() {
142         mLastSnapshot = null;
143         if (mPending != null) {
144             mPending.reset();
145         }
146         if (mSinceBoot != null) {
147             mSinceBoot.reset();
148         }
149         if (mComplete != null) {
150             mComplete.clear();
151         }
152     }
153 
getTotalSinceBootLocked(NetworkTemplate template)154     public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
155         return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE,
156                 NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotal(null);
157     }
158 
getSinceBoot()159     public NetworkStatsCollection getSinceBoot() {
160         return mSinceBoot;
161     }
162 
getBucketDuration()163     public long getBucketDuration() {
164         return mBucketDuration;
165     }
166 
167     @NonNull
getCookie()168     public String getCookie() {
169         return mCookie;
170     }
171 
172     /**
173      * Load complete history represented by {@link FileRotator}. Caches
174      * internally as a {@link WeakReference}, and updated with future
175      * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
176      * as reference is valid.
177      */
getOrLoadCompleteLocked()178     public NetworkStatsCollection getOrLoadCompleteLocked() {
179         Objects.requireNonNull(mRotator, "missing FileRotator");
180         NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
181         if (res == null) {
182             res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE);
183             mComplete = new WeakReference<NetworkStatsCollection>(res);
184         }
185         return res;
186     }
187 
getOrLoadPartialLocked(long start, long end)188     public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) {
189         Objects.requireNonNull(mRotator, "missing FileRotator");
190         NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
191         if (res == null) {
192             res = loadLocked(start, end);
193         }
194         return res;
195     }
196 
loadLocked(long start, long end)197     private NetworkStatsCollection loadLocked(long start, long end) {
198         if (LOGD) Log.d(TAG, "loadLocked() reading from disk for " + mCookie);
199         final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration);
200         try {
201             mRotator.readMatching(res, start, end);
202             res.recordCollection(mPending);
203         } catch (IOException e) {
204             Log.wtf(TAG, "problem completely reading network stats", e);
205             recoverAndDeleteData();
206         } catch (OutOfMemoryError e) {
207             Log.wtf(TAG, "problem completely reading network stats", e);
208             recoverAndDeleteData();
209         }
210         return res;
211     }
212 
213     /**
214      * Record any delta that occurred since last {@link NetworkStats} snapshot, using the given
215      * {@link Map} to identify network interfaces. First snapshot is considered bootstrap, and is
216      * not counted as delta.
217      */
recordSnapshotLocked(NetworkStats snapshot, Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis)218     public void recordSnapshotLocked(NetworkStats snapshot,
219             Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
220         final HashSet<String> unknownIfaces = new HashSet<>();
221 
222         // skip recording when snapshot missing
223         if (snapshot == null) return;
224 
225         // assume first snapshot is bootstrap and don't record
226         if (mLastSnapshot == null) {
227             mLastSnapshot = snapshot;
228             return;
229         }
230 
231         final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
232 
233         final NetworkStats delta = NetworkStats.subtract(
234                 snapshot, mLastSnapshot, mObserver, mCookie);
235         final long end = currentTimeMillis;
236         final long start = end - delta.getElapsedRealtime();
237 
238         NetworkStats.Entry entry = null;
239         for (int i = 0; i < delta.size(); i++) {
240             entry = delta.getValues(i, entry);
241 
242             // As a last-ditch check, report any negative values and
243             // clamp them so recording below doesn't croak.
244             if (entry.isNegative()) {
245                 if (mObserver != null) {
246                     mObserver.foundNonMonotonic(delta, i, mCookie);
247                 }
248                 entry.rxBytes = Math.max(entry.rxBytes, 0);
249                 entry.rxPackets = Math.max(entry.rxPackets, 0);
250                 entry.txBytes = Math.max(entry.txBytes, 0);
251                 entry.txPackets = Math.max(entry.txPackets, 0);
252                 entry.operations = Math.max(entry.operations, 0);
253             }
254 
255             final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
256             if (ident == null) {
257                 unknownIfaces.add(entry.iface);
258                 continue;
259             }
260 
261             // skip when no delta occurred
262             if (entry.isEmpty()) continue;
263 
264             // only record tag data when requested
265             if ((entry.tag == TAG_NONE) != mOnlyTags) {
266                 if (mPending != null) {
267                     mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
268                 }
269 
270                 // also record against boot stats when present
271                 if (mSinceBoot != null) {
272                     mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
273                 }
274 
275                 // also record against complete dataset when present
276                 if (complete != null) {
277                     complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
278                 }
279             }
280         }
281 
282         mLastSnapshot = snapshot;
283 
284         if (LOGV && unknownIfaces.size() > 0) {
285             Log.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
286         }
287     }
288 
289     /**
290      * Consider persisting any pending deltas, if they are beyond
291      * {@link #mPersistThresholdBytes}.
292      */
maybePersistLocked(long currentTimeMillis)293     public void maybePersistLocked(long currentTimeMillis) {
294         Objects.requireNonNull(mRotator, "missing FileRotator");
295         final long pendingBytes = mPending.getTotalBytes();
296         if (pendingBytes >= mPersistThresholdBytes) {
297             forcePersistLocked(currentTimeMillis);
298         } else {
299             mRotator.maybeRotate(currentTimeMillis);
300         }
301     }
302 
303     /**
304      * Force persisting any pending deltas.
305      */
forcePersistLocked(long currentTimeMillis)306     public void forcePersistLocked(long currentTimeMillis) {
307         Objects.requireNonNull(mRotator, "missing FileRotator");
308         if (mPending.isDirty()) {
309             if (LOGD) Log.d(TAG, "forcePersistLocked() writing for " + mCookie);
310             try {
311                 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
312                 mRotator.maybeRotate(currentTimeMillis);
313                 mPending.reset();
314             } catch (IOException e) {
315                 Log.wtf(TAG, "problem persisting pending stats", e);
316                 recoverAndDeleteData();
317             } catch (OutOfMemoryError e) {
318                 Log.wtf(TAG, "problem persisting pending stats", e);
319                 recoverAndDeleteData();
320             }
321         }
322     }
323 
324     /**
325      * Remove the given UID from all {@link FileRotator} history, migrating it
326      * to {@link TrafficStats#UID_REMOVED}.
327      */
removeUidsLocked(int[] uids)328     public void removeUidsLocked(int[] uids) {
329         if (mRotator != null) {
330             try {
331                 // Rewrite all persisted data to migrate UID stats
332                 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
333             } catch (IOException e) {
334                 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
335                 recoverAndDeleteData();
336             } catch (OutOfMemoryError e) {
337                 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
338                 recoverAndDeleteData();
339             }
340         }
341 
342         // Remove any pending stats
343         if (mPending != null) {
344             mPending.removeUids(uids);
345         }
346         if (mSinceBoot != null) {
347             mSinceBoot.removeUids(uids);
348         }
349 
350         // Clear UID from current stats snapshot
351         if (mLastSnapshot != null) {
352             mLastSnapshot.removeUids(uids);
353         }
354 
355         final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
356         if (complete != null) {
357             complete.removeUids(uids);
358         }
359     }
360 
361     /**
362      * Rewriter that will combine current {@link NetworkStatsCollection} values
363      * with anything read from disk, and write combined set to disk.
364      */
365     private static class CombiningRewriter implements FileRotator.Rewriter {
366         private final NetworkStatsCollection mCollection;
367 
CombiningRewriter(NetworkStatsCollection collection)368         public CombiningRewriter(NetworkStatsCollection collection) {
369             mCollection = Objects.requireNonNull(collection, "missing NetworkStatsCollection");
370         }
371 
372         @Override
reset()373         public void reset() {
374             // ignored
375         }
376 
377         @Override
read(InputStream in)378         public void read(InputStream in) throws IOException {
379             mCollection.read(in);
380         }
381 
382         @Override
shouldWrite()383         public boolean shouldWrite() {
384             return true;
385         }
386 
387         @Override
write(OutputStream out)388         public void write(OutputStream out) throws IOException {
389             mCollection.write(out);
390         }
391     }
392 
393     /**
394      * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
395      * the requested UID, only writing data back when modified.
396      */
397     public static class RemoveUidRewriter implements FileRotator.Rewriter {
398         private final NetworkStatsCollection mTemp;
399         private final int[] mUids;
400 
RemoveUidRewriter(long bucketDuration, int[] uids)401         public RemoveUidRewriter(long bucketDuration, int[] uids) {
402             mTemp = new NetworkStatsCollection(bucketDuration);
403             mUids = uids;
404         }
405 
406         @Override
reset()407         public void reset() {
408             mTemp.reset();
409         }
410 
411         @Override
read(InputStream in)412         public void read(InputStream in) throws IOException {
413             mTemp.read(in);
414             mTemp.clearDirty();
415             mTemp.removeUids(mUids);
416         }
417 
418         @Override
shouldWrite()419         public boolean shouldWrite() {
420             return mTemp.isDirty();
421         }
422 
423         @Override
write(OutputStream out)424         public void write(OutputStream out) throws IOException {
425             mTemp.write(out);
426         }
427     }
428 
429     /**
430      * Import a specified {@link NetworkStatsCollection} instance into this recorder,
431      * and write it into a standalone file.
432      * @param collection The target {@link NetworkStatsCollection} instance to be imported.
433      */
importCollectionLocked(@onNull NetworkStatsCollection collection)434     public void importCollectionLocked(@NonNull NetworkStatsCollection collection)
435             throws IOException {
436         if (mRotator != null) {
437             mRotator.rewriteSingle(new CombiningRewriter(collection), collection.getStartMillis(),
438                     collection.getEndMillis());
439         }
440 
441         if (mComplete != null) {
442             throw new IllegalStateException("cannot import data when data already loaded");
443         }
444     }
445 
446     /**
447      * Rewriter that will remove any histories or persisted data points before the
448      * specified cutoff time, only writing data back when modified.
449      */
450     public static class RemoveDataBeforeRewriter implements FileRotator.Rewriter {
451         private final NetworkStatsCollection mTemp;
452         private final long mCutoffMills;
453 
RemoveDataBeforeRewriter(long bucketDuration, long cutoffMills)454         public RemoveDataBeforeRewriter(long bucketDuration, long cutoffMills) {
455             mTemp = new NetworkStatsCollection(bucketDuration);
456             mCutoffMills = cutoffMills;
457         }
458 
459         @Override
reset()460         public void reset() {
461             mTemp.reset();
462         }
463 
464         @Override
read(InputStream in)465         public void read(InputStream in) throws IOException {
466             mTemp.read(in);
467             mTemp.clearDirty();
468             mTemp.removeHistoryBefore(mCutoffMills);
469         }
470 
471         @Override
shouldWrite()472         public boolean shouldWrite() {
473             return mTemp.isDirty();
474         }
475 
476         @Override
write(OutputStream out)477         public void write(OutputStream out) throws IOException {
478             mTemp.write(out);
479         }
480     }
481 
482     /**
483      * Remove persisted data which contains or is before the cutoff timestamp.
484      */
removeDataBefore(long cutoffMillis)485     public void removeDataBefore(long cutoffMillis) throws IOException {
486         if (mRotator != null) {
487             try {
488                 mRotator.rewriteAll(new RemoveDataBeforeRewriter(
489                         mBucketDuration, cutoffMillis));
490             } catch (IOException e) {
491                 Log.wtf(TAG, "problem importing netstats", e);
492                 recoverAndDeleteData();
493             } catch (OutOfMemoryError e) {
494                 Log.wtf(TAG, "problem importing netstats", e);
495                 recoverAndDeleteData();
496             }
497         }
498 
499         // Clean up any pending stats
500         if (mPending != null) {
501             mPending.removeHistoryBefore(cutoffMillis);
502         }
503         if (mSinceBoot != null) {
504             mSinceBoot.removeHistoryBefore(cutoffMillis);
505         }
506 
507         final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
508         if (complete != null) {
509             complete.removeHistoryBefore(cutoffMillis);
510         }
511     }
512 
dumpLocked(IndentingPrintWriter pw, boolean fullHistory)513     public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
514         if (mPending != null) {
515             pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
516         }
517         if (fullHistory) {
518             pw.println("Complete history:");
519             getOrLoadCompleteLocked().dump(pw);
520         } else {
521             pw.println("History since boot:");
522             mSinceBoot.dump(pw);
523         }
524     }
525 
dumpDebugLocked(ProtoOutputStream proto, long tag)526     public void dumpDebugLocked(ProtoOutputStream proto, long tag) {
527         final long start = proto.start(tag);
528         if (mPending != null) {
529             proto.write(NetworkStatsRecorderProto.PENDING_TOTAL_BYTES,
530                     mPending.getTotalBytes());
531         }
532         getOrLoadCompleteLocked().dumpDebug(proto,
533                 NetworkStatsRecorderProto.COMPLETE_HISTORY);
534         proto.end(start);
535     }
536 
dumpCheckin(PrintWriter pw, long start, long end)537     public void dumpCheckin(PrintWriter pw, long start, long end) {
538         // Only load and dump stats from the requested window
539         getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end);
540     }
541 
542     /**
543      * Recover from {@link FileRotator} failure by dumping state to
544      * {@link DropBoxManager} and deleting contents if this recorder
545      * sets {@code mWipeOnError} to true, otherwise keep the contents.
546      */
recoverAndDeleteData()547     void recoverAndDeleteData() {
548         if (DUMP_BEFORE_DELETE) {
549             final ByteArrayOutputStream os = new ByteArrayOutputStream();
550             try {
551                 mRotator.dumpAll(os);
552             } catch (IOException e) {
553                 // ignore partial contents
554                 os.reset();
555             } finally {
556                 IoUtils.closeQuietly(os);
557             }
558             mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
559         }
560         // Delete all files if this recorder is set wipe on error.
561         if (mWipeOnError) {
562             mRotator.deleteAll();
563         }
564     }
565 }
566