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