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