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