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