1 /* 2 * Copyright (C) 2012 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 com.android.server.net; 18 19 import static android.net.NetworkStats.DEFAULT_NETWORK_NO; 20 import static android.net.NetworkStats.DEFAULT_NETWORK_YES; 21 import static android.net.NetworkStats.IFACE_ALL; 22 import static android.net.NetworkStats.METERED_NO; 23 import static android.net.NetworkStats.METERED_YES; 24 import static android.net.NetworkStats.ROAMING_NO; 25 import static android.net.NetworkStats.ROAMING_YES; 26 import static android.net.NetworkStats.SET_ALL; 27 import static android.net.NetworkStats.SET_DEFAULT; 28 import static android.net.NetworkStats.TAG_NONE; 29 import static android.net.NetworkStats.UID_ALL; 30 import static android.net.TrafficStats.UID_REMOVED; 31 import static android.net.NetworkUtils.multiplySafeByRational; 32 import static android.text.format.DateUtils.WEEK_IN_MILLIS; 33 34 import static com.android.server.net.NetworkStatsService.TAG; 35 36 import android.net.NetworkIdentity; 37 import android.net.NetworkStats; 38 import android.net.NetworkStatsHistory; 39 import android.net.NetworkTemplate; 40 import android.net.TrafficStats; 41 import android.os.Binder; 42 import android.service.NetworkStatsCollectionKeyProto; 43 import android.service.NetworkStatsCollectionProto; 44 import android.service.NetworkStatsCollectionStatsProto; 45 import android.telephony.SubscriptionPlan; 46 import android.text.format.DateUtils; 47 import android.util.ArrayMap; 48 import android.util.AtomicFile; 49 import android.util.IntArray; 50 import android.util.MathUtils; 51 import android.util.Range; 52 import android.util.Slog; 53 import android.util.proto.ProtoOutputStream; 54 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.internal.util.ArrayUtils; 57 import com.android.internal.util.FileRotator; 58 import com.android.internal.util.IndentingPrintWriter; 59 60 import libcore.io.IoUtils; 61 62 import com.google.android.collect.Lists; 63 import com.google.android.collect.Maps; 64 65 import java.io.BufferedInputStream; 66 import java.io.DataInputStream; 67 import java.io.DataOutputStream; 68 import java.io.File; 69 import java.io.FileNotFoundException; 70 import java.io.IOException; 71 import java.io.InputStream; 72 import java.io.PrintWriter; 73 import java.net.ProtocolException; 74 import java.time.ZonedDateTime; 75 import java.util.ArrayList; 76 import java.util.Collections; 77 import java.util.HashMap; 78 import java.util.Iterator; 79 import java.util.Objects; 80 81 /** 82 * Collection of {@link NetworkStatsHistory}, stored based on combined key of 83 * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself. 84 */ 85 public class NetworkStatsCollection implements FileRotator.Reader { 86 /** File header magic number: "ANET" */ 87 private static final int FILE_MAGIC = 0x414E4554; 88 89 private static final int VERSION_NETWORK_INIT = 1; 90 91 private static final int VERSION_UID_INIT = 1; 92 private static final int VERSION_UID_WITH_IDENT = 2; 93 private static final int VERSION_UID_WITH_TAG = 3; 94 private static final int VERSION_UID_WITH_SET = 4; 95 96 private static final int VERSION_UNIFIED_INIT = 16; 97 98 private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>(); 99 100 private final long mBucketDuration; 101 102 private long mStartMillis; 103 private long mEndMillis; 104 private long mTotalBytes; 105 private boolean mDirty; 106 NetworkStatsCollection(long bucketDuration)107 public NetworkStatsCollection(long bucketDuration) { 108 mBucketDuration = bucketDuration; 109 reset(); 110 } 111 clear()112 public void clear() { 113 reset(); 114 } 115 reset()116 public void reset() { 117 mStats.clear(); 118 mStartMillis = Long.MAX_VALUE; 119 mEndMillis = Long.MIN_VALUE; 120 mTotalBytes = 0; 121 mDirty = false; 122 } 123 getStartMillis()124 public long getStartMillis() { 125 return mStartMillis; 126 } 127 128 /** 129 * Return first atomic bucket in this collection, which is more conservative 130 * than {@link #mStartMillis}. 131 */ getFirstAtomicBucketMillis()132 public long getFirstAtomicBucketMillis() { 133 if (mStartMillis == Long.MAX_VALUE) { 134 return Long.MAX_VALUE; 135 } else { 136 return mStartMillis + mBucketDuration; 137 } 138 } 139 getEndMillis()140 public long getEndMillis() { 141 return mEndMillis; 142 } 143 getTotalBytes()144 public long getTotalBytes() { 145 return mTotalBytes; 146 } 147 isDirty()148 public boolean isDirty() { 149 return mDirty; 150 } 151 clearDirty()152 public void clearDirty() { 153 mDirty = false; 154 } 155 isEmpty()156 public boolean isEmpty() { 157 return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; 158 } 159 160 @VisibleForTesting roundUp(long time)161 public long roundUp(long time) { 162 if (time == Long.MIN_VALUE || time == Long.MAX_VALUE 163 || time == SubscriptionPlan.TIME_UNKNOWN) { 164 return time; 165 } else { 166 final long mod = time % mBucketDuration; 167 if (mod > 0) { 168 time -= mod; 169 time += mBucketDuration; 170 } 171 return time; 172 } 173 } 174 175 @VisibleForTesting roundDown(long time)176 public long roundDown(long time) { 177 if (time == Long.MIN_VALUE || time == Long.MAX_VALUE 178 || time == SubscriptionPlan.TIME_UNKNOWN) { 179 return time; 180 } else { 181 final long mod = time % mBucketDuration; 182 if (mod > 0) { 183 time -= mod; 184 } 185 return time; 186 } 187 } 188 getRelevantUids(@etworkStatsAccess.Level int accessLevel)189 public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) { 190 return getRelevantUids(accessLevel, Binder.getCallingUid()); 191 } 192 getRelevantUids(@etworkStatsAccess.Level int accessLevel, final int callerUid)193 public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel, 194 final int callerUid) { 195 IntArray uids = new IntArray(); 196 for (int i = 0; i < mStats.size(); i++) { 197 final Key key = mStats.keyAt(i); 198 if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) { 199 int j = uids.binarySearch(key.uid); 200 201 if (j < 0) { 202 j = ~j; 203 uids.add(j, key.uid); 204 } 205 } 206 } 207 return uids.toArray(); 208 } 209 210 /** 211 * Combine all {@link NetworkStatsHistory} in this collection which match 212 * the requested parameters. 213 */ getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan, int uid, int set, int tag, int fields, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid)214 public NetworkStatsHistory getHistory(NetworkTemplate template, SubscriptionPlan augmentPlan, 215 int uid, int set, int tag, int fields, long start, long end, 216 @NetworkStatsAccess.Level int accessLevel, int callerUid) { 217 if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) { 218 throw new SecurityException("Network stats history of uid " + uid 219 + " is forbidden for caller " + callerUid); 220 } 221 222 // 180 days of history should be enough for anyone; if we end up needing 223 // more, we'll dynamically grow the history object. 224 final int bucketEstimate = (int) MathUtils.constrain(((end - start) / mBucketDuration), 0, 225 (180 * DateUtils.DAY_IN_MILLIS) / mBucketDuration); 226 final NetworkStatsHistory combined = new NetworkStatsHistory( 227 mBucketDuration, bucketEstimate, fields); 228 229 // shortcut when we know stats will be empty 230 if (start == end) return combined; 231 232 // Figure out the window of time that we should be augmenting (if any) 233 long augmentStart = SubscriptionPlan.TIME_UNKNOWN; 234 long augmentEnd = (augmentPlan != null) ? augmentPlan.getDataUsageTime() 235 : SubscriptionPlan.TIME_UNKNOWN; 236 // And if augmenting, we might need to collect more data to adjust with 237 long collectStart = start; 238 long collectEnd = end; 239 240 if (augmentEnd != SubscriptionPlan.TIME_UNKNOWN) { 241 final Iterator<Range<ZonedDateTime>> it = augmentPlan.cycleIterator(); 242 while (it.hasNext()) { 243 final Range<ZonedDateTime> cycle = it.next(); 244 final long cycleStart = cycle.getLower().toInstant().toEpochMilli(); 245 final long cycleEnd = cycle.getUpper().toInstant().toEpochMilli(); 246 if (cycleStart <= augmentEnd && augmentEnd < cycleEnd) { 247 augmentStart = cycleStart; 248 collectStart = Long.min(collectStart, augmentStart); 249 collectEnd = Long.max(collectEnd, augmentEnd); 250 break; 251 } 252 } 253 } 254 255 if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) { 256 // Shrink augmentation window so we don't risk undercounting. 257 augmentStart = roundUp(augmentStart); 258 augmentEnd = roundDown(augmentEnd); 259 // Grow collection window so we get all the stats needed. 260 collectStart = roundDown(collectStart); 261 collectEnd = roundUp(collectEnd); 262 } 263 264 for (int i = 0; i < mStats.size(); i++) { 265 final Key key = mStats.keyAt(i); 266 if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag 267 && templateMatches(template, key.ident)) { 268 final NetworkStatsHistory value = mStats.valueAt(i); 269 combined.recordHistory(value, collectStart, collectEnd); 270 } 271 } 272 273 if (augmentStart != SubscriptionPlan.TIME_UNKNOWN) { 274 final NetworkStatsHistory.Entry entry = combined.getValues( 275 augmentStart, augmentEnd, null); 276 277 // If we don't have any recorded data for this time period, give 278 // ourselves something to scale with. 279 if (entry.rxBytes == 0 || entry.txBytes == 0) { 280 combined.recordData(augmentStart, augmentEnd, 281 new NetworkStats.Entry(1, 0, 1, 0, 0)); 282 combined.getValues(augmentStart, augmentEnd, entry); 283 } 284 285 final long rawBytes = entry.rxBytes + entry.txBytes; 286 final long rawRxBytes = entry.rxBytes == 0 ? 1 : entry.rxBytes; 287 final long rawTxBytes = entry.txBytes == 0 ? 1 : entry.txBytes; 288 final long targetBytes = augmentPlan.getDataUsageBytes(); 289 290 final long targetRxBytes = multiplySafeByRational(targetBytes, rawRxBytes, rawBytes); 291 final long targetTxBytes = multiplySafeByRational(targetBytes, rawTxBytes, rawBytes); 292 293 294 // Scale all matching buckets to reach anchor target 295 final long beforeTotal = combined.getTotalBytes(); 296 for (int i = 0; i < combined.size(); i++) { 297 combined.getValues(i, entry); 298 if (entry.bucketStart >= augmentStart 299 && entry.bucketStart + entry.bucketDuration <= augmentEnd) { 300 entry.rxBytes = multiplySafeByRational( 301 targetRxBytes, entry.rxBytes, rawRxBytes); 302 entry.txBytes = multiplySafeByRational( 303 targetTxBytes, entry.txBytes, rawTxBytes); 304 // We purposefully clear out packet counters to indicate 305 // that this data has been augmented. 306 entry.rxPackets = 0; 307 entry.txPackets = 0; 308 combined.setValues(i, entry); 309 } 310 } 311 312 final long deltaTotal = combined.getTotalBytes() - beforeTotal; 313 if (deltaTotal != 0) { 314 Slog.d(TAG, "Augmented network usage by " + deltaTotal + " bytes"); 315 } 316 317 // Finally we can slice data as originally requested 318 final NetworkStatsHistory sliced = new NetworkStatsHistory( 319 mBucketDuration, bucketEstimate, fields); 320 sliced.recordHistory(combined, start, end); 321 return sliced; 322 } else { 323 return combined; 324 } 325 } 326 327 /** 328 * Summarize all {@link NetworkStatsHistory} in this collection which match 329 * the requested parameters. 330 */ getSummary(NetworkTemplate template, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid)331 public NetworkStats getSummary(NetworkTemplate template, long start, long end, 332 @NetworkStatsAccess.Level int accessLevel, int callerUid) { 333 final long now = System.currentTimeMillis(); 334 335 final NetworkStats stats = new NetworkStats(end - start, 24); 336 337 // shortcut when we know stats will be empty 338 if (start == end) return stats; 339 340 final NetworkStats.Entry entry = new NetworkStats.Entry(); 341 NetworkStatsHistory.Entry historyEntry = null; 342 343 for (int i = 0; i < mStats.size(); i++) { 344 final Key key = mStats.keyAt(i); 345 if (templateMatches(template, key.ident) 346 && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel) 347 && key.set < NetworkStats.SET_DEBUG_START) { 348 final NetworkStatsHistory value = mStats.valueAt(i); 349 historyEntry = value.getValues(start, end, now, historyEntry); 350 351 entry.iface = IFACE_ALL; 352 entry.uid = key.uid; 353 entry.set = key.set; 354 entry.tag = key.tag; 355 entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork() ? 356 DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO; 357 entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO; 358 entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO; 359 entry.rxBytes = historyEntry.rxBytes; 360 entry.rxPackets = historyEntry.rxPackets; 361 entry.txBytes = historyEntry.txBytes; 362 entry.txPackets = historyEntry.txPackets; 363 entry.operations = historyEntry.operations; 364 365 if (!entry.isEmpty()) { 366 stats.combineValues(entry); 367 } 368 } 369 } 370 371 return stats; 372 } 373 374 /** 375 * Record given {@link android.net.NetworkStats.Entry} into this collection. 376 */ recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, long end, NetworkStats.Entry entry)377 public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, 378 long end, NetworkStats.Entry entry) { 379 final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag); 380 history.recordData(start, end, entry); 381 noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes); 382 } 383 384 /** 385 * Record given {@link NetworkStatsHistory} into this collection. 386 */ recordHistory(Key key, NetworkStatsHistory history)387 private void recordHistory(Key key, NetworkStatsHistory history) { 388 if (history.size() == 0) return; 389 noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes()); 390 391 NetworkStatsHistory target = mStats.get(key); 392 if (target == null) { 393 target = new NetworkStatsHistory(history.getBucketDuration()); 394 mStats.put(key, target); 395 } 396 target.recordEntireHistory(history); 397 } 398 399 /** 400 * Record all {@link NetworkStatsHistory} contained in the given collection 401 * into this collection. 402 */ recordCollection(NetworkStatsCollection another)403 public void recordCollection(NetworkStatsCollection another) { 404 for (int i = 0; i < another.mStats.size(); i++) { 405 final Key key = another.mStats.keyAt(i); 406 final NetworkStatsHistory value = another.mStats.valueAt(i); 407 recordHistory(key, value); 408 } 409 } 410 findOrCreateHistory( NetworkIdentitySet ident, int uid, int set, int tag)411 private NetworkStatsHistory findOrCreateHistory( 412 NetworkIdentitySet ident, int uid, int set, int tag) { 413 final Key key = new Key(ident, uid, set, tag); 414 final NetworkStatsHistory existing = mStats.get(key); 415 416 // update when no existing, or when bucket duration changed 417 NetworkStatsHistory updated = null; 418 if (existing == null) { 419 updated = new NetworkStatsHistory(mBucketDuration, 10); 420 } else if (existing.getBucketDuration() != mBucketDuration) { 421 updated = new NetworkStatsHistory(existing, mBucketDuration); 422 } 423 424 if (updated != null) { 425 mStats.put(key, updated); 426 return updated; 427 } else { 428 return existing; 429 } 430 } 431 432 @Override read(InputStream in)433 public void read(InputStream in) throws IOException { 434 read(new DataInputStream(in)); 435 } 436 read(DataInputStream in)437 public void read(DataInputStream in) throws IOException { 438 // verify file magic header intact 439 final int magic = in.readInt(); 440 if (magic != FILE_MAGIC) { 441 throw new ProtocolException("unexpected magic: " + magic); 442 } 443 444 final int version = in.readInt(); 445 switch (version) { 446 case VERSION_UNIFIED_INIT: { 447 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) 448 final int identSize = in.readInt(); 449 for (int i = 0; i < identSize; i++) { 450 final NetworkIdentitySet ident = new NetworkIdentitySet(in); 451 452 final int size = in.readInt(); 453 for (int j = 0; j < size; j++) { 454 final int uid = in.readInt(); 455 final int set = in.readInt(); 456 final int tag = in.readInt(); 457 458 final Key key = new Key(ident, uid, set, tag); 459 final NetworkStatsHistory history = new NetworkStatsHistory(in); 460 recordHistory(key, history); 461 } 462 } 463 break; 464 } 465 default: { 466 throw new ProtocolException("unexpected version: " + version); 467 } 468 } 469 } 470 write(DataOutputStream out)471 public void write(DataOutputStream out) throws IOException { 472 // cluster key lists grouped by ident 473 final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap(); 474 for (Key key : mStats.keySet()) { 475 ArrayList<Key> keys = keysByIdent.get(key.ident); 476 if (keys == null) { 477 keys = Lists.newArrayList(); 478 keysByIdent.put(key.ident, keys); 479 } 480 keys.add(key); 481 } 482 483 out.writeInt(FILE_MAGIC); 484 out.writeInt(VERSION_UNIFIED_INIT); 485 486 out.writeInt(keysByIdent.size()); 487 for (NetworkIdentitySet ident : keysByIdent.keySet()) { 488 final ArrayList<Key> keys = keysByIdent.get(ident); 489 ident.writeToStream(out); 490 491 out.writeInt(keys.size()); 492 for (Key key : keys) { 493 final NetworkStatsHistory history = mStats.get(key); 494 out.writeInt(key.uid); 495 out.writeInt(key.set); 496 out.writeInt(key.tag); 497 history.writeToStream(out); 498 } 499 } 500 501 out.flush(); 502 } 503 504 @Deprecated readLegacyNetwork(File file)505 public void readLegacyNetwork(File file) throws IOException { 506 final AtomicFile inputFile = new AtomicFile(file); 507 508 DataInputStream in = null; 509 try { 510 in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); 511 512 // verify file magic header intact 513 final int magic = in.readInt(); 514 if (magic != FILE_MAGIC) { 515 throw new ProtocolException("unexpected magic: " + magic); 516 } 517 518 final int version = in.readInt(); 519 switch (version) { 520 case VERSION_NETWORK_INIT: { 521 // network := size *(NetworkIdentitySet NetworkStatsHistory) 522 final int size = in.readInt(); 523 for (int i = 0; i < size; i++) { 524 final NetworkIdentitySet ident = new NetworkIdentitySet(in); 525 final NetworkStatsHistory history = new NetworkStatsHistory(in); 526 527 final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE); 528 recordHistory(key, history); 529 } 530 break; 531 } 532 default: { 533 throw new ProtocolException("unexpected version: " + version); 534 } 535 } 536 } catch (FileNotFoundException e) { 537 // missing stats is okay, probably first boot 538 } finally { 539 IoUtils.closeQuietly(in); 540 } 541 } 542 543 @Deprecated readLegacyUid(File file, boolean onlyTags)544 public void readLegacyUid(File file, boolean onlyTags) throws IOException { 545 final AtomicFile inputFile = new AtomicFile(file); 546 547 DataInputStream in = null; 548 try { 549 in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); 550 551 // verify file magic header intact 552 final int magic = in.readInt(); 553 if (magic != FILE_MAGIC) { 554 throw new ProtocolException("unexpected magic: " + magic); 555 } 556 557 final int version = in.readInt(); 558 switch (version) { 559 case VERSION_UID_INIT: { 560 // uid := size *(UID NetworkStatsHistory) 561 562 // drop this data version, since we don't have a good 563 // mapping into NetworkIdentitySet. 564 break; 565 } 566 case VERSION_UID_WITH_IDENT: { 567 // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory)) 568 569 // drop this data version, since this version only existed 570 // for a short time. 571 break; 572 } 573 case VERSION_UID_WITH_TAG: 574 case VERSION_UID_WITH_SET: { 575 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) 576 final int identSize = in.readInt(); 577 for (int i = 0; i < identSize; i++) { 578 final NetworkIdentitySet ident = new NetworkIdentitySet(in); 579 580 final int size = in.readInt(); 581 for (int j = 0; j < size; j++) { 582 final int uid = in.readInt(); 583 final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt() 584 : SET_DEFAULT; 585 final int tag = in.readInt(); 586 587 final Key key = new Key(ident, uid, set, tag); 588 final NetworkStatsHistory history = new NetworkStatsHistory(in); 589 590 if ((tag == TAG_NONE) != onlyTags) { 591 recordHistory(key, history); 592 } 593 } 594 } 595 break; 596 } 597 default: { 598 throw new ProtocolException("unexpected version: " + version); 599 } 600 } 601 } catch (FileNotFoundException e) { 602 // missing stats is okay, probably first boot 603 } finally { 604 IoUtils.closeQuietly(in); 605 } 606 } 607 608 /** 609 * Remove any {@link NetworkStatsHistory} attributed to the requested UID, 610 * moving any {@link NetworkStats#TAG_NONE} series to 611 * {@link TrafficStats#UID_REMOVED}. 612 */ removeUids(int[] uids)613 public void removeUids(int[] uids) { 614 final ArrayList<Key> knownKeys = Lists.newArrayList(); 615 knownKeys.addAll(mStats.keySet()); 616 617 // migrate all UID stats into special "removed" bucket 618 for (Key key : knownKeys) { 619 if (ArrayUtils.contains(uids, key.uid)) { 620 // only migrate combined TAG_NONE history 621 if (key.tag == TAG_NONE) { 622 final NetworkStatsHistory uidHistory = mStats.get(key); 623 final NetworkStatsHistory removedHistory = findOrCreateHistory( 624 key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); 625 removedHistory.recordEntireHistory(uidHistory); 626 } 627 mStats.remove(key); 628 mDirty = true; 629 } 630 } 631 } 632 noteRecordedHistory(long startMillis, long endMillis, long totalBytes)633 private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) { 634 if (startMillis < mStartMillis) mStartMillis = startMillis; 635 if (endMillis > mEndMillis) mEndMillis = endMillis; 636 mTotalBytes += totalBytes; 637 mDirty = true; 638 } 639 estimateBuckets()640 private int estimateBuckets() { 641 return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5) 642 / mBucketDuration); 643 } 644 getSortedKeys()645 private ArrayList<Key> getSortedKeys() { 646 final ArrayList<Key> keys = Lists.newArrayList(); 647 keys.addAll(mStats.keySet()); 648 Collections.sort(keys); 649 return keys; 650 } 651 dump(IndentingPrintWriter pw)652 public void dump(IndentingPrintWriter pw) { 653 for (Key key : getSortedKeys()) { 654 pw.print("ident="); pw.print(key.ident.toString()); 655 pw.print(" uid="); pw.print(key.uid); 656 pw.print(" set="); pw.print(NetworkStats.setToString(key.set)); 657 pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag)); 658 659 final NetworkStatsHistory history = mStats.get(key); 660 pw.increaseIndent(); 661 history.dump(pw, true); 662 pw.decreaseIndent(); 663 } 664 } 665 writeToProto(ProtoOutputStream proto, long tag)666 public void writeToProto(ProtoOutputStream proto, long tag) { 667 final long start = proto.start(tag); 668 669 for (Key key : getSortedKeys()) { 670 final long startStats = proto.start(NetworkStatsCollectionProto.STATS); 671 672 // Key 673 final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY); 674 key.ident.writeToProto(proto, NetworkStatsCollectionKeyProto.IDENTITY); 675 proto.write(NetworkStatsCollectionKeyProto.UID, key.uid); 676 proto.write(NetworkStatsCollectionKeyProto.SET, key.set); 677 proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag); 678 proto.end(startKey); 679 680 // Value 681 final NetworkStatsHistory history = mStats.get(key); 682 history.writeToProto(proto, NetworkStatsCollectionStatsProto.HISTORY); 683 proto.end(startStats); 684 } 685 686 proto.end(start); 687 } 688 dumpCheckin(PrintWriter pw, long start, long end)689 public void dumpCheckin(PrintWriter pw, long start, long end) { 690 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell"); 691 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi"); 692 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth"); 693 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt"); 694 } 695 696 /** 697 * Dump all contained stats that match requested parameters, but group 698 * together all matching {@link NetworkTemplate} under a single prefix. 699 */ dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, String groupPrefix)700 private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, 701 String groupPrefix) { 702 final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>(); 703 704 // Walk through all history, grouping by matching network templates 705 for (int i = 0; i < mStats.size(); i++) { 706 final Key key = mStats.keyAt(i); 707 final NetworkStatsHistory value = mStats.valueAt(i); 708 709 if (!templateMatches(groupTemplate, key.ident)) continue; 710 if (key.set >= NetworkStats.SET_DEBUG_START) continue; 711 712 final Key groupKey = new Key(null, key.uid, key.set, key.tag); 713 NetworkStatsHistory groupHistory = grouped.get(groupKey); 714 if (groupHistory == null) { 715 groupHistory = new NetworkStatsHistory(value.getBucketDuration()); 716 grouped.put(groupKey, groupHistory); 717 } 718 groupHistory.recordHistory(value, start, end); 719 } 720 721 for (int i = 0; i < grouped.size(); i++) { 722 final Key key = grouped.keyAt(i); 723 final NetworkStatsHistory value = grouped.valueAt(i); 724 725 if (value.size() == 0) continue; 726 727 pw.print("c,"); 728 pw.print(groupPrefix); pw.print(','); 729 pw.print(key.uid); pw.print(','); 730 pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(','); 731 pw.print(key.tag); 732 pw.println(); 733 734 value.dumpCheckin(pw); 735 } 736 } 737 738 /** 739 * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} 740 * in the given {@link NetworkIdentitySet}. 741 */ templateMatches(NetworkTemplate template, NetworkIdentitySet identSet)742 private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) { 743 for (NetworkIdentity ident : identSet) { 744 if (template.matches(ident)) { 745 return true; 746 } 747 } 748 return false; 749 } 750 751 private static class Key implements Comparable<Key> { 752 public final NetworkIdentitySet ident; 753 public final int uid; 754 public final int set; 755 public final int tag; 756 757 private final int hashCode; 758 Key(NetworkIdentitySet ident, int uid, int set, int tag)759 public Key(NetworkIdentitySet ident, int uid, int set, int tag) { 760 this.ident = ident; 761 this.uid = uid; 762 this.set = set; 763 this.tag = tag; 764 hashCode = Objects.hash(ident, uid, set, tag); 765 } 766 767 @Override hashCode()768 public int hashCode() { 769 return hashCode; 770 } 771 772 @Override equals(Object obj)773 public boolean equals(Object obj) { 774 if (obj instanceof Key) { 775 final Key key = (Key) obj; 776 return uid == key.uid && set == key.set && tag == key.tag 777 && Objects.equals(ident, key.ident); 778 } 779 return false; 780 } 781 782 @Override compareTo(Key another)783 public int compareTo(Key another) { 784 int res = 0; 785 if (ident != null && another.ident != null) { 786 res = ident.compareTo(another.ident); 787 } 788 if (res == 0) { 789 res = Integer.compare(uid, another.uid); 790 } 791 if (res == 0) { 792 res = Integer.compare(set, another.set); 793 } 794 if (res == 0) { 795 res = Integer.compare(tag, another.tag); 796 } 797 return res; 798 } 799 } 800 } 801