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