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