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 106 public NetworkStatsCollection(long bucketDuration) { 107 mBucketDuration = bucketDuration; 108 reset(); 109 } 110 111 public void clear() { 112 reset(); 113 } 114 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 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 */ 131 public long getFirstAtomicBucketMillis() { 132 if (mStartMillis == Long.MAX_VALUE) { 133 return Long.MAX_VALUE; 134 } else { 135 return mStartMillis + mBucketDuration; 136 } 137 } 138 139 public long getEndMillis() { 140 return mEndMillis; 141 } 142 143 public long getTotalBytes() { 144 return mTotalBytes; 145 } 146 147 public boolean isDirty() { 148 return mDirty; 149 } 150 151 public void clearDirty() { 152 mDirty = false; 153 } 154 155 public boolean isEmpty() { 156 return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; 157 } 158 159 @VisibleForTesting 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 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 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 217 public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) { 218 return getRelevantUids(accessLevel, Binder.getCallingUid()); 219 } 220 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 457 public void read(InputStream in) throws IOException { 458 read(new DataInputStream(in)); 459 } 460 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 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 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 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 */ 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 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 664 private int estimateBuckets() { 665 return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5) 666 / mBucketDuration); 667 } 668 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 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 690 public void writeToProto(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.writeToProto(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.writeToProto(proto, NetworkStatsCollectionStatsProto.HISTORY); 707 proto.end(startStats); 708 } 709 710 proto.end(start); 711 } 712 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 */ 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 */ 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 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 792 public int hashCode() { 793 return hashCode; 794 } 795 796 @Override 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 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