1 /* 2 * Copyright (C) 2011 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 com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SystemApi; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.os.Build; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.os.Process; 30 import android.os.SystemClock; 31 import android.text.TextUtils; 32 import android.util.SparseBooleanArray; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.net.module.util.CollectionUtils; 36 37 import libcore.util.EmptyArray; 38 39 import java.io.CharArrayWriter; 40 import java.io.PrintWriter; 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.Arrays; 44 import java.util.HashSet; 45 import java.util.Iterator; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Objects; 49 import java.util.function.Predicate; 50 51 /** 52 * Collection of active network statistics. Can contain summary details across 53 * all interfaces, or details with per-UID granularity. Internally stores data 54 * as a large table, closely matching {@code /proc/} data format. This structure 55 * optimizes for rapid in-memory comparison, but consider using 56 * {@link NetworkStatsHistory} when persisting. 57 * 58 * @hide 59 */ 60 // @NotThreadSafe 61 @SystemApi 62 public final class NetworkStats implements Parcelable, Iterable<NetworkStats.Entry> { 63 private static final String TAG = "NetworkStats"; 64 65 /** 66 * {@link #iface} value when interface details unavailable. 67 * @hide 68 */ 69 @Nullable public static final String IFACE_ALL = null; 70 71 /** 72 * Virtual network interface for video telephony. This is for VT data usage counting 73 * purpose. 74 */ 75 public static final String IFACE_VT = "vt_data0"; 76 77 /** {@link #uid} value when UID details unavailable. */ 78 public static final int UID_ALL = -1; 79 /** Special UID value for data usage by tethering. */ 80 public static final int UID_TETHERING = -5; 81 82 /** 83 * {@link #tag} value matching any tag. 84 * @hide 85 */ 86 // TODO: Rename TAG_ALL to TAG_ANY. 87 public static final int TAG_ALL = -1; 88 /** {@link #set} value for all sets combined, not including debug sets. */ 89 public static final int SET_ALL = -1; 90 /** {@link #set} value where background data is accounted. */ 91 public static final int SET_DEFAULT = 0; 92 /** {@link #set} value where foreground data is accounted. */ 93 public static final int SET_FOREGROUND = 1; 94 /** 95 * All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. 96 * @hide 97 */ 98 public static final int SET_DEBUG_START = 1000; 99 /** 100 * Debug {@link #set} value when the VPN stats are moved in. 101 * @hide 102 */ 103 public static final int SET_DBG_VPN_IN = 1001; 104 /** 105 * Debug {@link #set} value when the VPN stats are moved out of a vpn UID. 106 * @hide 107 */ 108 public static final int SET_DBG_VPN_OUT = 1002; 109 110 /** @hide */ 111 @Retention(RetentionPolicy.SOURCE) 112 @IntDef(prefix = { "SET_" }, value = { 113 SET_ALL, 114 SET_DEFAULT, 115 SET_FOREGROUND, 116 }) 117 public @interface State { 118 } 119 120 /** 121 * Include all interfaces when filtering 122 * @hide 123 */ 124 public @Nullable static final String[] INTERFACES_ALL = null; 125 126 /** {@link #tag} value for total data across all tags. */ 127 public static final int TAG_NONE = 0; 128 129 /** {@link #metered} value to account for all metered states. */ 130 public static final int METERED_ALL = -1; 131 /** {@link #metered} value where native, unmetered data is accounted. */ 132 public static final int METERED_NO = 0; 133 /** {@link #metered} value where metered data is accounted. */ 134 public static final int METERED_YES = 1; 135 136 /** @hide */ 137 @Retention(RetentionPolicy.SOURCE) 138 @IntDef(prefix = { "METERED_" }, value = { 139 METERED_ALL, 140 METERED_NO, 141 METERED_YES 142 }) 143 public @interface Meteredness { 144 } 145 146 147 /** {@link #roaming} value to account for all roaming states. */ 148 public static final int ROAMING_ALL = -1; 149 /** {@link #roaming} value where native, non-roaming data is accounted. */ 150 public static final int ROAMING_NO = 0; 151 /** {@link #roaming} value where roaming data is accounted. */ 152 public static final int ROAMING_YES = 1; 153 154 /** @hide */ 155 @Retention(RetentionPolicy.SOURCE) 156 @IntDef(prefix = { "ROAMING_" }, value = { 157 ROAMING_ALL, 158 ROAMING_NO, 159 ROAMING_YES 160 }) 161 public @interface Roaming { 162 } 163 164 /** {@link #onDefaultNetwork} value to account for all default network states. */ 165 public static final int DEFAULT_NETWORK_ALL = -1; 166 /** {@link #onDefaultNetwork} value to account for usage while not the default network. */ 167 public static final int DEFAULT_NETWORK_NO = 0; 168 /** {@link #onDefaultNetwork} value to account for usage while the default network. */ 169 public static final int DEFAULT_NETWORK_YES = 1; 170 171 /** @hide */ 172 @Retention(RetentionPolicy.SOURCE) 173 @IntDef(prefix = { "DEFAULT_NETWORK_" }, value = { 174 DEFAULT_NETWORK_ALL, 175 DEFAULT_NETWORK_NO, 176 DEFAULT_NETWORK_YES 177 }) 178 public @interface DefaultNetwork { 179 } 180 181 /** 182 * Denotes a request for stats at the interface level. 183 * @hide 184 */ 185 public static final int STATS_PER_IFACE = 0; 186 /** 187 * Denotes a request for stats at the interface and UID level. 188 * @hide 189 */ 190 public static final int STATS_PER_UID = 1; 191 192 /** @hide */ 193 @Retention(RetentionPolicy.SOURCE) 194 @IntDef(prefix = { "STATS_PER_" }, value = { 195 STATS_PER_IFACE, 196 STATS_PER_UID 197 }) 198 public @interface StatsType { 199 } 200 201 private static final String CLATD_INTERFACE_PREFIX = "v4-"; 202 // Delta between IPv4 header (20b) and IPv6 header (40b). 203 // Used for correct stats accounting on clatd interfaces. 204 private static final int IPV4V6_HEADER_DELTA = 20; 205 206 // TODO: move fields to "mVariable" notation 207 208 /** 209 * {@link SystemClock#elapsedRealtime()} timestamp in milliseconds when this data was 210 * generated. 211 * It's a timestamps delta when {@link #subtract()}, 212 * {@code INetworkStatsSession#getSummaryForAllUid()} methods are used. 213 */ 214 private long elapsedRealtime; 215 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 216 private int size; 217 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 218 private int capacity; 219 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 220 private String[] iface; 221 @UnsupportedAppUsage 222 private int[] uid; 223 @UnsupportedAppUsage 224 private int[] set; 225 @UnsupportedAppUsage 226 private int[] tag; 227 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 228 private int[] metered; 229 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 230 private int[] roaming; 231 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 232 private int[] defaultNetwork; 233 @UnsupportedAppUsage 234 private long[] rxBytes; 235 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 236 private long[] rxPackets; 237 @UnsupportedAppUsage 238 private long[] txBytes; 239 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 240 private long[] txPackets; 241 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 242 private long[] operations; 243 244 /** 245 * Basic element of network statistics. Contains the number of packets and number of bytes 246 * transferred on both directions in a given set of conditions. See 247 * {@link Entry#Entry(String, int, int, int, int, int, int, long, long, long, long, long)}. 248 * 249 * @hide 250 */ 251 @SystemApi 252 public static class Entry { 253 /** @hide */ 254 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 255 public String iface; 256 /** @hide */ 257 @UnsupportedAppUsage 258 public int uid; 259 /** @hide */ 260 @UnsupportedAppUsage 261 public int set; 262 /** @hide */ 263 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 264 public int tag; 265 /** 266 * Note that this is only populated w/ the default value when read from /proc or written 267 * to disk. We merge in the correct value when reporting this value to clients of 268 * getSummary(). 269 * @hide 270 */ 271 public int metered; 272 /** 273 * Note that this is only populated w/ the default value when read from /proc or written 274 * to disk. We merge in the correct value when reporting this value to clients of 275 * getSummary(). 276 * @hide 277 */ 278 public int roaming; 279 /** 280 * Note that this is only populated w/ the default value when read from /proc or written 281 * to disk. We merge in the correct value when reporting this value to clients of 282 * getSummary(). 283 * @hide 284 */ 285 public int defaultNetwork; 286 /** @hide */ 287 @UnsupportedAppUsage 288 public long rxBytes; 289 /** @hide */ 290 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 291 public long rxPackets; 292 /** @hide */ 293 @UnsupportedAppUsage 294 public long txBytes; 295 /** @hide */ 296 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 297 public long txPackets; 298 /** @hide */ 299 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 300 public long operations; 301 302 /** @hide */ 303 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) Entry()304 public Entry() { 305 this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 306 DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L); 307 } 308 309 /** 310 * Construct a {@link Entry} object by giving statistics of packet and byte transferred on 311 * both direction, and associated with a set of given conditions. 312 * 313 * @param iface interface name of this {@link Entry}. Or null if not specified. 314 * @param uid uid of this {@link Entry}. {@link #UID_TETHERING} if this {@link Entry} is 315 * for tethering. Or {@link #UID_ALL} if this {@link NetworkStats} is only 316 * counting iface stats. 317 * @param set usage state of this {@link Entry}. 318 * @param tag tag of this {@link Entry}. 319 * @param metered metered state of this {@link Entry}. 320 * @param roaming roaming state of this {@link Entry}. 321 * @param defaultNetwork default network status of this {@link Entry}. 322 * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should 323 * represent the contents of IP packets, including IP headers. 324 * @param rxPackets Number of packets received for this {@link Entry}. Statistics should 325 * represent the contents of IP packets, including IP headers. 326 * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should 327 * represent the contents of IP packets, including IP headers. 328 * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should 329 * represent the contents of IP packets, including IP headers. 330 * @param operations count of network operations performed for this {@link Entry}. This can 331 * be used to derive bytes-per-operation. 332 */ Entry(@ullable String iface, int uid, @State int set, int tag, @Meteredness int metered, @Roaming int roaming, @DefaultNetwork int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)333 public Entry(@Nullable String iface, int uid, @State int set, int tag, 334 @Meteredness int metered, @Roaming int roaming, @DefaultNetwork int defaultNetwork, 335 long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { 336 this.iface = iface; 337 this.uid = uid; 338 this.set = set; 339 this.tag = tag; 340 this.metered = metered; 341 this.roaming = roaming; 342 this.defaultNetwork = defaultNetwork; 343 this.rxBytes = rxBytes; 344 this.rxPackets = rxPackets; 345 this.txBytes = txBytes; 346 this.txPackets = txPackets; 347 this.operations = operations; 348 } 349 350 /** @hide */ isNegative()351 public boolean isNegative() { 352 return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0; 353 } 354 355 /** @hide */ isEmpty()356 public boolean isEmpty() { 357 return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0 358 && operations == 0; 359 } 360 361 /** @hide */ add(Entry another)362 public void add(Entry another) { 363 this.rxBytes += another.rxBytes; 364 this.rxPackets += another.rxPackets; 365 this.txBytes += another.txBytes; 366 this.txPackets += another.txPackets; 367 this.operations += another.operations; 368 } 369 370 /** 371 * @return interface name of this entry. 372 * @hide 373 */ getIface()374 @Nullable public String getIface() { 375 return iface; 376 } 377 378 /** 379 * @return the uid of this entry. 380 */ getUid()381 public int getUid() { 382 return uid; 383 } 384 385 /** 386 * @return the set state of this entry. 387 */ getSet()388 @State public int getSet() { 389 return set; 390 } 391 392 /** 393 * @return the tag value of this entry. 394 */ getTag()395 public int getTag() { 396 return tag; 397 } 398 399 /** 400 * @return the metered state. 401 */ 402 @Meteredness getMetered()403 public int getMetered() { 404 return metered; 405 } 406 407 /** 408 * @return the roaming state. 409 */ 410 @Roaming getRoaming()411 public int getRoaming() { 412 return roaming; 413 } 414 415 /** 416 * @return the default network state. 417 */ 418 @DefaultNetwork getDefaultNetwork()419 public int getDefaultNetwork() { 420 return defaultNetwork; 421 } 422 423 /** 424 * @return the number of received bytes. 425 */ getRxBytes()426 public long getRxBytes() { 427 return rxBytes; 428 } 429 430 /** 431 * @return the number of received packets. 432 */ getRxPackets()433 public long getRxPackets() { 434 return rxPackets; 435 } 436 437 /** 438 * @return the number of transmitted bytes. 439 */ getTxBytes()440 public long getTxBytes() { 441 return txBytes; 442 } 443 444 /** 445 * @return the number of transmitted packets. 446 */ getTxPackets()447 public long getTxPackets() { 448 return txPackets; 449 } 450 451 /** 452 * @return the count of network operations performed for this entry. 453 */ getOperations()454 public long getOperations() { 455 return operations; 456 } 457 458 @Override toString()459 public String toString() { 460 final StringBuilder builder = new StringBuilder(); 461 builder.append("iface=").append(iface); 462 builder.append(" uid=").append(uid); 463 builder.append(" set=").append(setToString(set)); 464 builder.append(" tag=").append(tagToString(tag)); 465 builder.append(" metered=").append(meteredToString(metered)); 466 builder.append(" roaming=").append(roamingToString(roaming)); 467 builder.append(" defaultNetwork=").append(defaultNetworkToString(defaultNetwork)); 468 builder.append(" rxBytes=").append(rxBytes); 469 builder.append(" rxPackets=").append(rxPackets); 470 builder.append(" txBytes=").append(txBytes); 471 builder.append(" txPackets=").append(txPackets); 472 builder.append(" operations=").append(operations); 473 return builder.toString(); 474 } 475 476 /** @hide */ 477 @Override equals(@ullable Object o)478 public boolean equals(@Nullable Object o) { 479 if (o instanceof Entry) { 480 final Entry e = (Entry) o; 481 return uid == e.uid && set == e.set && tag == e.tag && metered == e.metered 482 && roaming == e.roaming && defaultNetwork == e.defaultNetwork 483 && rxBytes == e.rxBytes && rxPackets == e.rxPackets 484 && txBytes == e.txBytes && txPackets == e.txPackets 485 && operations == e.operations && TextUtils.equals(iface, e.iface); 486 } 487 return false; 488 } 489 490 /** @hide */ 491 @Override hashCode()492 public int hashCode() { 493 return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface); 494 } 495 } 496 NetworkStats(long elapsedRealtime, int initialSize)497 public NetworkStats(long elapsedRealtime, int initialSize) { 498 this.elapsedRealtime = elapsedRealtime; 499 this.size = 0; 500 if (initialSize > 0) { 501 this.capacity = initialSize; 502 this.iface = new String[initialSize]; 503 this.uid = new int[initialSize]; 504 this.set = new int[initialSize]; 505 this.tag = new int[initialSize]; 506 this.metered = new int[initialSize]; 507 this.roaming = new int[initialSize]; 508 this.defaultNetwork = new int[initialSize]; 509 this.rxBytes = new long[initialSize]; 510 this.rxPackets = new long[initialSize]; 511 this.txBytes = new long[initialSize]; 512 this.txPackets = new long[initialSize]; 513 this.operations = new long[initialSize]; 514 } else { 515 // Special case for use by NetworkStatsFactory to start out *really* empty. 516 clear(); 517 } 518 } 519 520 /** @hide */ 521 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) NetworkStats(Parcel parcel)522 public NetworkStats(Parcel parcel) { 523 elapsedRealtime = parcel.readLong(); 524 size = parcel.readInt(); 525 capacity = parcel.readInt(); 526 iface = parcel.createStringArray(); 527 uid = parcel.createIntArray(); 528 set = parcel.createIntArray(); 529 tag = parcel.createIntArray(); 530 metered = parcel.createIntArray(); 531 roaming = parcel.createIntArray(); 532 defaultNetwork = parcel.createIntArray(); 533 rxBytes = parcel.createLongArray(); 534 rxPackets = parcel.createLongArray(); 535 txBytes = parcel.createLongArray(); 536 txPackets = parcel.createLongArray(); 537 operations = parcel.createLongArray(); 538 } 539 540 @Override writeToParcel(@onNull Parcel dest, int flags)541 public void writeToParcel(@NonNull Parcel dest, int flags) { 542 dest.writeLong(elapsedRealtime); 543 dest.writeInt(size); 544 dest.writeInt(capacity); 545 dest.writeStringArray(iface); 546 dest.writeIntArray(uid); 547 dest.writeIntArray(set); 548 dest.writeIntArray(tag); 549 dest.writeIntArray(metered); 550 dest.writeIntArray(roaming); 551 dest.writeIntArray(defaultNetwork); 552 dest.writeLongArray(rxBytes); 553 dest.writeLongArray(rxPackets); 554 dest.writeLongArray(txBytes); 555 dest.writeLongArray(txPackets); 556 dest.writeLongArray(operations); 557 } 558 559 /** 560 * @hide 561 */ 562 @Override clone()563 public NetworkStats clone() { 564 final NetworkStats clone = new NetworkStats(elapsedRealtime, size); 565 NetworkStats.Entry entry = null; 566 for (int i = 0; i < size; i++) { 567 entry = getValues(i, entry); 568 clone.insertEntry(entry); 569 } 570 return clone; 571 } 572 573 /** 574 * Clear all data stored in this object. 575 * @hide 576 */ clear()577 public void clear() { 578 this.capacity = 0; 579 this.iface = EmptyArray.STRING; 580 this.uid = EmptyArray.INT; 581 this.set = EmptyArray.INT; 582 this.tag = EmptyArray.INT; 583 this.metered = EmptyArray.INT; 584 this.roaming = EmptyArray.INT; 585 this.defaultNetwork = EmptyArray.INT; 586 this.rxBytes = EmptyArray.LONG; 587 this.rxPackets = EmptyArray.LONG; 588 this.txBytes = EmptyArray.LONG; 589 this.txPackets = EmptyArray.LONG; 590 this.operations = EmptyArray.LONG; 591 } 592 593 /** @hide */ 594 @VisibleForTesting insertEntry( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets)595 public NetworkStats insertEntry( 596 String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { 597 return insertEntry( 598 iface, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 599 rxBytes, rxPackets, txBytes, txPackets, 0L); 600 } 601 602 /** @hide */ 603 @VisibleForTesting insertEntry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)604 public NetworkStats insertEntry(String iface, int uid, int set, int tag, long rxBytes, 605 long rxPackets, long txBytes, long txPackets, long operations) { 606 return insertEntry(new Entry( 607 iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 608 rxBytes, rxPackets, txBytes, txPackets, operations)); 609 } 610 611 /** @hide */ 612 @VisibleForTesting insertEntry(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)613 public NetworkStats insertEntry(String iface, int uid, int set, int tag, int metered, 614 int roaming, int defaultNetwork, long rxBytes, long rxPackets, long txBytes, 615 long txPackets, long operations) { 616 return insertEntry(new Entry( 617 iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets, 618 txBytes, txPackets, operations)); 619 } 620 621 /** 622 * Add new stats entry, copying from given {@link Entry}. The {@link Entry} 623 * object can be recycled across multiple calls. 624 * @hide 625 */ insertEntry(Entry entry)626 public NetworkStats insertEntry(Entry entry) { 627 if (size >= capacity) { 628 final int newLength = Math.max(size, 10) * 3 / 2; 629 iface = Arrays.copyOf(iface, newLength); 630 uid = Arrays.copyOf(uid, newLength); 631 set = Arrays.copyOf(set, newLength); 632 tag = Arrays.copyOf(tag, newLength); 633 metered = Arrays.copyOf(metered, newLength); 634 roaming = Arrays.copyOf(roaming, newLength); 635 defaultNetwork = Arrays.copyOf(defaultNetwork, newLength); 636 rxBytes = Arrays.copyOf(rxBytes, newLength); 637 rxPackets = Arrays.copyOf(rxPackets, newLength); 638 txBytes = Arrays.copyOf(txBytes, newLength); 639 txPackets = Arrays.copyOf(txPackets, newLength); 640 operations = Arrays.copyOf(operations, newLength); 641 capacity = newLength; 642 } 643 644 setValues(size, entry); 645 size++; 646 647 return this; 648 } 649 setValues(int i, Entry entry)650 private void setValues(int i, Entry entry) { 651 iface[i] = entry.iface; 652 uid[i] = entry.uid; 653 set[i] = entry.set; 654 tag[i] = entry.tag; 655 metered[i] = entry.metered; 656 roaming[i] = entry.roaming; 657 defaultNetwork[i] = entry.defaultNetwork; 658 rxBytes[i] = entry.rxBytes; 659 rxPackets[i] = entry.rxPackets; 660 txBytes[i] = entry.txBytes; 661 txPackets[i] = entry.txPackets; 662 operations[i] = entry.operations; 663 } 664 665 /** 666 * Iterate over Entry objects. 667 * 668 * Return an iterator of this object that will iterate through all contained Entry objects. 669 * 670 * This iterator does not support concurrent modification and makes no guarantee of fail-fast 671 * behavior. If any method that can mutate the contents of this object is called while 672 * iteration is in progress, either inside the loop or in another thread, then behavior is 673 * undefined. 674 * The remove() method is not implemented and will throw UnsupportedOperationException. 675 * @hide 676 */ 677 @SystemApi iterator()678 @NonNull public Iterator<Entry> iterator() { 679 return new Iterator<Entry>() { 680 int mIndex = 0; 681 682 @Override 683 public boolean hasNext() { 684 return mIndex < size; 685 } 686 687 @Override 688 public Entry next() { 689 return getValues(mIndex++, null); 690 } 691 }; 692 } 693 694 /** 695 * Return specific stats entry. 696 * @hide 697 */ 698 @UnsupportedAppUsage getValues(int i, @Nullable Entry recycle)699 public Entry getValues(int i, @Nullable Entry recycle) { 700 final Entry entry = recycle != null ? recycle : new Entry(); 701 entry.iface = iface[i]; 702 entry.uid = uid[i]; 703 entry.set = set[i]; 704 entry.tag = tag[i]; 705 entry.metered = metered[i]; 706 entry.roaming = roaming[i]; 707 entry.defaultNetwork = defaultNetwork[i]; 708 entry.rxBytes = rxBytes[i]; 709 entry.rxPackets = rxPackets[i]; 710 entry.txBytes = txBytes[i]; 711 entry.txPackets = txPackets[i]; 712 entry.operations = operations[i]; 713 return entry; 714 } 715 716 /** 717 * If @{code dest} is not equal to @{code src}, copy entry from index @{code src} to index 718 * @{code dest}. 719 */ maybeCopyEntry(int dest, int src)720 private void maybeCopyEntry(int dest, int src) { 721 if (dest == src) return; 722 iface[dest] = iface[src]; 723 uid[dest] = uid[src]; 724 set[dest] = set[src]; 725 tag[dest] = tag[src]; 726 metered[dest] = metered[src]; 727 roaming[dest] = roaming[src]; 728 defaultNetwork[dest] = defaultNetwork[src]; 729 rxBytes[dest] = rxBytes[src]; 730 rxPackets[dest] = rxPackets[src]; 731 txBytes[dest] = txBytes[src]; 732 txPackets[dest] = txPackets[src]; 733 operations[dest] = operations[src]; 734 } 735 736 /** @hide */ getElapsedRealtime()737 public long getElapsedRealtime() { 738 return elapsedRealtime; 739 } 740 741 /** @hide */ setElapsedRealtime(long time)742 public void setElapsedRealtime(long time) { 743 elapsedRealtime = time; 744 } 745 746 /** 747 * Return age of this {@link NetworkStats} object with respect to 748 * {@link SystemClock#elapsedRealtime()}. 749 * @hide 750 */ getElapsedRealtimeAge()751 public long getElapsedRealtimeAge() { 752 return SystemClock.elapsedRealtime() - elapsedRealtime; 753 } 754 755 /** @hide */ 756 @UnsupportedAppUsage size()757 public int size() { 758 return size; 759 } 760 761 /** @hide */ 762 @VisibleForTesting internalSize()763 public int internalSize() { 764 return capacity; 765 } 766 767 /** @hide */ 768 @Deprecated combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)769 public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, 770 long txBytes, long txPackets, long operations) { 771 return combineValues( 772 iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, 773 txPackets, operations); 774 } 775 776 /** @hide */ combineValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)777 public NetworkStats combineValues(String iface, int uid, int set, int tag, 778 long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { 779 return combineValues(new Entry( 780 iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 781 rxBytes, rxPackets, txBytes, txPackets, operations)); 782 } 783 784 /** 785 * Combine given values with an existing row, or create a new row if 786 * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can 787 * also be used to subtract values from existing rows. This method mutates the referencing 788 * {@link NetworkStats} object. 789 * 790 * @param entry the {@link Entry} to combine. 791 * @return a reference to this mutated {@link NetworkStats} object. 792 * @hide 793 */ combineValues(@onNull Entry entry)794 public @NonNull NetworkStats combineValues(@NonNull Entry entry) { 795 final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered, 796 entry.roaming, entry.defaultNetwork); 797 if (i == -1) { 798 // only create new entry when positive contribution 799 insertEntry(entry); 800 } else { 801 rxBytes[i] += entry.rxBytes; 802 rxPackets[i] += entry.rxPackets; 803 txBytes[i] += entry.txBytes; 804 txPackets[i] += entry.txPackets; 805 operations[i] += entry.operations; 806 } 807 return this; 808 } 809 810 /** 811 * Add given values with an existing row, or create a new row if 812 * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can 813 * also be used to subtract values from existing rows. 814 * 815 * @param entry the {@link Entry} to add. 816 * @return a new constructed {@link NetworkStats} object that contains the result. 817 */ addEntry(@onNull Entry entry)818 public @NonNull NetworkStats addEntry(@NonNull Entry entry) { 819 return this.clone().combineValues(entry); 820 } 821 822 /** 823 * Add the given {@link NetworkStats} objects. 824 * 825 * @return the sum of two objects. 826 */ add(@onNull NetworkStats another)827 public @NonNull NetworkStats add(@NonNull NetworkStats another) { 828 final NetworkStats ret = this.clone(); 829 ret.combineAllValues(another); 830 return ret; 831 } 832 833 /** 834 * Combine all values from another {@link NetworkStats} into this object. 835 * @hide 836 */ combineAllValues(@onNull NetworkStats another)837 public void combineAllValues(@NonNull NetworkStats another) { 838 NetworkStats.Entry entry = null; 839 for (int i = 0; i < another.size; i++) { 840 entry = another.getValues(i, entry); 841 combineValues(entry); 842 } 843 } 844 845 /** 846 * Find first stats index that matches the requested parameters. 847 * @hide 848 */ findIndex(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork)849 public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming, 850 int defaultNetwork) { 851 for (int i = 0; i < size; i++) { 852 if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] 853 && metered == this.metered[i] && roaming == this.roaming[i] 854 && defaultNetwork == this.defaultNetwork[i] 855 && Objects.equals(iface, this.iface[i])) { 856 return i; 857 } 858 } 859 return -1; 860 } 861 862 /** 863 * Find first stats index that matches the requested parameters, starting 864 * search around the hinted index as an optimization. 865 * @hide 866 */ 867 @VisibleForTesting findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming, int defaultNetwork, int hintIndex)868 public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming, 869 int defaultNetwork, int hintIndex) { 870 for (int offset = 0; offset < size; offset++) { 871 final int halfOffset = offset / 2; 872 873 // search outwards from hint index, alternating forward and backward 874 final int i; 875 if (offset % 2 == 0) { 876 i = (hintIndex + halfOffset) % size; 877 } else { 878 i = (size + hintIndex - halfOffset - 1) % size; 879 } 880 881 if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] 882 && metered == this.metered[i] && roaming == this.roaming[i] 883 && defaultNetwork == this.defaultNetwork[i] 884 && Objects.equals(iface, this.iface[i])) { 885 return i; 886 } 887 } 888 return -1; 889 } 890 891 /** 892 * Splice in {@link #operations} from the given {@link NetworkStats} based 893 * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface}, 894 * since operation counts are at data layer. 895 * @hide 896 */ spliceOperationsFrom(NetworkStats stats)897 public void spliceOperationsFrom(NetworkStats stats) { 898 for (int i = 0; i < size; i++) { 899 final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i], 900 defaultNetwork[i]); 901 if (j == -1) { 902 operations[i] = 0; 903 } else { 904 operations[i] = stats.operations[j]; 905 } 906 } 907 } 908 909 /** 910 * Return list of unique interfaces known by this data structure. 911 * @hide 912 */ getUniqueIfaces()913 public String[] getUniqueIfaces() { 914 final HashSet<String> ifaces = new HashSet<String>(); 915 for (String iface : this.iface) { 916 if (iface != IFACE_ALL) { 917 ifaces.add(iface); 918 } 919 } 920 return ifaces.toArray(new String[ifaces.size()]); 921 } 922 923 /** 924 * Return list of unique UIDs known by this data structure. 925 * @hide 926 */ 927 @UnsupportedAppUsage getUniqueUids()928 public int[] getUniqueUids() { 929 final SparseBooleanArray uids = new SparseBooleanArray(); 930 for (int uid : this.uid) { 931 uids.put(uid, true); 932 } 933 934 final int size = uids.size(); 935 final int[] result = new int[size]; 936 for (int i = 0; i < size; i++) { 937 result[i] = uids.keyAt(i); 938 } 939 return result; 940 } 941 942 /** 943 * Return total bytes represented by this snapshot object, usually used when 944 * checking if a {@link #subtract(NetworkStats)} delta passes a threshold. 945 * @hide 946 */ 947 @UnsupportedAppUsage getTotalBytes()948 public long getTotalBytes() { 949 final Entry entry = getTotal(null); 950 return entry.rxBytes + entry.txBytes; 951 } 952 953 /** 954 * Return total of all fields represented by this snapshot object. 955 * @hide 956 */ 957 @UnsupportedAppUsage getTotal(Entry recycle)958 public Entry getTotal(Entry recycle) { 959 return getTotal(recycle, null, UID_ALL, false); 960 } 961 962 /** 963 * Return total of all fields represented by this snapshot object matching 964 * the requested {@link #uid}. 965 * @hide 966 */ 967 @UnsupportedAppUsage getTotal(Entry recycle, int limitUid)968 public Entry getTotal(Entry recycle, int limitUid) { 969 return getTotal(recycle, null, limitUid, false); 970 } 971 972 /** 973 * Return total of all fields represented by this snapshot object matching 974 * the requested {@link #iface}. 975 * @hide 976 */ getTotal(Entry recycle, HashSet<String> limitIface)977 public Entry getTotal(Entry recycle, HashSet<String> limitIface) { 978 return getTotal(recycle, limitIface, UID_ALL, false); 979 } 980 981 /** @hide */ 982 @UnsupportedAppUsage getTotalIncludingTags(Entry recycle)983 public Entry getTotalIncludingTags(Entry recycle) { 984 return getTotal(recycle, null, UID_ALL, true); 985 } 986 987 /** 988 * Return total of all fields represented by this snapshot object matching 989 * the requested {@link #iface} and {@link #uid}. 990 * 991 * @param limitIface Set of {@link #iface} to include in total; or {@code 992 * null} to include all ifaces. 993 */ getTotal( Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags)994 private Entry getTotal( 995 Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) { 996 final Entry entry = recycle != null ? recycle : new Entry(); 997 998 entry.iface = IFACE_ALL; 999 entry.uid = limitUid; 1000 entry.set = SET_ALL; 1001 entry.tag = TAG_NONE; 1002 entry.metered = METERED_ALL; 1003 entry.roaming = ROAMING_ALL; 1004 entry.defaultNetwork = DEFAULT_NETWORK_ALL; 1005 entry.rxBytes = 0; 1006 entry.rxPackets = 0; 1007 entry.txBytes = 0; 1008 entry.txPackets = 0; 1009 entry.operations = 0; 1010 1011 for (int i = 0; i < size; i++) { 1012 final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]); 1013 final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i])); 1014 1015 if (matchesUid && matchesIface) { 1016 // skip specific tags, since already counted in TAG_NONE 1017 if (tag[i] != TAG_NONE && !includeTags) continue; 1018 1019 entry.rxBytes += rxBytes[i]; 1020 entry.rxPackets += rxPackets[i]; 1021 entry.txBytes += txBytes[i]; 1022 entry.txPackets += txPackets[i]; 1023 entry.operations += operations[i]; 1024 } 1025 } 1026 return entry; 1027 } 1028 1029 /** 1030 * Fast path for battery stats. 1031 * @hide 1032 */ getTotalPackets()1033 public long getTotalPackets() { 1034 long total = 0; 1035 for (int i = size - 1; i >= 0; i--) { 1036 total += rxPackets[i] + txPackets[i]; 1037 } 1038 return total; 1039 } 1040 1041 /** 1042 * Subtract the given {@link NetworkStats}, effectively leaving the delta 1043 * between two snapshots in time. Assumes that statistics rows collect over 1044 * time, and that none of them have disappeared. This method does not mutate 1045 * the referencing object. 1046 * 1047 * @return the delta between two objects. 1048 */ subtract(@onNull NetworkStats right)1049 public @NonNull NetworkStats subtract(@NonNull NetworkStats right) { 1050 return subtract(this, right, null, null); 1051 } 1052 1053 /** 1054 * Subtract the two given {@link NetworkStats} objects, returning the delta 1055 * between two snapshots in time. Assumes that statistics rows collect over 1056 * time, and that none of them have disappeared. 1057 * <p> 1058 * If counters have rolled backwards, they are clamped to {@code 0} and 1059 * reported to the given {@link NonMonotonicObserver}. 1060 * @hide 1061 */ subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie)1062 public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, 1063 NonMonotonicObserver<C> observer, C cookie) { 1064 return subtract(left, right, observer, cookie, null); 1065 } 1066 1067 /** 1068 * Subtract the two given {@link NetworkStats} objects, returning the delta 1069 * between two snapshots in time. Assumes that statistics rows collect over 1070 * time, and that none of them have disappeared. 1071 * <p> 1072 * If counters have rolled backwards, they are clamped to {@code 0} and 1073 * reported to the given {@link NonMonotonicObserver}. 1074 * <p> 1075 * If <var>recycle</var> is supplied, this NetworkStats object will be 1076 * reused (and returned) as the result if it is large enough to contain 1077 * the data. 1078 * @hide 1079 */ subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle)1080 public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, 1081 NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) { 1082 long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime; 1083 if (deltaRealtime < 0) { 1084 if (observer != null) { 1085 observer.foundNonMonotonic(left, -1, right, -1, cookie); 1086 } 1087 deltaRealtime = 0; 1088 } 1089 1090 // result will have our rows, and elapsed time between snapshots 1091 final Entry entry = new Entry(); 1092 final NetworkStats result; 1093 if (recycle != null && recycle.capacity >= left.size) { 1094 result = recycle; 1095 result.size = 0; 1096 result.elapsedRealtime = deltaRealtime; 1097 } else { 1098 result = new NetworkStats(deltaRealtime, left.size); 1099 } 1100 for (int i = 0; i < left.size; i++) { 1101 entry.iface = left.iface[i]; 1102 entry.uid = left.uid[i]; 1103 entry.set = left.set[i]; 1104 entry.tag = left.tag[i]; 1105 entry.metered = left.metered[i]; 1106 entry.roaming = left.roaming[i]; 1107 entry.defaultNetwork = left.defaultNetwork[i]; 1108 entry.rxBytes = left.rxBytes[i]; 1109 entry.rxPackets = left.rxPackets[i]; 1110 entry.txBytes = left.txBytes[i]; 1111 entry.txPackets = left.txPackets[i]; 1112 entry.operations = left.operations[i]; 1113 1114 // find remote row that matches, and subtract 1115 final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, 1116 entry.metered, entry.roaming, entry.defaultNetwork, i); 1117 if (j != -1) { 1118 // Found matching row, subtract remote value. 1119 entry.rxBytes -= right.rxBytes[j]; 1120 entry.rxPackets -= right.rxPackets[j]; 1121 entry.txBytes -= right.txBytes[j]; 1122 entry.txPackets -= right.txPackets[j]; 1123 entry.operations -= right.operations[j]; 1124 } 1125 1126 if (entry.isNegative()) { 1127 if (observer != null) { 1128 observer.foundNonMonotonic(left, i, right, j, cookie); 1129 } 1130 entry.rxBytes = Math.max(entry.rxBytes, 0); 1131 entry.rxPackets = Math.max(entry.rxPackets, 0); 1132 entry.txBytes = Math.max(entry.txBytes, 0); 1133 entry.txPackets = Math.max(entry.txPackets, 0); 1134 entry.operations = Math.max(entry.operations, 0); 1135 } 1136 1137 result.insertEntry(entry); 1138 } 1139 1140 return result; 1141 } 1142 1143 /** 1144 * Calculate and apply adjustments to captured statistics for 464xlat traffic. 1145 * 1146 * <p>This mutates stacked traffic stats, to account for IPv4/IPv6 header size difference. 1147 * 1148 * <p>UID stats, which are only accounted on the stacked interface, need to be increased 1149 * by 20 bytes/packet to account for translation overhead. 1150 * 1151 * <p>The potential additional overhead of 8 bytes/packet for ip fragments is ignored. 1152 * 1153 * <p>Interface stats need to sum traffic on both stacked and base interface because: 1154 * - eBPF offloaded packets appear only on the stacked interface 1155 * - Non-offloaded ingress packets appear only on the stacked interface 1156 * (due to iptables raw PREROUTING drop rules) 1157 * - Non-offloaded egress packets appear only on the stacked interface 1158 * (due to ignoring traffic from clat daemon by uid match) 1159 * (and of course the 20 bytes/packet overhead needs to be applied to stacked interface stats) 1160 * 1161 * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only 1162 * {@code ConcurrentHashMap} 1163 * @param baseTraffic Traffic on the base interfaces. Will be mutated. 1164 * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated. 1165 * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. 1166 * @hide 1167 */ apply464xlatAdjustments(NetworkStats baseTraffic, NetworkStats stackedTraffic, Map<String, String> stackedIfaces)1168 public static void apply464xlatAdjustments(NetworkStats baseTraffic, 1169 NetworkStats stackedTraffic, Map<String, String> stackedIfaces) { 1170 // For recycling 1171 Entry entry = null; 1172 for (int i = 0; i < stackedTraffic.size; i++) { 1173 entry = stackedTraffic.getValues(i, entry); 1174 if (entry == null) continue; 1175 if (entry.iface == null) continue; 1176 if (!entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) continue; 1177 1178 // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet 1179 // sent on the stacked interface with prefix "v4-" and drops the IPv6 header size after 1180 // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes 1181 // difference for all packets (http://b/12249687, http:/b/33681750). 1182 // 1183 // Note: this doesn't account for LRO/GRO/GSO/TSO (ie. >mtu) traffic correctly, nor 1184 // does it correctly account for the 8 extra bytes in the IPv6 fragmentation header. 1185 // 1186 // While the ebpf code path does try to simulate proper post segmentation packet 1187 // counts, we have nothing of the sort of xt_qtaguid stats. 1188 entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA; 1189 entry.txBytes += entry.txPackets * IPV4V6_HEADER_DELTA; 1190 stackedTraffic.setValues(i, entry); 1191 } 1192 } 1193 1194 /** 1195 * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice. 1196 * 1197 * <p>This mutates the object this method is called on. Equivalent to calling 1198 * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as 1199 * base and stacked traffic. 1200 * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. 1201 * @hide 1202 */ apply464xlatAdjustments(Map<String, String> stackedIfaces)1203 public void apply464xlatAdjustments(Map<String, String> stackedIfaces) { 1204 apply464xlatAdjustments(this, this, stackedIfaces); 1205 } 1206 1207 /** 1208 * Return total statistics grouped by {@link #iface}; doesn't mutate the 1209 * original structure. 1210 * @hide 1211 */ groupedByIface()1212 public NetworkStats groupedByIface() { 1213 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 1214 1215 final Entry entry = new Entry(); 1216 entry.uid = UID_ALL; 1217 entry.set = SET_ALL; 1218 entry.tag = TAG_NONE; 1219 entry.metered = METERED_ALL; 1220 entry.roaming = ROAMING_ALL; 1221 entry.defaultNetwork = DEFAULT_NETWORK_ALL; 1222 entry.operations = 0L; 1223 1224 for (int i = 0; i < size; i++) { 1225 // skip specific tags, since already counted in TAG_NONE 1226 if (tag[i] != TAG_NONE) continue; 1227 1228 entry.iface = iface[i]; 1229 entry.rxBytes = rxBytes[i]; 1230 entry.rxPackets = rxPackets[i]; 1231 entry.txBytes = txBytes[i]; 1232 entry.txPackets = txPackets[i]; 1233 stats.combineValues(entry); 1234 } 1235 1236 return stats; 1237 } 1238 1239 /** 1240 * Return total statistics grouped by {@link #uid}; doesn't mutate the 1241 * original structure. 1242 * @hide 1243 */ groupedByUid()1244 public NetworkStats groupedByUid() { 1245 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 1246 1247 final Entry entry = new Entry(); 1248 entry.iface = IFACE_ALL; 1249 entry.set = SET_ALL; 1250 entry.tag = TAG_NONE; 1251 entry.metered = METERED_ALL; 1252 entry.roaming = ROAMING_ALL; 1253 entry.defaultNetwork = DEFAULT_NETWORK_ALL; 1254 1255 for (int i = 0; i < size; i++) { 1256 // skip specific tags, since already counted in TAG_NONE 1257 if (tag[i] != TAG_NONE) continue; 1258 1259 entry.uid = uid[i]; 1260 entry.rxBytes = rxBytes[i]; 1261 entry.rxPackets = rxPackets[i]; 1262 entry.txBytes = txBytes[i]; 1263 entry.txPackets = txPackets[i]; 1264 entry.operations = operations[i]; 1265 stats.combineValues(entry); 1266 } 1267 1268 return stats; 1269 } 1270 1271 /** 1272 * Remove all rows that match one of specified UIDs. 1273 * This mutates the original structure in place. 1274 * @hide 1275 */ removeUids(int[] uids)1276 public void removeUids(int[] uids) { 1277 filter(e -> !CollectionUtils.contains(uids, e.uid)); 1278 } 1279 1280 /** 1281 * Remove all rows that match one of specified UIDs. 1282 * @return the result object. 1283 * @hide 1284 */ 1285 @NonNull removeEmptyEntries()1286 public NetworkStats removeEmptyEntries() { 1287 final NetworkStats ret = this.clone(); 1288 ret.filter(e -> e.rxBytes != 0 || e.rxPackets != 0 || e.txBytes != 0 || e.txPackets != 0 1289 || e.operations != 0); 1290 return ret; 1291 } 1292 1293 /** 1294 * Removes the interface name from all entries. 1295 * This mutates the original structure in place. 1296 * @hide 1297 */ clearInterfaces()1298 public void clearInterfaces() { 1299 for (int i = 0; i < size; i++) { 1300 iface[i] = null; 1301 } 1302 } 1303 1304 /** 1305 * Only keep entries that match all specified filters. 1306 * 1307 * <p>This mutates the original structure in place. After this method is called, 1308 * size is the number of matching entries, and capacity is the previous capacity. 1309 * @param limitUid UID to filter for, or {@link #UID_ALL}. 1310 * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}. 1311 * @param limitTag Tag to filter for, or {@link #TAG_ALL}. 1312 * @hide 1313 */ filter(int limitUid, String[] limitIfaces, int limitTag)1314 public void filter(int limitUid, String[] limitIfaces, int limitTag) { 1315 if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) { 1316 return; 1317 } 1318 filter(e -> (limitUid == UID_ALL || limitUid == e.uid) 1319 && (limitTag == TAG_ALL || limitTag == e.tag) 1320 && (limitIfaces == INTERFACES_ALL 1321 || CollectionUtils.contains(limitIfaces, e.iface))); 1322 } 1323 1324 /** 1325 * Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}. 1326 * 1327 * <p>This mutates the original structure in place. 1328 * @hide 1329 */ filterDebugEntries()1330 public void filterDebugEntries() { 1331 filter(e -> e.set < SET_DEBUG_START); 1332 } 1333 filter(Predicate<Entry> predicate)1334 private void filter(Predicate<Entry> predicate) { 1335 Entry entry = new Entry(); 1336 int nextOutputEntry = 0; 1337 for (int i = 0; i < size; i++) { 1338 entry = getValues(i, entry); 1339 if (predicate.test(entry)) { 1340 if (nextOutputEntry != i) { 1341 setValues(nextOutputEntry, entry); 1342 } 1343 nextOutputEntry++; 1344 } 1345 } 1346 size = nextOutputEntry; 1347 } 1348 1349 /** @hide */ dump(String prefix, PrintWriter pw)1350 public void dump(String prefix, PrintWriter pw) { 1351 pw.print(prefix); 1352 pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); 1353 for (int i = 0; i < size; i++) { 1354 pw.print(prefix); 1355 pw.print(" ["); pw.print(i); pw.print("]"); 1356 pw.print(" iface="); pw.print(iface[i]); 1357 pw.print(" uid="); pw.print(uid[i]); 1358 pw.print(" set="); pw.print(setToString(set[i])); 1359 pw.print(" tag="); pw.print(tagToString(tag[i])); 1360 pw.print(" metered="); pw.print(meteredToString(metered[i])); 1361 pw.print(" roaming="); pw.print(roamingToString(roaming[i])); 1362 pw.print(" defaultNetwork="); pw.print(defaultNetworkToString(defaultNetwork[i])); 1363 pw.print(" rxBytes="); pw.print(rxBytes[i]); 1364 pw.print(" rxPackets="); pw.print(rxPackets[i]); 1365 pw.print(" txBytes="); pw.print(txBytes[i]); 1366 pw.print(" txPackets="); pw.print(txPackets[i]); 1367 pw.print(" operations="); pw.println(operations[i]); 1368 } 1369 } 1370 1371 /** 1372 * Return text description of {@link #set} value. 1373 * @hide 1374 */ setToString(int set)1375 public static String setToString(int set) { 1376 switch (set) { 1377 case SET_ALL: 1378 return "ALL"; 1379 case SET_DEFAULT: 1380 return "DEFAULT"; 1381 case SET_FOREGROUND: 1382 return "FOREGROUND"; 1383 case SET_DBG_VPN_IN: 1384 return "DBG_VPN_IN"; 1385 case SET_DBG_VPN_OUT: 1386 return "DBG_VPN_OUT"; 1387 default: 1388 return "UNKNOWN"; 1389 } 1390 } 1391 1392 /** 1393 * Return text description of {@link #set} value. 1394 * @hide 1395 */ setToCheckinString(int set)1396 public static String setToCheckinString(int set) { 1397 switch (set) { 1398 case SET_ALL: 1399 return "all"; 1400 case SET_DEFAULT: 1401 return "def"; 1402 case SET_FOREGROUND: 1403 return "fg"; 1404 case SET_DBG_VPN_IN: 1405 return "vpnin"; 1406 case SET_DBG_VPN_OUT: 1407 return "vpnout"; 1408 default: 1409 return "unk"; 1410 } 1411 } 1412 1413 /** 1414 * @return true if the querySet matches the dataSet. 1415 * @hide 1416 */ setMatches(int querySet, int dataSet)1417 public static boolean setMatches(int querySet, int dataSet) { 1418 if (querySet == dataSet) { 1419 return true; 1420 } 1421 // SET_ALL matches all non-debugging sets. 1422 return querySet == SET_ALL && dataSet < SET_DEBUG_START; 1423 } 1424 1425 /** 1426 * Return text description of {@link #tag} value. 1427 * @hide 1428 */ tagToString(int tag)1429 public static String tagToString(int tag) { 1430 return "0x" + Integer.toHexString(tag); 1431 } 1432 1433 /** 1434 * Return text description of {@link #metered} value. 1435 * @hide 1436 */ meteredToString(int metered)1437 public static String meteredToString(int metered) { 1438 switch (metered) { 1439 case METERED_ALL: 1440 return "ALL"; 1441 case METERED_NO: 1442 return "NO"; 1443 case METERED_YES: 1444 return "YES"; 1445 default: 1446 return "UNKNOWN"; 1447 } 1448 } 1449 1450 /** 1451 * Return text description of {@link #roaming} value. 1452 * @hide 1453 */ roamingToString(int roaming)1454 public static String roamingToString(int roaming) { 1455 switch (roaming) { 1456 case ROAMING_ALL: 1457 return "ALL"; 1458 case ROAMING_NO: 1459 return "NO"; 1460 case ROAMING_YES: 1461 return "YES"; 1462 default: 1463 return "UNKNOWN"; 1464 } 1465 } 1466 1467 /** 1468 * Return text description of {@link #defaultNetwork} value. 1469 * @hide 1470 */ defaultNetworkToString(int defaultNetwork)1471 public static String defaultNetworkToString(int defaultNetwork) { 1472 switch (defaultNetwork) { 1473 case DEFAULT_NETWORK_ALL: 1474 return "ALL"; 1475 case DEFAULT_NETWORK_NO: 1476 return "NO"; 1477 case DEFAULT_NETWORK_YES: 1478 return "YES"; 1479 default: 1480 return "UNKNOWN"; 1481 } 1482 } 1483 1484 /** @hide */ 1485 @Override toString()1486 public String toString() { 1487 final CharArrayWriter writer = new CharArrayWriter(); 1488 dump("", new PrintWriter(writer)); 1489 return writer.toString(); 1490 } 1491 1492 @Override describeContents()1493 public int describeContents() { 1494 return 0; 1495 } 1496 1497 public static final @NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { 1498 @Override 1499 public NetworkStats createFromParcel(Parcel in) { 1500 return new NetworkStats(in); 1501 } 1502 1503 @Override 1504 public NetworkStats[] newArray(int size) { 1505 return new NetworkStats[size]; 1506 } 1507 }; 1508 1509 /** @hide */ 1510 public interface NonMonotonicObserver<C> { foundNonMonotonic( NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie)1511 public void foundNonMonotonic( 1512 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); foundNonMonotonic( NetworkStats stats, int statsIndex, C cookie)1513 public void foundNonMonotonic( 1514 NetworkStats stats, int statsIndex, C cookie); 1515 } 1516 1517 /** 1518 * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface. 1519 * 1520 * <p>This method should only be called on delta NetworkStats. Do not call this method on a 1521 * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may change 1522 * over time. 1523 * 1524 * <p>This method performs adjustments for one active VPN package and one VPN iface at a time. 1525 * 1526 * @param tunUid uid of the VPN application 1527 * @param tunIface iface of the vpn tunnel 1528 * @param underlyingIfaces underlying network ifaces used by the VPN application 1529 * @hide 1530 */ migrateTun(int tunUid, @NonNull String tunIface, @NonNull List<String> underlyingIfaces)1531 public void migrateTun(int tunUid, @NonNull String tunIface, 1532 @NonNull List<String> underlyingIfaces) { 1533 // Combined usage by all apps using VPN. 1534 final Entry tunIfaceTotal = new Entry(); 1535 // Usage by VPN, grouped by its {@code underlyingIfaces}. 1536 final Entry[] perInterfaceTotal = new Entry[underlyingIfaces.size()]; 1537 // Usage by VPN, summed across all its {@code underlyingIfaces}. 1538 final Entry underlyingIfacesTotal = new Entry(); 1539 1540 for (int i = 0; i < perInterfaceTotal.length; i++) { 1541 perInterfaceTotal[i] = new Entry(); 1542 } 1543 1544 tunAdjustmentInit(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, perInterfaceTotal, 1545 underlyingIfacesTotal); 1546 1547 // If tunIface < underlyingIfacesTotal, it leaves the overhead traffic in the VPN app. 1548 // If tunIface > underlyingIfacesTotal, the VPN app doesn't get credit for data compression. 1549 // Negative stats should be avoided. 1550 final Entry[] moved = 1551 addTrafficToApplications(tunUid, tunIface, underlyingIfaces, tunIfaceTotal, 1552 perInterfaceTotal, underlyingIfacesTotal); 1553 deductTrafficFromVpnApp(tunUid, underlyingIfaces, moved); 1554 } 1555 1556 /** 1557 * Initializes the data used by the migrateTun() method. 1558 * 1559 * <p>This is the first pass iteration which does the following work: 1560 * 1561 * <ul> 1562 * <li>Adds up all the traffic through the tunUid's underlyingIfaces (both foreground and 1563 * background). 1564 * <li>Adds up all the traffic through tun0 excluding traffic from the vpn app itself. 1565 * </ul> 1566 * 1567 * @param tunUid uid of the VPN application 1568 * @param tunIface iface of the vpn tunnel 1569 * @param underlyingIfaces underlying network ifaces used by the VPN application 1570 * @param tunIfaceTotal output parameter; combined data usage by all apps using VPN 1571 * @param perInterfaceTotal output parameter; data usage by VPN app, grouped by its {@code 1572 * underlyingIfaces} 1573 * @param underlyingIfacesTotal output parameter; data usage by VPN, summed across all of its 1574 * {@code underlyingIfaces} 1575 */ tunAdjustmentInit(int tunUid, @NonNull String tunIface, @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal, @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal)1576 private void tunAdjustmentInit(int tunUid, @NonNull String tunIface, 1577 @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal, 1578 @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) { 1579 final Entry recycle = new Entry(); 1580 for (int i = 0; i < size; i++) { 1581 getValues(i, recycle); 1582 if (recycle.uid == UID_ALL) { 1583 throw new IllegalStateException( 1584 "Cannot adjust VPN accounting on an iface aggregated NetworkStats."); 1585 } 1586 if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) { 1587 throw new IllegalStateException( 1588 "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*"); 1589 } 1590 if (recycle.tag != TAG_NONE) { 1591 // TODO(b/123666283): Take all tags for tunUid into account. 1592 continue; 1593 } 1594 1595 if (tunUid == Process.SYSTEM_UID) { 1596 // Kernel-based VPN or VCN, traffic sent by apps on the VPN/VCN network 1597 // 1598 // Since the data is not UID-accounted on underlying networks, just use VPN/VCN 1599 // network usage as ground truth. Encrypted traffic on the underlying networks will 1600 // never be processed here because encrypted traffic on the underlying interfaces 1601 // is not present in UID stats, and this method is only called on UID stats. 1602 if (tunIface.equals(recycle.iface)) { 1603 tunIfaceTotal.add(recycle); 1604 underlyingIfacesTotal.add(recycle); 1605 1606 // In steady state, there should always be one network, but edge cases may 1607 // result in the network being null (network lost), and thus no underlying 1608 // ifaces is possible. 1609 if (perInterfaceTotal.length > 0) { 1610 // While platform VPNs and VCNs have exactly one underlying network, that 1611 // network may have multiple interfaces (eg for 464xlat). This layer does 1612 // not have the required information to identify which of the interfaces 1613 // were used. Select "any" of the interfaces. Since overhead is already 1614 // lost, this number is an approximation anyways. 1615 perInterfaceTotal[0].add(recycle); 1616 } 1617 } 1618 } else if (recycle.uid == tunUid) { 1619 // VpnService VPN, traffic sent by the VPN app over underlying networks 1620 for (int j = 0; j < underlyingIfaces.size(); j++) { 1621 if (Objects.equals(underlyingIfaces.get(j), recycle.iface)) { 1622 perInterfaceTotal[j].add(recycle); 1623 underlyingIfacesTotal.add(recycle); 1624 break; 1625 } 1626 } 1627 } else if (tunIface.equals(recycle.iface)) { 1628 // VpnService VPN; traffic sent by apps on the VPN network 1629 tunIfaceTotal.add(recycle); 1630 } 1631 } 1632 } 1633 1634 /** 1635 * Distributes traffic across apps that are using given {@code tunIface}, and returns the total 1636 * traffic that should be moved off of {@code tunUid} grouped by {@code underlyingIfaces}. 1637 * 1638 * @param tunUid uid of the VPN application 1639 * @param tunIface iface of the vpn tunnel 1640 * @param underlyingIfaces underlying network ifaces used by the VPN application 1641 * @param tunIfaceTotal combined data usage across all apps using {@code tunIface} 1642 * @param perInterfaceTotal data usage by VPN app, grouped by its {@code underlyingIfaces} 1643 * @param underlyingIfacesTotal data usage by VPN, summed across all of its {@code 1644 * underlyingIfaces} 1645 */ addTrafficToApplications(int tunUid, @NonNull String tunIface, @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal, @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal)1646 private Entry[] addTrafficToApplications(int tunUid, @NonNull String tunIface, 1647 @NonNull List<String> underlyingIfaces, @NonNull Entry tunIfaceTotal, 1648 @NonNull Entry[] perInterfaceTotal, @NonNull Entry underlyingIfacesTotal) { 1649 // Traffic that should be moved off of each underlying interface for tunUid (see 1650 // deductTrafficFromVpnApp below). 1651 final Entry[] moved = new Entry[underlyingIfaces.size()]; 1652 for (int i = 0; i < underlyingIfaces.size(); i++) { 1653 moved[i] = new Entry(); 1654 } 1655 1656 final Entry tmpEntry = new Entry(); 1657 final int origSize = size; 1658 for (int i = 0; i < origSize; i++) { 1659 if (!Objects.equals(iface[i], tunIface)) { 1660 // Consider only entries that go onto the VPN interface. 1661 continue; 1662 } 1663 1664 if (uid[i] == tunUid && tunUid != Process.SYSTEM_UID) { 1665 // Exclude VPN app from the redistribution, as it can choose to create packet 1666 // streams by writing to itself. 1667 // 1668 // However, for platform VPNs, do not exclude the system's usage of the VPN network, 1669 // since it is never local-only, and never double counted 1670 continue; 1671 } 1672 tmpEntry.uid = uid[i]; 1673 tmpEntry.tag = tag[i]; 1674 tmpEntry.metered = metered[i]; 1675 tmpEntry.roaming = roaming[i]; 1676 tmpEntry.defaultNetwork = defaultNetwork[i]; 1677 1678 // In a first pass, compute this entry's total share of data across all 1679 // underlyingIfaces. This is computed on the basis of the share of this entry's usage 1680 // over tunIface. 1681 // TODO: Consider refactoring first pass into a separate helper method. 1682 long totalRxBytes = 0; 1683 if (tunIfaceTotal.rxBytes > 0) { 1684 // Note - The multiplication below should not overflow since NetworkStatsService 1685 // processes this every time device has transmitted/received amount equivalent to 1686 // global threshold alert (~ 2MB) across all interfaces. 1687 final long rxBytesAcrossUnderlyingIfaces = 1688 multiplySafeByRational(underlyingIfacesTotal.rxBytes, 1689 rxBytes[i], tunIfaceTotal.rxBytes); 1690 // app must not be blamed for more than it consumed on tunIface 1691 totalRxBytes = Math.min(rxBytes[i], rxBytesAcrossUnderlyingIfaces); 1692 } 1693 long totalRxPackets = 0; 1694 if (tunIfaceTotal.rxPackets > 0) { 1695 final long rxPacketsAcrossUnderlyingIfaces = 1696 multiplySafeByRational(underlyingIfacesTotal.rxPackets, 1697 rxPackets[i], tunIfaceTotal.rxPackets); 1698 totalRxPackets = Math.min(rxPackets[i], rxPacketsAcrossUnderlyingIfaces); 1699 } 1700 long totalTxBytes = 0; 1701 if (tunIfaceTotal.txBytes > 0) { 1702 final long txBytesAcrossUnderlyingIfaces = 1703 multiplySafeByRational(underlyingIfacesTotal.txBytes, 1704 txBytes[i], tunIfaceTotal.txBytes); 1705 totalTxBytes = Math.min(txBytes[i], txBytesAcrossUnderlyingIfaces); 1706 } 1707 long totalTxPackets = 0; 1708 if (tunIfaceTotal.txPackets > 0) { 1709 final long txPacketsAcrossUnderlyingIfaces = 1710 multiplySafeByRational(underlyingIfacesTotal.txPackets, 1711 txPackets[i], tunIfaceTotal.txPackets); 1712 totalTxPackets = Math.min(txPackets[i], txPacketsAcrossUnderlyingIfaces); 1713 } 1714 long totalOperations = 0; 1715 if (tunIfaceTotal.operations > 0) { 1716 final long operationsAcrossUnderlyingIfaces = 1717 multiplySafeByRational(underlyingIfacesTotal.operations, 1718 operations[i], tunIfaceTotal.operations); 1719 totalOperations = Math.min(operations[i], operationsAcrossUnderlyingIfaces); 1720 } 1721 // In a second pass, distribute these values across interfaces in the proportion that 1722 // each interface represents of the total traffic of the underlying interfaces. 1723 for (int j = 0; j < underlyingIfaces.size(); j++) { 1724 tmpEntry.iface = underlyingIfaces.get(j); 1725 tmpEntry.rxBytes = 0; 1726 // Reset 'set' to correct value since it gets updated when adding debug info below. 1727 tmpEntry.set = set[i]; 1728 if (underlyingIfacesTotal.rxBytes > 0) { 1729 tmpEntry.rxBytes = 1730 multiplySafeByRational(totalRxBytes, 1731 perInterfaceTotal[j].rxBytes, 1732 underlyingIfacesTotal.rxBytes); 1733 } 1734 tmpEntry.rxPackets = 0; 1735 if (underlyingIfacesTotal.rxPackets > 0) { 1736 tmpEntry.rxPackets = 1737 multiplySafeByRational(totalRxPackets, 1738 perInterfaceTotal[j].rxPackets, 1739 underlyingIfacesTotal.rxPackets); 1740 } 1741 tmpEntry.txBytes = 0; 1742 if (underlyingIfacesTotal.txBytes > 0) { 1743 tmpEntry.txBytes = 1744 multiplySafeByRational(totalTxBytes, 1745 perInterfaceTotal[j].txBytes, 1746 underlyingIfacesTotal.txBytes); 1747 } 1748 tmpEntry.txPackets = 0; 1749 if (underlyingIfacesTotal.txPackets > 0) { 1750 tmpEntry.txPackets = 1751 multiplySafeByRational(totalTxPackets, 1752 perInterfaceTotal[j].txPackets, 1753 underlyingIfacesTotal.txPackets); 1754 } 1755 tmpEntry.operations = 0; 1756 if (underlyingIfacesTotal.operations > 0) { 1757 tmpEntry.operations = 1758 multiplySafeByRational(totalOperations, 1759 perInterfaceTotal[j].operations, 1760 underlyingIfacesTotal.operations); 1761 } 1762 // tmpEntry now contains the migrated data of the i-th entry for the j-th underlying 1763 // interface. Add that data usage to this object. 1764 combineValues(tmpEntry); 1765 if (tag[i] == TAG_NONE) { 1766 // Add the migrated data to moved so it is deducted from the VPN app later. 1767 moved[j].add(tmpEntry); 1768 // Add debug info 1769 tmpEntry.set = SET_DBG_VPN_IN; 1770 combineValues(tmpEntry); 1771 } 1772 } 1773 } 1774 return moved; 1775 } 1776 deductTrafficFromVpnApp( int tunUid, @NonNull List<String> underlyingIfaces, @NonNull Entry[] moved)1777 private void deductTrafficFromVpnApp( 1778 int tunUid, 1779 @NonNull List<String> underlyingIfaces, 1780 @NonNull Entry[] moved) { 1781 if (tunUid == Process.SYSTEM_UID) { 1782 // No traffic recorded on a per-UID basis for in-kernel VPN/VCNs over underlying 1783 // networks; thus no traffic to deduct. 1784 return; 1785 } 1786 1787 for (int i = 0; i < underlyingIfaces.size(); i++) { 1788 moved[i].uid = tunUid; 1789 // Add debug info 1790 moved[i].set = SET_DBG_VPN_OUT; 1791 moved[i].tag = TAG_NONE; 1792 moved[i].iface = underlyingIfaces.get(i); 1793 moved[i].metered = METERED_ALL; 1794 moved[i].roaming = ROAMING_ALL; 1795 moved[i].defaultNetwork = DEFAULT_NETWORK_ALL; 1796 combineValues(moved[i]); 1797 1798 // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than 1799 // the TAG_NONE traffic. 1800 // 1801 // Relies on the fact that the underlying traffic only has state ROAMING_NO and 1802 // METERED_NO, which should be the case as it comes directly from the /proc file. 1803 // We only blend in the roaming data after applying these adjustments, by checking the 1804 // NetworkIdentity of the underlying iface. 1805 final int idxVpnBackground = findIndex(underlyingIfaces.get(i), tunUid, SET_DEFAULT, 1806 TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); 1807 if (idxVpnBackground != -1) { 1808 // Note - tunSubtract also updates moved[i]; whatever traffic that's left is removed 1809 // from foreground usage. 1810 tunSubtract(idxVpnBackground, this, moved[i]); 1811 } 1812 1813 final int idxVpnForeground = findIndex(underlyingIfaces.get(i), tunUid, SET_FOREGROUND, 1814 TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); 1815 if (idxVpnForeground != -1) { 1816 tunSubtract(idxVpnForeground, this, moved[i]); 1817 } 1818 } 1819 } 1820 tunSubtract(int i, @NonNull NetworkStats left, @NonNull Entry right)1821 private static void tunSubtract(int i, @NonNull NetworkStats left, @NonNull Entry right) { 1822 long rxBytes = Math.min(left.rxBytes[i], right.rxBytes); 1823 left.rxBytes[i] -= rxBytes; 1824 right.rxBytes -= rxBytes; 1825 1826 long rxPackets = Math.min(left.rxPackets[i], right.rxPackets); 1827 left.rxPackets[i] -= rxPackets; 1828 right.rxPackets -= rxPackets; 1829 1830 long txBytes = Math.min(left.txBytes[i], right.txBytes); 1831 left.txBytes[i] -= txBytes; 1832 right.txBytes -= txBytes; 1833 1834 long txPackets = Math.min(left.txPackets[i], right.txPackets); 1835 left.txPackets[i] -= txPackets; 1836 right.txPackets -= txPackets; 1837 } 1838 } 1839