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