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