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