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) == 0 ? 1 : 294 (entry.rxBytes + entry.txBytes); 295 final long rawRxBytes = entry.rxBytes == 0 ? 1 : entry.rxBytes; 296 final long rawTxBytes = entry.txBytes == 0 ? 1 : entry.txBytes; 297 final long targetBytes = augmentPlan.getDataUsageBytes(); 298 299 final long targetRxBytes = multiplySafeByRational(targetBytes, rawRxBytes, rawBytes); 300 final long targetTxBytes = multiplySafeByRational(targetBytes, rawTxBytes, rawBytes); 301 302 303 // Scale all matching buckets to reach anchor target 304 final long beforeTotal = combined.getTotalBytes(); 305 for (int i = 0; i < combined.size(); i++) { 306 combined.getValues(i, entry); 307 if (entry.bucketStart >= augmentStart 308 && entry.bucketStart + entry.bucketDuration <= augmentEnd) { 309 entry.rxBytes = multiplySafeByRational( 310 targetRxBytes, entry.rxBytes, rawRxBytes); 311 entry.txBytes = multiplySafeByRational( 312 targetTxBytes, entry.txBytes, rawTxBytes); 313 // We purposefully clear out packet counters to indicate 314 // that this data has been augmented. 315 entry.rxPackets = 0; 316 entry.txPackets = 0; 317 combined.setValues(i, entry); 318 } 319 } 320 321 final long deltaTotal = combined.getTotalBytes() - beforeTotal; 322 if (deltaTotal != 0) { 323 Slog.d(TAG, "Augmented network usage by " + deltaTotal + " bytes"); 324 } 325 326 // Finally we can slice data as originally requested 327 final NetworkStatsHistory sliced = new NetworkStatsHistory( 328 mBucketDuration, bucketEstimate, fields); 329 sliced.recordHistory(combined, start, end); 330 return sliced; 331 } else { 332 return combined; 333 } 334 } 335 336 /** 337 * Summarize all {@link NetworkStatsHistory} in this collection which match 338 * the requested parameters. 339 */ getSummary(NetworkTemplate template, long start, long end, @NetworkStatsAccess.Level int accessLevel, int callerUid)340 public NetworkStats getSummary(NetworkTemplate template, long start, long end, 341 @NetworkStatsAccess.Level int accessLevel, int callerUid) { 342 final long now = System.currentTimeMillis(); 343 344 final NetworkStats stats = new NetworkStats(end - start, 24); 345 346 // shortcut when we know stats will be empty 347 if (start == end) return stats; 348 349 final NetworkStats.Entry entry = new NetworkStats.Entry(); 350 NetworkStatsHistory.Entry historyEntry = null; 351 352 for (int i = 0; i < mStats.size(); i++) { 353 final Key key = mStats.keyAt(i); 354 if (templateMatches(template, key.ident) 355 && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel) 356 && key.set < NetworkStats.SET_DEBUG_START) { 357 final NetworkStatsHistory value = mStats.valueAt(i); 358 historyEntry = value.getValues(start, end, now, historyEntry); 359 360 entry.iface = IFACE_ALL; 361 entry.uid = key.uid; 362 entry.set = key.set; 363 entry.tag = key.tag; 364 entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork() ? 365 DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO; 366 entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO; 367 entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO; 368 entry.rxBytes = historyEntry.rxBytes; 369 entry.rxPackets = historyEntry.rxPackets; 370 entry.txBytes = historyEntry.txBytes; 371 entry.txPackets = historyEntry.txPackets; 372 entry.operations = historyEntry.operations; 373 374 if (!entry.isEmpty()) { 375 stats.combineValues(entry); 376 } 377 } 378 } 379 380 return stats; 381 } 382 383 /** 384 * Record given {@link android.net.NetworkStats.Entry} into this collection. 385 */ recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, long end, NetworkStats.Entry entry)386 public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, 387 long end, NetworkStats.Entry entry) { 388 final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag); 389 history.recordData(start, end, entry); 390 noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes); 391 } 392 393 /** 394 * Record given {@link NetworkStatsHistory} into this collection. 395 */ recordHistory(Key key, NetworkStatsHistory history)396 private void recordHistory(Key key, NetworkStatsHistory history) { 397 if (history.size() == 0) return; 398 noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes()); 399 400 NetworkStatsHistory target = mStats.get(key); 401 if (target == null) { 402 target = new NetworkStatsHistory(history.getBucketDuration()); 403 mStats.put(key, target); 404 } 405 target.recordEntireHistory(history); 406 } 407 408 /** 409 * Record all {@link NetworkStatsHistory} contained in the given collection 410 * into this collection. 411 */ recordCollection(NetworkStatsCollection another)412 public void recordCollection(NetworkStatsCollection another) { 413 for (int i = 0; i < another.mStats.size(); i++) { 414 final Key key = another.mStats.keyAt(i); 415 final NetworkStatsHistory value = another.mStats.valueAt(i); 416 recordHistory(key, value); 417 } 418 } 419 findOrCreateHistory( NetworkIdentitySet ident, int uid, int set, int tag)420 private NetworkStatsHistory findOrCreateHistory( 421 NetworkIdentitySet ident, int uid, int set, int tag) { 422 final Key key = new Key(ident, uid, set, tag); 423 final NetworkStatsHistory existing = mStats.get(key); 424 425 // update when no existing, or when bucket duration changed 426 NetworkStatsHistory updated = null; 427 if (existing == null) { 428 updated = new NetworkStatsHistory(mBucketDuration, 10); 429 } else if (existing.getBucketDuration() != mBucketDuration) { 430 updated = new NetworkStatsHistory(existing, mBucketDuration); 431 } 432 433 if (updated != null) { 434 mStats.put(key, updated); 435 return updated; 436 } else { 437 return existing; 438 } 439 } 440 441 @Override read(InputStream in)442 public void read(InputStream in) throws IOException { 443 final FastDataInput dataIn = new FastDataInput(in, BUFFER_SIZE); 444 read(dataIn); 445 } 446 read(DataInput in)447 private void read(DataInput in) throws IOException { 448 // verify file magic header intact 449 final int magic = in.readInt(); 450 if (magic != FILE_MAGIC) { 451 throw new ProtocolException("unexpected magic: " + magic); 452 } 453 454 final int version = in.readInt(); 455 switch (version) { 456 case VERSION_UNIFIED_INIT: { 457 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) 458 final int identSize = in.readInt(); 459 for (int i = 0; i < identSize; i++) { 460 final NetworkIdentitySet ident = new NetworkIdentitySet(in); 461 462 final int size = in.readInt(); 463 for (int j = 0; j < size; j++) { 464 final int uid = in.readInt(); 465 final int set = in.readInt(); 466 final int tag = in.readInt(); 467 468 final Key key = new Key(ident, uid, set, tag); 469 final NetworkStatsHistory history = new NetworkStatsHistory(in); 470 recordHistory(key, history); 471 } 472 } 473 break; 474 } 475 default: { 476 throw new ProtocolException("unexpected version: " + version); 477 } 478 } 479 } 480 481 @Override write(OutputStream out)482 public void write(OutputStream out) throws IOException { 483 final FastDataOutput dataOut = new FastDataOutput(out, BUFFER_SIZE); 484 write(dataOut); 485 dataOut.flush(); 486 } 487 write(DataOutput out)488 private void write(DataOutput out) throws IOException { 489 // cluster key lists grouped by ident 490 final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap(); 491 for (Key key : mStats.keySet()) { 492 ArrayList<Key> keys = keysByIdent.get(key.ident); 493 if (keys == null) { 494 keys = Lists.newArrayList(); 495 keysByIdent.put(key.ident, keys); 496 } 497 keys.add(key); 498 } 499 500 out.writeInt(FILE_MAGIC); 501 out.writeInt(VERSION_UNIFIED_INIT); 502 503 out.writeInt(keysByIdent.size()); 504 for (NetworkIdentitySet ident : keysByIdent.keySet()) { 505 final ArrayList<Key> keys = keysByIdent.get(ident); 506 ident.writeToStream(out); 507 508 out.writeInt(keys.size()); 509 for (Key key : keys) { 510 final NetworkStatsHistory history = mStats.get(key); 511 out.writeInt(key.uid); 512 out.writeInt(key.set); 513 out.writeInt(key.tag); 514 history.writeToStream(out); 515 } 516 } 517 } 518 519 @Deprecated readLegacyNetwork(File file)520 public void readLegacyNetwork(File file) throws IOException { 521 final AtomicFile inputFile = new AtomicFile(file); 522 523 DataInputStream in = null; 524 try { 525 in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); 526 527 // verify file magic header intact 528 final int magic = in.readInt(); 529 if (magic != FILE_MAGIC) { 530 throw new ProtocolException("unexpected magic: " + magic); 531 } 532 533 final int version = in.readInt(); 534 switch (version) { 535 case VERSION_NETWORK_INIT: { 536 // network := size *(NetworkIdentitySet NetworkStatsHistory) 537 final int size = in.readInt(); 538 for (int i = 0; i < size; i++) { 539 final NetworkIdentitySet ident = new NetworkIdentitySet(in); 540 final NetworkStatsHistory history = new NetworkStatsHistory(in); 541 542 final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE); 543 recordHistory(key, history); 544 } 545 break; 546 } 547 default: { 548 throw new ProtocolException("unexpected version: " + version); 549 } 550 } 551 } catch (FileNotFoundException e) { 552 // missing stats is okay, probably first boot 553 } finally { 554 IoUtils.closeQuietly(in); 555 } 556 } 557 558 @Deprecated readLegacyUid(File file, boolean onlyTags)559 public void readLegacyUid(File file, boolean onlyTags) throws IOException { 560 final AtomicFile inputFile = new AtomicFile(file); 561 562 DataInputStream in = null; 563 try { 564 in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); 565 566 // verify file magic header intact 567 final int magic = in.readInt(); 568 if (magic != FILE_MAGIC) { 569 throw new ProtocolException("unexpected magic: " + magic); 570 } 571 572 final int version = in.readInt(); 573 switch (version) { 574 case VERSION_UID_INIT: { 575 // uid := size *(UID NetworkStatsHistory) 576 577 // drop this data version, since we don't have a good 578 // mapping into NetworkIdentitySet. 579 break; 580 } 581 case VERSION_UID_WITH_IDENT: { 582 // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory)) 583 584 // drop this data version, since this version only existed 585 // for a short time. 586 break; 587 } 588 case VERSION_UID_WITH_TAG: 589 case VERSION_UID_WITH_SET: { 590 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) 591 final int identSize = in.readInt(); 592 for (int i = 0; i < identSize; i++) { 593 final NetworkIdentitySet ident = new NetworkIdentitySet(in); 594 595 final int size = in.readInt(); 596 for (int j = 0; j < size; j++) { 597 final int uid = in.readInt(); 598 final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt() 599 : SET_DEFAULT; 600 final int tag = in.readInt(); 601 602 final Key key = new Key(ident, uid, set, tag); 603 final NetworkStatsHistory history = new NetworkStatsHistory(in); 604 605 if ((tag == TAG_NONE) != onlyTags) { 606 recordHistory(key, history); 607 } 608 } 609 } 610 break; 611 } 612 default: { 613 throw new ProtocolException("unexpected version: " + version); 614 } 615 } 616 } catch (FileNotFoundException e) { 617 // missing stats is okay, probably first boot 618 } finally { 619 IoUtils.closeQuietly(in); 620 } 621 } 622 623 /** 624 * Remove any {@link NetworkStatsHistory} attributed to the requested UID, 625 * moving any {@link NetworkStats#TAG_NONE} series to 626 * {@link TrafficStats#UID_REMOVED}. 627 */ removeUids(int[] uids)628 public void removeUids(int[] uids) { 629 final ArrayList<Key> knownKeys = Lists.newArrayList(); 630 knownKeys.addAll(mStats.keySet()); 631 632 // migrate all UID stats into special "removed" bucket 633 for (Key key : knownKeys) { 634 if (ArrayUtils.contains(uids, key.uid)) { 635 // only migrate combined TAG_NONE history 636 if (key.tag == TAG_NONE) { 637 final NetworkStatsHistory uidHistory = mStats.get(key); 638 final NetworkStatsHistory removedHistory = findOrCreateHistory( 639 key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); 640 removedHistory.recordEntireHistory(uidHistory); 641 } 642 mStats.remove(key); 643 mDirty = true; 644 } 645 } 646 } 647 noteRecordedHistory(long startMillis, long endMillis, long totalBytes)648 private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) { 649 if (startMillis < mStartMillis) mStartMillis = startMillis; 650 if (endMillis > mEndMillis) mEndMillis = endMillis; 651 mTotalBytes += totalBytes; 652 mDirty = true; 653 } 654 estimateBuckets()655 private int estimateBuckets() { 656 return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5) 657 / mBucketDuration); 658 } 659 getSortedKeys()660 private ArrayList<Key> getSortedKeys() { 661 final ArrayList<Key> keys = Lists.newArrayList(); 662 keys.addAll(mStats.keySet()); 663 Collections.sort(keys); 664 return keys; 665 } 666 dump(IndentingPrintWriter pw)667 public void dump(IndentingPrintWriter pw) { 668 for (Key key : getSortedKeys()) { 669 pw.print("ident="); pw.print(key.ident.toString()); 670 pw.print(" uid="); pw.print(key.uid); 671 pw.print(" set="); pw.print(NetworkStats.setToString(key.set)); 672 pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag)); 673 674 final NetworkStatsHistory history = mStats.get(key); 675 pw.increaseIndent(); 676 history.dump(pw, true); 677 pw.decreaseIndent(); 678 } 679 } 680 dumpDebug(ProtoOutputStream proto, long tag)681 public void dumpDebug(ProtoOutputStream proto, long tag) { 682 final long start = proto.start(tag); 683 684 for (Key key : getSortedKeys()) { 685 final long startStats = proto.start(NetworkStatsCollectionProto.STATS); 686 687 // Key 688 final long startKey = proto.start(NetworkStatsCollectionStatsProto.KEY); 689 key.ident.dumpDebug(proto, NetworkStatsCollectionKeyProto.IDENTITY); 690 proto.write(NetworkStatsCollectionKeyProto.UID, key.uid); 691 proto.write(NetworkStatsCollectionKeyProto.SET, key.set); 692 proto.write(NetworkStatsCollectionKeyProto.TAG, key.tag); 693 proto.end(startKey); 694 695 // Value 696 final NetworkStatsHistory history = mStats.get(key); 697 history.dumpDebug(proto, NetworkStatsCollectionStatsProto.HISTORY); 698 proto.end(startStats); 699 } 700 701 proto.end(start); 702 } 703 dumpCheckin(PrintWriter pw, long start, long end)704 public void dumpCheckin(PrintWriter pw, long start, long end) { 705 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell"); 706 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi"); 707 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth"); 708 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt"); 709 } 710 711 /** 712 * Dump all contained stats that match requested parameters, but group 713 * together all matching {@link NetworkTemplate} under a single prefix. 714 */ dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, String groupPrefix)715 private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, 716 String groupPrefix) { 717 final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>(); 718 719 // Walk through all history, grouping by matching network templates 720 for (int i = 0; i < mStats.size(); i++) { 721 final Key key = mStats.keyAt(i); 722 final NetworkStatsHistory value = mStats.valueAt(i); 723 724 if (!templateMatches(groupTemplate, key.ident)) continue; 725 if (key.set >= NetworkStats.SET_DEBUG_START) continue; 726 727 final Key groupKey = new Key(null, key.uid, key.set, key.tag); 728 NetworkStatsHistory groupHistory = grouped.get(groupKey); 729 if (groupHistory == null) { 730 groupHistory = new NetworkStatsHistory(value.getBucketDuration()); 731 grouped.put(groupKey, groupHistory); 732 } 733 groupHistory.recordHistory(value, start, end); 734 } 735 736 for (int i = 0; i < grouped.size(); i++) { 737 final Key key = grouped.keyAt(i); 738 final NetworkStatsHistory value = grouped.valueAt(i); 739 740 if (value.size() == 0) continue; 741 742 pw.print("c,"); 743 pw.print(groupPrefix); pw.print(','); 744 pw.print(key.uid); pw.print(','); 745 pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(','); 746 pw.print(key.tag); 747 pw.println(); 748 749 value.dumpCheckin(pw); 750 } 751 } 752 753 /** 754 * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} 755 * in the given {@link NetworkIdentitySet}. 756 */ templateMatches(NetworkTemplate template, NetworkIdentitySet identSet)757 private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) { 758 for (NetworkIdentity ident : identSet) { 759 if (template.matches(ident)) { 760 return true; 761 } 762 } 763 return false; 764 } 765 766 private static class Key implements Comparable<Key> { 767 public final NetworkIdentitySet ident; 768 public final int uid; 769 public final int set; 770 public final int tag; 771 772 private final int hashCode; 773 Key(NetworkIdentitySet ident, int uid, int set, int tag)774 public Key(NetworkIdentitySet ident, int uid, int set, int tag) { 775 this.ident = ident; 776 this.uid = uid; 777 this.set = set; 778 this.tag = tag; 779 hashCode = Objects.hash(ident, uid, set, tag); 780 } 781 782 @Override hashCode()783 public int hashCode() { 784 return hashCode; 785 } 786 787 @Override equals(Object obj)788 public boolean equals(Object obj) { 789 if (obj instanceof Key) { 790 final Key key = (Key) obj; 791 return uid == key.uid && set == key.set && tag == key.tag 792 && Objects.equals(ident, key.ident); 793 } 794 return false; 795 } 796 797 @Override compareTo(Key another)798 public int compareTo(Key another) { 799 int res = 0; 800 if (ident != null && another.ident != null) { 801 res = ident.compareTo(another.ident); 802 } 803 if (res == 0) { 804 res = Integer.compare(uid, another.uid); 805 } 806 if (res == 0) { 807 res = Integer.compare(set, another.set); 808 } 809 if (res == 0) { 810 res = Integer.compare(tag, another.tag); 811 } 812 return res; 813 } 814 } 815 } 816