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