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 android.os.Parcel; 20 import android.os.Parcelable; 21 import android.os.SystemClock; 22 import android.util.Slog; 23 import android.util.SparseBooleanArray; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.internal.util.ArrayUtils; 27 28 import libcore.util.EmptyArray; 29 30 import java.io.CharArrayWriter; 31 import java.io.PrintWriter; 32 import java.util.Arrays; 33 import java.util.HashSet; 34 import java.util.Map; 35 import java.util.Objects; 36 37 /** 38 * Collection of active network statistics. Can contain summary details across 39 * all interfaces, or details with per-UID granularity. Internally stores data 40 * as a large table, closely matching {@code /proc/} data format. This structure 41 * optimizes for rapid in-memory comparison, but consider using 42 * {@link NetworkStatsHistory} when persisting. 43 * 44 * @hide 45 */ 46 public class NetworkStats implements Parcelable { 47 private static final String TAG = "NetworkStats"; 48 /** {@link #iface} value when interface details unavailable. */ 49 public static final String IFACE_ALL = null; 50 /** {@link #uid} value when UID details unavailable. */ 51 public static final int UID_ALL = -1; 52 /** {@link #tag} value matching any tag. */ 53 // TODO: Rename TAG_ALL to TAG_ANY. 54 public static final int TAG_ALL = -1; 55 /** {@link #set} value for all sets combined, not including debug sets. */ 56 public static final int SET_ALL = -1; 57 /** {@link #set} value where background data is accounted. */ 58 public static final int SET_DEFAULT = 0; 59 /** {@link #set} value where foreground data is accounted. */ 60 public static final int SET_FOREGROUND = 1; 61 /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */ 62 public static final int SET_DEBUG_START = 1000; 63 /** Debug {@link #set} value when the VPN stats are moved in. */ 64 public static final int SET_DBG_VPN_IN = 1001; 65 /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */ 66 public static final int SET_DBG_VPN_OUT = 1002; 67 68 /** Include all interfaces when filtering */ 69 public static final String[] INTERFACES_ALL = null; 70 71 /** {@link #tag} value for total data across all tags. */ 72 // TODO: Rename TAG_NONE to TAG_ALL. 73 public static final int TAG_NONE = 0; 74 75 /** {@link #metered} value to account for all metered states. */ 76 public static final int METERED_ALL = -1; 77 /** {@link #metered} value where native, unmetered data is accounted. */ 78 public static final int METERED_NO = 0; 79 /** {@link #metered} value where metered data is accounted. */ 80 public static final int METERED_YES = 1; 81 82 /** {@link #roaming} value to account for all roaming states. */ 83 public static final int ROAMING_ALL = -1; 84 /** {@link #roaming} value where native, non-roaming data is accounted. */ 85 public static final int ROAMING_NO = 0; 86 /** {@link #roaming} value where roaming data is accounted. */ 87 public static final int ROAMING_YES = 1; 88 89 /** {@link #onDefaultNetwork} value to account for all default network states. */ 90 public static final int DEFAULT_NETWORK_ALL = -1; 91 /** {@link #onDefaultNetwork} value to account for usage while not the default network. */ 92 public static final int DEFAULT_NETWORK_NO = 0; 93 /** {@link #onDefaultNetwork} value to account for usage while the default network. */ 94 public static final int DEFAULT_NETWORK_YES = 1; 95 96 /** Denotes a request for stats at the interface level. */ 97 public static final int STATS_PER_IFACE = 0; 98 /** Denotes a request for stats at the interface and UID level. */ 99 public static final int STATS_PER_UID = 1; 100 101 private static final String CLATD_INTERFACE_PREFIX = "v4-"; 102 // Delta between IPv4 header (20b) and IPv6 header (40b). 103 // Used for correct stats accounting on clatd interfaces. 104 private static final int IPV4V6_HEADER_DELTA = 20; 105 106 // TODO: move fields to "mVariable" notation 107 108 /** 109 * {@link SystemClock#elapsedRealtime()} timestamp when this data was 110 * generated. 111 */ 112 private long elapsedRealtime; 113 private int size; 114 private int capacity; 115 private String[] iface; 116 private int[] uid; 117 private int[] set; 118 private int[] tag; 119 private int[] metered; 120 private int[] roaming; 121 private int[] defaultNetwork; 122 private long[] rxBytes; 123 private long[] rxPackets; 124 private long[] txBytes; 125 private long[] txPackets; 126 private long[] operations; 127 128 public static class Entry { 129 public String iface; 130 public int uid; 131 public int set; 132 public int tag; 133 /** 134 * Note that this is only populated w/ the default value when read from /proc or written 135 * to disk. We merge in the correct value when reporting this value to clients of 136 * getSummary(). 137 */ 138 public int metered; 139 /** 140 * Note that this is only populated w/ the default value when read from /proc or written 141 * to disk. We merge in the correct value when reporting this value to clients of 142 * getSummary(). 143 */ 144 public int roaming; 145 /** 146 * Note that this is only populated w/ the default value when read from /proc or written 147 * to disk. We merge in the correct value when reporting this value to clients of 148 * getSummary(). 149 */ 150 public int defaultNetwork; 151 public long rxBytes; 152 public long rxPackets; 153 public long txBytes; 154 public long txPackets; 155 public long operations; 156 157 public Entry() { 158 this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 159 } 160 161 public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { 162 this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 163 operations); 164 } 165 166 public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, 167 long txBytes, long txPackets, long operations) { 168 this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 169 rxBytes, rxPackets, txBytes, txPackets, operations); 170 } 171 172 public Entry(String iface, int uid, int set, int tag, int metered, int roaming, 173 int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, 174 long operations) { 175 this.iface = iface; 176 this.uid = uid; 177 this.set = set; 178 this.tag = tag; 179 this.metered = metered; 180 this.roaming = roaming; 181 this.defaultNetwork = defaultNetwork; 182 this.rxBytes = rxBytes; 183 this.rxPackets = rxPackets; 184 this.txBytes = txBytes; 185 this.txPackets = txPackets; 186 this.operations = operations; 187 } 188 189 public boolean isNegative() { 190 return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0; 191 } 192 193 public boolean isEmpty() { 194 return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0 195 && operations == 0; 196 } 197 198 public void add(Entry another) { 199 this.rxBytes += another.rxBytes; 200 this.rxPackets += another.rxPackets; 201 this.txBytes += another.txBytes; 202 this.txPackets += another.txPackets; 203 this.operations += another.operations; 204 } 205 206 @Override 207 public String toString() { 208 final StringBuilder builder = new StringBuilder(); 209 builder.append("iface=").append(iface); 210 builder.append(" uid=").append(uid); 211 builder.append(" set=").append(setToString(set)); 212 builder.append(" tag=").append(tagToString(tag)); 213 builder.append(" metered=").append(meteredToString(metered)); 214 builder.append(" roaming=").append(roamingToString(roaming)); 215 builder.append(" defaultNetwork=").append(defaultNetworkToString(defaultNetwork)); 216 builder.append(" rxBytes=").append(rxBytes); 217 builder.append(" rxPackets=").append(rxPackets); 218 builder.append(" txBytes=").append(txBytes); 219 builder.append(" txPackets=").append(txPackets); 220 builder.append(" operations=").append(operations); 221 return builder.toString(); 222 } 223 224 @Override 225 public boolean equals(Object o) { 226 if (o instanceof Entry) { 227 final Entry e = (Entry) o; 228 return uid == e.uid && set == e.set && tag == e.tag && metered == e.metered 229 && roaming == e.roaming && defaultNetwork == e.defaultNetwork 230 && rxBytes == e.rxBytes && rxPackets == e.rxPackets 231 && txBytes == e.txBytes && txPackets == e.txPackets 232 && operations == e.operations && iface.equals(e.iface); 233 } 234 return false; 235 } 236 237 @Override 238 public int hashCode() { 239 return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface); 240 } 241 } 242 243 public NetworkStats(long elapsedRealtime, int initialSize) { 244 this.elapsedRealtime = elapsedRealtime; 245 this.size = 0; 246 if (initialSize > 0) { 247 this.capacity = initialSize; 248 this.iface = new String[initialSize]; 249 this.uid = new int[initialSize]; 250 this.set = new int[initialSize]; 251 this.tag = new int[initialSize]; 252 this.metered = new int[initialSize]; 253 this.roaming = new int[initialSize]; 254 this.defaultNetwork = new int[initialSize]; 255 this.rxBytes = new long[initialSize]; 256 this.rxPackets = new long[initialSize]; 257 this.txBytes = new long[initialSize]; 258 this.txPackets = new long[initialSize]; 259 this.operations = new long[initialSize]; 260 } else { 261 // Special case for use by NetworkStatsFactory to start out *really* empty. 262 clear(); 263 } 264 } 265 266 public NetworkStats(Parcel parcel) { 267 elapsedRealtime = parcel.readLong(); 268 size = parcel.readInt(); 269 capacity = parcel.readInt(); 270 iface = parcel.createStringArray(); 271 uid = parcel.createIntArray(); 272 set = parcel.createIntArray(); 273 tag = parcel.createIntArray(); 274 metered = parcel.createIntArray(); 275 roaming = parcel.createIntArray(); 276 defaultNetwork = parcel.createIntArray(); 277 rxBytes = parcel.createLongArray(); 278 rxPackets = parcel.createLongArray(); 279 txBytes = parcel.createLongArray(); 280 txPackets = parcel.createLongArray(); 281 operations = parcel.createLongArray(); 282 } 283 284 @Override 285 public void writeToParcel(Parcel dest, int flags) { 286 dest.writeLong(elapsedRealtime); 287 dest.writeInt(size); 288 dest.writeInt(capacity); 289 dest.writeStringArray(iface); 290 dest.writeIntArray(uid); 291 dest.writeIntArray(set); 292 dest.writeIntArray(tag); 293 dest.writeIntArray(metered); 294 dest.writeIntArray(roaming); 295 dest.writeIntArray(defaultNetwork); 296 dest.writeLongArray(rxBytes); 297 dest.writeLongArray(rxPackets); 298 dest.writeLongArray(txBytes); 299 dest.writeLongArray(txPackets); 300 dest.writeLongArray(operations); 301 } 302 303 @Override 304 public NetworkStats clone() { 305 final NetworkStats clone = new NetworkStats(elapsedRealtime, size); 306 NetworkStats.Entry entry = null; 307 for (int i = 0; i < size; i++) { 308 entry = getValues(i, entry); 309 clone.addValues(entry); 310 } 311 return clone; 312 } 313 314 /** 315 * Clear all data stored in this object. 316 */ 317 public void clear() { 318 this.capacity = 0; 319 this.iface = EmptyArray.STRING; 320 this.uid = EmptyArray.INT; 321 this.set = EmptyArray.INT; 322 this.tag = EmptyArray.INT; 323 this.metered = EmptyArray.INT; 324 this.roaming = EmptyArray.INT; 325 this.defaultNetwork = EmptyArray.INT; 326 this.rxBytes = EmptyArray.LONG; 327 this.rxPackets = EmptyArray.LONG; 328 this.txBytes = EmptyArray.LONG; 329 this.txPackets = EmptyArray.LONG; 330 this.operations = EmptyArray.LONG; 331 } 332 333 @VisibleForTesting 334 public NetworkStats addIfaceValues( 335 String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { 336 return addValues( 337 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L); 338 } 339 340 @VisibleForTesting 341 public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes, 342 long rxPackets, long txBytes, long txPackets, long operations) { 343 return addValues(new Entry( 344 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); 345 } 346 347 @VisibleForTesting 348 public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming, 349 int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets, 350 long operations) { 351 return addValues(new Entry( 352 iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets, 353 txBytes, txPackets, operations)); 354 } 355 356 /** 357 * Add new stats entry, copying from given {@link Entry}. The {@link Entry} 358 * object can be recycled across multiple calls. 359 */ 360 public NetworkStats addValues(Entry entry) { 361 if (size >= capacity) { 362 final int newLength = Math.max(size, 10) * 3 / 2; 363 iface = Arrays.copyOf(iface, newLength); 364 uid = Arrays.copyOf(uid, newLength); 365 set = Arrays.copyOf(set, newLength); 366 tag = Arrays.copyOf(tag, newLength); 367 metered = Arrays.copyOf(metered, newLength); 368 roaming = Arrays.copyOf(roaming, newLength); 369 defaultNetwork = Arrays.copyOf(defaultNetwork, newLength); 370 rxBytes = Arrays.copyOf(rxBytes, newLength); 371 rxPackets = Arrays.copyOf(rxPackets, newLength); 372 txBytes = Arrays.copyOf(txBytes, newLength); 373 txPackets = Arrays.copyOf(txPackets, newLength); 374 operations = Arrays.copyOf(operations, newLength); 375 capacity = newLength; 376 } 377 378 setValues(size, entry); 379 size++; 380 381 return this; 382 } 383 384 private void setValues(int i, Entry entry) { 385 iface[i] = entry.iface; 386 uid[i] = entry.uid; 387 set[i] = entry.set; 388 tag[i] = entry.tag; 389 metered[i] = entry.metered; 390 roaming[i] = entry.roaming; 391 defaultNetwork[i] = entry.defaultNetwork; 392 rxBytes[i] = entry.rxBytes; 393 rxPackets[i] = entry.rxPackets; 394 txBytes[i] = entry.txBytes; 395 txPackets[i] = entry.txPackets; 396 operations[i] = entry.operations; 397 } 398 399 /** 400 * Return specific stats entry. 401 */ 402 public Entry getValues(int i, Entry recycle) { 403 final Entry entry = recycle != null ? recycle : new Entry(); 404 entry.iface = iface[i]; 405 entry.uid = uid[i]; 406 entry.set = set[i]; 407 entry.tag = tag[i]; 408 entry.metered = metered[i]; 409 entry.roaming = roaming[i]; 410 entry.defaultNetwork = defaultNetwork[i]; 411 entry.rxBytes = rxBytes[i]; 412 entry.rxPackets = rxPackets[i]; 413 entry.txBytes = txBytes[i]; 414 entry.txPackets = txPackets[i]; 415 entry.operations = operations[i]; 416 return entry; 417 } 418 419 public long getElapsedRealtime() { 420 return elapsedRealtime; 421 } 422 423 public void setElapsedRealtime(long time) { 424 elapsedRealtime = time; 425 } 426 427 /** 428 * Return age of this {@link NetworkStats} object with respect to 429 * {@link SystemClock#elapsedRealtime()}. 430 */ 431 public long getElapsedRealtimeAge() { 432 return SystemClock.elapsedRealtime() - elapsedRealtime; 433 } 434 435 public int size() { 436 return size; 437 } 438 439 @VisibleForTesting 440 public int internalSize() { 441 return capacity; 442 } 443 444 @Deprecated 445 public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, 446 long txBytes, long txPackets, long operations) { 447 return combineValues( 448 iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, 449 txPackets, operations); 450 } 451 452 public NetworkStats combineValues(String iface, int uid, int set, int tag, 453 long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { 454 return combineValues(new Entry( 455 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); 456 } 457 458 /** 459 * Combine given values with an existing row, or create a new row if 460 * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can 461 * also be used to subtract values from existing rows. 462 */ 463 public NetworkStats combineValues(Entry entry) { 464 final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered, 465 entry.roaming, entry.defaultNetwork); 466 if (i == -1) { 467 // only create new entry when positive contribution 468 addValues(entry); 469 } else { 470 rxBytes[i] += entry.rxBytes; 471 rxPackets[i] += entry.rxPackets; 472 txBytes[i] += entry.txBytes; 473 txPackets[i] += entry.txPackets; 474 operations[i] += entry.operations; 475 } 476 return this; 477 } 478 479 /** 480 * Combine all values from another {@link NetworkStats} into this object. 481 */ 482 public void combineAllValues(NetworkStats another) { 483 NetworkStats.Entry entry = null; 484 for (int i = 0; i < another.size; i++) { 485 entry = another.getValues(i, entry); 486 combineValues(entry); 487 } 488 } 489 490 /** 491 * Find first stats index that matches the requested parameters. 492 */ 493 public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming, 494 int defaultNetwork) { 495 for (int i = 0; i < size; i++) { 496 if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] 497 && metered == this.metered[i] && roaming == this.roaming[i] 498 && defaultNetwork == this.defaultNetwork[i] 499 && Objects.equals(iface, this.iface[i])) { 500 return i; 501 } 502 } 503 return -1; 504 } 505 506 /** 507 * Find first stats index that matches the requested parameters, starting 508 * search around the hinted index as an optimization. 509 */ 510 @VisibleForTesting 511 public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming, 512 int defaultNetwork, int hintIndex) { 513 for (int offset = 0; offset < size; offset++) { 514 final int halfOffset = offset / 2; 515 516 // search outwards from hint index, alternating forward and backward 517 final int i; 518 if (offset % 2 == 0) { 519 i = (hintIndex + halfOffset) % size; 520 } else { 521 i = (size + hintIndex - halfOffset - 1) % size; 522 } 523 524 if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] 525 && metered == this.metered[i] && roaming == this.roaming[i] 526 && defaultNetwork == this.defaultNetwork[i] 527 && Objects.equals(iface, this.iface[i])) { 528 return i; 529 } 530 } 531 return -1; 532 } 533 534 /** 535 * Splice in {@link #operations} from the given {@link NetworkStats} based 536 * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface}, 537 * since operation counts are at data layer. 538 */ 539 public void spliceOperationsFrom(NetworkStats stats) { 540 for (int i = 0; i < size; i++) { 541 final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i], metered[i], roaming[i], 542 defaultNetwork[i]); 543 if (j == -1) { 544 operations[i] = 0; 545 } else { 546 operations[i] = stats.operations[j]; 547 } 548 } 549 } 550 551 /** 552 * Return list of unique interfaces known by this data structure. 553 */ 554 public String[] getUniqueIfaces() { 555 final HashSet<String> ifaces = new HashSet<String>(); 556 for (String iface : this.iface) { 557 if (iface != IFACE_ALL) { 558 ifaces.add(iface); 559 } 560 } 561 return ifaces.toArray(new String[ifaces.size()]); 562 } 563 564 /** 565 * Return list of unique UIDs known by this data structure. 566 */ 567 public int[] getUniqueUids() { 568 final SparseBooleanArray uids = new SparseBooleanArray(); 569 for (int uid : this.uid) { 570 uids.put(uid, true); 571 } 572 573 final int size = uids.size(); 574 final int[] result = new int[size]; 575 for (int i = 0; i < size; i++) { 576 result[i] = uids.keyAt(i); 577 } 578 return result; 579 } 580 581 /** 582 * Return total bytes represented by this snapshot object, usually used when 583 * checking if a {@link #subtract(NetworkStats)} delta passes a threshold. 584 */ 585 public long getTotalBytes() { 586 final Entry entry = getTotal(null); 587 return entry.rxBytes + entry.txBytes; 588 } 589 590 /** 591 * Return total of all fields represented by this snapshot object. 592 */ 593 public Entry getTotal(Entry recycle) { 594 return getTotal(recycle, null, UID_ALL, false); 595 } 596 597 /** 598 * Return total of all fields represented by this snapshot object matching 599 * the requested {@link #uid}. 600 */ 601 public Entry getTotal(Entry recycle, int limitUid) { 602 return getTotal(recycle, null, limitUid, false); 603 } 604 605 /** 606 * Return total of all fields represented by this snapshot object matching 607 * the requested {@link #iface}. 608 */ 609 public Entry getTotal(Entry recycle, HashSet<String> limitIface) { 610 return getTotal(recycle, limitIface, UID_ALL, false); 611 } 612 613 public Entry getTotalIncludingTags(Entry recycle) { 614 return getTotal(recycle, null, UID_ALL, true); 615 } 616 617 /** 618 * Return total of all fields represented by this snapshot object matching 619 * the requested {@link #iface} and {@link #uid}. 620 * 621 * @param limitIface Set of {@link #iface} to include in total; or {@code 622 * null} to include all ifaces. 623 */ 624 private Entry getTotal( 625 Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) { 626 final Entry entry = recycle != null ? recycle : new Entry(); 627 628 entry.iface = IFACE_ALL; 629 entry.uid = limitUid; 630 entry.set = SET_ALL; 631 entry.tag = TAG_NONE; 632 entry.metered = METERED_ALL; 633 entry.roaming = ROAMING_ALL; 634 entry.defaultNetwork = DEFAULT_NETWORK_ALL; 635 entry.rxBytes = 0; 636 entry.rxPackets = 0; 637 entry.txBytes = 0; 638 entry.txPackets = 0; 639 entry.operations = 0; 640 641 for (int i = 0; i < size; i++) { 642 final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]); 643 final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i])); 644 645 if (matchesUid && matchesIface) { 646 // skip specific tags, since already counted in TAG_NONE 647 if (tag[i] != TAG_NONE && !includeTags) continue; 648 649 entry.rxBytes += rxBytes[i]; 650 entry.rxPackets += rxPackets[i]; 651 entry.txBytes += txBytes[i]; 652 entry.txPackets += txPackets[i]; 653 entry.operations += operations[i]; 654 } 655 } 656 return entry; 657 } 658 659 /** 660 * Fast path for battery stats. 661 */ 662 public long getTotalPackets() { 663 long total = 0; 664 for (int i = size-1; i >= 0; i--) { 665 total += rxPackets[i] + txPackets[i]; 666 } 667 return total; 668 } 669 670 /** 671 * Subtract the given {@link NetworkStats}, effectively leaving the delta 672 * between two snapshots in time. Assumes that statistics rows collect over 673 * time, and that none of them have disappeared. 674 */ 675 public NetworkStats subtract(NetworkStats right) { 676 return subtract(this, right, null, null); 677 } 678 679 /** 680 * Subtract the two given {@link NetworkStats} objects, returning the delta 681 * between two snapshots in time. Assumes that statistics rows collect over 682 * time, and that none of them have disappeared. 683 * <p> 684 * If counters have rolled backwards, they are clamped to {@code 0} and 685 * reported to the given {@link NonMonotonicObserver}. 686 */ 687 public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, 688 NonMonotonicObserver<C> observer, C cookie) { 689 return subtract(left, right, observer, cookie, null); 690 } 691 692 /** 693 * Subtract the two given {@link NetworkStats} objects, returning the delta 694 * between two snapshots in time. Assumes that statistics rows collect over 695 * time, and that none of them have disappeared. 696 * <p> 697 * If counters have rolled backwards, they are clamped to {@code 0} and 698 * reported to the given {@link NonMonotonicObserver}. 699 * <p> 700 * If <var>recycle</var> is supplied, this NetworkStats object will be 701 * reused (and returned) as the result if it is large enough to contain 702 * the data. 703 */ 704 public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, 705 NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) { 706 long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime; 707 if (deltaRealtime < 0) { 708 if (observer != null) { 709 observer.foundNonMonotonic(left, -1, right, -1, cookie); 710 } 711 deltaRealtime = 0; 712 } 713 714 // result will have our rows, and elapsed time between snapshots 715 final Entry entry = new Entry(); 716 final NetworkStats result; 717 if (recycle != null && recycle.capacity >= left.size) { 718 result = recycle; 719 result.size = 0; 720 result.elapsedRealtime = deltaRealtime; 721 } else { 722 result = new NetworkStats(deltaRealtime, left.size); 723 } 724 for (int i = 0; i < left.size; i++) { 725 entry.iface = left.iface[i]; 726 entry.uid = left.uid[i]; 727 entry.set = left.set[i]; 728 entry.tag = left.tag[i]; 729 entry.metered = left.metered[i]; 730 entry.roaming = left.roaming[i]; 731 entry.defaultNetwork = left.defaultNetwork[i]; 732 entry.rxBytes = left.rxBytes[i]; 733 entry.rxPackets = left.rxPackets[i]; 734 entry.txBytes = left.txBytes[i]; 735 entry.txPackets = left.txPackets[i]; 736 entry.operations = left.operations[i]; 737 738 // find remote row that matches, and subtract 739 final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, 740 entry.metered, entry.roaming, entry.defaultNetwork, i); 741 if (j != -1) { 742 // Found matching row, subtract remote value. 743 entry.rxBytes -= right.rxBytes[j]; 744 entry.rxPackets -= right.rxPackets[j]; 745 entry.txBytes -= right.txBytes[j]; 746 entry.txPackets -= right.txPackets[j]; 747 entry.operations -= right.operations[j]; 748 } 749 750 if (entry.isNegative()) { 751 if (observer != null) { 752 observer.foundNonMonotonic(left, i, right, j, cookie); 753 } 754 entry.rxBytes = Math.max(entry.rxBytes, 0); 755 entry.rxPackets = Math.max(entry.rxPackets, 0); 756 entry.txBytes = Math.max(entry.txBytes, 0); 757 entry.txPackets = Math.max(entry.txPackets, 0); 758 entry.operations = Math.max(entry.operations, 0); 759 } 760 761 result.addValues(entry); 762 } 763 764 return result; 765 } 766 767 /** 768 * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice. 769 * 770 * <p>This mutates both base and stacked traffic stats, to account respectively for 771 * double-counted traffic and IPv4/IPv6 header size difference. 772 * 773 * <p>For 464xlat traffic, xt_qtaguid sees every IPv4 packet twice, once as a native IPv4 774 * packet on the stacked interface, and once as translated to an IPv6 packet on the 775 * base interface. For correct stats accounting on the base interface, every 464xlat 776 * packet needs to be subtracted from the root UID on the base interface both for tx 777 * and rx traffic (http://b/12249687, http:/b/33681750). 778 * 779 * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only 780 * {@code ConcurrentHashMap} 781 * @param baseTraffic Traffic on the base interfaces. Will be mutated. 782 * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated. 783 * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. 784 */ 785 public static void apply464xlatAdjustments(NetworkStats baseTraffic, 786 NetworkStats stackedTraffic, Map<String, String> stackedIfaces) { 787 // Total 464xlat traffic to subtract from uid 0 on all base interfaces. 788 // stackedIfaces may grow afterwards, but NetworkStats will just be resized automatically. 789 final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size()); 790 791 // For recycling 792 Entry entry = null; 793 Entry adjust = new NetworkStats.Entry(IFACE_ALL, 0, 0, 0, 0, 0, 0, 0L, 0L, 0L, 0L, 0L); 794 795 for (int i = 0; i < stackedTraffic.size; i++) { 796 entry = stackedTraffic.getValues(i, entry); 797 if (entry.iface == null || !entry.iface.startsWith(CLATD_INTERFACE_PREFIX)) { 798 continue; 799 } 800 final String baseIface = stackedIfaces.get(entry.iface); 801 if (baseIface == null) { 802 continue; 803 } 804 // Subtract any 464lat traffic seen for the root UID on the current base interface. 805 adjust.iface = baseIface; 806 adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA); 807 adjust.txBytes = -(entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA); 808 adjust.rxPackets = -entry.rxPackets; 809 adjust.txPackets = -entry.txPackets; 810 adjustments.combineValues(adjust); 811 812 // For 464xlat traffic, xt_qtaguid only counts the bytes of the native IPv4 packet sent 813 // on the stacked interface with prefix "v4-" and drops the IPv6 header size after 814 // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes 815 // difference for all packets (http://b/12249687, http:/b/33681750). 816 entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA; 817 entry.txBytes += entry.txPackets * IPV4V6_HEADER_DELTA; 818 stackedTraffic.setValues(i, entry); 819 } 820 821 baseTraffic.combineAllValues(adjustments); 822 } 823 824 /** 825 * Calculate and apply adjustments to captured statistics for 464xlat traffic counted twice. 826 * 827 * <p>This mutates the object this method is called on. Equivalent to calling 828 * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as 829 * base and stacked traffic. 830 * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. 831 */ 832 public void apply464xlatAdjustments(Map<String, String> stackedIfaces) { 833 apply464xlatAdjustments(this, this, stackedIfaces); 834 } 835 836 /** 837 * Return total statistics grouped by {@link #iface}; doesn't mutate the 838 * original structure. 839 */ 840 public NetworkStats groupedByIface() { 841 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 842 843 final Entry entry = new Entry(); 844 entry.uid = UID_ALL; 845 entry.set = SET_ALL; 846 entry.tag = TAG_NONE; 847 entry.metered = METERED_ALL; 848 entry.roaming = ROAMING_ALL; 849 entry.defaultNetwork = DEFAULT_NETWORK_ALL; 850 entry.operations = 0L; 851 852 for (int i = 0; i < size; i++) { 853 // skip specific tags, since already counted in TAG_NONE 854 if (tag[i] != TAG_NONE) continue; 855 856 entry.iface = iface[i]; 857 entry.rxBytes = rxBytes[i]; 858 entry.rxPackets = rxPackets[i]; 859 entry.txBytes = txBytes[i]; 860 entry.txPackets = txPackets[i]; 861 stats.combineValues(entry); 862 } 863 864 return stats; 865 } 866 867 /** 868 * Return total statistics grouped by {@link #uid}; doesn't mutate the 869 * original structure. 870 */ 871 public NetworkStats groupedByUid() { 872 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 873 874 final Entry entry = new Entry(); 875 entry.iface = IFACE_ALL; 876 entry.set = SET_ALL; 877 entry.tag = TAG_NONE; 878 entry.metered = METERED_ALL; 879 entry.roaming = ROAMING_ALL; 880 entry.defaultNetwork = DEFAULT_NETWORK_ALL; 881 882 for (int i = 0; i < size; i++) { 883 // skip specific tags, since already counted in TAG_NONE 884 if (tag[i] != TAG_NONE) continue; 885 886 entry.uid = uid[i]; 887 entry.rxBytes = rxBytes[i]; 888 entry.rxPackets = rxPackets[i]; 889 entry.txBytes = txBytes[i]; 890 entry.txPackets = txPackets[i]; 891 entry.operations = operations[i]; 892 stats.combineValues(entry); 893 } 894 895 return stats; 896 } 897 898 /** 899 * Return all rows except those attributed to the requested UID; doesn't 900 * mutate the original structure. 901 */ 902 public NetworkStats withoutUids(int[] uids) { 903 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 904 905 Entry entry = new Entry(); 906 for (int i = 0; i < size; i++) { 907 entry = getValues(i, entry); 908 if (!ArrayUtils.contains(uids, entry.uid)) { 909 stats.addValues(entry); 910 } 911 } 912 913 return stats; 914 } 915 916 /** 917 * Only keep entries that match all specified filters. 918 * 919 * <p>This mutates the original structure in place. After this method is called, 920 * size is the number of matching entries, and capacity is the previous capacity. 921 * @param limitUid UID to filter for, or {@link #UID_ALL}. 922 * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}. 923 * @param limitTag Tag to filter for, or {@link #TAG_ALL}. 924 */ 925 public void filter(int limitUid, String[] limitIfaces, int limitTag) { 926 if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) { 927 return; 928 } 929 930 Entry entry = new Entry(); 931 int nextOutputEntry = 0; 932 for (int i = 0; i < size; i++) { 933 entry = getValues(i, entry); 934 final boolean matches = 935 (limitUid == UID_ALL || limitUid == entry.uid) 936 && (limitTag == TAG_ALL || limitTag == entry.tag) 937 && (limitIfaces == INTERFACES_ALL 938 || ArrayUtils.contains(limitIfaces, entry.iface)); 939 940 if (matches) { 941 setValues(nextOutputEntry, entry); 942 nextOutputEntry++; 943 } 944 } 945 946 size = nextOutputEntry; 947 } 948 949 public void dump(String prefix, PrintWriter pw) { 950 pw.print(prefix); 951 pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); 952 for (int i = 0; i < size; i++) { 953 pw.print(prefix); 954 pw.print(" ["); pw.print(i); pw.print("]"); 955 pw.print(" iface="); pw.print(iface[i]); 956 pw.print(" uid="); pw.print(uid[i]); 957 pw.print(" set="); pw.print(setToString(set[i])); 958 pw.print(" tag="); pw.print(tagToString(tag[i])); 959 pw.print(" metered="); pw.print(meteredToString(metered[i])); 960 pw.print(" roaming="); pw.print(roamingToString(roaming[i])); 961 pw.print(" defaultNetwork="); pw.print(defaultNetworkToString(defaultNetwork[i])); 962 pw.print(" rxBytes="); pw.print(rxBytes[i]); 963 pw.print(" rxPackets="); pw.print(rxPackets[i]); 964 pw.print(" txBytes="); pw.print(txBytes[i]); 965 pw.print(" txPackets="); pw.print(txPackets[i]); 966 pw.print(" operations="); pw.println(operations[i]); 967 } 968 } 969 970 /** 971 * Return text description of {@link #set} value. 972 */ 973 public static String setToString(int set) { 974 switch (set) { 975 case SET_ALL: 976 return "ALL"; 977 case SET_DEFAULT: 978 return "DEFAULT"; 979 case SET_FOREGROUND: 980 return "FOREGROUND"; 981 case SET_DBG_VPN_IN: 982 return "DBG_VPN_IN"; 983 case SET_DBG_VPN_OUT: 984 return "DBG_VPN_OUT"; 985 default: 986 return "UNKNOWN"; 987 } 988 } 989 990 /** 991 * Return text description of {@link #set} value. 992 */ 993 public static String setToCheckinString(int set) { 994 switch (set) { 995 case SET_ALL: 996 return "all"; 997 case SET_DEFAULT: 998 return "def"; 999 case SET_FOREGROUND: 1000 return "fg"; 1001 case SET_DBG_VPN_IN: 1002 return "vpnin"; 1003 case SET_DBG_VPN_OUT: 1004 return "vpnout"; 1005 default: 1006 return "unk"; 1007 } 1008 } 1009 1010 /** 1011 * @return true if the querySet matches the dataSet. 1012 */ 1013 public static boolean setMatches(int querySet, int dataSet) { 1014 if (querySet == dataSet) { 1015 return true; 1016 } 1017 // SET_ALL matches all non-debugging sets. 1018 return querySet == SET_ALL && dataSet < SET_DEBUG_START; 1019 } 1020 1021 /** 1022 * Return text description of {@link #tag} value. 1023 */ 1024 public static String tagToString(int tag) { 1025 return "0x" + Integer.toHexString(tag); 1026 } 1027 1028 /** 1029 * Return text description of {@link #metered} value. 1030 */ 1031 public static String meteredToString(int metered) { 1032 switch (metered) { 1033 case METERED_ALL: 1034 return "ALL"; 1035 case METERED_NO: 1036 return "NO"; 1037 case METERED_YES: 1038 return "YES"; 1039 default: 1040 return "UNKNOWN"; 1041 } 1042 } 1043 1044 /** 1045 * Return text description of {@link #roaming} value. 1046 */ 1047 public static String roamingToString(int roaming) { 1048 switch (roaming) { 1049 case ROAMING_ALL: 1050 return "ALL"; 1051 case ROAMING_NO: 1052 return "NO"; 1053 case ROAMING_YES: 1054 return "YES"; 1055 default: 1056 return "UNKNOWN"; 1057 } 1058 } 1059 1060 /** 1061 * Return text description of {@link #defaultNetwork} value. 1062 */ 1063 public static String defaultNetworkToString(int defaultNetwork) { 1064 switch (defaultNetwork) { 1065 case DEFAULT_NETWORK_ALL: 1066 return "ALL"; 1067 case DEFAULT_NETWORK_NO: 1068 return "NO"; 1069 case DEFAULT_NETWORK_YES: 1070 return "YES"; 1071 default: 1072 return "UNKNOWN"; 1073 } 1074 } 1075 1076 @Override 1077 public String toString() { 1078 final CharArrayWriter writer = new CharArrayWriter(); 1079 dump("", new PrintWriter(writer)); 1080 return writer.toString(); 1081 } 1082 1083 @Override 1084 public int describeContents() { 1085 return 0; 1086 } 1087 1088 public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { 1089 @Override 1090 public NetworkStats createFromParcel(Parcel in) { 1091 return new NetworkStats(in); 1092 } 1093 1094 @Override 1095 public NetworkStats[] newArray(int size) { 1096 return new NetworkStats[size]; 1097 } 1098 }; 1099 1100 public interface NonMonotonicObserver<C> { 1101 public void foundNonMonotonic( 1102 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); 1103 public void foundNonMonotonic( 1104 NetworkStats stats, int statsIndex, C cookie); 1105 } 1106 1107 /** 1108 * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface. 1109 * 1110 * This method should only be called on delta NetworkStats. Do not call this method on a 1111 * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may 1112 * change over time. 1113 * 1114 * This method performs adjustments for one active VPN package and one VPN iface at a time. 1115 * 1116 * It is possible for the VPN software to use multiple underlying networks. This method 1117 * only migrates traffic for the primary underlying network. 1118 * 1119 * @param tunUid uid of the VPN application 1120 * @param tunIface iface of the vpn tunnel 1121 * @param underlyingIface the primary underlying network iface used by the VPN application 1122 * @return true if it successfully adjusts the accounting for VPN, false otherwise 1123 */ 1124 public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) { 1125 Entry tunIfaceTotal = new Entry(); 1126 Entry underlyingIfaceTotal = new Entry(); 1127 1128 tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal); 1129 1130 // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app. 1131 // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression. 1132 // Negative stats should be avoided. 1133 Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal); 1134 if (pool.isEmpty()) { 1135 return true; 1136 } 1137 Entry moved = 1138 addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool); 1139 deductTrafficFromVpnApp(tunUid, underlyingIface, moved); 1140 1141 if (!moved.isEmpty()) { 1142 Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved=" 1143 + moved); 1144 return false; 1145 } 1146 return true; 1147 } 1148 1149 /** 1150 * Initializes the data used by the migrateTun() method. 1151 * 1152 * This is the first pass iteration which does the following work: 1153 * (1) Adds up all the traffic through the tunUid's underlyingIface 1154 * (both foreground and background). 1155 * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself. 1156 */ 1157 private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface, 1158 Entry tunIfaceTotal, Entry underlyingIfaceTotal) { 1159 Entry recycle = new Entry(); 1160 for (int i = 0; i < size; i++) { 1161 getValues(i, recycle); 1162 if (recycle.uid == UID_ALL) { 1163 throw new IllegalStateException( 1164 "Cannot adjust VPN accounting on an iface aggregated NetworkStats."); 1165 } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) { 1166 throw new IllegalStateException( 1167 "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*"); 1168 } 1169 1170 if (recycle.uid == tunUid && recycle.tag == TAG_NONE 1171 && Objects.equals(underlyingIface, recycle.iface)) { 1172 underlyingIfaceTotal.add(recycle); 1173 } 1174 1175 if (recycle.uid != tunUid && recycle.tag == TAG_NONE 1176 && Objects.equals(tunIface, recycle.iface)) { 1177 // Add up all tunIface traffic excluding traffic from the vpn app itself. 1178 tunIfaceTotal.add(recycle); 1179 } 1180 } 1181 } 1182 1183 private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) { 1184 Entry pool = new Entry(); 1185 pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes); 1186 pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets); 1187 pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes); 1188 pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets); 1189 pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations); 1190 return pool; 1191 } 1192 1193 private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface, 1194 Entry tunIfaceTotal, Entry pool) { 1195 Entry moved = new Entry(); 1196 Entry tmpEntry = new Entry(); 1197 tmpEntry.iface = underlyingIface; 1198 for (int i = 0; i < size; i++) { 1199 // the vpn app is excluded from the redistribution but all moved traffic will be 1200 // deducted from the vpn app (see deductTrafficFromVpnApp below). 1201 if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) { 1202 if (tunIfaceTotal.rxBytes > 0) { 1203 tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes; 1204 } else { 1205 tmpEntry.rxBytes = 0; 1206 } 1207 if (tunIfaceTotal.rxPackets > 0) { 1208 tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets; 1209 } else { 1210 tmpEntry.rxPackets = 0; 1211 } 1212 if (tunIfaceTotal.txBytes > 0) { 1213 tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes; 1214 } else { 1215 tmpEntry.txBytes = 0; 1216 } 1217 if (tunIfaceTotal.txPackets > 0) { 1218 tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets; 1219 } else { 1220 tmpEntry.txPackets = 0; 1221 } 1222 if (tunIfaceTotal.operations > 0) { 1223 tmpEntry.operations = 1224 pool.operations * operations[i] / tunIfaceTotal.operations; 1225 } else { 1226 tmpEntry.operations = 0; 1227 } 1228 tmpEntry.uid = uid[i]; 1229 tmpEntry.tag = tag[i]; 1230 tmpEntry.set = set[i]; 1231 tmpEntry.metered = metered[i]; 1232 tmpEntry.roaming = roaming[i]; 1233 tmpEntry.defaultNetwork = defaultNetwork[i]; 1234 combineValues(tmpEntry); 1235 if (tag[i] == TAG_NONE) { 1236 moved.add(tmpEntry); 1237 // Add debug info 1238 tmpEntry.set = SET_DBG_VPN_IN; 1239 combineValues(tmpEntry); 1240 } 1241 } 1242 } 1243 return moved; 1244 } 1245 1246 private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) { 1247 // Add debug info 1248 moved.uid = tunUid; 1249 moved.set = SET_DBG_VPN_OUT; 1250 moved.tag = TAG_NONE; 1251 moved.iface = underlyingIface; 1252 moved.metered = METERED_ALL; 1253 moved.roaming = ROAMING_ALL; 1254 moved.defaultNetwork = DEFAULT_NETWORK_ALL; 1255 combineValues(moved); 1256 1257 // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than 1258 // the TAG_NONE traffic. 1259 // 1260 // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO, 1261 // which should be the case as it comes directly from the /proc file. We only blend in the 1262 // roaming data after applying these adjustments, by checking the NetworkIdentity of the 1263 // underlying iface. 1264 int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, 1265 METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); 1266 if (idxVpnBackground != -1) { 1267 tunSubtract(idxVpnBackground, this, moved); 1268 } 1269 1270 int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, 1271 METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO); 1272 if (idxVpnForeground != -1) { 1273 tunSubtract(idxVpnForeground, this, moved); 1274 } 1275 } 1276 1277 private static void tunSubtract(int i, NetworkStats left, Entry right) { 1278 long rxBytes = Math.min(left.rxBytes[i], right.rxBytes); 1279 left.rxBytes[i] -= rxBytes; 1280 right.rxBytes -= rxBytes; 1281 1282 long rxPackets = Math.min(left.rxPackets[i], right.rxPackets); 1283 left.rxPackets[i] -= rxPackets; 1284 right.rxPackets -= rxPackets; 1285 1286 long txBytes = Math.min(left.txBytes[i], right.txBytes); 1287 left.txBytes[i] -= txBytes; 1288 right.txBytes -= txBytes; 1289 1290 long txPackets = Math.min(left.txPackets[i], right.txPackets); 1291 left.txPackets[i] -= txPackets; 1292 right.txPackets -= txPackets; 1293 } 1294 } 1295