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