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