NetworkStats.java revision ad5e2827ead7fa8ebafc291ceb6b4672e70c1775
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 17package android.net; 18 19import android.os.Parcel; 20import android.os.Parcelable; 21import android.os.SystemClock; 22import android.util.Slog; 23import android.util.SparseBooleanArray; 24 25import com.android.internal.annotations.VisibleForTesting; 26import com.android.internal.util.ArrayUtils; 27 28import libcore.util.EmptyArray; 29 30import java.io.CharArrayWriter; 31import java.io.PrintWriter; 32import java.util.Arrays; 33import java.util.HashSet; 34import 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 */ 45public 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 entry.rxBytes = left.rxBytes[i]; 676 entry.rxPackets = left.rxPackets[i]; 677 entry.txBytes = left.txBytes[i]; 678 entry.txPackets = left.txPackets[i]; 679 entry.operations = left.operations[i]; 680 681 // find remote row that matches, and subtract 682 final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, 683 entry.metered, entry.roaming, i); 684 if (j != -1) { 685 // Found matching row, subtract remote value. 686 entry.rxBytes -= right.rxBytes[j]; 687 entry.rxPackets -= right.rxPackets[j]; 688 entry.txBytes -= right.txBytes[j]; 689 entry.txPackets -= right.txPackets[j]; 690 entry.operations -= right.operations[j]; 691 } 692 693 if (entry.isNegative()) { 694 if (observer != null) { 695 observer.foundNonMonotonic(left, i, right, j, cookie); 696 } 697 entry.rxBytes = Math.max(entry.rxBytes, 0); 698 entry.rxPackets = Math.max(entry.rxPackets, 0); 699 entry.txBytes = Math.max(entry.txBytes, 0); 700 entry.txPackets = Math.max(entry.txPackets, 0); 701 entry.operations = Math.max(entry.operations, 0); 702 } 703 704 result.addValues(entry); 705 } 706 707 return result; 708 } 709 710 /** 711 * Return total statistics grouped by {@link #iface}; doesn't mutate the 712 * original structure. 713 */ 714 public NetworkStats groupedByIface() { 715 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 716 717 final Entry entry = new Entry(); 718 entry.uid = UID_ALL; 719 entry.set = SET_ALL; 720 entry.tag = TAG_NONE; 721 entry.metered = METERED_ALL; 722 entry.roaming = ROAMING_ALL; 723 entry.operations = 0L; 724 725 for (int i = 0; i < size; i++) { 726 // skip specific tags, since already counted in TAG_NONE 727 if (tag[i] != TAG_NONE) continue; 728 729 entry.iface = iface[i]; 730 entry.rxBytes = rxBytes[i]; 731 entry.rxPackets = rxPackets[i]; 732 entry.txBytes = txBytes[i]; 733 entry.txPackets = txPackets[i]; 734 stats.combineValues(entry); 735 } 736 737 return stats; 738 } 739 740 /** 741 * Return total statistics grouped by {@link #uid}; doesn't mutate the 742 * original structure. 743 */ 744 public NetworkStats groupedByUid() { 745 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 746 747 final Entry entry = new Entry(); 748 entry.iface = IFACE_ALL; 749 entry.set = SET_ALL; 750 entry.tag = TAG_NONE; 751 entry.metered = METERED_ALL; 752 entry.roaming = ROAMING_ALL; 753 754 for (int i = 0; i < size; i++) { 755 // skip specific tags, since already counted in TAG_NONE 756 if (tag[i] != TAG_NONE) continue; 757 758 entry.uid = uid[i]; 759 entry.rxBytes = rxBytes[i]; 760 entry.rxPackets = rxPackets[i]; 761 entry.txBytes = txBytes[i]; 762 entry.txPackets = txPackets[i]; 763 entry.operations = operations[i]; 764 stats.combineValues(entry); 765 } 766 767 return stats; 768 } 769 770 /** 771 * Return all rows except those attributed to the requested UID; doesn't 772 * mutate the original structure. 773 */ 774 public NetworkStats withoutUids(int[] uids) { 775 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 776 777 Entry entry = new Entry(); 778 for (int i = 0; i < size; i++) { 779 entry = getValues(i, entry); 780 if (!ArrayUtils.contains(uids, entry.uid)) { 781 stats.addValues(entry); 782 } 783 } 784 785 return stats; 786 } 787 788 public void dump(String prefix, PrintWriter pw) { 789 pw.print(prefix); 790 pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); 791 for (int i = 0; i < size; i++) { 792 pw.print(prefix); 793 pw.print(" ["); pw.print(i); pw.print("]"); 794 pw.print(" iface="); pw.print(iface[i]); 795 pw.print(" uid="); pw.print(uid[i]); 796 pw.print(" set="); pw.print(setToString(set[i])); 797 pw.print(" tag="); pw.print(tagToString(tag[i])); 798 pw.print(" metered="); pw.print(meteredToString(metered[i])); 799 pw.print(" roaming="); pw.print(roamingToString(roaming[i])); 800 pw.print(" rxBytes="); pw.print(rxBytes[i]); 801 pw.print(" rxPackets="); pw.print(rxPackets[i]); 802 pw.print(" txBytes="); pw.print(txBytes[i]); 803 pw.print(" txPackets="); pw.print(txPackets[i]); 804 pw.print(" operations="); pw.println(operations[i]); 805 } 806 } 807 808 /** 809 * Return text description of {@link #set} value. 810 */ 811 public static String setToString(int set) { 812 switch (set) { 813 case SET_ALL: 814 return "ALL"; 815 case SET_DEFAULT: 816 return "DEFAULT"; 817 case SET_FOREGROUND: 818 return "FOREGROUND"; 819 case SET_DBG_VPN_IN: 820 return "DBG_VPN_IN"; 821 case SET_DBG_VPN_OUT: 822 return "DBG_VPN_OUT"; 823 default: 824 return "UNKNOWN"; 825 } 826 } 827 828 /** 829 * Return text description of {@link #set} value. 830 */ 831 public static String setToCheckinString(int set) { 832 switch (set) { 833 case SET_ALL: 834 return "all"; 835 case SET_DEFAULT: 836 return "def"; 837 case SET_FOREGROUND: 838 return "fg"; 839 case SET_DBG_VPN_IN: 840 return "vpnin"; 841 case SET_DBG_VPN_OUT: 842 return "vpnout"; 843 default: 844 return "unk"; 845 } 846 } 847 848 /** 849 * @return true if the querySet matches the dataSet. 850 */ 851 public static boolean setMatches(int querySet, int dataSet) { 852 if (querySet == dataSet) { 853 return true; 854 } 855 // SET_ALL matches all non-debugging sets. 856 return querySet == SET_ALL && dataSet < SET_DEBUG_START; 857 } 858 859 /** 860 * Return text description of {@link #tag} value. 861 */ 862 public static String tagToString(int tag) { 863 return "0x" + Integer.toHexString(tag); 864 } 865 866 /** 867 * Return text description of {@link #metered} value. 868 */ 869 public static String meteredToString(int metered) { 870 switch (metered) { 871 case METERED_ALL: 872 return "ALL"; 873 case METERED_NO: 874 return "NO"; 875 case METERED_YES: 876 return "YES"; 877 default: 878 return "UNKNOWN"; 879 } 880 } 881 882 /** 883 * Return text description of {@link #roaming} value. 884 */ 885 public static String roamingToString(int roaming) { 886 switch (roaming) { 887 case ROAMING_ALL: 888 return "ALL"; 889 case ROAMING_NO: 890 return "NO"; 891 case ROAMING_YES: 892 return "YES"; 893 default: 894 return "UNKNOWN"; 895 } 896 } 897 898 @Override 899 public String toString() { 900 final CharArrayWriter writer = new CharArrayWriter(); 901 dump("", new PrintWriter(writer)); 902 return writer.toString(); 903 } 904 905 @Override 906 public int describeContents() { 907 return 0; 908 } 909 910 public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { 911 @Override 912 public NetworkStats createFromParcel(Parcel in) { 913 return new NetworkStats(in); 914 } 915 916 @Override 917 public NetworkStats[] newArray(int size) { 918 return new NetworkStats[size]; 919 } 920 }; 921 922 public interface NonMonotonicObserver<C> { 923 public void foundNonMonotonic( 924 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); 925 } 926 927 /** 928 * VPN accounting. Move some VPN's underlying traffic to other UIDs that use tun0 iface. 929 * 930 * This method should only be called on delta NetworkStats. Do not call this method on a 931 * snapshot {@link NetworkStats} object because the tunUid and/or the underlyingIface may 932 * change over time. 933 * 934 * This method performs adjustments for one active VPN package and one VPN iface at a time. 935 * 936 * It is possible for the VPN software to use multiple underlying networks. This method 937 * only migrates traffic for the primary underlying network. 938 * 939 * @param tunUid uid of the VPN application 940 * @param tunIface iface of the vpn tunnel 941 * @param underlyingIface the primary underlying network iface used by the VPN application 942 * @return true if it successfully adjusts the accounting for VPN, false otherwise 943 */ 944 public boolean migrateTun(int tunUid, String tunIface, String underlyingIface) { 945 Entry tunIfaceTotal = new Entry(); 946 Entry underlyingIfaceTotal = new Entry(); 947 948 tunAdjustmentInit(tunUid, tunIface, underlyingIface, tunIfaceTotal, underlyingIfaceTotal); 949 950 // If tunIface < underlyingIface, it leaves the overhead traffic in the VPN app. 951 // If tunIface > underlyingIface, the VPN app doesn't get credit for data compression. 952 // Negative stats should be avoided. 953 Entry pool = tunGetPool(tunIfaceTotal, underlyingIfaceTotal); 954 if (pool.isEmpty()) { 955 return true; 956 } 957 Entry moved = 958 addTrafficToApplications(tunUid, tunIface, underlyingIface, tunIfaceTotal, pool); 959 deductTrafficFromVpnApp(tunUid, underlyingIface, moved); 960 961 if (!moved.isEmpty()) { 962 Slog.wtf(TAG, "Failed to deduct underlying network traffic from VPN package. Moved=" 963 + moved); 964 return false; 965 } 966 return true; 967 } 968 969 /** 970 * Initializes the data used by the migrateTun() method. 971 * 972 * This is the first pass iteration which does the following work: 973 * (1) Adds up all the traffic through the tunUid's underlyingIface 974 * (both foreground and background). 975 * (2) Adds up all the traffic through tun0 excluding traffic from the vpn app itself. 976 */ 977 private void tunAdjustmentInit(int tunUid, String tunIface, String underlyingIface, 978 Entry tunIfaceTotal, Entry underlyingIfaceTotal) { 979 Entry recycle = new Entry(); 980 for (int i = 0; i < size; i++) { 981 getValues(i, recycle); 982 if (recycle.uid == UID_ALL) { 983 throw new IllegalStateException( 984 "Cannot adjust VPN accounting on an iface aggregated NetworkStats."); 985 } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) { 986 throw new IllegalStateException( 987 "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*"); 988 } 989 990 if (recycle.uid == tunUid && recycle.tag == TAG_NONE 991 && Objects.equals(underlyingIface, recycle.iface)) { 992 underlyingIfaceTotal.add(recycle); 993 } 994 995 if (recycle.uid != tunUid && recycle.tag == TAG_NONE 996 && Objects.equals(tunIface, recycle.iface)) { 997 // Add up all tunIface traffic excluding traffic from the vpn app itself. 998 tunIfaceTotal.add(recycle); 999 } 1000 } 1001 } 1002 1003 private static Entry tunGetPool(Entry tunIfaceTotal, Entry underlyingIfaceTotal) { 1004 Entry pool = new Entry(); 1005 pool.rxBytes = Math.min(tunIfaceTotal.rxBytes, underlyingIfaceTotal.rxBytes); 1006 pool.rxPackets = Math.min(tunIfaceTotal.rxPackets, underlyingIfaceTotal.rxPackets); 1007 pool.txBytes = Math.min(tunIfaceTotal.txBytes, underlyingIfaceTotal.txBytes); 1008 pool.txPackets = Math.min(tunIfaceTotal.txPackets, underlyingIfaceTotal.txPackets); 1009 pool.operations = Math.min(tunIfaceTotal.operations, underlyingIfaceTotal.operations); 1010 return pool; 1011 } 1012 1013 private Entry addTrafficToApplications(int tunUid, String tunIface, String underlyingIface, 1014 Entry tunIfaceTotal, Entry pool) { 1015 Entry moved = new Entry(); 1016 Entry tmpEntry = new Entry(); 1017 tmpEntry.iface = underlyingIface; 1018 for (int i = 0; i < size; i++) { 1019 // the vpn app is excluded from the redistribution but all moved traffic will be 1020 // deducted from the vpn app (see deductTrafficFromVpnApp below). 1021 if (Objects.equals(iface[i], tunIface) && uid[i] != tunUid) { 1022 if (tunIfaceTotal.rxBytes > 0) { 1023 tmpEntry.rxBytes = pool.rxBytes * rxBytes[i] / tunIfaceTotal.rxBytes; 1024 } else { 1025 tmpEntry.rxBytes = 0; 1026 } 1027 if (tunIfaceTotal.rxPackets > 0) { 1028 tmpEntry.rxPackets = pool.rxPackets * rxPackets[i] / tunIfaceTotal.rxPackets; 1029 } else { 1030 tmpEntry.rxPackets = 0; 1031 } 1032 if (tunIfaceTotal.txBytes > 0) { 1033 tmpEntry.txBytes = pool.txBytes * txBytes[i] / tunIfaceTotal.txBytes; 1034 } else { 1035 tmpEntry.txBytes = 0; 1036 } 1037 if (tunIfaceTotal.txPackets > 0) { 1038 tmpEntry.txPackets = pool.txPackets * txPackets[i] / tunIfaceTotal.txPackets; 1039 } else { 1040 tmpEntry.txPackets = 0; 1041 } 1042 if (tunIfaceTotal.operations > 0) { 1043 tmpEntry.operations = 1044 pool.operations * operations[i] / tunIfaceTotal.operations; 1045 } else { 1046 tmpEntry.operations = 0; 1047 } 1048 tmpEntry.uid = uid[i]; 1049 tmpEntry.tag = tag[i]; 1050 tmpEntry.set = set[i]; 1051 tmpEntry.metered = metered[i]; 1052 tmpEntry.roaming = roaming[i]; 1053 combineValues(tmpEntry); 1054 if (tag[i] == TAG_NONE) { 1055 moved.add(tmpEntry); 1056 // Add debug info 1057 tmpEntry.set = SET_DBG_VPN_IN; 1058 combineValues(tmpEntry); 1059 } 1060 } 1061 } 1062 return moved; 1063 } 1064 1065 private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) { 1066 // Add debug info 1067 moved.uid = tunUid; 1068 moved.set = SET_DBG_VPN_OUT; 1069 moved.tag = TAG_NONE; 1070 moved.iface = underlyingIface; 1071 moved.metered = METERED_ALL; 1072 moved.roaming = ROAMING_ALL; 1073 combineValues(moved); 1074 1075 // Caveat: if the vpn software uses tag, the total tagged traffic may be greater than 1076 // the TAG_NONE traffic. 1077 // 1078 // Relies on the fact that the underlying traffic only has state ROAMING_NO and METERED_NO, 1079 // which should be the case as it comes directly from the /proc file. We only blend in the 1080 // roaming data after applying these adjustments, by checking the NetworkIdentity of the 1081 // underlying iface. 1082 int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, 1083 METERED_NO, ROAMING_NO); 1084 if (idxVpnBackground != -1) { 1085 tunSubtract(idxVpnBackground, this, moved); 1086 } 1087 1088 int idxVpnForeground = findIndex(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, 1089 METERED_NO, ROAMING_NO); 1090 if (idxVpnForeground != -1) { 1091 tunSubtract(idxVpnForeground, this, moved); 1092 } 1093 } 1094 1095 private static void tunSubtract(int i, NetworkStats left, Entry right) { 1096 long rxBytes = Math.min(left.rxBytes[i], right.rxBytes); 1097 left.rxBytes[i] -= rxBytes; 1098 right.rxBytes -= rxBytes; 1099 1100 long rxPackets = Math.min(left.rxPackets[i], right.rxPackets); 1101 left.rxPackets[i] -= rxPackets; 1102 right.rxPackets -= rxPackets; 1103 1104 long txBytes = Math.min(left.txBytes[i], right.txBytes); 1105 left.txBytes[i] -= txBytes; 1106 right.txBytes -= txBytes; 1107 1108 long txPackets = Math.min(left.txPackets[i], right.txPackets); 1109 left.txPackets[i] -= txPackets; 1110 right.txPackets -= txPackets; 1111 } 1112} 1113