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