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