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.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 
     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 
    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 
    109     public void resetLocked() {
    110         mLastSnapshot = null;
    111         mPending.reset();
    112         mSinceBoot.reset();
    113         mComplete.clear();
    114     }
    115 
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    269         public CombiningRewriter(NetworkStatsCollection collection) {
    270             mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
    271         }
    272 
    273         @Override
    274         public void reset() {
    275             // ignored
    276         }
    277 
    278         @Override
    279         public void read(InputStream in) throws IOException {
    280             mCollection.read(in);
    281         }
    282 
    283         @Override
    284         public boolean shouldWrite() {
    285             return true;
    286         }
    287 
    288         @Override
    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 
    303         public RemoveUidRewriter(long bucketDuration, int[] uids) {
    304             mTemp = new NetworkStatsCollection(bucketDuration);
    305             mUids = uids;
    306         }
    307 
    308         @Override
    309         public void reset() {
    310             mTemp.reset();
    311         }
    312 
    313         @Override
    314         public void read(InputStream in) throws IOException {
    315             mTemp.read(in);
    316             mTemp.clearDirty();
    317             mTemp.removeUids(mUids);
    318         }
    319 
    320         @Override
    321         public boolean shouldWrite() {
    322             return mTemp.isDirty();
    323         }
    324 
    325         @Override
    326         public void write(OutputStream out) throws IOException {
    327             mTemp.write(new DataOutputStream(out));
    328         }
    329     }
    330 
    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 
    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 
    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      */
    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