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.DEFAULT_NETWORK_NO;
     20 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
     21 import static android.net.NetworkStats.IFACE_ALL;
     22 import static android.net.NetworkStats.METERED_NO;
     23 import static android.net.NetworkStats.METERED_YES;
     24 import static android.net.NetworkStats.ROAMING_NO;
     25 import static android.net.NetworkStats.ROAMING_YES;
     26 import static android.net.NetworkStats.SET_ALL;
     27 import static android.net.NetworkStats.SET_DEFAULT;
     28 import static android.net.NetworkStats.TAG_NONE;
     29 import static android.net.NetworkStats.UID_ALL;
     30 import static android.net.TrafficStats.UID_REMOVED;
     31 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
     32 
     33 import static com.android.server.net.NetworkStatsService.TAG;
     34 
     35 import android.net.NetworkIdentity;
     36 import android.net.NetworkStats;
     37 import android.net.NetworkStatsHistory;
     38 import android.net.NetworkTemplate;
     39 import android.net.TrafficStats;
     40 import android.os.Binder;
     41 import android.service.NetworkStatsCollectionKeyProto;
     42 import android.service.NetworkStatsCollectionProto;
     43 import android.service.NetworkStatsCollectionStatsProto;
     44 import android.telephony.SubscriptionPlan;
     45 import android.text.format.DateUtils;
     46 import android.util.ArrayMap;
     47 import android.util.AtomicFile;
     48 import android.util.IntArray;
     49 import android.util.MathUtils;
     50 import android.util.Range;
     51 import android.util.Slog;
     52 import android.util.proto.ProtoOutputStream;
     53 
     54 import com.android.internal.annotations.VisibleForTesting;
     55 import com.android.internal.util.ArrayUtils;
     56 import com.android.internal.util.FileRotator;
     57 import com.android.internal.util.IndentingPrintWriter;
     58 
     59 import libcore.io.IoUtils;
     60 
     61 import com.google.android.collect.Lists;
     62 import com.google.android.collect.Maps;
     63 
     64 import java.io.BufferedInputStream;
     65 import java.io.DataInputStream;
     66 import java.io.DataOutputStream;
     67 import java.io.File;
     68 import java.io.FileNotFoundException;
     69 import java.io.IOException;
     70 import java.io.InputStream;
     71 import java.io.PrintWriter;
     72 import java.net.ProtocolException;
     73 import java.time.ZonedDateTime;
     74 import java.util.ArrayList;
     75 import java.util.Collections;
     76 import java.util.HashMap;
     77 import java.util.Iterator;
     78 import java.util.Objects;
     79 
     80 /**
     81  * Collection of {@link NetworkStatsHistory}, stored based on combined key of
     82  * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
     83  */
     84 public class NetworkStatsCollection implements FileRotator.Reader {
     85     /** File header magic number: "ANET" */
     86     private static final int FILE_MAGIC = 0x414E4554;
     87 
     88     private static final int VERSION_NETWORK_INIT = 1;
     89 
     90     private static final int VERSION_UID_INIT = 1;
     91     private static final int VERSION_UID_WITH_IDENT = 2;
     92     private static final int VERSION_UID_WITH_TAG = 3;
     93     private static final int VERSION_UID_WITH_SET = 4;
     94 
     95     private static final int VERSION_UNIFIED_INIT = 16;
     96 
     97     private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>();
     98 
     99     private final long mBucketDuration;
    100 
    101     private long mStartMillis;
    102     private long mEndMillis;
    103     private long mTotalBytes;
    104     private boolean mDirty;
    105 
    106     public NetworkStatsCollection(long bucketDuration) {
    107         mBucketDuration = bucketDuration;
    108         reset();
    109     }
    110 
    111     public void clear() {
    112         reset();
    113     }
    114 
    115     public void reset() {
    116         mStats.clear();
    117         mStartMillis = Long.MAX_VALUE;
    118         mEndMillis = Long.MIN_VALUE;
    119         mTotalBytes = 0;
    120         mDirty = false;
    121     }
    122 
    123     public long getStartMillis() {
    124         return mStartMillis;
    125     }
    126 
    127     /**
    128      * Return first atomic bucket in this collection, which is more conservative
    129      * than {@link #mStartMillis}.
    130      */
    131     public long getFirstAtomicBucketMillis() {
    132         if (mStartMillis == Long.MAX_VALUE) {
    133             return Long.MAX_VALUE;
    134         } else {
    135             return mStartMillis + mBucketDuration;
    136         }
    137     }
    138 
    139     public long getEndMillis() {
    140         return mEndMillis;
    141     }
    142 
    143     public long getTotalBytes() {
    144         return mTotalBytes;
    145     }
    146 
    147     public boolean isDirty() {
    148         return mDirty;
    149     }
    150 
    151     public void clearDirty() {
    152         mDirty = false;
    153     }
    154 
    155     public boolean isEmpty() {
    156         return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
    157     }
    158 
    159     @VisibleForTesting
    160     public long roundUp(long time) {
    161         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
    162                 || time == SubscriptionPlan.TIME_UNKNOWN) {
    163             return time;
    164         } else {
    165             final long mod = time % mBucketDuration;
    166             if (mod > 0) {
    167                 time -= mod;
    168                 time += mBucketDuration;
    169             }
    170             return time;
    171         }
    172     }
    173 
    174     @VisibleForTesting
    175     public long roundDown(long time) {
    176         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
    177                 || time == SubscriptionPlan.TIME_UNKNOWN) {
    178             return time;
    179         } else {
    180             final long mod = time % mBucketDuration;
    181             if (mod > 0) {
    182                 time -= mod;
    183             }
    184             return time;
    185         }
    186     }
    187 
    188     /**
    189      * Safely multiple a value by a rational.
    190      * <p>
    191      * Internally it uses integer-based math whenever possible, but switches
    192      * over to double-based math if values would overflow.
    193      */
    194     @VisibleForTesting
    195     public static long multiplySafe(long value, long num, long den) {
    196         if (den == 0) den = 1;
    197         long x = value;
    198         long y = num;
    199 
    200         // Logic shamelessly borrowed from Math.multiplyExact()
    201         long r = x * y;
    202         long ax = Math.abs(x);
    203         long ay = Math.abs(y);
    204         if (((ax | ay) >>> 31 != 0)) {
    205             // Some bits greater than 2^31 that might cause overflow
    206             // Check the result using the divide operator
    207             // and check for the special case of Long.MIN_VALUE * -1
    208             if (((y != 0) && (r / y != x)) ||
    209                     (x == Long.MIN_VALUE && y == -1)) {
    210                 // Use double math to avoid overflowing
    211                 return (long) (((double) num / den) * value);
    212             }
    213         }
    214         return r / den;
    215     }
    216 
    217     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
    218         return getRelevantUids(accessLevel, Binder.getCallingUid());
    219     }
    220 
    221     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
    222                 final int callerUid) {
    223         IntArray uids = new IntArray();
    224         for (int i = 0; i < mStats.size(); i++) {
    225             final Key key = mStats.keyAt(i);
    226             if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) {
    227                 int j = uids.binarySearch(key.uid);
    228 
    229                 if (j < 0) {
    230                     j = ~j;
    231                     uids.add(j, key.uid);
    232                 }
    233             }
    234         }
    235         return uids.toArray();
    236     }
    237 
    238     /**
    239      * Combine all {@link NetworkStatsHistory} in this collection which match
    240      * the requested parameters.
    241      */
    242     public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan,
    243             int uid, int set, int tag, int fields, long start, long end,
    244             @NetworkStatsAccess.Level int accessLevel, int callerUid) {
    245         if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
    246             throw new SecurityException("Network stats history of uid " + uid
    247                     + " is forbidden for caller " + callerUid);
    248         }
    249 
    250         // 180 days of history should be enough for anyone; if we end up needing
    251         // more, we'll dynamically grow the history object.
    252         final int bucketEstimate = (int) MathUtils.constrain(((end - start) / mBucketDuration), 0,
    253                 (180 * DateUtils.DAY_IN_MILLIS) / mBucketDuration);
    254         final NetworkStatsHistory combined = new NetworkStatsHistory(
    255                 mBucketDuration, bucketEstimate, fields);
    256 
    257         // shortcut when we know stats will be empty
    258         if (start == end) return combined;
    259 
    260         // Figure out the window of time that we should be augmenting (if any)
    261         long augmentStart = SubscriptionPlan.TIME_UNKNOWN;
    262         long augmentEnd = (augmentPlan != null) ? augmentPlan.getDataUsageTime()
    263                 : SubscriptionPlan.TIME_UNKNOWN;
    264         // And if augmenting, we might need to collect more data to adjust with
    265         long collectStart = start;
    266         long collectEnd = end;
    267 
    268         if (augmentEnd != SubscriptionPlan.TIME_UNKNOWN) {
    269             final Iterator<Range<ZonedDateTime>> it = augmentPlan.cycleIterator();
    270             while (it.hasNext()) {
    271                 final Range<ZonedDateTime> cycle = it.next();
    272                 final long cycleStart = cycle.getLower().toInstant().toEpochMilli();
    273                 final long cycleEnd = cycle.getUpper().toInstant().toEpochMilli();
    274                 if (cycleStart <= augmentEnd && augmentEnd < cycleEnd) {
    275                     augmentStart = cycleStart;
    276                     collectStart = Long.min(collectStart, augmentStart);
    277                     collectEnd = Long.max(collectEnd, augmentEnd);
    278                     break;
    279                 }
    280             }
    281         }
    282 
    283         if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
    284             // Shrink augmentation window so we don't risk undercounting.
    285             augmentStart = roundUp(augmentStart);
    286             augmentEnd = roundDown(augmentEnd);
    287             // Grow collection window so we get all the stats needed.
    288             collectStart = roundDown(collectStart);
    289             collectEnd = roundUp(collectEnd);
    290         }
    291 
    292         for (int i = 0; i < mStats.size(); i++) {
    293             final Key key = mStats.keyAt(i);
    294             if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag
    295                     && templateMatches(template, key.ident)) {
    296                 final NetworkStatsHistory value = mStats.valueAt(i);
    297                 combined.recordHistory(value, collectStart, collectEnd);
    298             }
    299         }
    300 
    301         if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
    302             final NetworkStatsHistory.Entry entry = combined.getValues(
    303                     augmentStart, augmentEnd, null);
    304 
    305             // If we don't have any recorded data for this time period, give
    306             // ourselves something to scale with.
    307             if (entry.rxBytes == 0 || entry.txBytes == 0) {
    308                 combined.recordData(augmentStart, augmentEnd,
    309                         new NetworkStats.Entry(1, 0, 1, 0, 0));
    310                 combined.getValues(augmentStart, augmentEnd, entry);
    311             }
    312 
    313             final long rawBytes = entry.rxBytes + entry.txBytes;
    314             final long rawRxBytes = entry.rxBytes;
    315             final long rawTxBytes = entry.txBytes;
    316             final long targetBytes = augmentPlan.getDataUsageBytes();
    317             final long targetRxBytes = multiplySafe(targetBytes, rawRxBytes, rawBytes);
    318             final long targetTxBytes = multiplySafe(targetBytes, rawTxBytes, rawBytes);
    319 
    320             // Scale all matching buckets to reach anchor target
    321             final long beforeTotal = combined.getTotalBytes();
    322             for (int i = 0; i < combined.size(); i++) {
    323                 combined.getValues(i, entry);
    324                 if (entry.bucketStart >= augmentStart
    325                         && entry.bucketStart + entry.bucketDuration <= augmentEnd) {
    326                     entry.rxBytes = multiplySafe(targetRxBytes, entry.rxBytes, rawRxBytes);
    327                     entry.txBytes = multiplySafe(targetTxBytes, entry.txBytes, rawTxBytes);
    328                     // We purposefully clear out packet counters to indicate
    329                     // that this data has been augmented.
    330                     entry.rxPackets = 0;
    331                     entry.txPackets = 0;
    332                     combined.setValues(i, entry);
    333                 }
    334             }
    335 
    336             final long deltaTotal = combined.getTotalBytes() - beforeTotal;
    337             if (deltaTotal != 0) {
    338                 Slog.d(TAG, "Augmented network usage by " + deltaTotal + " bytes");
    339             }
    340 
    341             // Finally we can slice data as originally requested
    342             final NetworkStatsHistory sliced = new NetworkStatsHistory(
    343                     mBucketDuration, bucketEstimate, fields);
    344             sliced.recordHistory(combined, start, end);
    345             return sliced;
    346         } else {
    347             return combined;
    348         }
    349     }
    350 
    351     /**
    352      * Summarize all {@link NetworkStatsHistory} in this collection which match
    353      * the requested parameters.
    354      */
    355     public NetworkStats getSummary(NetworkTemplate template, long start, long end,
    356             @NetworkStatsAccess.Level int accessLevel, int callerUid) {
    357         final long now = System.currentTimeMillis();
    358 
    359         final NetworkStats stats = new NetworkStats(end - start, 24);
    360 
    361         // shortcut when we know stats will be empty
    362         if (start == end) return stats;
    363 
    364         final NetworkStats.Entry entry = new NetworkStats.Entry();
    365         NetworkStatsHistory.Entry historyEntry = null;
    366 
    367         for (int i = 0; i < mStats.size(); i++) {
    368             final Key key = mStats.keyAt(i);
    369             if (templateMatches(template, key.ident)
    370                     && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)
    371                     && key.set < NetworkStats.SET_DEBUG_START) {
    372                 final NetworkStatsHistory value = mStats.valueAt(i);
    373                 historyEntry = value.getValues(start, end, now, historyEntry);
    374 
    375                 entry.iface = IFACE_ALL;
    376                 entry.uid = key.uid;
    377                 entry.set = key.set;
    378                 entry.tag = key.tag;
    379                 entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork() ?
    380                         DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
    381                 entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO;
    382                 entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO;
    383                 entry.rxBytes = historyEntry.rxBytes;
    384                 entry.rxPackets = historyEntry.rxPackets;
    385                 entry.txBytes = historyEntry.txBytes;
    386                 entry.txPackets = historyEntry.txPackets;
    387                 entry.operations = historyEntry.operations;
    388 
    389                 if (!entry.isEmpty()) {
    390                     stats.combineValues(entry);
    391                 }
    392             }
    393         }
    394 
    395         return stats;
    396     }
    397 
    398     /**
    399      * Record given {@link android.net.NetworkStats.Entry} into this collection.
    400      */
    401     public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
    402             long end, NetworkStats.Entry entry) {
    403         final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag);
    404         history.recordData(start, end, entry);
    405         noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes);
    406     }
    407 
    408     /**
    409      * Record given {@link NetworkStatsHistory} into this collection.
    410      */
    411     private void recordHistory(Key key, NetworkStatsHistory history) {
    412         if (history.size() == 0) return;
    413         noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
    414 
    415         NetworkStatsHistory target = mStats.get(key);
    416         if (target == null) {
    417             target = new NetworkStatsHistory(history.getBucketDuration());
    418             mStats.put(key, target);
    419         }
    420         target.recordEntireHistory(history);
    421     }
    422 
    423     /**
    424      * Record all {@link NetworkStatsHistory} contained in the given collection
    425      * into this collection.
    426      */
    427     public void recordCollection(NetworkStatsCollection another) {
    428         for (int i = 0; i < another.mStats.size(); i++) {
    429             final Key key = another.mStats.keyAt(i);
    430             final NetworkStatsHistory value = another.mStats.valueAt(i);
    431             recordHistory(key, value);
    432         }
    433     }
    434 
    435     private NetworkStatsHistory findOrCreateHistory(
    436             NetworkIdentitySet ident, int uid, int set, int tag) {
    437         final Key key = new Key(ident, uid, set, tag);
    438         final NetworkStatsHistory existing = mStats.get(key);
    439 
    440         // update when no existing, or when bucket duration changed
    441         NetworkStatsHistory updated = null;
    442         if (existing == null) {
    443             updated = new NetworkStatsHistory(mBucketDuration, 10);
    444         } else if (existing.getBucketDuration() != mBucketDuration) {
    445             updated = new NetworkStatsHistory(existing, mBucketDuration);
    446         }
    447 
    448         if (updated != null) {
    449             mStats.put(key, updated);
    450             return updated;
    451         } else {
    452             return existing;
    453         }
    454     }
    455 
    456     @Override
    457     public void read(InputStream in) throws IOException {
    458         read(new DataInputStream(in));
    459     }
    460 
    461     public void read(DataInputStream in) throws IOException {
    462         // verify file magic header intact
    463         final int magic = in.readInt();
    464         if (magic != FILE_MAGIC) {
    465             throw new ProtocolException("unexpected magic: " + magic);
    466         }
    467 
    468         final int version = in.readInt();
    469         switch (version) {
    470             case VERSION_UNIFIED_INIT: {
    471                 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
    472                 final int identSize = in.readInt();
    473                 for (int i = 0; i < identSize; i++) {
    474                     final NetworkIdentitySet ident = new NetworkIdentitySet(in);
    475 
    476                     final int size = in.readInt();
    477                     for (int j = 0; j < size; j++) {
    478                         final int uid = in.readInt();
    479                         final int set = in.readInt();
    480                         final int tag = in.readInt();
    481 
    482                         final Key key = new Key(ident, uid, set, tag);
    483                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
    484                         recordHistory(key, history);
    485                     }
    486                 }
    487                 break;
    488             }
    489             default: {
    490                 throw new ProtocolException("unexpected version: " + version);
    491             }
    492         }
    493     }
    494 
    495     public void write(DataOutputStream out) throws IOException {
    496         // cluster key lists grouped by ident
    497         final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
    498         for (Key key : mStats.keySet()) {
    499             ArrayList<Key> keys = keysByIdent.get(key.ident);
    500             if (keys == null) {
    501                 keys = Lists.newArrayList();
    502                 keysByIdent.put(key.ident, keys);
    503             }
    504             keys.add(key);
    505         }
    506 
    507         out.writeInt(FILE_MAGIC);
    508         out.writeInt(VERSION_UNIFIED_INIT);
    509 
    510         out.writeInt(keysByIdent.size());
    511         for (NetworkIdentitySet ident : keysByIdent.keySet()) {
    512             final ArrayList<Key> keys = keysByIdent.get(ident);
    513             ident.writeToStream(out);
    514 
    515             out.writeInt(keys.size());
    516             for (Key key : keys) {
    517                 final NetworkStatsHistory history = mStats.get(key);
    518                 out.writeInt(key.uid);
    519                 out.writeInt(key.set);
    520                 out.writeInt(key.tag);
    521                 history.writeToStream(out);
    522             }
    523         }
    524 
    525         out.flush();
    526     }
    527 
    528     @Deprecated
    529     public void readLegacyNetwork(File file) throws IOException {
    530         final AtomicFile inputFile = new AtomicFile(file);
    531 
    532         DataInputStream in = null;
    533         try {
    534             in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
    535 
    536             // verify file magic header intact
    537             final int magic = in.readInt();
    538             if (magic != FILE_MAGIC) {
    539                 throw new ProtocolException("unexpected magic: " + magic);
    540             }
    541 
    542             final int version = in.readInt();
    543             switch (version) {
    544                 case VERSION_NETWORK_INIT: {
    545                     // network := size *(NetworkIdentitySet NetworkStatsHistory)
    546                     final int size = in.readInt();
    547                     for (int i = 0; i < size; i++) {
    548                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
    549                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
    550 
    551                         final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
    552                         recordHistory(key, history);
    553                     }
    554                     break;
    555                 }
    556                 default: {
    557                     throw new ProtocolException("unexpected version: " + version);
    558                 }
    559             }
    560         } catch (FileNotFoundException e) {
    561             // missing stats is okay, probably first boot
    562         } finally {
    563             IoUtils.closeQuietly(in);
    564         }
    565     }
    566 
    567     @Deprecated
    568     public void readLegacyUid(File file, boolean onlyTags) throws IOException {
    569         final AtomicFile inputFile = new AtomicFile(file);
    570 
    571         DataInputStream in = null;
    572         try {
    573             in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
    574 
    575             // verify file magic header intact
    576             final int magic = in.readInt();
    577             if (magic != FILE_MAGIC) {
    578                 throw new ProtocolException("unexpected magic: " + magic);
    579             }
    580 
    581             final int version = in.readInt();
    582             switch (version) {
    583                 case VERSION_UID_INIT: {
    584                     // uid := size *(UID NetworkStatsHistory)
    585 
    586                     // drop this data version, since we don't have a good
    587                     // mapping into NetworkIdentitySet.
    588                     break;
    589                 }
    590                 case VERSION_UID_WITH_IDENT: {
    591                     // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
    592 
    593                     // drop this data version, since this version only existed
    594                     // for a short time.
    595                     break;
    596                 }
    597                 case VERSION_UID_WITH_TAG:
    598                 case VERSION_UID_WITH_SET: {
    599                     // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
    600                     final int identSize = in.readInt();
    601                     for (int i = 0; i < identSize; i++) {
    602                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
    603 
    604                         final int size = in.readInt();
    605                         for (int j = 0; j < size; j++) {
    606                             final int uid = in.readInt();
    607                             final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
    608                                     : SET_DEFAULT;
    609                             final int tag = in.readInt();
    610 
    611                             final Key key = new Key(ident, uid, set, tag);
    612                             final NetworkStatsHistory history = new NetworkStatsHistory(in);
    613 
    614                             if ((tag == TAG_NONE) != onlyTags) {
    615                                 recordHistory(key, history);
    616                             }
    617                         }
    618                     }
    619                     break;
    620                 }
    621                 default: {
    622                     throw new ProtocolException("unexpected version: " + version);
    623                 }
    624             }
    625         } catch (FileNotFoundException e) {
    626             // missing stats is okay, probably first boot
    627         } finally {
    628             IoUtils.closeQuietly(in);
    629         }
    630     }
    631 
    632     /**
    633      * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
    634      * moving any {@link NetworkStats#TAG_NONE} series to
    635      * {@link TrafficStats#UID_REMOVED}.
    636      */
    637     public void removeUids(int[] uids) {
    638         final ArrayList<Key> knownKeys = Lists.newArrayList();
    639         knownKeys.addAll(mStats.keySet());
    640 
    641         // migrate all UID stats into special "removed" bucket
    642         for (Key key : knownKeys) {
    643             if (ArrayUtils.contains(uids, key.uid)) {
    644                 // only migrate combined TAG_NONE history
    645                 if (key.tag == TAG_NONE) {
    646                     final NetworkStatsHistory uidHistory = mStats.get(key);
    647                     final NetworkStatsHistory removedHistory = findOrCreateHistory(
    648                             key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
    649                     removedHistory.recordEntireHistory(uidHistory);
    650                 }
    651                 mStats.remove(key);
    652                 mDirty = true;
    653             }
    654         }
    655     }
    656 
    657     private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
    658         if (startMillis < mStartMillis) mStartMillis = startMillis;
    659         if (endMillis > mEndMillis) mEndMillis = endMillis;
    660         mTotalBytes += totalBytes;
    661         mDirty = true;
    662     }
    663 
    664     private int estimateBuckets() {
    665         return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5)
    666                 / mBucketDuration);
    667     }
    668 
    669     private ArrayList<Key> getSortedKeys() {
    670         final ArrayList<Key> keys = Lists.newArrayList();
    671         keys.addAll(mStats.keySet());
    672         Collections.sort(keys);
    673         return keys;
    674     }
    675 
    676     public void dump(IndentingPrintWriter pw) {
    677         for (Key key : getSortedKeys()) {
    678             pw.print("ident="); pw.print(key.ident.toString());
    679             pw.print(" uid="); pw.print(key.uid);
    680             pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
    681             pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
    682 
    683             final NetworkStatsHistory history = mStats.get(key);
    684             pw.increaseIndent();
    685             history.dump(pw, true);
    686             pw.decreaseIndent();
    687         }
    688     }
    689 
    690     public void writeToProto(ProtoOutputStream proto, long tag) {
    691         final long start = proto.start(tag);
    692 
    693         for (Key key : getSortedKeys()) {
    694             final long startStats = proto.start(NetworkStatsCollectionProto.STATS);
    695 
    696             // Key
    697             final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY);
    698             key.ident.writeToProto(proto, NetworkStatsCollectionKeyProto.IDENTITY);
    699             proto.write(NetworkStatsCollectionKeyProto.UID, key.uid);
    700             proto.write(NetworkStatsCollectionKeyProto.SET, key.set);
    701             proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag);
    702             proto.end(startKey);
    703 
    704             // Value
    705             final NetworkStatsHistory history = mStats.get(key);
    706             history.writeToProto(proto, NetworkStatsCollectionStatsProto.HISTORY);
    707             proto.end(startStats);
    708         }
    709 
    710         proto.end(start);
    711     }
    712 
    713     public void dumpCheckin(PrintWriter pw, long start, long end) {
    714         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell");
    715         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi");
    716         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth");
    717         dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt");
    718     }
    719 
    720     /**
    721      * Dump all contained stats that match requested parameters, but group
    722      * together all matching {@link NetworkTemplate} under a single prefix.
    723      */
    724     private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate,
    725             String groupPrefix) {
    726         final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>();
    727 
    728         // Walk through all history, grouping by matching network templates
    729         for (int i = 0; i < mStats.size(); i++) {
    730             final Key key = mStats.keyAt(i);
    731             final NetworkStatsHistory value = mStats.valueAt(i);
    732 
    733             if (!templateMatches(groupTemplate, key.ident)) continue;
    734             if (key.set >= NetworkStats.SET_DEBUG_START) continue;
    735 
    736             final Key groupKey = new Key(null, key.uid, key.set, key.tag);
    737             NetworkStatsHistory groupHistory = grouped.get(groupKey);
    738             if (groupHistory == null) {
    739                 groupHistory = new NetworkStatsHistory(value.getBucketDuration());
    740                 grouped.put(groupKey, groupHistory);
    741             }
    742             groupHistory.recordHistory(value, start, end);
    743         }
    744 
    745         for (int i = 0; i < grouped.size(); i++) {
    746             final Key key = grouped.keyAt(i);
    747             final NetworkStatsHistory value = grouped.valueAt(i);
    748 
    749             if (value.size() == 0) continue;
    750 
    751             pw.print("c,");
    752             pw.print(groupPrefix); pw.print(',');
    753             pw.print(key.uid); pw.print(',');
    754             pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(',');
    755             pw.print(key.tag);
    756             pw.println();
    757 
    758             value.dumpCheckin(pw);
    759         }
    760     }
    761 
    762     /**
    763      * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
    764      * in the given {@link NetworkIdentitySet}.
    765      */
    766     private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
    767         for (NetworkIdentity ident : identSet) {
    768             if (template.matches(ident)) {
    769                 return true;
    770             }
    771         }
    772         return false;
    773     }
    774 
    775     private static class Key implements Comparable<Key> {
    776         public final NetworkIdentitySet ident;
    777         public final int uid;
    778         public final int set;
    779         public final int tag;
    780 
    781         private final int hashCode;
    782 
    783         public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
    784             this.ident = ident;
    785             this.uid = uid;
    786             this.set = set;
    787             this.tag = tag;
    788             hashCode = Objects.hash(ident, uid, set, tag);
    789         }
    790 
    791         @Override
    792         public int hashCode() {
    793             return hashCode;
    794         }
    795 
    796         @Override
    797         public boolean equals(Object obj) {
    798             if (obj instanceof Key) {
    799                 final Key key = (Key) obj;
    800                 return uid == key.uid && set == key.set && tag == key.tag
    801                         && Objects.equals(ident, key.ident);
    802             }
    803             return false;
    804         }
    805 
    806         @Override
    807         public int compareTo(Key another) {
    808             int res = 0;
    809             if (ident != null && another.ident != null) {
    810                 res = ident.compareTo(another.ident);
    811             }
    812             if (res == 0) {
    813                 res = Integer.compare(uid, another.uid);
    814             }
    815             if (res == 0) {
    816                 res = Integer.compare(set, another.set);
    817             }
    818             if (res == 0) {
    819                 res = Integer.compare(tag, another.tag);
    820             }
    821             return res;
    822         }
    823     }
    824 }
    825