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