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.IFACE_ALL;
     20 import static android.net.NetworkStats.SET_ALL;
     21 import static android.net.NetworkStats.SET_DEFAULT;
     22 import static android.net.NetworkStats.TAG_NONE;
     23 import static android.net.NetworkStats.UID_ALL;
     24 import static android.net.TrafficStats.UID_REMOVED;
     25 
     26 import android.net.NetworkIdentity;
     27 import android.net.NetworkStats;
     28 import android.net.NetworkStatsHistory;
     29 import android.net.NetworkTemplate;
     30 import android.net.TrafficStats;
     31 import android.text.format.DateUtils;
     32 import android.util.AtomicFile;
     33 
     34 import com.android.internal.util.ArrayUtils;
     35 import com.android.internal.util.FileRotator;
     36 import com.android.internal.util.IndentingPrintWriter;
     37 import com.android.internal.util.Objects;
     38 import com.google.android.collect.Lists;
     39 import com.google.android.collect.Maps;
     40 
     41 import java.io.BufferedInputStream;
     42 import java.io.DataInputStream;
     43 import java.io.DataOutputStream;
     44 import java.io.File;
     45 import java.io.FileNotFoundException;
     46 import java.io.IOException;
     47 import java.io.InputStream;
     48 import java.net.ProtocolException;
     49 import java.util.ArrayList;
     50 import java.util.Collections;
     51 import java.util.HashMap;
     52 import java.util.Map;
     53 
     54 import libcore.io.IoUtils;
     55 
     56 /**
     57  * Collection of {@link NetworkStatsHistory}, stored based on combined key of
     58  * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
     59  */
     60 public class NetworkStatsCollection implements FileRotator.Reader {
     61     /** File header magic number: "ANET" */
     62     private static final int FILE_MAGIC = 0x414E4554;
     63 
     64     private static final int VERSION_NETWORK_INIT = 1;
     65 
     66     private static final int VERSION_UID_INIT = 1;
     67     private static final int VERSION_UID_WITH_IDENT = 2;
     68     private static final int VERSION_UID_WITH_TAG = 3;
     69     private static final int VERSION_UID_WITH_SET = 4;
     70 
     71     private static final int VERSION_UNIFIED_INIT = 16;
     72 
     73     private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap();
     74 
     75     private final long mBucketDuration;
     76 
     77     private long mStartMillis;
     78     private long mEndMillis;
     79     private long mTotalBytes;
     80     private boolean mDirty;
     81 
     82     public NetworkStatsCollection(long bucketDuration) {
     83         mBucketDuration = bucketDuration;
     84         reset();
     85     }
     86 
     87     public void reset() {
     88         mStats.clear();
     89         mStartMillis = Long.MAX_VALUE;
     90         mEndMillis = Long.MIN_VALUE;
     91         mTotalBytes = 0;
     92         mDirty = false;
     93     }
     94 
     95     public long getStartMillis() {
     96         return mStartMillis;
     97     }
     98 
     99     /**
    100      * Return first atomic bucket in this collection, which is more conservative
    101      * than {@link #mStartMillis}.
    102      */
    103     public long getFirstAtomicBucketMillis() {
    104         if (mStartMillis == Long.MAX_VALUE) {
    105             return Long.MAX_VALUE;
    106         } else {
    107             return mStartMillis + mBucketDuration;
    108         }
    109     }
    110 
    111     public long getEndMillis() {
    112         return mEndMillis;
    113     }
    114 
    115     public long getTotalBytes() {
    116         return mTotalBytes;
    117     }
    118 
    119     public boolean isDirty() {
    120         return mDirty;
    121     }
    122 
    123     public void clearDirty() {
    124         mDirty = false;
    125     }
    126 
    127     public boolean isEmpty() {
    128         return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
    129     }
    130 
    131     /**
    132      * Combine all {@link NetworkStatsHistory} in this collection which match
    133      * the requested parameters.
    134      */
    135     public NetworkStatsHistory getHistory(
    136             NetworkTemplate template, int uid, int set, int tag, int fields) {
    137         return getHistory(template, uid, set, tag, fields, Long.MIN_VALUE, Long.MAX_VALUE);
    138     }
    139 
    140     /**
    141      * Combine all {@link NetworkStatsHistory} in this collection which match
    142      * the requested parameters.
    143      */
    144     public NetworkStatsHistory getHistory(
    145             NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) {
    146         final NetworkStatsHistory combined = new NetworkStatsHistory(
    147                 mBucketDuration, estimateBuckets(), fields);
    148         for (Map.Entry<Key, NetworkStatsHistory> entry : mStats.entrySet()) {
    149             final Key key = entry.getKey();
    150             final boolean setMatches = set == SET_ALL || key.set == set;
    151             if (key.uid == uid && setMatches && key.tag == tag
    152                     && templateMatches(template, key.ident)) {
    153                 combined.recordHistory(entry.getValue(), start, end);
    154             }
    155         }
    156         return combined;
    157     }
    158 
    159     /**
    160      * Summarize all {@link NetworkStatsHistory} in this collection which match
    161      * the requested parameters.
    162      */
    163     public NetworkStats getSummary(NetworkTemplate template, long start, long end) {
    164         final long now = System.currentTimeMillis();
    165 
    166         final NetworkStats stats = new NetworkStats(end - start, 24);
    167         final NetworkStats.Entry entry = new NetworkStats.Entry();
    168         NetworkStatsHistory.Entry historyEntry = null;
    169 
    170         // shortcut when we know stats will be empty
    171         if (start == end) return stats;
    172 
    173         for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) {
    174             final Key key = mapEntry.getKey();
    175             if (templateMatches(template, key.ident)) {
    176                 final NetworkStatsHistory history = mapEntry.getValue();
    177                 historyEntry = history.getValues(start, end, now, historyEntry);
    178 
    179                 entry.iface = IFACE_ALL;
    180                 entry.uid = key.uid;
    181                 entry.set = key.set;
    182                 entry.tag = key.tag;
    183                 entry.rxBytes = historyEntry.rxBytes;
    184                 entry.rxPackets = historyEntry.rxPackets;
    185                 entry.txBytes = historyEntry.txBytes;
    186                 entry.txPackets = historyEntry.txPackets;
    187                 entry.operations = historyEntry.operations;
    188 
    189                 if (!entry.isEmpty()) {
    190                     stats.combineValues(entry);
    191                 }
    192             }
    193         }
    194 
    195         return stats;
    196     }
    197 
    198     /**
    199      * Record given {@link android.net.NetworkStats.Entry} into this collection.
    200      */
    201     public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
    202             long end, NetworkStats.Entry entry) {
    203         final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag);
    204         history.recordData(start, end, entry);
    205         noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes);
    206     }
    207 
    208     /**
    209      * Record given {@link NetworkStatsHistory} into this collection.
    210      */
    211     private void recordHistory(Key key, NetworkStatsHistory history) {
    212         if (history.size() == 0) return;
    213         noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
    214 
    215         NetworkStatsHistory target = mStats.get(key);
    216         if (target == null) {
    217             target = new NetworkStatsHistory(history.getBucketDuration());
    218             mStats.put(key, target);
    219         }
    220         target.recordEntireHistory(history);
    221     }
    222 
    223     /**
    224      * Record all {@link NetworkStatsHistory} contained in the given collection
    225      * into this collection.
    226      */
    227     public void recordCollection(NetworkStatsCollection another) {
    228         for (Map.Entry<Key, NetworkStatsHistory> entry : another.mStats.entrySet()) {
    229             recordHistory(entry.getKey(), entry.getValue());
    230         }
    231     }
    232 
    233     private NetworkStatsHistory findOrCreateHistory(
    234             NetworkIdentitySet ident, int uid, int set, int tag) {
    235         final Key key = new Key(ident, uid, set, tag);
    236         final NetworkStatsHistory existing = mStats.get(key);
    237 
    238         // update when no existing, or when bucket duration changed
    239         NetworkStatsHistory updated = null;
    240         if (existing == null) {
    241             updated = new NetworkStatsHistory(mBucketDuration, 10);
    242         } else if (existing.getBucketDuration() != mBucketDuration) {
    243             updated = new NetworkStatsHistory(existing, mBucketDuration);
    244         }
    245 
    246         if (updated != null) {
    247             mStats.put(key, updated);
    248             return updated;
    249         } else {
    250             return existing;
    251         }
    252     }
    253 
    254     @Override
    255     public void read(InputStream in) throws IOException {
    256         read(new DataInputStream(in));
    257     }
    258 
    259     public void read(DataInputStream in) throws IOException {
    260         // verify file magic header intact
    261         final int magic = in.readInt();
    262         if (magic != FILE_MAGIC) {
    263             throw new ProtocolException("unexpected magic: " + magic);
    264         }
    265 
    266         final int version = in.readInt();
    267         switch (version) {
    268             case VERSION_UNIFIED_INIT: {
    269                 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
    270                 final int identSize = in.readInt();
    271                 for (int i = 0; i < identSize; i++) {
    272                     final NetworkIdentitySet ident = new NetworkIdentitySet(in);
    273 
    274                     final int size = in.readInt();
    275                     for (int j = 0; j < size; j++) {
    276                         final int uid = in.readInt();
    277                         final int set = in.readInt();
    278                         final int tag = in.readInt();
    279 
    280                         final Key key = new Key(ident, uid, set, tag);
    281                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
    282                         recordHistory(key, history);
    283                     }
    284                 }
    285                 break;
    286             }
    287             default: {
    288                 throw new ProtocolException("unexpected version: " + version);
    289             }
    290         }
    291     }
    292 
    293     public void write(DataOutputStream out) throws IOException {
    294         // cluster key lists grouped by ident
    295         final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
    296         for (Key key : mStats.keySet()) {
    297             ArrayList<Key> keys = keysByIdent.get(key.ident);
    298             if (keys == null) {
    299                 keys = Lists.newArrayList();
    300                 keysByIdent.put(key.ident, keys);
    301             }
    302             keys.add(key);
    303         }
    304 
    305         out.writeInt(FILE_MAGIC);
    306         out.writeInt(VERSION_UNIFIED_INIT);
    307 
    308         out.writeInt(keysByIdent.size());
    309         for (NetworkIdentitySet ident : keysByIdent.keySet()) {
    310             final ArrayList<Key> keys = keysByIdent.get(ident);
    311             ident.writeToStream(out);
    312 
    313             out.writeInt(keys.size());
    314             for (Key key : keys) {
    315                 final NetworkStatsHistory history = mStats.get(key);
    316                 out.writeInt(key.uid);
    317                 out.writeInt(key.set);
    318                 out.writeInt(key.tag);
    319                 history.writeToStream(out);
    320             }
    321         }
    322 
    323         out.flush();
    324     }
    325 
    326     @Deprecated
    327     public void readLegacyNetwork(File file) throws IOException {
    328         final AtomicFile inputFile = new AtomicFile(file);
    329 
    330         DataInputStream in = null;
    331         try {
    332             in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
    333 
    334             // verify file magic header intact
    335             final int magic = in.readInt();
    336             if (magic != FILE_MAGIC) {
    337                 throw new ProtocolException("unexpected magic: " + magic);
    338             }
    339 
    340             final int version = in.readInt();
    341             switch (version) {
    342                 case VERSION_NETWORK_INIT: {
    343                     // network := size *(NetworkIdentitySet NetworkStatsHistory)
    344                     final int size = in.readInt();
    345                     for (int i = 0; i < size; i++) {
    346                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
    347                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
    348 
    349                         final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
    350                         recordHistory(key, history);
    351                     }
    352                     break;
    353                 }
    354                 default: {
    355                     throw new ProtocolException("unexpected version: " + version);
    356                 }
    357             }
    358         } catch (FileNotFoundException e) {
    359             // missing stats is okay, probably first boot
    360         } finally {
    361             IoUtils.closeQuietly(in);
    362         }
    363     }
    364 
    365     @Deprecated
    366     public void readLegacyUid(File file, boolean onlyTags) throws IOException {
    367         final AtomicFile inputFile = new AtomicFile(file);
    368 
    369         DataInputStream in = null;
    370         try {
    371             in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
    372 
    373             // verify file magic header intact
    374             final int magic = in.readInt();
    375             if (magic != FILE_MAGIC) {
    376                 throw new ProtocolException("unexpected magic: " + magic);
    377             }
    378 
    379             final int version = in.readInt();
    380             switch (version) {
    381                 case VERSION_UID_INIT: {
    382                     // uid := size *(UID NetworkStatsHistory)
    383 
    384                     // drop this data version, since we don't have a good
    385                     // mapping into NetworkIdentitySet.
    386                     break;
    387                 }
    388                 case VERSION_UID_WITH_IDENT: {
    389                     // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
    390 
    391                     // drop this data version, since this version only existed
    392                     // for a short time.
    393                     break;
    394                 }
    395                 case VERSION_UID_WITH_TAG:
    396                 case VERSION_UID_WITH_SET: {
    397                     // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
    398                     final int identSize = in.readInt();
    399                     for (int i = 0; i < identSize; i++) {
    400                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
    401 
    402                         final int size = in.readInt();
    403                         for (int j = 0; j < size; j++) {
    404                             final int uid = in.readInt();
    405                             final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
    406                                     : SET_DEFAULT;
    407                             final int tag = in.readInt();
    408 
    409                             final Key key = new Key(ident, uid, set, tag);
    410                             final NetworkStatsHistory history = new NetworkStatsHistory(in);
    411 
    412                             if ((tag == TAG_NONE) != onlyTags) {
    413                                 recordHistory(key, history);
    414                             }
    415                         }
    416                     }
    417                     break;
    418                 }
    419                 default: {
    420                     throw new ProtocolException("unexpected version: " + version);
    421                 }
    422             }
    423         } catch (FileNotFoundException e) {
    424             // missing stats is okay, probably first boot
    425         } finally {
    426             IoUtils.closeQuietly(in);
    427         }
    428     }
    429 
    430     /**
    431      * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
    432      * moving any {@link NetworkStats#TAG_NONE} series to
    433      * {@link TrafficStats#UID_REMOVED}.
    434      */
    435     public void removeUids(int[] uids) {
    436         final ArrayList<Key> knownKeys = Lists.newArrayList();
    437         knownKeys.addAll(mStats.keySet());
    438 
    439         // migrate all UID stats into special "removed" bucket
    440         for (Key key : knownKeys) {
    441             if (ArrayUtils.contains(uids, key.uid)) {
    442                 // only migrate combined TAG_NONE history
    443                 if (key.tag == TAG_NONE) {
    444                     final NetworkStatsHistory uidHistory = mStats.get(key);
    445                     final NetworkStatsHistory removedHistory = findOrCreateHistory(
    446                             key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
    447                     removedHistory.recordEntireHistory(uidHistory);
    448                 }
    449                 mStats.remove(key);
    450                 mDirty = true;
    451             }
    452         }
    453     }
    454 
    455     private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
    456         if (startMillis < mStartMillis) mStartMillis = startMillis;
    457         if (endMillis > mEndMillis) mEndMillis = endMillis;
    458         mTotalBytes += totalBytes;
    459         mDirty = true;
    460     }
    461 
    462     private int estimateBuckets() {
    463         return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5)
    464                 / mBucketDuration);
    465     }
    466 
    467     public void dump(IndentingPrintWriter pw) {
    468         final ArrayList<Key> keys = Lists.newArrayList();
    469         keys.addAll(mStats.keySet());
    470         Collections.sort(keys);
    471 
    472         for (Key key : keys) {
    473             pw.print("ident="); pw.print(key.ident.toString());
    474             pw.print(" uid="); pw.print(key.uid);
    475             pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
    476             pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
    477 
    478             final NetworkStatsHistory history = mStats.get(key);
    479             pw.increaseIndent();
    480             history.dump(pw, true);
    481             pw.decreaseIndent();
    482         }
    483     }
    484 
    485     /**
    486      * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
    487      * in the given {@link NetworkIdentitySet}.
    488      */
    489     private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
    490         for (NetworkIdentity ident : identSet) {
    491             if (template.matches(ident)) {
    492                 return true;
    493             }
    494         }
    495         return false;
    496     }
    497 
    498     private static class Key implements Comparable<Key> {
    499         public final NetworkIdentitySet ident;
    500         public final int uid;
    501         public final int set;
    502         public final int tag;
    503 
    504         private final int hashCode;
    505 
    506         public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
    507             this.ident = ident;
    508             this.uid = uid;
    509             this.set = set;
    510             this.tag = tag;
    511             hashCode = Objects.hashCode(ident, uid, set, tag);
    512         }
    513 
    514         @Override
    515         public int hashCode() {
    516             return hashCode;
    517         }
    518 
    519         @Override
    520         public boolean equals(Object obj) {
    521             if (obj instanceof Key) {
    522                 final Key key = (Key) obj;
    523                 return uid == key.uid && set == key.set && tag == key.tag
    524                         && Objects.equal(ident, key.ident);
    525             }
    526             return false;
    527         }
    528 
    529         @Override
    530         public int compareTo(Key another) {
    531             return Integer.compare(uid, another.uid);
    532         }
    533     }
    534 }
    535