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.SparseBooleanArray; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.internal.util.ArrayUtils; 26 27 import libcore.util.EmptyArray; 28 29 import java.io.CharArrayWriter; 30 import java.io.PrintWriter; 31 import java.util.Arrays; 32 import java.util.HashSet; 33 import java.util.Objects; 34 35 /** 36 * Collection of active network statistics. Can contain summary details across 37 * all interfaces, or details with per-UID granularity. Internally stores data 38 * as a large table, closely matching {@code /proc/} data format. This structure 39 * optimizes for rapid in-memory comparison, but consider using 40 * {@link NetworkStatsHistory} when persisting. 41 * 42 * @hide 43 */ 44 public class NetworkStats implements Parcelable { 45 /** {@link #iface} value when interface details unavailable. */ 46 public static final String IFACE_ALL = null; 47 /** {@link #uid} value when UID details unavailable. */ 48 public static final int UID_ALL = -1; 49 /** {@link #tag} value matching any tag. */ 50 public static final int TAG_ALL = -1; 51 /** {@link #set} value when all sets combined. */ 52 public static final int SET_ALL = -1; 53 /** {@link #set} value where background data is accounted. */ 54 public static final int SET_DEFAULT = 0; 55 /** {@link #set} value where foreground data is accounted. */ 56 public static final int SET_FOREGROUND = 1; 57 /** {@link #tag} value for total data across all tags. */ 58 public static final int TAG_NONE = 0; 59 60 // TODO: move fields to "mVariable" notation 61 62 /** 63 * {@link SystemClock#elapsedRealtime()} timestamp when this data was 64 * generated. 65 */ 66 private long elapsedRealtime; 67 private int size; 68 private int capacity; 69 private String[] iface; 70 private int[] uid; 71 private int[] set; 72 private int[] tag; 73 private long[] rxBytes; 74 private long[] rxPackets; 75 private long[] txBytes; 76 private long[] txPackets; 77 private long[] operations; 78 79 public static class Entry { 80 public String iface; 81 public int uid; 82 public int set; 83 public int tag; 84 public long rxBytes; 85 public long rxPackets; 86 public long txBytes; 87 public long txPackets; 88 public long operations; 89 Entry()90 public Entry() { 91 this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 92 } 93 Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)94 public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) { 95 this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 96 operations); 97 } 98 Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)99 public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, 100 long txBytes, long txPackets, long operations) { 101 this.iface = iface; 102 this.uid = uid; 103 this.set = set; 104 this.tag = tag; 105 this.rxBytes = rxBytes; 106 this.rxPackets = rxPackets; 107 this.txBytes = txBytes; 108 this.txPackets = txPackets; 109 this.operations = operations; 110 } 111 isNegative()112 public boolean isNegative() { 113 return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0; 114 } 115 isEmpty()116 public boolean isEmpty() { 117 return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0 118 && operations == 0; 119 } 120 add(Entry another)121 public void add(Entry another) { 122 this.rxBytes += another.rxBytes; 123 this.rxPackets += another.rxPackets; 124 this.txBytes += another.txBytes; 125 this.txPackets += another.txPackets; 126 this.operations += another.operations; 127 } 128 129 @Override toString()130 public String toString() { 131 final StringBuilder builder = new StringBuilder(); 132 builder.append("iface=").append(iface); 133 builder.append(" uid=").append(uid); 134 builder.append(" set=").append(setToString(set)); 135 builder.append(" tag=").append(tagToString(tag)); 136 builder.append(" rxBytes=").append(rxBytes); 137 builder.append(" rxPackets=").append(rxPackets); 138 builder.append(" txBytes=").append(txBytes); 139 builder.append(" txPackets=").append(txPackets); 140 builder.append(" operations=").append(operations); 141 return builder.toString(); 142 } 143 144 @Override equals(Object o)145 public boolean equals(Object o) { 146 if (o instanceof Entry) { 147 final Entry e = (Entry) o; 148 return uid == e.uid && set == e.set && tag == e.tag && rxBytes == e.rxBytes 149 && rxPackets == e.rxPackets && txBytes == e.txBytes 150 && txPackets == e.txPackets && operations == e.operations 151 && iface.equals(e.iface); 152 } 153 return false; 154 } 155 } 156 NetworkStats(long elapsedRealtime, int initialSize)157 public NetworkStats(long elapsedRealtime, int initialSize) { 158 this.elapsedRealtime = elapsedRealtime; 159 this.size = 0; 160 if (initialSize >= 0) { 161 this.capacity = initialSize; 162 this.iface = new String[initialSize]; 163 this.uid = new int[initialSize]; 164 this.set = new int[initialSize]; 165 this.tag = new int[initialSize]; 166 this.rxBytes = new long[initialSize]; 167 this.rxPackets = new long[initialSize]; 168 this.txBytes = new long[initialSize]; 169 this.txPackets = new long[initialSize]; 170 this.operations = new long[initialSize]; 171 } else { 172 // Special case for use by NetworkStatsFactory to start out *really* empty. 173 this.capacity = 0; 174 this.iface = EmptyArray.STRING; 175 this.uid = EmptyArray.INT; 176 this.set = EmptyArray.INT; 177 this.tag = EmptyArray.INT; 178 this.rxBytes = EmptyArray.LONG; 179 this.rxPackets = EmptyArray.LONG; 180 this.txBytes = EmptyArray.LONG; 181 this.txPackets = EmptyArray.LONG; 182 this.operations = EmptyArray.LONG; 183 } 184 } 185 NetworkStats(Parcel parcel)186 public NetworkStats(Parcel parcel) { 187 elapsedRealtime = parcel.readLong(); 188 size = parcel.readInt(); 189 capacity = parcel.readInt(); 190 iface = parcel.createStringArray(); 191 uid = parcel.createIntArray(); 192 set = parcel.createIntArray(); 193 tag = parcel.createIntArray(); 194 rxBytes = parcel.createLongArray(); 195 rxPackets = parcel.createLongArray(); 196 txBytes = parcel.createLongArray(); 197 txPackets = parcel.createLongArray(); 198 operations = parcel.createLongArray(); 199 } 200 201 @Override writeToParcel(Parcel dest, int flags)202 public void writeToParcel(Parcel dest, int flags) { 203 dest.writeLong(elapsedRealtime); 204 dest.writeInt(size); 205 dest.writeInt(capacity); 206 dest.writeStringArray(iface); 207 dest.writeIntArray(uid); 208 dest.writeIntArray(set); 209 dest.writeIntArray(tag); 210 dest.writeLongArray(rxBytes); 211 dest.writeLongArray(rxPackets); 212 dest.writeLongArray(txBytes); 213 dest.writeLongArray(txPackets); 214 dest.writeLongArray(operations); 215 } 216 217 @Override clone()218 public NetworkStats clone() { 219 final NetworkStats clone = new NetworkStats(elapsedRealtime, size); 220 NetworkStats.Entry entry = null; 221 for (int i = 0; i < size; i++) { 222 entry = getValues(i, entry); 223 clone.addValues(entry); 224 } 225 return clone; 226 } 227 228 @VisibleForTesting addIfaceValues( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets)229 public NetworkStats addIfaceValues( 230 String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { 231 return addValues( 232 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L); 233 } 234 235 @VisibleForTesting addValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)236 public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes, 237 long rxPackets, long txBytes, long txPackets, long operations) { 238 return addValues(new Entry( 239 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); 240 } 241 242 /** 243 * Add new stats entry, copying from given {@link Entry}. The {@link Entry} 244 * object can be recycled across multiple calls. 245 */ addValues(Entry entry)246 public NetworkStats addValues(Entry entry) { 247 if (size >= capacity) { 248 final int newLength = Math.max(size, 10) * 3 / 2; 249 iface = Arrays.copyOf(iface, newLength); 250 uid = Arrays.copyOf(uid, newLength); 251 set = Arrays.copyOf(set, newLength); 252 tag = Arrays.copyOf(tag, newLength); 253 rxBytes = Arrays.copyOf(rxBytes, newLength); 254 rxPackets = Arrays.copyOf(rxPackets, newLength); 255 txBytes = Arrays.copyOf(txBytes, newLength); 256 txPackets = Arrays.copyOf(txPackets, newLength); 257 operations = Arrays.copyOf(operations, newLength); 258 capacity = newLength; 259 } 260 261 iface[size] = entry.iface; 262 uid[size] = entry.uid; 263 set[size] = entry.set; 264 tag[size] = entry.tag; 265 rxBytes[size] = entry.rxBytes; 266 rxPackets[size] = entry.rxPackets; 267 txBytes[size] = entry.txBytes; 268 txPackets[size] = entry.txPackets; 269 operations[size] = entry.operations; 270 size++; 271 272 return this; 273 } 274 275 /** 276 * Return specific stats entry. 277 */ getValues(int i, Entry recycle)278 public Entry getValues(int i, Entry recycle) { 279 final Entry entry = recycle != null ? recycle : new Entry(); 280 entry.iface = iface[i]; 281 entry.uid = uid[i]; 282 entry.set = set[i]; 283 entry.tag = tag[i]; 284 entry.rxBytes = rxBytes[i]; 285 entry.rxPackets = rxPackets[i]; 286 entry.txBytes = txBytes[i]; 287 entry.txPackets = txPackets[i]; 288 entry.operations = operations[i]; 289 return entry; 290 } 291 getElapsedRealtime()292 public long getElapsedRealtime() { 293 return elapsedRealtime; 294 } 295 setElapsedRealtime(long time)296 public void setElapsedRealtime(long time) { 297 elapsedRealtime = time; 298 } 299 300 /** 301 * Return age of this {@link NetworkStats} object with respect to 302 * {@link SystemClock#elapsedRealtime()}. 303 */ getElapsedRealtimeAge()304 public long getElapsedRealtimeAge() { 305 return SystemClock.elapsedRealtime() - elapsedRealtime; 306 } 307 size()308 public int size() { 309 return size; 310 } 311 312 @VisibleForTesting internalSize()313 public int internalSize() { 314 return capacity; 315 } 316 317 @Deprecated combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)318 public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, 319 long txBytes, long txPackets, long operations) { 320 return combineValues( 321 iface, uid, SET_DEFAULT, tag, rxBytes, rxPackets, txBytes, txPackets, operations); 322 } 323 combineValues(String iface, int uid, int set, int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations)324 public NetworkStats combineValues(String iface, int uid, int set, int tag, long rxBytes, 325 long rxPackets, long txBytes, long txPackets, long operations) { 326 return combineValues(new Entry( 327 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); 328 } 329 330 /** 331 * Combine given values with an existing row, or create a new row if 332 * {@link #findIndex(String, int, int, int)} is unable to find match. Can 333 * also be used to subtract values from existing rows. 334 */ combineValues(Entry entry)335 public NetworkStats combineValues(Entry entry) { 336 final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag); 337 if (i == -1) { 338 // only create new entry when positive contribution 339 addValues(entry); 340 } else { 341 rxBytes[i] += entry.rxBytes; 342 rxPackets[i] += entry.rxPackets; 343 txBytes[i] += entry.txBytes; 344 txPackets[i] += entry.txPackets; 345 operations[i] += entry.operations; 346 } 347 return this; 348 } 349 350 /** 351 * Combine all values from another {@link NetworkStats} into this object. 352 */ combineAllValues(NetworkStats another)353 public void combineAllValues(NetworkStats another) { 354 NetworkStats.Entry entry = null; 355 for (int i = 0; i < another.size; i++) { 356 entry = another.getValues(i, entry); 357 combineValues(entry); 358 } 359 } 360 361 /** 362 * Find first stats index that matches the requested parameters. 363 */ findIndex(String iface, int uid, int set, int tag)364 public int findIndex(String iface, int uid, int set, int tag) { 365 for (int i = 0; i < size; i++) { 366 if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] 367 && Objects.equals(iface, this.iface[i])) { 368 return i; 369 } 370 } 371 return -1; 372 } 373 374 /** 375 * Find first stats index that matches the requested parameters, starting 376 * search around the hinted index as an optimization. 377 */ 378 @VisibleForTesting findIndexHinted(String iface, int uid, int set, int tag, int hintIndex)379 public int findIndexHinted(String iface, int uid, int set, int tag, int hintIndex) { 380 for (int offset = 0; offset < size; offset++) { 381 final int halfOffset = offset / 2; 382 383 // search outwards from hint index, alternating forward and backward 384 final int i; 385 if (offset % 2 == 0) { 386 i = (hintIndex + halfOffset) % size; 387 } else { 388 i = (size + hintIndex - halfOffset - 1) % size; 389 } 390 391 if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] 392 && Objects.equals(iface, this.iface[i])) { 393 return i; 394 } 395 } 396 return -1; 397 } 398 399 /** 400 * Splice in {@link #operations} from the given {@link NetworkStats} based 401 * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface}, 402 * since operation counts are at data layer. 403 */ spliceOperationsFrom(NetworkStats stats)404 public void spliceOperationsFrom(NetworkStats stats) { 405 for (int i = 0; i < size; i++) { 406 final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i]); 407 if (j == -1) { 408 operations[i] = 0; 409 } else { 410 operations[i] = stats.operations[j]; 411 } 412 } 413 } 414 415 /** 416 * Return list of unique interfaces known by this data structure. 417 */ getUniqueIfaces()418 public String[] getUniqueIfaces() { 419 final HashSet<String> ifaces = new HashSet<String>(); 420 for (String iface : this.iface) { 421 if (iface != IFACE_ALL) { 422 ifaces.add(iface); 423 } 424 } 425 return ifaces.toArray(new String[ifaces.size()]); 426 } 427 428 /** 429 * Return list of unique UIDs known by this data structure. 430 */ getUniqueUids()431 public int[] getUniqueUids() { 432 final SparseBooleanArray uids = new SparseBooleanArray(); 433 for (int uid : this.uid) { 434 uids.put(uid, true); 435 } 436 437 final int size = uids.size(); 438 final int[] result = new int[size]; 439 for (int i = 0; i < size; i++) { 440 result[i] = uids.keyAt(i); 441 } 442 return result; 443 } 444 445 /** 446 * Return total bytes represented by this snapshot object, usually used when 447 * checking if a {@link #subtract(NetworkStats)} delta passes a threshold. 448 */ getTotalBytes()449 public long getTotalBytes() { 450 final Entry entry = getTotal(null); 451 return entry.rxBytes + entry.txBytes; 452 } 453 454 /** 455 * Return total of all fields represented by this snapshot object. 456 */ getTotal(Entry recycle)457 public Entry getTotal(Entry recycle) { 458 return getTotal(recycle, null, UID_ALL, false); 459 } 460 461 /** 462 * Return total of all fields represented by this snapshot object matching 463 * the requested {@link #uid}. 464 */ getTotal(Entry recycle, int limitUid)465 public Entry getTotal(Entry recycle, int limitUid) { 466 return getTotal(recycle, null, limitUid, false); 467 } 468 469 /** 470 * Return total of all fields represented by this snapshot object matching 471 * the requested {@link #iface}. 472 */ getTotal(Entry recycle, HashSet<String> limitIface)473 public Entry getTotal(Entry recycle, HashSet<String> limitIface) { 474 return getTotal(recycle, limitIface, UID_ALL, false); 475 } 476 getTotalIncludingTags(Entry recycle)477 public Entry getTotalIncludingTags(Entry recycle) { 478 return getTotal(recycle, null, UID_ALL, true); 479 } 480 481 /** 482 * Return total of all fields represented by this snapshot object matching 483 * the requested {@link #iface} and {@link #uid}. 484 * 485 * @param limitIface Set of {@link #iface} to include in total; or {@code 486 * null} to include all ifaces. 487 */ getTotal( Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags)488 private Entry getTotal( 489 Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) { 490 final Entry entry = recycle != null ? recycle : new Entry(); 491 492 entry.iface = IFACE_ALL; 493 entry.uid = limitUid; 494 entry.set = SET_ALL; 495 entry.tag = TAG_NONE; 496 entry.rxBytes = 0; 497 entry.rxPackets = 0; 498 entry.txBytes = 0; 499 entry.txPackets = 0; 500 entry.operations = 0; 501 502 for (int i = 0; i < size; i++) { 503 final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]); 504 final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i])); 505 506 if (matchesUid && matchesIface) { 507 // skip specific tags, since already counted in TAG_NONE 508 if (tag[i] != TAG_NONE && !includeTags) continue; 509 510 entry.rxBytes += rxBytes[i]; 511 entry.rxPackets += rxPackets[i]; 512 entry.txBytes += txBytes[i]; 513 entry.txPackets += txPackets[i]; 514 entry.operations += operations[i]; 515 } 516 } 517 return entry; 518 } 519 520 /** 521 * Fast path for battery stats. 522 */ getTotalPackets()523 public long getTotalPackets() { 524 long total = 0; 525 for (int i = size-1; i >= 0; i--) { 526 total += rxPackets[i] + txPackets[i]; 527 } 528 return total; 529 } 530 531 /** 532 * Subtract the given {@link NetworkStats}, effectively leaving the delta 533 * between two snapshots in time. Assumes that statistics rows collect over 534 * time, and that none of them have disappeared. 535 */ subtract(NetworkStats right)536 public NetworkStats subtract(NetworkStats right) { 537 return subtract(this, right, null, null); 538 } 539 540 /** 541 * Subtract the two given {@link NetworkStats} objects, returning the delta 542 * between two snapshots in time. Assumes that statistics rows collect over 543 * time, and that none of them have disappeared. 544 * <p> 545 * If counters have rolled backwards, they are clamped to {@code 0} and 546 * reported to the given {@link NonMonotonicObserver}. 547 */ subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie)548 public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, 549 NonMonotonicObserver<C> observer, C cookie) { 550 return subtract(left, right, observer, cookie, null); 551 } 552 553 /** 554 * Subtract the two given {@link NetworkStats} objects, returning the delta 555 * between two snapshots in time. Assumes that statistics rows collect over 556 * time, and that none of them have disappeared. 557 * <p> 558 * If counters have rolled backwards, they are clamped to {@code 0} and 559 * reported to the given {@link NonMonotonicObserver}. 560 * <p> 561 * If <var>recycle</var> is supplied, this NetworkStats object will be 562 * reused (and returned) as the result if it is large enough to contain 563 * the data. 564 */ subtract(NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle)565 public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right, 566 NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) { 567 long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime; 568 if (deltaRealtime < 0) { 569 if (observer != null) { 570 observer.foundNonMonotonic(left, -1, right, -1, cookie); 571 } 572 deltaRealtime = 0; 573 } 574 575 // result will have our rows, and elapsed time between snapshots 576 final Entry entry = new Entry(); 577 final NetworkStats result; 578 if (recycle != null && recycle.capacity >= left.size) { 579 result = recycle; 580 result.size = 0; 581 result.elapsedRealtime = deltaRealtime; 582 } else { 583 result = new NetworkStats(deltaRealtime, left.size); 584 } 585 for (int i = 0; i < left.size; i++) { 586 entry.iface = left.iface[i]; 587 entry.uid = left.uid[i]; 588 entry.set = left.set[i]; 589 entry.tag = left.tag[i]; 590 591 // find remote row that matches, and subtract 592 final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i); 593 if (j == -1) { 594 // newly appearing row, return entire value 595 entry.rxBytes = left.rxBytes[i]; 596 entry.rxPackets = left.rxPackets[i]; 597 entry.txBytes = left.txBytes[i]; 598 entry.txPackets = left.txPackets[i]; 599 entry.operations = left.operations[i]; 600 } else { 601 // existing row, subtract remote value 602 entry.rxBytes = left.rxBytes[i] - right.rxBytes[j]; 603 entry.rxPackets = left.rxPackets[i] - right.rxPackets[j]; 604 entry.txBytes = left.txBytes[i] - right.txBytes[j]; 605 entry.txPackets = left.txPackets[i] - right.txPackets[j]; 606 entry.operations = left.operations[i] - right.operations[j]; 607 608 if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 609 || entry.txPackets < 0 || entry.operations < 0) { 610 if (observer != null) { 611 observer.foundNonMonotonic(left, i, right, j, cookie); 612 } 613 entry.rxBytes = Math.max(entry.rxBytes, 0); 614 entry.rxPackets = Math.max(entry.rxPackets, 0); 615 entry.txBytes = Math.max(entry.txBytes, 0); 616 entry.txPackets = Math.max(entry.txPackets, 0); 617 entry.operations = Math.max(entry.operations, 0); 618 } 619 } 620 621 result.addValues(entry); 622 } 623 624 return result; 625 } 626 627 /** 628 * Return total statistics grouped by {@link #iface}; doesn't mutate the 629 * original structure. 630 */ groupedByIface()631 public NetworkStats groupedByIface() { 632 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 633 634 final Entry entry = new Entry(); 635 entry.uid = UID_ALL; 636 entry.set = SET_ALL; 637 entry.tag = TAG_NONE; 638 entry.operations = 0L; 639 640 for (int i = 0; i < size; i++) { 641 // skip specific tags, since already counted in TAG_NONE 642 if (tag[i] != TAG_NONE) continue; 643 644 entry.iface = iface[i]; 645 entry.rxBytes = rxBytes[i]; 646 entry.rxPackets = rxPackets[i]; 647 entry.txBytes = txBytes[i]; 648 entry.txPackets = txPackets[i]; 649 stats.combineValues(entry); 650 } 651 652 return stats; 653 } 654 655 /** 656 * Return total statistics grouped by {@link #uid}; doesn't mutate the 657 * original structure. 658 */ groupedByUid()659 public NetworkStats groupedByUid() { 660 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 661 662 final Entry entry = new Entry(); 663 entry.iface = IFACE_ALL; 664 entry.set = SET_ALL; 665 entry.tag = TAG_NONE; 666 667 for (int i = 0; i < size; i++) { 668 // skip specific tags, since already counted in TAG_NONE 669 if (tag[i] != TAG_NONE) continue; 670 671 entry.uid = uid[i]; 672 entry.rxBytes = rxBytes[i]; 673 entry.rxPackets = rxPackets[i]; 674 entry.txBytes = txBytes[i]; 675 entry.txPackets = txPackets[i]; 676 entry.operations = operations[i]; 677 stats.combineValues(entry); 678 } 679 680 return stats; 681 } 682 683 /** 684 * Return all rows except those attributed to the requested UID; doesn't 685 * mutate the original structure. 686 */ withoutUids(int[] uids)687 public NetworkStats withoutUids(int[] uids) { 688 final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); 689 690 Entry entry = new Entry(); 691 for (int i = 0; i < size; i++) { 692 entry = getValues(i, entry); 693 if (!ArrayUtils.contains(uids, entry.uid)) { 694 stats.addValues(entry); 695 } 696 } 697 698 return stats; 699 } 700 dump(String prefix, PrintWriter pw)701 public void dump(String prefix, PrintWriter pw) { 702 pw.print(prefix); 703 pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); 704 for (int i = 0; i < size; i++) { 705 pw.print(prefix); 706 pw.print(" ["); pw.print(i); pw.print("]"); 707 pw.print(" iface="); pw.print(iface[i]); 708 pw.print(" uid="); pw.print(uid[i]); 709 pw.print(" set="); pw.print(setToString(set[i])); 710 pw.print(" tag="); pw.print(tagToString(tag[i])); 711 pw.print(" rxBytes="); pw.print(rxBytes[i]); 712 pw.print(" rxPackets="); pw.print(rxPackets[i]); 713 pw.print(" txBytes="); pw.print(txBytes[i]); 714 pw.print(" txPackets="); pw.print(txPackets[i]); 715 pw.print(" operations="); pw.println(operations[i]); 716 } 717 } 718 719 /** 720 * Return text description of {@link #set} value. 721 */ setToString(int set)722 public static String setToString(int set) { 723 switch (set) { 724 case SET_ALL: 725 return "ALL"; 726 case SET_DEFAULT: 727 return "DEFAULT"; 728 case SET_FOREGROUND: 729 return "FOREGROUND"; 730 default: 731 return "UNKNOWN"; 732 } 733 } 734 735 /** 736 * Return text description of {@link #set} value. 737 */ setToCheckinString(int set)738 public static String setToCheckinString(int set) { 739 switch (set) { 740 case SET_ALL: 741 return "all"; 742 case SET_DEFAULT: 743 return "def"; 744 case SET_FOREGROUND: 745 return "fg"; 746 default: 747 return "unk"; 748 } 749 } 750 751 /** 752 * Return text description of {@link #tag} value. 753 */ tagToString(int tag)754 public static String tagToString(int tag) { 755 return "0x" + Integer.toHexString(tag); 756 } 757 758 @Override toString()759 public String toString() { 760 final CharArrayWriter writer = new CharArrayWriter(); 761 dump("", new PrintWriter(writer)); 762 return writer.toString(); 763 } 764 765 @Override describeContents()766 public int describeContents() { 767 return 0; 768 } 769 770 public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { 771 @Override 772 public NetworkStats createFromParcel(Parcel in) { 773 return new NetworkStats(in); 774 } 775 776 @Override 777 public NetworkStats[] newArray(int size) { 778 return new NetworkStats[size]; 779 } 780 }; 781 782 public interface NonMonotonicObserver<C> { foundNonMonotonic( NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie)783 public void foundNonMonotonic( 784 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); 785 } 786 } 787