NetworkStats.java revision a63ba59260cd1bb3f5c16e395ace45a61f1d4461
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 #tag} value for without tag. */ 46 public static final int TAG_NONE = 0; 47 48 /** 49 * {@link SystemClock#elapsedRealtime()} timestamp when this data was 50 * generated. 51 */ 52 private final long elapsedRealtime; 53 private int size; 54 private String[] iface; 55 private int[] uid; 56 private int[] tag; 57 private long[] rxBytes; 58 private long[] rxPackets; 59 private long[] txBytes; 60 private long[] txPackets; 61 private int[] operations; 62 63 public static class Entry { 64 public String iface; 65 public int uid; 66 public int tag; 67 public long rxBytes; 68 public long rxPackets; 69 public long txBytes; 70 public long txPackets; 71 public int operations; 72 73 public Entry() { 74 } 75 76 public Entry(String iface, int uid, int tag, long rxBytes, long rxPackets, long txBytes, 77 long txPackets, int operations) { 78 this.iface = iface; 79 this.uid = uid; 80 this.tag = tag; 81 this.rxBytes = rxBytes; 82 this.rxPackets = rxPackets; 83 this.txBytes = txBytes; 84 this.txPackets = txPackets; 85 this.operations = operations; 86 } 87 } 88 89 public NetworkStats(long elapsedRealtime, int initialSize) { 90 this.elapsedRealtime = elapsedRealtime; 91 this.size = 0; 92 this.iface = new String[initialSize]; 93 this.uid = new int[initialSize]; 94 this.tag = new int[initialSize]; 95 this.rxBytes = new long[initialSize]; 96 this.rxPackets = new long[initialSize]; 97 this.txBytes = new long[initialSize]; 98 this.txPackets = new long[initialSize]; 99 this.operations = new int[initialSize]; 100 } 101 102 public NetworkStats(Parcel parcel) { 103 elapsedRealtime = parcel.readLong(); 104 size = parcel.readInt(); 105 iface = parcel.createStringArray(); 106 uid = parcel.createIntArray(); 107 tag = parcel.createIntArray(); 108 rxBytes = parcel.createLongArray(); 109 rxPackets = parcel.createLongArray(); 110 txBytes = parcel.createLongArray(); 111 txPackets = parcel.createLongArray(); 112 operations = parcel.createIntArray(); 113 } 114 115 /** {@inheritDoc} */ 116 public void writeToParcel(Parcel dest, int flags) { 117 dest.writeLong(elapsedRealtime); 118 dest.writeInt(size); 119 dest.writeStringArray(iface); 120 dest.writeIntArray(uid); 121 dest.writeIntArray(tag); 122 dest.writeLongArray(rxBytes); 123 dest.writeLongArray(rxPackets); 124 dest.writeLongArray(txBytes); 125 dest.writeLongArray(txPackets); 126 dest.writeIntArray(operations); 127 } 128 129 public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets, 130 long txBytes, long txPackets) { 131 return addValues(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, 0); 132 } 133 134 public NetworkStats addValues(String iface, int uid, int tag, long rxBytes, long rxPackets, 135 long txBytes, long txPackets, int operations) { 136 return addValues( 137 new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); 138 } 139 140 /** 141 * Add new stats entry, copying from given {@link Entry}. The {@link Entry} 142 * object can be recycled across multiple calls. 143 */ 144 public NetworkStats addValues(Entry entry) { 145 if (size >= this.iface.length) { 146 final int newLength = Math.max(iface.length, 10) * 3 / 2; 147 iface = Arrays.copyOf(iface, newLength); 148 uid = Arrays.copyOf(uid, newLength); 149 tag = Arrays.copyOf(tag, newLength); 150 rxBytes = Arrays.copyOf(rxBytes, newLength); 151 rxPackets = Arrays.copyOf(rxPackets, newLength); 152 txBytes = Arrays.copyOf(txBytes, newLength); 153 txPackets = Arrays.copyOf(txPackets, newLength); 154 operations = Arrays.copyOf(operations, newLength); 155 } 156 157 iface[size] = entry.iface; 158 uid[size] = entry.uid; 159 tag[size] = entry.tag; 160 rxBytes[size] = entry.rxBytes; 161 rxPackets[size] = entry.rxPackets; 162 txBytes[size] = entry.txBytes; 163 txPackets[size] = entry.txPackets; 164 operations[size] = entry.operations; 165 size++; 166 167 return this; 168 } 169 170 /** 171 * Return specific stats entry. 172 */ 173 public Entry getValues(int i, Entry recycle) { 174 final Entry entry = recycle != null ? recycle : new Entry(); 175 entry.iface = iface[i]; 176 entry.uid = uid[i]; 177 entry.tag = tag[i]; 178 entry.rxBytes = rxBytes[i]; 179 entry.rxPackets = rxPackets[i]; 180 entry.txBytes = txBytes[i]; 181 entry.txPackets = txPackets[i]; 182 entry.operations = operations[i]; 183 return entry; 184 } 185 186 public long getElapsedRealtime() { 187 return elapsedRealtime; 188 } 189 190 public int size() { 191 return size; 192 } 193 194 // @VisibleForTesting 195 public int internalSize() { 196 return iface.length; 197 } 198 199 public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets, 200 long txBytes, long txPackets, int operations) { 201 return combineValues( 202 new Entry(iface, uid, tag, rxBytes, rxPackets, txBytes, txPackets, operations)); 203 } 204 205 /** 206 * Combine given values with an existing row, or create a new row if 207 * {@link #findIndex(String, int, int)} is unable to find match. Can also be 208 * used to subtract values from existing rows. 209 */ 210 public NetworkStats combineValues(Entry entry) { 211 final int i = findIndex(entry.iface, entry.uid, entry.tag); 212 if (i == -1) { 213 // only create new entry when positive contribution 214 addValues(entry); 215 } else { 216 rxBytes[i] += entry.rxBytes; 217 rxPackets[i] += entry.rxPackets; 218 txBytes[i] += entry.txBytes; 219 txPackets[i] += entry.txPackets; 220 operations[i] += entry.operations; 221 } 222 return this; 223 } 224 225 /** 226 * Find first stats index that matches the requested parameters. 227 */ 228 public int findIndex(String iface, int uid, int tag) { 229 for (int i = 0; i < size; i++) { 230 if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && tag == this.tag[i]) { 231 return i; 232 } 233 } 234 return -1; 235 } 236 237 /** 238 * Splice in {@link #operations} from the given {@link NetworkStats} based 239 * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface}, 240 * since operation counts are at data layer. 241 */ 242 public void spliceOperationsFrom(NetworkStats stats) { 243 for (int i = 0; i < size; i++) { 244 final int j = stats.findIndex(IFACE_ALL, uid[i], tag[i]); 245 if (j == -1) { 246 operations[i] = 0; 247 } else { 248 operations[i] = stats.operations[j]; 249 } 250 } 251 } 252 253 /** 254 * Return list of unique interfaces known by this data structure. 255 */ 256 public String[] getUniqueIfaces() { 257 final HashSet<String> ifaces = new HashSet<String>(); 258 for (String iface : this.iface) { 259 if (iface != IFACE_ALL) { 260 ifaces.add(iface); 261 } 262 } 263 return ifaces.toArray(new String[ifaces.size()]); 264 } 265 266 /** 267 * Return list of unique UIDs known by this data structure. 268 */ 269 public int[] getUniqueUids() { 270 final SparseBooleanArray uids = new SparseBooleanArray(); 271 for (int uid : this.uid) { 272 uids.put(uid, true); 273 } 274 275 final int size = uids.size(); 276 final int[] result = new int[size]; 277 for (int i = 0; i < size; i++) { 278 result[i] = uids.keyAt(i); 279 } 280 return result; 281 } 282 283 /** 284 * Subtract the given {@link NetworkStats}, effectively leaving the delta 285 * between two snapshots in time. Assumes that statistics rows collect over 286 * time, and that none of them have disappeared. 287 * 288 * @throws IllegalArgumentException when given {@link NetworkStats} is 289 * non-monotonic. 290 */ 291 public NetworkStats subtract(NetworkStats value) { 292 return subtract(value, true, false); 293 } 294 295 /** 296 * Subtract the given {@link NetworkStats}, effectively leaving the delta 297 * between two snapshots in time. Assumes that statistics rows collect over 298 * time, and that none of them have disappeared. 299 * <p> 300 * Instead of throwing when counters are non-monotonic, this variant clamps 301 * results to never be negative. 302 */ 303 public NetworkStats subtractClamped(NetworkStats value) { 304 return subtract(value, false, true); 305 } 306 307 /** 308 * Subtract the given {@link NetworkStats}, effectively leaving the delta 309 * between two snapshots in time. Assumes that statistics rows collect over 310 * time, and that none of them have disappeared. 311 * 312 * @param enforceMonotonic Validate that incoming value is strictly 313 * monotonic compared to this object. 314 * @param clampNegative Instead of throwing like {@code enforceMonotonic}, 315 * clamp resulting counters at 0 to prevent negative values. 316 */ 317 private NetworkStats subtract( 318 NetworkStats value, boolean enforceMonotonic, boolean clampNegative) { 319 final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime; 320 if (enforceMonotonic && deltaRealtime < 0) { 321 throw new IllegalArgumentException("found non-monotonic realtime"); 322 } 323 324 // result will have our rows, and elapsed time between snapshots 325 final Entry entry = new Entry(); 326 final NetworkStats result = new NetworkStats(deltaRealtime, size); 327 for (int i = 0; i < size; i++) { 328 entry.iface = iface[i]; 329 entry.uid = uid[i]; 330 entry.tag = tag[i]; 331 332 // find remote row that matches, and subtract 333 final int j = value.findIndex(entry.iface, entry.uid, entry.tag); 334 if (j == -1) { 335 // newly appearing row, return entire value 336 entry.rxBytes = rxBytes[i]; 337 entry.rxPackets = rxPackets[i]; 338 entry.txBytes = txBytes[i]; 339 entry.txPackets = txPackets[i]; 340 entry.operations = operations[i]; 341 } else { 342 // existing row, subtract remote value 343 entry.rxBytes = rxBytes[i] - value.rxBytes[j]; 344 entry.rxPackets = rxPackets[i] - value.rxPackets[j]; 345 entry.txBytes = txBytes[i] - value.txBytes[j]; 346 entry.txPackets = txPackets[i] - value.txPackets[j]; 347 entry.operations = operations[i] - value.operations[j]; 348 if (enforceMonotonic 349 && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 350 || entry.txPackets < 0 || entry.operations < 0)) { 351 throw new IllegalArgumentException("found non-monotonic values"); 352 } 353 if (clampNegative) { 354 entry.rxBytes = Math.max(0, entry.rxBytes); 355 entry.rxPackets = Math.max(0, entry.rxPackets); 356 entry.txBytes = Math.max(0, entry.txBytes); 357 entry.txPackets = Math.max(0, entry.txPackets); 358 entry.operations = Math.max(0, entry.operations); 359 } 360 } 361 362 result.addValues(entry); 363 } 364 365 return result; 366 } 367 368 public void dump(String prefix, PrintWriter pw) { 369 pw.print(prefix); 370 pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); 371 for (int i = 0; i < size; i++) { 372 pw.print(prefix); 373 pw.print(" iface="); pw.print(iface[i]); 374 pw.print(" uid="); pw.print(uid[i]); 375 pw.print(" tag=0x"); pw.print(Integer.toHexString(tag[i])); 376 pw.print(" rxBytes="); pw.print(rxBytes[i]); 377 pw.print(" rxPackets="); pw.print(rxPackets[i]); 378 pw.print(" txBytes="); pw.print(txBytes[i]); 379 pw.print(" txPackets="); pw.print(txPackets[i]); 380 pw.print(" operations="); pw.println(operations[i]); 381 } 382 } 383 384 @Override 385 public String toString() { 386 final CharArrayWriter writer = new CharArrayWriter(); 387 dump("", new PrintWriter(writer)); 388 return writer.toString(); 389 } 390 391 /** {@inheritDoc} */ 392 public int describeContents() { 393 return 0; 394 } 395 396 public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { 397 public NetworkStats createFromParcel(Parcel in) { 398 return new NetworkStats(in); 399 } 400 401 public NetworkStats[] newArray(int size) { 402 return new NetworkStats[size]; 403 } 404 }; 405} 406