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 17package com.android.server.net; 18 19import static android.net.NetworkStats.IFACE_ALL; 20import static android.net.NetworkStats.ROAMING_NO; 21import static android.net.NetworkStats.ROAMING_YES; 22import static android.net.NetworkStats.SET_ALL; 23import static android.net.NetworkStats.SET_DEFAULT; 24import static android.net.NetworkStats.TAG_NONE; 25import static android.net.NetworkStats.UID_ALL; 26import static android.net.TrafficStats.UID_REMOVED; 27import static android.text.format.DateUtils.WEEK_IN_MILLIS; 28 29import android.net.NetworkIdentity; 30import android.net.NetworkStats; 31import android.net.NetworkStatsHistory; 32import android.net.NetworkTemplate; 33import android.net.TrafficStats; 34import android.os.Binder; 35import android.util.ArrayMap; 36import android.util.AtomicFile; 37import android.util.IntArray; 38 39import com.android.internal.util.ArrayUtils; 40import com.android.internal.util.FileRotator; 41import com.android.internal.util.IndentingPrintWriter; 42 43import com.google.android.collect.Lists; 44import com.google.android.collect.Maps; 45 46import libcore.io.IoUtils; 47 48import java.io.BufferedInputStream; 49import java.io.DataInputStream; 50import java.io.DataOutputStream; 51import java.io.File; 52import java.io.FileNotFoundException; 53import java.io.IOException; 54import java.io.InputStream; 55import java.io.PrintWriter; 56import java.net.ProtocolException; 57import java.util.ArrayList; 58import java.util.Collections; 59import java.util.HashMap; 60import java.util.Objects; 61 62/** 63 * Collection of {@link NetworkStatsHistory}, stored based on combined key of 64 * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself. 65 */ 66public class NetworkStatsCollection implements FileRotator.Reader { 67 /** File header magic number: "ANET" */ 68 private static final int FILE_MAGIC = 0x414E4554; 69 70 private static final int VERSION_NETWORK_INIT = 1; 71 72 private static final int VERSION_UID_INIT = 1; 73 private static final int VERSION_UID_WITH_IDENT = 2; 74 private static final int VERSION_UID_WITH_TAG = 3; 75 private static final int VERSION_UID_WITH_SET = 4; 76 77 private static final int VERSION_UNIFIED_INIT = 16; 78 79 private ArrayMap<Key, NetworkStatsHistory> mStats = new ArrayMap<>(); 80 81 private final long mBucketDuration; 82 83 private long mStartMillis; 84 private long mEndMillis; 85 private long mTotalBytes; 86 private boolean mDirty; 87 88 public NetworkStatsCollection(long bucketDuration) { 89 mBucketDuration = bucketDuration; 90 reset(); 91 } 92 93 public void reset() { 94 mStats.clear(); 95 mStartMillis = Long.MAX_VALUE; 96 mEndMillis = Long.MIN_VALUE; 97 mTotalBytes = 0; 98 mDirty = false; 99 } 100 101 public long getStartMillis() { 102 return mStartMillis; 103 } 104 105 /** 106 * Return first atomic bucket in this collection, which is more conservative 107 * than {@link #mStartMillis}. 108 */ 109 public long getFirstAtomicBucketMillis() { 110 if (mStartMillis == Long.MAX_VALUE) { 111 return Long.MAX_VALUE; 112 } else { 113 return mStartMillis + mBucketDuration; 114 } 115 } 116 117 public long getEndMillis() { 118 return mEndMillis; 119 } 120 121 public long getTotalBytes() { 122 return mTotalBytes; 123 } 124 125 public boolean isDirty() { 126 return mDirty; 127 } 128 129 public void clearDirty() { 130 mDirty = false; 131 } 132 133 public boolean isEmpty() { 134 return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE; 135 } 136 137 public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) { 138 return getRelevantUids(accessLevel, Binder.getCallingUid()); 139 } 140 141 public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel, 142 final int callerUid) { 143 IntArray uids = new IntArray(); 144 for (int i = 0; i < mStats.size(); i++) { 145 final Key key = mStats.keyAt(i); 146 if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) { 147 int j = uids.binarySearch(key.uid); 148 149 if (j < 0) { 150 j = ~j; 151 uids.add(j, key.uid); 152 } 153 } 154 } 155 return uids.toArray(); 156 } 157 158 /** 159 * Combine all {@link NetworkStatsHistory} in this collection which match 160 * the requested parameters. 161 */ 162 public NetworkStatsHistory getHistory( 163 NetworkTemplate template, int uid, int set, int tag, int fields, 164 @NetworkStatsAccess.Level int accessLevel) { 165 return getHistory(template, uid, set, tag, fields, Long.MIN_VALUE, Long.MAX_VALUE, 166 accessLevel); 167 } 168 169 /** 170 * Combine all {@link NetworkStatsHistory} in this collection which match 171 * the requested parameters. 172 */ 173 public NetworkStatsHistory getHistory( 174 NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end, 175 @NetworkStatsAccess.Level int accessLevel) { 176 return getHistory(template, uid, set, tag, fields, start, end, accessLevel, 177 Binder.getCallingUid()); 178 } 179 180 /** 181 * Combine all {@link NetworkStatsHistory} in this collection which match 182 * the requested parameters. 183 */ 184 public NetworkStatsHistory getHistory( 185 NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end, 186 @NetworkStatsAccess.Level int accessLevel, int callerUid) { 187 if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) { 188 throw new SecurityException("Network stats history of uid " + uid 189 + " is forbidden for caller " + callerUid); 190 } 191 192 final NetworkStatsHistory combined = new NetworkStatsHistory( 193 mBucketDuration, start == end ? 1 : estimateBuckets(), fields); 194 195 // shortcut when we know stats will be empty 196 if (start == end) return combined; 197 198 for (int i = 0; i < mStats.size(); i++) { 199 final Key key = mStats.keyAt(i); 200 if (key.uid == uid && NetworkStats.setMatches(set, key.set) && key.tag == tag 201 && templateMatches(template, key.ident)) { 202 final NetworkStatsHistory value = mStats.valueAt(i); 203 combined.recordHistory(value, start, end); 204 } 205 } 206 return combined; 207 } 208 209 /** 210 * Summarize all {@link NetworkStatsHistory} in this collection which match 211 * the requested parameters. 212 */ 213 public NetworkStats getSummary(NetworkTemplate template, long start, long end, 214 @NetworkStatsAccess.Level int accessLevel) { 215 return getSummary(template, start, end, accessLevel, Binder.getCallingUid()); 216 } 217 218 /** 219 * Summarize all {@link NetworkStatsHistory} in this collection which match 220 * the requested parameters. 221 */ 222 public NetworkStats getSummary(NetworkTemplate template, long start, long end, 223 @NetworkStatsAccess.Level int accessLevel, int callerUid) { 224 final long now = System.currentTimeMillis(); 225 226 final NetworkStats stats = new NetworkStats(end - start, 24); 227 // shortcut when we know stats will be empty 228 if (start == end) return stats; 229 230 final NetworkStats.Entry entry = new NetworkStats.Entry(); 231 NetworkStatsHistory.Entry historyEntry = null; 232 233 for (int i = 0; i < mStats.size(); i++) { 234 final Key key = mStats.keyAt(i); 235 if (templateMatches(template, key.ident) 236 && NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel) 237 && key.set < NetworkStats.SET_DEBUG_START) { 238 final NetworkStatsHistory value = mStats.valueAt(i); 239 historyEntry = value.getValues(start, end, now, historyEntry); 240 241 entry.iface = IFACE_ALL; 242 entry.uid = key.uid; 243 entry.set = key.set; 244 entry.tag = key.tag; 245 entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO; 246 entry.rxBytes = historyEntry.rxBytes; 247 entry.rxPackets = historyEntry.rxPackets; 248 entry.txBytes = historyEntry.txBytes; 249 entry.txPackets = historyEntry.txPackets; 250 entry.operations = historyEntry.operations; 251 252 if (!entry.isEmpty()) { 253 stats.combineValues(entry); 254 } 255 } 256 } 257 258 return stats; 259 } 260 261 /** 262 * Record given {@link android.net.NetworkStats.Entry} into this collection. 263 */ 264 public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start, 265 long end, NetworkStats.Entry entry) { 266 final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag); 267 history.recordData(start, end, entry); 268 noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes); 269 } 270 271 /** 272 * Record given {@link NetworkStatsHistory} into this collection. 273 */ 274 private void recordHistory(Key key, NetworkStatsHistory history) { 275 if (history.size() == 0) return; 276 noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes()); 277 278 NetworkStatsHistory target = mStats.get(key); 279 if (target == null) { 280 target = new NetworkStatsHistory(history.getBucketDuration()); 281 mStats.put(key, target); 282 } 283 target.recordEntireHistory(history); 284 } 285 286 /** 287 * Record all {@link NetworkStatsHistory} contained in the given collection 288 * into this collection. 289 */ 290 public void recordCollection(NetworkStatsCollection another) { 291 for (int i = 0; i < another.mStats.size(); i++) { 292 final Key key = another.mStats.keyAt(i); 293 final NetworkStatsHistory value = another.mStats.valueAt(i); 294 recordHistory(key, value); 295 } 296 } 297 298 private NetworkStatsHistory findOrCreateHistory( 299 NetworkIdentitySet ident, int uid, int set, int tag) { 300 final Key key = new Key(ident, uid, set, tag); 301 final NetworkStatsHistory existing = mStats.get(key); 302 303 // update when no existing, or when bucket duration changed 304 NetworkStatsHistory updated = null; 305 if (existing == null) { 306 updated = new NetworkStatsHistory(mBucketDuration, 10); 307 } else if (existing.getBucketDuration() != mBucketDuration) { 308 updated = new NetworkStatsHistory(existing, mBucketDuration); 309 } 310 311 if (updated != null) { 312 mStats.put(key, updated); 313 return updated; 314 } else { 315 return existing; 316 } 317 } 318 319 @Override 320 public void read(InputStream in) throws IOException { 321 read(new DataInputStream(in)); 322 } 323 324 public void read(DataInputStream in) throws IOException { 325 // verify file magic header intact 326 final int magic = in.readInt(); 327 if (magic != FILE_MAGIC) { 328 throw new ProtocolException("unexpected magic: " + magic); 329 } 330 331 final int version = in.readInt(); 332 switch (version) { 333 case VERSION_UNIFIED_INIT: { 334 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) 335 final int identSize = in.readInt(); 336 for (int i = 0; i < identSize; i++) { 337 final NetworkIdentitySet ident = new NetworkIdentitySet(in); 338 339 final int size = in.readInt(); 340 for (int j = 0; j < size; j++) { 341 final int uid = in.readInt(); 342 final int set = in.readInt(); 343 final int tag = in.readInt(); 344 345 final Key key = new Key(ident, uid, set, tag); 346 final NetworkStatsHistory history = new NetworkStatsHistory(in); 347 recordHistory(key, history); 348 } 349 } 350 break; 351 } 352 default: { 353 throw new ProtocolException("unexpected version: " + version); 354 } 355 } 356 } 357 358 public void write(DataOutputStream out) throws IOException { 359 // cluster key lists grouped by ident 360 final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap(); 361 for (Key key : mStats.keySet()) { 362 ArrayList<Key> keys = keysByIdent.get(key.ident); 363 if (keys == null) { 364 keys = Lists.newArrayList(); 365 keysByIdent.put(key.ident, keys); 366 } 367 keys.add(key); 368 } 369 370 out.writeInt(FILE_MAGIC); 371 out.writeInt(VERSION_UNIFIED_INIT); 372 373 out.writeInt(keysByIdent.size()); 374 for (NetworkIdentitySet ident : keysByIdent.keySet()) { 375 final ArrayList<Key> keys = keysByIdent.get(ident); 376 ident.writeToStream(out); 377 378 out.writeInt(keys.size()); 379 for (Key key : keys) { 380 final NetworkStatsHistory history = mStats.get(key); 381 out.writeInt(key.uid); 382 out.writeInt(key.set); 383 out.writeInt(key.tag); 384 history.writeToStream(out); 385 } 386 } 387 388 out.flush(); 389 } 390 391 @Deprecated 392 public void readLegacyNetwork(File file) throws IOException { 393 final AtomicFile inputFile = new AtomicFile(file); 394 395 DataInputStream in = null; 396 try { 397 in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); 398 399 // verify file magic header intact 400 final int magic = in.readInt(); 401 if (magic != FILE_MAGIC) { 402 throw new ProtocolException("unexpected magic: " + magic); 403 } 404 405 final int version = in.readInt(); 406 switch (version) { 407 case VERSION_NETWORK_INIT: { 408 // network := size *(NetworkIdentitySet NetworkStatsHistory) 409 final int size = in.readInt(); 410 for (int i = 0; i < size; i++) { 411 final NetworkIdentitySet ident = new NetworkIdentitySet(in); 412 final NetworkStatsHistory history = new NetworkStatsHistory(in); 413 414 final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE); 415 recordHistory(key, history); 416 } 417 break; 418 } 419 default: { 420 throw new ProtocolException("unexpected version: " + version); 421 } 422 } 423 } catch (FileNotFoundException e) { 424 // missing stats is okay, probably first boot 425 } finally { 426 IoUtils.closeQuietly(in); 427 } 428 } 429 430 @Deprecated 431 public void readLegacyUid(File file, boolean onlyTags) throws IOException { 432 final AtomicFile inputFile = new AtomicFile(file); 433 434 DataInputStream in = null; 435 try { 436 in = new DataInputStream(new BufferedInputStream(inputFile.openRead())); 437 438 // verify file magic header intact 439 final int magic = in.readInt(); 440 if (magic != FILE_MAGIC) { 441 throw new ProtocolException("unexpected magic: " + magic); 442 } 443 444 final int version = in.readInt(); 445 switch (version) { 446 case VERSION_UID_INIT: { 447 // uid := size *(UID NetworkStatsHistory) 448 449 // drop this data version, since we don't have a good 450 // mapping into NetworkIdentitySet. 451 break; 452 } 453 case VERSION_UID_WITH_IDENT: { 454 // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory)) 455 456 // drop this data version, since this version only existed 457 // for a short time. 458 break; 459 } 460 case VERSION_UID_WITH_TAG: 461 case VERSION_UID_WITH_SET: { 462 // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory)) 463 final int identSize = in.readInt(); 464 for (int i = 0; i < identSize; i++) { 465 final NetworkIdentitySet ident = new NetworkIdentitySet(in); 466 467 final int size = in.readInt(); 468 for (int j = 0; j < size; j++) { 469 final int uid = in.readInt(); 470 final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt() 471 : SET_DEFAULT; 472 final int tag = in.readInt(); 473 474 final Key key = new Key(ident, uid, set, tag); 475 final NetworkStatsHistory history = new NetworkStatsHistory(in); 476 477 if ((tag == TAG_NONE) != onlyTags) { 478 recordHistory(key, history); 479 } 480 } 481 } 482 break; 483 } 484 default: { 485 throw new ProtocolException("unexpected version: " + version); 486 } 487 } 488 } catch (FileNotFoundException e) { 489 // missing stats is okay, probably first boot 490 } finally { 491 IoUtils.closeQuietly(in); 492 } 493 } 494 495 /** 496 * Remove any {@link NetworkStatsHistory} attributed to the requested UID, 497 * moving any {@link NetworkStats#TAG_NONE} series to 498 * {@link TrafficStats#UID_REMOVED}. 499 */ 500 public void removeUids(int[] uids) { 501 final ArrayList<Key> knownKeys = Lists.newArrayList(); 502 knownKeys.addAll(mStats.keySet()); 503 504 // migrate all UID stats into special "removed" bucket 505 for (Key key : knownKeys) { 506 if (ArrayUtils.contains(uids, key.uid)) { 507 // only migrate combined TAG_NONE history 508 if (key.tag == TAG_NONE) { 509 final NetworkStatsHistory uidHistory = mStats.get(key); 510 final NetworkStatsHistory removedHistory = findOrCreateHistory( 511 key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE); 512 removedHistory.recordEntireHistory(uidHistory); 513 } 514 mStats.remove(key); 515 mDirty = true; 516 } 517 } 518 } 519 520 private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) { 521 if (startMillis < mStartMillis) mStartMillis = startMillis; 522 if (endMillis > mEndMillis) mEndMillis = endMillis; 523 mTotalBytes += totalBytes; 524 mDirty = true; 525 } 526 527 private int estimateBuckets() { 528 return (int) (Math.min(mEndMillis - mStartMillis, WEEK_IN_MILLIS * 5) 529 / mBucketDuration); 530 } 531 532 public void dump(IndentingPrintWriter pw) { 533 final ArrayList<Key> keys = Lists.newArrayList(); 534 keys.addAll(mStats.keySet()); 535 Collections.sort(keys); 536 537 for (Key key : keys) { 538 pw.print("ident="); pw.print(key.ident.toString()); 539 pw.print(" uid="); pw.print(key.uid); 540 pw.print(" set="); pw.print(NetworkStats.setToString(key.set)); 541 pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag)); 542 543 final NetworkStatsHistory history = mStats.get(key); 544 pw.increaseIndent(); 545 history.dump(pw, true); 546 pw.decreaseIndent(); 547 } 548 } 549 550 public void dumpCheckin(PrintWriter pw, long start, long end) { 551 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateMobileWildcard(), "cell"); 552 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateWifiWildcard(), "wifi"); 553 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateEthernet(), "eth"); 554 dumpCheckin(pw, start, end, NetworkTemplate.buildTemplateBluetooth(), "bt"); 555 } 556 557 /** 558 * Dump all contained stats that match requested parameters, but group 559 * together all matching {@link NetworkTemplate} under a single prefix. 560 */ 561 private void dumpCheckin(PrintWriter pw, long start, long end, NetworkTemplate groupTemplate, 562 String groupPrefix) { 563 final ArrayMap<Key, NetworkStatsHistory> grouped = new ArrayMap<>(); 564 565 // Walk through all history, grouping by matching network templates 566 for (int i = 0; i < mStats.size(); i++) { 567 final Key key = mStats.keyAt(i); 568 final NetworkStatsHistory value = mStats.valueAt(i); 569 570 if (!templateMatches(groupTemplate, key.ident)) continue; 571 if (key.set >= NetworkStats.SET_DEBUG_START) continue; 572 573 final Key groupKey = new Key(null, key.uid, key.set, key.tag); 574 NetworkStatsHistory groupHistory = grouped.get(groupKey); 575 if (groupHistory == null) { 576 groupHistory = new NetworkStatsHistory(value.getBucketDuration()); 577 grouped.put(groupKey, groupHistory); 578 } 579 groupHistory.recordHistory(value, start, end); 580 } 581 582 for (int i = 0; i < grouped.size(); i++) { 583 final Key key = grouped.keyAt(i); 584 final NetworkStatsHistory value = grouped.valueAt(i); 585 586 if (value.size() == 0) continue; 587 588 pw.print("c,"); 589 pw.print(groupPrefix); pw.print(','); 590 pw.print(key.uid); pw.print(','); 591 pw.print(NetworkStats.setToCheckinString(key.set)); pw.print(','); 592 pw.print(key.tag); 593 pw.println(); 594 595 value.dumpCheckin(pw); 596 } 597 } 598 599 /** 600 * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity} 601 * in the given {@link NetworkIdentitySet}. 602 */ 603 private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) { 604 for (NetworkIdentity ident : identSet) { 605 if (template.matches(ident)) { 606 return true; 607 } 608 } 609 return false; 610 } 611 612 private static class Key implements Comparable<Key> { 613 public final NetworkIdentitySet ident; 614 public final int uid; 615 public final int set; 616 public final int tag; 617 618 private final int hashCode; 619 620 public Key(NetworkIdentitySet ident, int uid, int set, int tag) { 621 this.ident = ident; 622 this.uid = uid; 623 this.set = set; 624 this.tag = tag; 625 hashCode = Objects.hash(ident, uid, set, tag); 626 } 627 628 @Override 629 public int hashCode() { 630 return hashCode; 631 } 632 633 @Override 634 public boolean equals(Object obj) { 635 if (obj instanceof Key) { 636 final Key key = (Key) obj; 637 return uid == key.uid && set == key.set && tag == key.tag 638 && Objects.equals(ident, key.ident); 639 } 640 return false; 641 } 642 643 @Override 644 public int compareTo(Key another) { 645 int res = 0; 646 if (ident != null && another.ident != null) { 647 res = ident.compareTo(another.ident); 648 } 649 if (res == 0) { 650 res = Integer.compare(uid, another.uid); 651 } 652 if (res == 0) { 653 res = Integer.compare(set, another.set); 654 } 655 if (res == 0) { 656 res = Integer.compare(tag, another.tag); 657 } 658 return res; 659 } 660 } 661} 662