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