NetworkStatsHistory.java revision 04cd0e47dbc1e9769ac6f258c923d5b17fa57986
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 android.net.NetworkStats.IFACE_ALL; 20import static android.net.NetworkStats.TAG_NONE; 21import static android.net.NetworkStats.UID_ALL; 22import static android.net.NetworkStatsHistory.DataStreamUtils.readFullLongArray; 23import static android.net.NetworkStatsHistory.DataStreamUtils.readVarLongArray; 24import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray; 25import static android.net.NetworkStatsHistory.Entry.UNKNOWN; 26import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; 27import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; 28 29import android.os.Parcel; 30import android.os.Parcelable; 31 32import java.io.CharArrayWriter; 33import java.io.DataInputStream; 34import java.io.DataOutputStream; 35import java.io.IOException; 36import java.io.PrintWriter; 37import java.net.ProtocolException; 38import java.util.Arrays; 39import java.util.Random; 40 41/** 42 * Collection of historical network statistics, recorded into equally-sized 43 * "buckets" in time. Internally it stores data in {@code long} series for more 44 * efficient persistence. 45 * <p> 46 * Each bucket is defined by a {@link #bucketStart} timestamp, and lasts for 47 * {@link #bucketDuration}. Internally assumes that {@link #bucketStart} is 48 * sorted at all times. 49 * 50 * @hide 51 */ 52public class NetworkStatsHistory implements Parcelable { 53 private static final int VERSION_INIT = 1; 54 private static final int VERSION_ADD_PACKETS = 2; 55 56 public static final int FIELD_RX_BYTES = 0x01; 57 public static final int FIELD_RX_PACKETS = 0x02; 58 public static final int FIELD_TX_BYTES = 0x04; 59 public static final int FIELD_TX_PACKETS = 0x08; 60 public static final int FIELD_OPERATIONS = 0x10; 61 62 public static final int FIELD_ALL = 0xFFFFFFFF; 63 64 private long bucketDuration; 65 private int bucketCount; 66 private long[] bucketStart; 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 static final long UNKNOWN = -1; 75 76 public long bucketStart; 77 public long bucketDuration; 78 public long rxBytes; 79 public long rxPackets; 80 public long txBytes; 81 public long txPackets; 82 public long operations; 83 } 84 85 public NetworkStatsHistory(long bucketDuration) { 86 this(bucketDuration, 10, FIELD_ALL); 87 } 88 89 public NetworkStatsHistory(long bucketDuration, int initialSize) { 90 this(bucketDuration, initialSize, FIELD_ALL); 91 } 92 93 public NetworkStatsHistory(long bucketDuration, int initialSize, int fields) { 94 this.bucketDuration = bucketDuration; 95 bucketStart = new long[initialSize]; 96 if ((fields & FIELD_RX_BYTES) != 0) rxBytes = new long[initialSize]; 97 if ((fields & FIELD_RX_PACKETS) != 0) rxPackets = new long[initialSize]; 98 if ((fields & FIELD_TX_BYTES) != 0) txBytes = new long[initialSize]; 99 if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize]; 100 if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize]; 101 bucketCount = 0; 102 } 103 104 public NetworkStatsHistory(Parcel in) { 105 bucketDuration = in.readLong(); 106 bucketStart = readLongArray(in); 107 rxBytes = readLongArray(in); 108 rxPackets = readLongArray(in); 109 txBytes = readLongArray(in); 110 txPackets = readLongArray(in); 111 operations = readLongArray(in); 112 bucketCount = bucketStart.length; 113 } 114 115 /** {@inheritDoc} */ 116 public void writeToParcel(Parcel out, int flags) { 117 out.writeLong(bucketDuration); 118 writeLongArray(out, bucketStart, bucketCount); 119 writeLongArray(out, rxBytes, bucketCount); 120 writeLongArray(out, rxPackets, bucketCount); 121 writeLongArray(out, txBytes, bucketCount); 122 writeLongArray(out, txPackets, bucketCount); 123 writeLongArray(out, operations, bucketCount); 124 } 125 126 public NetworkStatsHistory(DataInputStream in) throws IOException { 127 final int version = in.readInt(); 128 switch (version) { 129 case VERSION_INIT: { 130 bucketDuration = in.readLong(); 131 bucketStart = readFullLongArray(in); 132 rxBytes = readFullLongArray(in); 133 rxPackets = new long[bucketStart.length]; 134 txBytes = readFullLongArray(in); 135 txPackets = new long[bucketStart.length]; 136 operations = new long[bucketStart.length]; 137 bucketCount = bucketStart.length; 138 break; 139 } 140 case VERSION_ADD_PACKETS: { 141 bucketDuration = in.readLong(); 142 bucketStart = readVarLongArray(in); 143 rxBytes = readVarLongArray(in); 144 rxPackets = readVarLongArray(in); 145 txBytes = readVarLongArray(in); 146 txPackets = readVarLongArray(in); 147 operations = readVarLongArray(in); 148 bucketCount = bucketStart.length; 149 break; 150 } 151 default: { 152 throw new ProtocolException("unexpected version: " + version); 153 } 154 } 155 } 156 157 public void writeToStream(DataOutputStream out) throws IOException { 158 out.writeInt(VERSION_ADD_PACKETS); 159 out.writeLong(bucketDuration); 160 writeVarLongArray(out, bucketStart, bucketCount); 161 writeVarLongArray(out, rxBytes, bucketCount); 162 writeVarLongArray(out, rxPackets, bucketCount); 163 writeVarLongArray(out, txBytes, bucketCount); 164 writeVarLongArray(out, txPackets, bucketCount); 165 writeVarLongArray(out, operations, bucketCount); 166 } 167 168 /** {@inheritDoc} */ 169 public int describeContents() { 170 return 0; 171 } 172 173 public int size() { 174 return bucketCount; 175 } 176 177 public long getBucketDuration() { 178 return bucketDuration; 179 } 180 181 public long getStart() { 182 if (bucketCount > 0) { 183 return bucketStart[0]; 184 } else { 185 return Long.MAX_VALUE; 186 } 187 } 188 189 public long getEnd() { 190 if (bucketCount > 0) { 191 return bucketStart[bucketCount - 1] + bucketDuration; 192 } else { 193 return Long.MIN_VALUE; 194 } 195 } 196 197 /** 198 * Return specific stats entry. 199 */ 200 public Entry getValues(int i, Entry recycle) { 201 final Entry entry = recycle != null ? recycle : new Entry(); 202 entry.bucketStart = bucketStart[i]; 203 entry.bucketDuration = bucketDuration; 204 entry.rxBytes = getLong(rxBytes, i, UNKNOWN); 205 entry.rxPackets = getLong(rxPackets, i, UNKNOWN); 206 entry.txBytes = getLong(txBytes, i, UNKNOWN); 207 entry.txPackets = getLong(txPackets, i, UNKNOWN); 208 entry.operations = getLong(operations, i, UNKNOWN); 209 return entry; 210 } 211 212 /** 213 * Record that data traffic occurred in the given time range. Will 214 * distribute across internal buckets, creating new buckets as needed. 215 */ 216 @Deprecated 217 public void recordData(long start, long end, long rxBytes, long txBytes) { 218 recordData(start, end, 219 new NetworkStats.Entry(IFACE_ALL, UID_ALL, TAG_NONE, rxBytes, 0L, txBytes, 0L, 0L)); 220 } 221 222 /** 223 * Record that data traffic occurred in the given time range. Will 224 * distribute across internal buckets, creating new buckets as needed. 225 */ 226 public void recordData(long start, long end, NetworkStats.Entry entry) { 227 if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 || entry.txPackets < 0 228 || entry.operations < 0) { 229 throw new IllegalArgumentException("tried recording negative data"); 230 } 231 232 // create any buckets needed by this range 233 ensureBuckets(start, end); 234 235 // distribute data usage into buckets 236 long duration = end - start; 237 for (int i = bucketCount - 1; i >= 0; i--) { 238 final long curStart = bucketStart[i]; 239 final long curEnd = curStart + bucketDuration; 240 241 // bucket is older than record; we're finished 242 if (curEnd < start) break; 243 // bucket is newer than record; keep looking 244 if (curStart > end) continue; 245 246 final long overlap = Math.min(curEnd, end) - Math.max(curStart, start); 247 if (overlap <= 0) continue; 248 249 // integer math each time is faster than floating point 250 final long fracRxBytes = entry.rxBytes * overlap / duration; 251 final long fracRxPackets = entry.rxPackets * overlap / duration; 252 final long fracTxBytes = entry.txBytes * overlap / duration; 253 final long fracTxPackets = entry.txPackets * overlap / duration; 254 final int fracOperations = (int) (entry.operations * overlap / duration); 255 256 addLong(rxBytes, i, fracRxBytes); entry.rxBytes -= fracRxBytes; 257 addLong(rxPackets, i, fracRxPackets); entry.rxPackets -= fracRxPackets; 258 addLong(txBytes, i, fracTxBytes); entry.txBytes -= fracTxBytes; 259 addLong(txPackets, i, fracTxPackets); entry.txPackets -= fracTxPackets; 260 addLong(operations, i, fracOperations); entry.operations -= fracOperations; 261 262 duration -= overlap; 263 } 264 } 265 266 /** 267 * Record an entire {@link NetworkStatsHistory} into this history. Usually 268 * for combining together stats for external reporting. 269 */ 270 public void recordEntireHistory(NetworkStatsHistory input) { 271 final NetworkStats.Entry entry = new NetworkStats.Entry( 272 IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 273 for (int i = 0; i < input.bucketCount; i++) { 274 final long start = input.bucketStart[i]; 275 final long end = start + input.bucketDuration; 276 277 entry.rxBytes = getLong(input.rxBytes, i, 0L); 278 entry.rxPackets = getLong(input.rxPackets, i, 0L); 279 entry.txBytes = getLong(input.txBytes, i, 0L); 280 entry.txPackets = getLong(input.txPackets, i, 0L); 281 entry.operations = getLong(input.operations, i, 0L); 282 283 recordData(start, end, entry); 284 } 285 } 286 287 /** 288 * Ensure that buckets exist for given time range, creating as needed. 289 */ 290 private void ensureBuckets(long start, long end) { 291 // normalize incoming range to bucket boundaries 292 start -= start % bucketDuration; 293 end += (bucketDuration - (end % bucketDuration)) % bucketDuration; 294 295 for (long now = start; now < end; now += bucketDuration) { 296 // try finding existing bucket 297 final int index = Arrays.binarySearch(bucketStart, 0, bucketCount, now); 298 if (index < 0) { 299 // bucket missing, create and insert 300 insertBucket(~index, now); 301 } 302 } 303 } 304 305 /** 306 * Insert new bucket at requested index and starting time. 307 */ 308 private void insertBucket(int index, long start) { 309 // create more buckets when needed 310 if (bucketCount >= bucketStart.length) { 311 final int newLength = Math.max(bucketStart.length, 10) * 3 / 2; 312 bucketStart = Arrays.copyOf(bucketStart, newLength); 313 if (rxBytes != null) rxBytes = Arrays.copyOf(rxBytes, newLength); 314 if (rxPackets != null) rxPackets = Arrays.copyOf(rxPackets, newLength); 315 if (txBytes != null) txBytes = Arrays.copyOf(txBytes, newLength); 316 if (txPackets != null) txPackets = Arrays.copyOf(txPackets, newLength); 317 if (operations != null) operations = Arrays.copyOf(operations, newLength); 318 } 319 320 // create gap when inserting bucket in middle 321 if (index < bucketCount) { 322 final int dstPos = index + 1; 323 final int length = bucketCount - index; 324 325 System.arraycopy(bucketStart, index, bucketStart, dstPos, length); 326 if (rxBytes != null) System.arraycopy(rxBytes, index, rxBytes, dstPos, length); 327 if (rxPackets != null) System.arraycopy(rxPackets, index, rxPackets, dstPos, length); 328 if (txBytes != null) System.arraycopy(txBytes, index, txBytes, dstPos, length); 329 if (txPackets != null) System.arraycopy(txPackets, index, txPackets, dstPos, length); 330 if (operations != null) System.arraycopy(operations, index, operations, dstPos, length); 331 } 332 333 bucketStart[index] = start; 334 setLong(rxBytes, index, 0L); 335 setLong(rxPackets, index, 0L); 336 setLong(txBytes, index, 0L); 337 setLong(txPackets, index, 0L); 338 setLong(operations, index, 0L); 339 bucketCount++; 340 } 341 342 /** 343 * Remove buckets older than requested cutoff. 344 */ 345 public void removeBucketsBefore(long cutoff) { 346 int i; 347 for (i = 0; i < bucketCount; i++) { 348 final long curStart = bucketStart[i]; 349 final long curEnd = curStart + bucketDuration; 350 351 // cutoff happens before or during this bucket; everything before 352 // this bucket should be removed. 353 if (curEnd > cutoff) break; 354 } 355 356 if (i > 0) { 357 final int length = bucketStart.length; 358 bucketStart = Arrays.copyOfRange(bucketStart, i, length); 359 if (rxBytes != null) rxBytes = Arrays.copyOfRange(rxBytes, i, length); 360 if (rxPackets != null) rxPackets = Arrays.copyOfRange(rxPackets, i, length); 361 if (txBytes != null) txBytes = Arrays.copyOfRange(txBytes, i, length); 362 if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length); 363 if (operations != null) operations = Arrays.copyOfRange(operations, i, length); 364 bucketCount -= i; 365 } 366 } 367 368 /** 369 * Return interpolated data usage across the requested range. Interpolates 370 * across buckets, so values may be rounded slightly. 371 */ 372 public Entry getValues(long start, long end, Entry recycle) { 373 return getValues(start, end, Long.MAX_VALUE, recycle); 374 } 375 376 /** 377 * Return interpolated data usage across the requested range. Interpolates 378 * across buckets, so values may be rounded slightly. 379 */ 380 public Entry getValues(long start, long end, long now, Entry recycle) { 381 final Entry entry = recycle != null ? recycle : new Entry(); 382 entry.bucketStart = start; 383 entry.bucketDuration = end - start; 384 entry.rxBytes = rxBytes != null ? 0 : UNKNOWN; 385 entry.rxPackets = rxPackets != null ? 0 : UNKNOWN; 386 entry.txBytes = txBytes != null ? 0 : UNKNOWN; 387 entry.txPackets = txPackets != null ? 0 : UNKNOWN; 388 entry.operations = operations != null ? 0 : UNKNOWN; 389 390 for (int i = bucketCount - 1; i >= 0; i--) { 391 final long curStart = bucketStart[i]; 392 final long curEnd = curStart + bucketDuration; 393 394 // bucket is older than record; we're finished 395 if (curEnd < start) break; 396 // bucket is newer than record; keep looking 397 if (curStart > end) continue; 398 399 // include full value for active buckets, otherwise only fractional 400 final boolean activeBucket = curStart < now && curEnd > now; 401 final long overlap = activeBucket ? bucketDuration 402 : Math.min(curEnd, end) - Math.max(curStart, start); 403 if (overlap <= 0) continue; 404 405 // integer math each time is faster than floating point 406 if (rxBytes != null) entry.rxBytes += rxBytes[i] * overlap / bucketDuration; 407 if (rxPackets != null) entry.rxPackets += rxPackets[i] * overlap / bucketDuration; 408 if (txBytes != null) entry.txBytes += txBytes[i] * overlap / bucketDuration; 409 if (txPackets != null) entry.txPackets += txPackets[i] * overlap / bucketDuration; 410 if (operations != null) entry.operations += operations[i] * overlap / bucketDuration; 411 } 412 413 return entry; 414 } 415 416 /** 417 * @deprecated only for temporary testing 418 */ 419 @Deprecated 420 public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes, 421 long txPackets, long operations) { 422 ensureBuckets(start, end); 423 424 final NetworkStats.Entry entry = new NetworkStats.Entry( 425 IFACE_ALL, UID_ALL, TAG_NONE, 0L, 0L, 0L, 0L, 0L); 426 final Random r = new Random(); 427 while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128 428 || operations > 32) { 429 final long curStart = randomLong(r, start, end); 430 final long curEnd = randomLong(r, curStart, end); 431 432 entry.rxBytes = randomLong(r, 0, rxBytes); 433 entry.rxPackets = randomLong(r, 0, rxPackets); 434 entry.txBytes = randomLong(r, 0, txBytes); 435 entry.txPackets = randomLong(r, 0, txPackets); 436 entry.operations = randomLong(r, 0, operations); 437 438 rxBytes -= entry.rxBytes; 439 rxPackets -= entry.rxPackets; 440 txBytes -= entry.txBytes; 441 txPackets -= entry.txPackets; 442 operations -= entry.operations; 443 444 recordData(curStart, curEnd, entry); 445 } 446 } 447 448 private static long randomLong(Random r, long start, long end) { 449 return (long) (start + (r.nextFloat() * (end - start))); 450 } 451 452 public void dump(String prefix, PrintWriter pw, boolean fullHistory) { 453 pw.print(prefix); 454 pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); 455 456 final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); 457 if (start > 0) { 458 pw.print(prefix); 459 pw.print(" (omitting "); pw.print(start); pw.println(" buckets)"); 460 } 461 462 for (int i = start; i < bucketCount; i++) { 463 pw.print(prefix); 464 pw.print(" bucketStart="); pw.print(bucketStart[i]); 465 if (rxBytes != null) pw.print(" rxBytes="); pw.print(rxBytes[i]); 466 if (rxPackets != null) pw.print(" rxPackets="); pw.print(rxPackets[i]); 467 if (txBytes != null) pw.print(" txBytes="); pw.print(txBytes[i]); 468 if (txPackets != null) pw.print(" txPackets="); pw.print(txPackets[i]); 469 if (operations != null) pw.print(" operations="); pw.print(operations[i]); 470 pw.println(); 471 } 472 } 473 474 @Override 475 public String toString() { 476 final CharArrayWriter writer = new CharArrayWriter(); 477 dump("", new PrintWriter(writer), false); 478 return writer.toString(); 479 } 480 481 public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { 482 public NetworkStatsHistory createFromParcel(Parcel in) { 483 return new NetworkStatsHistory(in); 484 } 485 486 public NetworkStatsHistory[] newArray(int size) { 487 return new NetworkStatsHistory[size]; 488 } 489 }; 490 491 private static long getLong(long[] array, int i, long value) { 492 return array != null ? array[i] : value; 493 } 494 495 private static void setLong(long[] array, int i, long value) { 496 if (array != null) array[i] = value; 497 } 498 499 private static void addLong(long[] array, int i, long value) { 500 if (array != null) array[i] += value; 501 } 502 503 /** 504 * Utility methods for interacting with {@link DataInputStream} and 505 * {@link DataOutputStream}, mostly dealing with writing partial arrays. 506 */ 507 public static class DataStreamUtils { 508 @Deprecated 509 public static long[] readFullLongArray(DataInputStream in) throws IOException { 510 final int size = in.readInt(); 511 final long[] values = new long[size]; 512 for (int i = 0; i < values.length; i++) { 513 values[i] = in.readLong(); 514 } 515 return values; 516 } 517 518 /** 519 * Read variable-length {@link Long} using protobuf-style approach. 520 */ 521 public static long readVarLong(DataInputStream in) throws IOException { 522 int shift = 0; 523 long result = 0; 524 while (shift < 64) { 525 byte b = in.readByte(); 526 result |= (long) (b & 0x7F) << shift; 527 if ((b & 0x80) == 0) 528 return result; 529 shift += 7; 530 } 531 throw new ProtocolException("malformed long"); 532 } 533 534 /** 535 * Write variable-length {@link Long} using protobuf-style approach. 536 */ 537 public static void writeVarLong(DataOutputStream out, long value) throws IOException { 538 while (true) { 539 if ((value & ~0x7FL) == 0) { 540 out.writeByte((int) value); 541 return; 542 } else { 543 out.writeByte(((int) value & 0x7F) | 0x80); 544 value >>>= 7; 545 } 546 } 547 } 548 549 public static long[] readVarLongArray(DataInputStream in) throws IOException { 550 final int size = in.readInt(); 551 if (size == -1) return null; 552 final long[] values = new long[size]; 553 for (int i = 0; i < values.length; i++) { 554 values[i] = readVarLong(in); 555 } 556 return values; 557 } 558 559 public static void writeVarLongArray(DataOutputStream out, long[] values, int size) 560 throws IOException { 561 if (values == null) { 562 out.writeInt(-1); 563 return; 564 } 565 if (size > values.length) { 566 throw new IllegalArgumentException("size larger than length"); 567 } 568 out.writeInt(size); 569 for (int i = 0; i < size; i++) { 570 writeVarLong(out, values[i]); 571 } 572 } 573 } 574 575 /** 576 * Utility methods for interacting with {@link Parcel} structures, mostly 577 * dealing with writing partial arrays. 578 */ 579 public static class ParcelUtils { 580 public static long[] readLongArray(Parcel in) { 581 final int size = in.readInt(); 582 if (size == -1) return null; 583 final long[] values = new long[size]; 584 for (int i = 0; i < values.length; i++) { 585 values[i] = in.readLong(); 586 } 587 return values; 588 } 589 590 public static void writeLongArray(Parcel out, long[] values, int size) { 591 if (values == null) { 592 out.writeInt(-1); 593 return; 594 } 595 if (size > values.length) { 596 throw new IllegalArgumentException("size larger than length"); 597 } 598 out.writeInt(size); 599 for (int i = 0; i < size; i++) { 600 out.writeLong(values[i]); 601 } 602 } 603 } 604 605} 606