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