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