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