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