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