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