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