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