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