• 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 android.net;
18 
19 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
20 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
21 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
22 import static android.net.NetworkStats.IFACE_ALL;
23 import static android.net.NetworkStats.METERED_NO;
24 import static android.net.NetworkStats.METERED_YES;
25 import static android.net.NetworkStats.ROAMING_NO;
26 import static android.net.NetworkStats.ROAMING_YES;
27 import static android.net.NetworkStats.SET_ALL;
28 import static android.net.NetworkStats.SET_DEFAULT;
29 import static android.net.NetworkStats.TAG_NONE;
30 import static android.net.NetworkStats.UID_ALL;
31 import static android.net.NetworkTemplate.MATCH_BLUETOOTH;
32 import static android.net.NetworkTemplate.MATCH_ETHERNET;
33 import static android.net.NetworkTemplate.MATCH_MOBILE;
34 import static android.net.NetworkTemplate.MATCH_WIFI;
35 import static android.net.TrafficStats.UID_REMOVED;
36 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
37 
38 import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
39 
40 import android.annotation.NonNull;
41 import android.annotation.Nullable;
42 import android.annotation.SystemApi;
43 import android.net.NetworkStats.State;
44 import android.net.NetworkStatsHistory.Entry;
45 import android.os.Binder;
46 import android.service.NetworkStatsCollectionKeyProto;
47 import android.service.NetworkStatsCollectionProto;
48 import android.service.NetworkStatsCollectionStatsProto;
49 import android.telephony.SubscriptionPlan;
50 import android.text.format.DateUtils;
51 import android.util.ArrayMap;
52 import android.util.AtomicFile;
53 import android.util.IndentingPrintWriter;
54 import android.util.Log;
55 import android.util.Range;
56 import android.util.proto.ProtoOutputStream;
57 
58 import com.android.internal.annotations.VisibleForTesting;
59 import com.android.internal.util.FileRotator;
60 import com.android.net.module.util.CollectionUtils;
61 import com.android.net.module.util.NetworkStatsUtils;
62 
63 import libcore.io.IoUtils;
64 
65 import java.io.BufferedInputStream;
66 import java.io.DataInput;
67 import java.io.DataInputStream;
68 import java.io.DataOutput;
69 import java.io.DataOutputStream;
70 import java.io.File;
71 import java.io.FileNotFoundException;
72 import java.io.IOException;
73 import java.io.InputStream;
74 import java.io.OutputStream;
75 import java.io.PrintWriter;
76 import java.net.ProtocolException;
77 import java.time.ZonedDateTime;
78 import java.util.ArrayList;
79 import java.util.Collections;
80 import java.util.HashMap;
81 import java.util.Iterator;
82 import java.util.List;
83 import java.util.Map;
84 import java.util.Objects;
85 import java.util.Set;
86 
87 /**
88  * Collection of {@link NetworkStatsHistory}, stored based on combined key of
89  * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
90  *
91  * @hide
92  */
93 @SystemApi(client = MODULE_LIBRARIES)
94 public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
95     private static final String TAG = NetworkStatsCollection.class.getSimpleName();
96     /** File header magic number: "ANET" */
97     private static final int FILE_MAGIC = 0x414E4554;
98 
99     private static final int VERSION_NETWORK_INIT = 1;
100 
101     private static final int VERSION_UID_INIT = 1;
102     private static final int VERSION_UID_WITH_IDENT = 2;
103     private static final int VERSION_UID_WITH_TAG = 3;
104     private static final int VERSION_UID_WITH_SET = 4;
105 
106     private static final int VERSION_UNIFIED_INIT = 16;
107 
108     private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>();
109 
110     private final long mBucketDurationMillis;
111 
112     private long mStartMillis;
113     private long mEndMillis;
114     private long mTotalBytes;
115     private boolean mDirty;
116 
117     /**
118      * Construct a {@link NetworkStatsCollection} object.
119      *
120      * @param bucketDuration duration of the buckets in this object, in milliseconds.
121      * @hide
122      */
NetworkStatsCollection(long bucketDurationMillis)123     public NetworkStatsCollection(long bucketDurationMillis) {
124         mBucketDurationMillis = bucketDurationMillis;
125         reset();
126     }
127 
128     /** @hide */
clear()129     public void clear() {
130         reset();
131     }
132 
133     /** @hide */
reset()134     public void reset() {
135         mStats.clear();
136         mStartMillis = Long.MAX_VALUE;
137         mEndMillis = Long.MIN_VALUE;
138         mTotalBytes = 0;
139         mDirty = false;
140     }
141 
142     /** @hide */
getStartMillis()143     public long getStartMillis() {
144         return mStartMillis;
145     }
146 
147     /**
148      * Return first atomic bucket in this collection, which is more conservative
149      * than {@link #mStartMillis}.
150      * @hide
151      */
getFirstAtomicBucketMillis()152     public long getFirstAtomicBucketMillis() {
153         if (mStartMillis == Long.MAX_VALUE) {
154             return Long.MAX_VALUE;
155         } else {
156             return mStartMillis + mBucketDurationMillis;
157         }
158     }
159 
160     /** @hide */
getEndMillis()161     public long getEndMillis() {
162         return mEndMillis;
163     }
164 
165     /** @hide */
getTotalBytes()166     public long getTotalBytes() {
167         return mTotalBytes;
168     }
169 
170     /** @hide */
isDirty()171     public boolean isDirty() {
172         return mDirty;
173     }
174 
175     /** @hide */
clearDirty()176     public void clearDirty() {
177         mDirty = false;
178     }
179 
180     /** @hide */
isEmpty()181     public boolean isEmpty() {
182         return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
183     }
184 
185     /** @hide */
186     @VisibleForTesting
roundUp(long time)187     public long roundUp(long time) {
188         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
189                 || time == SubscriptionPlan.TIME_UNKNOWN) {
190             return time;
191         } else {
192             final long mod = time % mBucketDurationMillis;
193             if (mod > 0) {
194                 time -= mod;
195                 time += mBucketDurationMillis;
196             }
197             return time;
198         }
199     }
200 
201     /** @hide */
202     @VisibleForTesting
roundDown(long time)203     public long roundDown(long time) {
204         if (time == Long.MIN_VALUE || time == Long.MAX_VALUE
205                 || time == SubscriptionPlan.TIME_UNKNOWN) {
206             return time;
207         } else {
208             final long mod = time % mBucketDurationMillis;
209             if (mod > 0) {
210                 time -= mod;
211             }
212             return time;
213         }
214     }
215 
216     /** @hide */
getRelevantUids(@etworkStatsAccess.Level int accessLevel)217     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
218         return getRelevantUids(accessLevel, Binder.getCallingUid());
219     }
220 
221     /** @hide */
getRelevantUids(@etworkStatsAccess.Level int accessLevel, final int callerUid)222     public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel,
223                 final int callerUid) {
224         final ArrayList<Integer> uids = new ArrayList<>();
225         for (int i = 0; i < mStats.size(); i++) {
226             final Key key = mStats.keyAt(i);
227             if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) {
228                 int j = Collections.binarySearch(uids, new Integer(key.uid));
229 
230                 if (j < 0) {
231                     j = ~j;
232                     uids.add(j, key.uid);
233                 }
234             }
235         }
236         return CollectionUtils.toIntArray(uids);
237     }
238 
239     /**
240      * Combine all {@link NetworkStatsHistory} in this collection which match
241      * the requested parameters.
242      * @hide
243      */
getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan, int uid, int set, int tag, int fields, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid)244     public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan,
245             int uid, int set, int tag, int fields, long start, long end,
246             @NetworkStatsAccess.Level int accessLevel, int callerUid) {
247         if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
248             throw new SecurityException("Network stats history of uid " + uid
249                     + " is forbidden for caller " + callerUid);
250         }
251 
252         // 180 days of history should be enough for anyone; if we end up needing
253         // more, we'll dynamically grow the history object.
254         final int bucketEstimate = (int) NetworkStatsUtils.constrain(
255                 ((end - start) / mBucketDurationMillis), 0,
256                 (180 * DateUtils.DAY_IN_MILLIS) / mBucketDurationMillis);
257         final NetworkStatsHistory combined = new NetworkStatsHistory(
258                 mBucketDurationMillis, bucketEstimate, fields);
259 
260         // shortcut when we know stats will be empty
261         if (start == end) return combined;
262 
263         // Figure out the window of time that we should be augmenting (if any)
264         long augmentStart = SubscriptionPlan.TIME_UNKNOWN;
265         long augmentEnd = (augmentPlan != null) ? augmentPlan.getDataUsageTime()
266                 : SubscriptionPlan.TIME_UNKNOWN;
267         // And if augmenting, we might need to collect more data to adjust with
268         long collectStart = start;
269         long collectEnd = end;
270 
271         if (augmentEnd != SubscriptionPlan.TIME_UNKNOWN) {
272             final Iterator<Range<ZonedDateTime>> it = augmentPlan.cycleIterator();
273             while (it.hasNext()) {
274                 final Range<ZonedDateTime> cycle = it.next();
275                 final long cycleStart = cycle.getLower().toInstant().toEpochMilli();
276                 final long cycleEnd = cycle.getUpper().toInstant().toEpochMilli();
277                 if (cycleStart <= augmentEnd && augmentEnd < cycleEnd) {
278                     augmentStart = cycleStart;
279                     collectStart = Long.min(collectStart, augmentStart);
280                     collectEnd = Long.max(collectEnd, augmentEnd);
281                     break;
282                 }
283             }
284         }
285 
286         if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
287             // Shrink augmentation window so we don't risk undercounting.
288             augmentStart = roundUp(augmentStart);
289             augmentEnd = roundDown(augmentEnd);
290             // Grow collection window so we get all the stats needed.
291             collectStart = roundDown(collectStart);
292             collectEnd = roundUp(collectEnd);
293         }
294 
295         for (int i = 0; i < mStats.size(); i++) {
296             final Key key = mStats.keyAt(i);
297             if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag
298                     && templateMatches(template, key.ident)) {
299                 final NetworkStatsHistory value = mStats.valueAt(i);
300                 combined.recordHistory(value, collectStart, collectEnd);
301             }
302         }
303 
304         if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) {
305             final NetworkStatsHistory.Entry entry = combined.getValues(
306                     augmentStart, augmentEnd, null);
307 
308             // If we don't have any recorded data for this time period, give
309             // ourselves something to scale with.
310             if (entry.rxBytes == 0 || entry.txBytes == 0) {
311                 combined.recordData(augmentStart, augmentEnd,
312                         new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE,
313                         METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 0L, 1L, 0L, 0L));
314                 combined.getValues(augmentStart, augmentEnd, entry);
315             }
316 
317             final long rawBytes = (entry.rxBytes + entry.txBytes) == 0 ? 1 :
318                     (entry.rxBytes + entry.txBytes);
319             final long rawRxBytes = entry.rxBytes == 0 ? 1 : entry.rxBytes;
320             final long rawTxBytes = entry.txBytes == 0 ? 1 : entry.txBytes;
321             final long targetBytes = augmentPlan.getDataUsageBytes();
322 
323             final long targetRxBytes = multiplySafeByRational(targetBytes, rawRxBytes, rawBytes);
324             final long targetTxBytes = multiplySafeByRational(targetBytes, rawTxBytes, rawBytes);
325 
326 
327             // Scale all matching buckets to reach anchor target
328             final long beforeTotal = combined.getTotalBytes();
329             for (int i = 0; i < combined.size(); i++) {
330                 combined.getValues(i, entry);
331                 if (entry.bucketStart >= augmentStart
332                         && entry.bucketStart + entry.bucketDuration <= augmentEnd) {
333                     entry.rxBytes = multiplySafeByRational(
334                             targetRxBytes, entry.rxBytes, rawRxBytes);
335                     entry.txBytes = multiplySafeByRational(
336                             targetTxBytes, entry.txBytes, rawTxBytes);
337                     // We purposefully clear out packet counters to indicate
338                     // that this data has been augmented.
339                     entry.rxPackets = 0;
340                     entry.txPackets = 0;
341                     combined.setValues(i, entry);
342                 }
343             }
344 
345             final long deltaTotal = combined.getTotalBytes() - beforeTotal;
346             if (deltaTotal != 0) {
347                 Log.d(TAG, "Augmented network usage by " + deltaTotal + " bytes");
348             }
349 
350             // Finally we can slice data as originally requested
351             final NetworkStatsHistory sliced = new NetworkStatsHistory(
352                     mBucketDurationMillis, bucketEstimate, fields);
353             sliced.recordHistory(combined, start, end);
354             return sliced;
355         } else {
356             return combined;
357         }
358     }
359 
360     /**
361      * Summarize all {@link NetworkStatsHistory} in this collection which match
362      * the requested parameters across the requested range.
363      *
364      * @param template - a predicate for filtering netstats.
365      * @param start - start of the range, timestamp in milliseconds since the epoch.
366      * @param end - end of the range, timestamp in milliseconds since the epoch.
367      * @param accessLevel - caller access level.
368      * @param callerUid - caller UID.
369      * @hide
370      */
getSummary(NetworkTemplate template, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid)371     public NetworkStats getSummary(NetworkTemplate template, long start, long end,
372             @NetworkStatsAccess.Level int accessLevel, int callerUid) {
373         final long now = System.currentTimeMillis();
374 
375         final NetworkStats stats = new NetworkStats(end - start, 24);
376 
377         // shortcut when we know stats will be empty
378         if (start == end) return stats;
379 
380         final NetworkStats.Entry entry = new NetworkStats.Entry();
381         NetworkStatsHistory.Entry historyEntry = null;
382 
383         for (int i = 0; i < mStats.size(); i++) {
384             final Key key = mStats.keyAt(i);
385             if (templateMatches(template, key.ident)
386                     && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)
387                     && key.set < NetworkStats.SET_DEBUG_START) {
388                 final NetworkStatsHistory value = mStats.valueAt(i);
389                 historyEntry = value.getValues(start, end, now, historyEntry);
390 
391                 entry.iface = IFACE_ALL;
392                 entry.uid = key.uid;
393                 entry.set = key.set;
394                 entry.tag = key.tag;
395                 entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork()
396                         ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
397                 entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO;
398                 entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO;
399                 entry.rxBytes = historyEntry.rxBytes;
400                 entry.rxPackets = historyEntry.rxPackets;
401                 entry.txBytes = historyEntry.txBytes;
402                 entry.txPackets = historyEntry.txPackets;
403                 entry.operations = historyEntry.operations;
404 
405                 if (!entry.isEmpty()) {
406                     stats.combineValues(entry);
407                 }
408             }
409         }
410 
411         return stats;
412     }
413 
414     /**
415      * Record given {@link android.net.NetworkStats.Entry} into this collection.
416      * @hide
417      */
recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, long end, NetworkStats.Entry entry)418     public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
419             long end, NetworkStats.Entry entry) {
420         final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag);
421         history.recordData(start, end, entry);
422         noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes);
423     }
424 
425     /**
426      * Record given {@link NetworkStatsHistory} into this collection.
427      *
428      * @hide
429      */
recordHistory(@onNull Key key, @NonNull NetworkStatsHistory history)430     public void recordHistory(@NonNull Key key, @NonNull NetworkStatsHistory history) {
431         Objects.requireNonNull(key);
432         Objects.requireNonNull(history);
433         if (history.size() == 0) return;
434         noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
435 
436         NetworkStatsHistory target = mStats.get(key);
437         if (target == null) {
438             target = new NetworkStatsHistory(history.getBucketDuration());
439             mStats.put(key, target);
440         }
441         target.recordEntireHistory(history);
442     }
443 
444     /**
445      * Record all {@link NetworkStatsHistory} contained in the given collection
446      * into this collection.
447      *
448      * @hide
449      */
recordCollection(@onNull NetworkStatsCollection another)450     public void recordCollection(@NonNull NetworkStatsCollection another) {
451         Objects.requireNonNull(another);
452         for (int i = 0; i < another.mStats.size(); i++) {
453             final Key key = another.mStats.keyAt(i);
454             final NetworkStatsHistory value = another.mStats.valueAt(i);
455             recordHistory(key, value);
456         }
457     }
458 
findOrCreateHistory( NetworkIdentitySet ident, int uid, int set, int tag)459     private NetworkStatsHistory findOrCreateHistory(
460             NetworkIdentitySet ident, int uid, int set, int tag) {
461         final Key key = new Key(ident, uid, set, tag);
462         final NetworkStatsHistory existing = mStats.get(key);
463 
464         // update when no existing, or when bucket duration changed
465         NetworkStatsHistory updated = null;
466         if (existing == null) {
467             updated = new NetworkStatsHistory(mBucketDurationMillis, 10);
468         } else if (existing.getBucketDuration() != mBucketDurationMillis) {
469             updated = new NetworkStatsHistory(existing, mBucketDurationMillis);
470         }
471 
472         if (updated != null) {
473             mStats.put(key, updated);
474             return updated;
475         } else {
476             return existing;
477         }
478     }
479 
480     /** @hide */
481     @Override
read(InputStream in)482     public void read(InputStream in) throws IOException {
483         read((DataInput) new DataInputStream(in));
484     }
485 
read(DataInput in)486     private void read(DataInput in) throws IOException {
487         // verify file magic header intact
488         final int magic = in.readInt();
489         if (magic != FILE_MAGIC) {
490             throw new ProtocolException("unexpected magic: " + magic);
491         }
492 
493         final int version = in.readInt();
494         switch (version) {
495             case VERSION_UNIFIED_INIT: {
496                 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
497                 final int identSize = in.readInt();
498                 for (int i = 0; i < identSize; i++) {
499                     final NetworkIdentitySet ident = new NetworkIdentitySet(in);
500 
501                     final int size = in.readInt();
502                     for (int j = 0; j < size; j++) {
503                         final int uid = in.readInt();
504                         final int set = in.readInt();
505                         final int tag = in.readInt();
506 
507                         final Key key = new Key(ident, uid, set, tag);
508                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
509                         recordHistory(key, history);
510                     }
511                 }
512                 break;
513             }
514             default: {
515                 throw new ProtocolException("unexpected version: " + version);
516             }
517         }
518     }
519 
520     /** @hide */
521     @Override
write(OutputStream out)522     public void write(OutputStream out) throws IOException {
523         write((DataOutput) new DataOutputStream(out));
524         out.flush();
525     }
526 
write(DataOutput out)527     private void write(DataOutput out) throws IOException {
528         // cluster key lists grouped by ident
529         final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = new HashMap<>();
530         for (Key key : mStats.keySet()) {
531             ArrayList<Key> keys = keysByIdent.get(key.ident);
532             if (keys == null) {
533                 keys = new ArrayList<>();
534                 keysByIdent.put(key.ident, keys);
535             }
536             keys.add(key);
537         }
538 
539         out.writeInt(FILE_MAGIC);
540         out.writeInt(VERSION_UNIFIED_INIT);
541 
542         out.writeInt(keysByIdent.size());
543         for (NetworkIdentitySet ident : keysByIdent.keySet()) {
544             final ArrayList<Key> keys = keysByIdent.get(ident);
545             ident.writeToStream(out);
546 
547             out.writeInt(keys.size());
548             for (Key key : keys) {
549                 final NetworkStatsHistory history = mStats.get(key);
550                 out.writeInt(key.uid);
551                 out.writeInt(key.set);
552                 out.writeInt(key.tag);
553                 history.writeToStream(out);
554             }
555         }
556     }
557 
558     /**
559      * Read legacy network summary statistics file format into the collection,
560      * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
561      *
562      * @deprecated
563      * @hide
564      */
565     @Deprecated
readLegacyNetwork(File file)566     public void readLegacyNetwork(File file) throws IOException {
567         final AtomicFile inputFile = new AtomicFile(file);
568 
569         DataInputStream in = null;
570         try {
571             in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
572 
573             // verify file magic header intact
574             final int magic = in.readInt();
575             if (magic != FILE_MAGIC) {
576                 throw new ProtocolException("unexpected magic: " + magic);
577             }
578 
579             final int version = in.readInt();
580             switch (version) {
581                 case VERSION_NETWORK_INIT: {
582                     // network := size *(NetworkIdentitySet NetworkStatsHistory)
583                     final int size = in.readInt();
584                     for (int i = 0; i < size; i++) {
585                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
586                         final NetworkStatsHistory history = new NetworkStatsHistory(in);
587 
588                         final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
589                         recordHistory(key, history);
590                     }
591                     break;
592                 }
593                 default: {
594                     throw new ProtocolException("unexpected version: " + version);
595                 }
596             }
597         } catch (FileNotFoundException e) {
598             // missing stats is okay, probably first boot
599         } finally {
600             IoUtils.closeQuietly(in);
601         }
602     }
603 
604     /**
605      * Read legacy Uid statistics file format into the collection,
606      * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
607      *
608      * @deprecated
609      * @hide
610      */
611     @Deprecated
readLegacyUid(File file, boolean onlyTags)612     public void readLegacyUid(File file, boolean onlyTags) throws IOException {
613         final AtomicFile inputFile = new AtomicFile(file);
614 
615         DataInputStream in = null;
616         try {
617             in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
618 
619             // verify file magic header intact
620             final int magic = in.readInt();
621             if (magic != FILE_MAGIC) {
622                 throw new ProtocolException("unexpected magic: " + magic);
623             }
624 
625             final int version = in.readInt();
626             switch (version) {
627                 case VERSION_UID_INIT: {
628                     // uid := size *(UID NetworkStatsHistory)
629 
630                     // drop this data version, since we don't have a good
631                     // mapping into NetworkIdentitySet.
632                     break;
633                 }
634                 case VERSION_UID_WITH_IDENT: {
635                     // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
636 
637                     // drop this data version, since this version only existed
638                     // for a short time.
639                     break;
640                 }
641                 case VERSION_UID_WITH_TAG:
642                 case VERSION_UID_WITH_SET: {
643                     // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
644                     final int identSize = in.readInt();
645                     for (int i = 0; i < identSize; i++) {
646                         final NetworkIdentitySet ident = new NetworkIdentitySet(in);
647 
648                         final int size = in.readInt();
649                         for (int j = 0; j < size; j++) {
650                             final int uid = in.readInt();
651                             final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
652                                     : SET_DEFAULT;
653                             final int tag = in.readInt();
654 
655                             final Key key = new Key(ident, uid, set, tag);
656                             final NetworkStatsHistory history = new NetworkStatsHistory(in);
657 
658                             if ((tag == TAG_NONE) != onlyTags) {
659                                 recordHistory(key, history);
660                             }
661                         }
662                     }
663                     break;
664                 }
665                 default: {
666                     throw new ProtocolException("unexpected version: " + version);
667                 }
668             }
669         } catch (FileNotFoundException e) {
670             // missing stats is okay, probably first boot
671         } finally {
672             IoUtils.closeQuietly(in);
673         }
674     }
675 
676     /**
677      * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
678      * moving any {@link NetworkStats#TAG_NONE} series to
679      * {@link TrafficStats#UID_REMOVED}.
680      * @hide
681      */
removeUids(int[] uids)682     public void removeUids(int[] uids) {
683         final ArrayList<Key> knownKeys = new ArrayList<>();
684         knownKeys.addAll(mStats.keySet());
685 
686         // migrate all UID stats into special "removed" bucket
687         for (Key key : knownKeys) {
688             if (CollectionUtils.contains(uids, key.uid)) {
689                 // only migrate combined TAG_NONE history
690                 if (key.tag == TAG_NONE) {
691                     final NetworkStatsHistory uidHistory = mStats.get(key);
692                     final NetworkStatsHistory removedHistory = findOrCreateHistory(
693                             key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
694                     removedHistory.recordEntireHistory(uidHistory);
695                 }
696                 mStats.remove(key);
697                 mDirty = true;
698             }
699         }
700     }
701 
702     /**
703      * Remove histories which contains or is before the cutoff timestamp.
704      * @hide
705      */
removeHistoryBefore(long cutoffMillis)706     public void removeHistoryBefore(long cutoffMillis) {
707         final ArrayList<Key> knownKeys = new ArrayList<>();
708         knownKeys.addAll(mStats.keySet());
709 
710         for (Key key : knownKeys) {
711             final NetworkStatsHistory history = mStats.get(key);
712             if (history.getStart() > cutoffMillis) continue;
713 
714             history.removeBucketsStartingBefore(cutoffMillis);
715             if (history.size() == 0) {
716                 mStats.remove(key);
717             }
718             mDirty = true;
719         }
720     }
721 
noteRecordedHistory(long startMillis, long endMillis, long totalBytes)722     private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
723         if (startMillis < mStartMillis) mStartMillis = startMillis;
724         if (endMillis > mEndMillis) mEndMillis = endMillis;
725         mTotalBytes += totalBytes;
726         mDirty = true;
727     }
728 
estimateBuckets()729     private int estimateBuckets() {
730         return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5)
731                 / mBucketDurationMillis);
732     }
733 
getSortedKeys()734     private ArrayList<Key> getSortedKeys() {
735         final ArrayList<Key> keys = new ArrayList<>();
736         keys.addAll(mStats.keySet());
737         Collections.sort(keys, (left, right) -> Key.compare(left, right));
738         return keys;
739     }
740 
741     /** @hide */
dump(IndentingPrintWriter pw)742     public void dump(IndentingPrintWriter pw) {
743         for (Key key : getSortedKeys()) {
744             pw.print("ident="); pw.print(key.ident.toString());
745             pw.print(" uid="); pw.print(key.uid);
746             pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
747             pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
748 
749             final NetworkStatsHistory history = mStats.get(key);
750             pw.increaseIndent();
751             history.dump(pw, true);
752             pw.decreaseIndent();
753         }
754     }
755 
756     /** @hide */
dumpDebug(ProtoOutputStream proto, long tag)757     public void dumpDebug(ProtoOutputStream proto, long tag) {
758         final long start = proto.start(tag);
759 
760         for (Key key : getSortedKeys()) {
761             final long startStats = proto.start(NetworkStatsCollectionProto.STATS);
762 
763             // Key
764             final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY);
765             key.ident.dumpDebug(proto, NetworkStatsCollectionKeyProto.IDENTITY);
766             proto.write(NetworkStatsCollectionKeyProto.UID, key.uid);
767             proto.write(NetworkStatsCollectionKeyProto.SET, key.set);
768             proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag);
769             proto.end(startKey);
770 
771             // Value
772             final NetworkStatsHistory history = mStats.get(key);
773             history.dumpDebug(proto, NetworkStatsCollectionStatsProto.HISTORY);
774             proto.end(startStats);
775         }
776 
777         proto.end(start);
778     }
779 
780     /** @hide */
dumpCheckin(PrintWriter pw, long start, long end)781     public void dumpCheckin(PrintWriter pw, long start, long end) {
782         dumpCheckin(pw, start, end, new NetworkTemplate.Builder(MATCH_MOBILE)
783                 .setMeteredness(METERED_YES).build(), "cell");
784         dumpCheckin(pw, start, end, new NetworkTemplate.Builder(MATCH_WIFI).build(), "wifi");
785         dumpCheckin(pw, start, end, new NetworkTemplate.Builder(MATCH_ETHERNET).build(), "eth");
786         dumpCheckin(pw, start, end, new NetworkTemplate.Builder(MATCH_BLUETOOTH).build(), "bt");
787     }
788 
789     /**
790      * Dump all contained stats that match requested parameters, but group
791      * together all matching {@link NetworkTemplate} under a single prefix.
792      */
dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, String groupPrefix)793     private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate,
794             String groupPrefix) {
795         final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>();
796 
797         // Walk through all history, grouping by matching network templates
798         for (int i = 0; i < mStats.size(); i++) {
799             final Key key = mStats.keyAt(i);
800             final NetworkStatsHistory value = mStats.valueAt(i);
801 
802             if (!templateMatches(groupTemplate, key.ident)) continue;
803             if (key.set >= NetworkStats.SET_DEBUG_START) continue;
804 
805             final Key groupKey = new Key(new NetworkIdentitySet(), key.uid, key.set, key.tag);
806             NetworkStatsHistory groupHistory = grouped.get(groupKey);
807             if (groupHistory == null) {
808                 groupHistory = new NetworkStatsHistory(value.getBucketDuration());
809                 grouped.put(groupKey, groupHistory);
810             }
811             groupHistory.recordHistory(value, start, end);
812         }
813 
814         for (int i = 0; i < grouped.size(); i++) {
815             final Key key = grouped.keyAt(i);
816             final NetworkStatsHistory value = grouped.valueAt(i);
817 
818             if (value.size() == 0) continue;
819 
820             pw.print("c,");
821             pw.print(groupPrefix); pw.print(',');
822             pw.print(key.uid); pw.print(',');
823             pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(',');
824             pw.print(key.tag);
825             pw.println();
826 
827             value.dumpCheckin(pw);
828         }
829     }
830 
831     /**
832      * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
833      * in the given {@link NetworkIdentitySet}.
834      */
templateMatches(NetworkTemplate template, NetworkIdentitySet identSet)835     private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
836         for (NetworkIdentity ident : identSet) {
837             if (template.matches(ident)) {
838                 return true;
839             }
840         }
841         return false;
842     }
843 
844     /**
845      * Get the all historical stats of the collection {@link NetworkStatsCollection}.
846      *
847      * @return All {@link NetworkStatsHistory} in this collection.
848      */
849     @NonNull
getEntries()850     public Map<Key, NetworkStatsHistory> getEntries() {
851         return new ArrayMap(mStats);
852     }
853 
854     /**
855      * Builder class for {@link NetworkStatsCollection}.
856      */
857     public static final class Builder {
858         private final long mBucketDurationMillis;
859         private final ArrayMap<Key, NetworkStatsHistory> mEntries = new ArrayMap<>();
860 
861         /**
862          * Creates a new Builder with given bucket duration.
863          *
864          * @param bucketDuration Duration of the buckets of the object, in milliseconds.
865          */
Builder(long bucketDurationMillis)866         public Builder(long bucketDurationMillis) {
867             mBucketDurationMillis = bucketDurationMillis;
868         }
869 
870         /**
871          * Add association of the history with the specified key in this map.
872          *
873          * @param key The object used to identify a network, see {@link Key}.
874          *            If history already exists for this key, then the passed-in history is appended
875          *            to the previously-passed in history. The caller must ensure that the history
876          *            passed-in timestamps are greater than all previously-passed-in timestamps.
877          * @param history {@link NetworkStatsHistory} instance associated to the given {@link Key}.
878          * @return The builder object.
879          */
880         @NonNull
addEntry(@onNull Key key, @NonNull NetworkStatsHistory history)881         public NetworkStatsCollection.Builder addEntry(@NonNull Key key,
882                 @NonNull NetworkStatsHistory history) {
883             Objects.requireNonNull(key);
884             Objects.requireNonNull(history);
885             final List<Entry> historyEntries = history.getEntries();
886             final NetworkStatsHistory existing = mEntries.get(key);
887 
888             final int size = historyEntries.size() + ((existing != null) ? existing.size() : 0);
889             final NetworkStatsHistory.Builder historyBuilder =
890                     new NetworkStatsHistory.Builder(mBucketDurationMillis, size);
891 
892             // TODO: this simply appends the entries to any entries that were already present in
893             // the builder, which requires the caller to pass in entries in order. We might be
894             // able to do better with something like recordHistory.
895             if (existing != null) {
896                 for (Entry entry : existing.getEntries()) {
897                     historyBuilder.addEntry(entry);
898                 }
899             }
900 
901             for (Entry entry : historyEntries) {
902                 historyBuilder.addEntry(entry);
903             }
904 
905             mEntries.put(key, historyBuilder.build());
906             return this;
907         }
908 
909         /**
910          * Builds the instance of the {@link NetworkStatsCollection}.
911          *
912          * @return the built instance of {@link NetworkStatsCollection}.
913          */
914         @NonNull
build()915         public NetworkStatsCollection build() {
916             final NetworkStatsCollection collection =
917                     new NetworkStatsCollection(mBucketDurationMillis);
918             for (int i = 0; i < mEntries.size(); i++) {
919                 collection.recordHistory(mEntries.keyAt(i), mEntries.valueAt(i));
920             }
921             return collection;
922         }
923     }
924 
925     /**
926      * the identifier that associate with the {@link NetworkStatsHistory} object to identify
927      * a certain record in the {@link NetworkStatsCollection} object.
928      */
929     public static final class Key {
930         /** @hide */
931         public final NetworkIdentitySet ident;
932         /** @hide */
933         public final int uid;
934         /** @hide */
935         public final int set;
936         /** @hide */
937         public final int tag;
938 
939         private final int mHashCode;
940 
941         /**
942          * Construct a {@link Key} object.
943          *
944          * @param ident a Set of {@link NetworkIdentity} that associated with the record.
945          * @param uid Uid of the record.
946          * @param set Set of the record, see {@code NetworkStats#SET_*}.
947          * @param tag Tag of the record, see {@link TrafficStats#setThreadStatsTag(int)}.
948          */
Key(@onNull Set<NetworkIdentity> ident, int uid, @State int set, int tag)949         public Key(@NonNull Set<NetworkIdentity> ident, int uid, @State int set, int tag) {
950             this(new NetworkIdentitySet(Objects.requireNonNull(ident)), uid, set, tag);
951         }
952 
953         /** @hide */
Key(@onNull NetworkIdentitySet ident, int uid, int set, int tag)954         public Key(@NonNull NetworkIdentitySet ident, int uid, int set, int tag) {
955             this.ident = Objects.requireNonNull(ident);
956             this.uid = uid;
957             this.set = set;
958             this.tag = tag;
959             mHashCode = Objects.hash(ident, uid, set, tag);
960         }
961 
962         @Override
hashCode()963         public int hashCode() {
964             return mHashCode;
965         }
966 
967         @Override
equals(@ullable Object obj)968         public boolean equals(@Nullable Object obj) {
969             if (obj instanceof Key) {
970                 final Key key = (Key) obj;
971                 return uid == key.uid && set == key.set && tag == key.tag
972                         && Objects.equals(ident, key.ident);
973             }
974             return false;
975         }
976 
977         /** @hide */
compare(@onNull Key left, @NonNull Key right)978         public static int compare(@NonNull Key left, @NonNull Key right) {
979             Objects.requireNonNull(left);
980             Objects.requireNonNull(right);
981             int res = 0;
982             if (left.ident != null && right.ident != null) {
983                 res = NetworkIdentitySet.compare(left.ident, right.ident);
984             }
985             if (res == 0) {
986                 res = Integer.compare(left.uid, right.uid);
987             }
988             if (res == 0) {
989                 res = Integer.compare(left.set, right.set);
990             }
991             if (res == 0) {
992                 res = Integer.compare(left.tag, right.tag);
993             }
994             return res;
995         }
996     }
997 }
998