NetworkStatsRecorder.java revision 1efb1335814aea8ee0696144ca0ab24159b86e54
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.TAG_NONE; 20import static android.net.TrafficStats.KB_IN_BYTES; 21import static android.net.TrafficStats.MB_IN_BYTES; 22import static com.android.internal.util.Preconditions.checkNotNull; 23 24import android.net.NetworkStats; 25import android.net.NetworkStats.NonMonotonicObserver; 26import android.net.NetworkStatsHistory; 27import android.net.NetworkTemplate; 28import android.net.TrafficStats; 29import android.os.DropBoxManager; 30import android.util.Log; 31import android.util.MathUtils; 32import android.util.Slog; 33 34import com.android.internal.net.VpnInfo; 35import com.android.internal.util.FileRotator; 36import com.android.internal.util.IndentingPrintWriter; 37import com.google.android.collect.Sets; 38 39import java.io.ByteArrayOutputStream; 40import java.io.DataOutputStream; 41import java.io.File; 42import java.io.IOException; 43import java.io.InputStream; 44import java.io.OutputStream; 45import java.io.PrintWriter; 46import java.lang.ref.WeakReference; 47import java.util.Arrays; 48import java.util.HashSet; 49import java.util.Map; 50 51import libcore.io.IoUtils; 52 53/** 54 * Logic to record deltas between periodic {@link NetworkStats} snapshots into 55 * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}. 56 * Keeps pending changes in memory until they pass a specific threshold, in 57 * bytes. Uses {@link FileRotator} for persistence logic. 58 * <p> 59 * Not inherently thread safe. 60 */ 61public class NetworkStatsRecorder { 62 private static final String TAG = "NetworkStatsRecorder"; 63 private static final boolean LOGD = false; 64 private static final boolean LOGV = false; 65 66 private static final String TAG_NETSTATS_DUMP = "netstats_dump"; 67 68 /** Dump before deleting in {@link #recoverFromWtf()}. */ 69 private static final boolean DUMP_BEFORE_DELETE = true; 70 71 private final FileRotator mRotator; 72 private final NonMonotonicObserver<String> mObserver; 73 private final DropBoxManager mDropBox; 74 private final String mCookie; 75 76 private final long mBucketDuration; 77 private final boolean mOnlyTags; 78 79 private long mPersistThresholdBytes = 2 * MB_IN_BYTES; 80 private NetworkStats mLastSnapshot; 81 82 private final NetworkStatsCollection mPending; 83 private final NetworkStatsCollection mSinceBoot; 84 85 private final CombiningRewriter mPendingRewriter; 86 87 private WeakReference<NetworkStatsCollection> mComplete; 88 89 public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer, 90 DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) { 91 mRotator = checkNotNull(rotator, "missing FileRotator"); 92 mObserver = checkNotNull(observer, "missing NonMonotonicObserver"); 93 mDropBox = checkNotNull(dropBox, "missing DropBoxManager"); 94 mCookie = cookie; 95 96 mBucketDuration = bucketDuration; 97 mOnlyTags = onlyTags; 98 99 mPending = new NetworkStatsCollection(bucketDuration); 100 mSinceBoot = new NetworkStatsCollection(bucketDuration); 101 102 mPendingRewriter = new CombiningRewriter(mPending); 103 } 104 105 public void setPersistThreshold(long thresholdBytes) { 106 if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes); 107 mPersistThresholdBytes = MathUtils.constrain( 108 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES); 109 } 110 111 public void resetLocked() { 112 mLastSnapshot = null; 113 mPending.reset(); 114 mSinceBoot.reset(); 115 mComplete.clear(); 116 } 117 118 public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) { 119 return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE, 120 NetworkStatsAccess.Level.DEVICE).getTotal(null); 121 } 122 123 /** 124 * Load complete history represented by {@link FileRotator}. Caches 125 * internally as a {@link WeakReference}, and updated with future 126 * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long 127 * as reference is valid. 128 */ 129 public NetworkStatsCollection getOrLoadCompleteLocked() { 130 NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; 131 if (res == null) { 132 res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE); 133 mComplete = new WeakReference<NetworkStatsCollection>(res); 134 } 135 return res; 136 } 137 138 public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) { 139 NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; 140 if (res == null) { 141 res = loadLocked(start, end); 142 } 143 return res; 144 } 145 146 private NetworkStatsCollection loadLocked(long start, long end) { 147 if (LOGD) Slog.d(TAG, "loadLocked() reading from disk for " + mCookie); 148 final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration); 149 try { 150 mRotator.readMatching(res, start, end); 151 res.recordCollection(mPending); 152 } catch (IOException e) { 153 Log.wtf(TAG, "problem completely reading network stats", e); 154 recoverFromWtf(); 155 } catch (OutOfMemoryError e) { 156 Log.wtf(TAG, "problem completely reading network stats", e); 157 recoverFromWtf(); 158 } 159 return res; 160 } 161 162 /** 163 * Record any delta that occurred since last {@link NetworkStats} snapshot, 164 * using the given {@link Map} to identify network interfaces. First 165 * snapshot is considered bootstrap, and is not counted as delta. 166 */ 167 public void recordSnapshotLocked(NetworkStats snapshot, 168 Map<String, NetworkIdentitySet> ifaceIdent, VpnInfo[] vpnArray, 169 long currentTimeMillis) { 170 final HashSet<String> unknownIfaces = Sets.newHashSet(); 171 172 // skip recording when snapshot missing 173 if (snapshot == null) return; 174 175 // assume first snapshot is bootstrap and don't record 176 if (mLastSnapshot == null) { 177 mLastSnapshot = snapshot; 178 return; 179 } 180 181 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; 182 183 final NetworkStats delta = NetworkStats.subtract( 184 snapshot, mLastSnapshot, mObserver, mCookie); 185 final long end = currentTimeMillis; 186 final long start = end - delta.getElapsedRealtime(); 187 188 if (vpnArray != null) { 189 for (VpnInfo info : vpnArray) { 190 delta.migrateTun(info.ownerUid, info.vpnIface, info.primaryUnderlyingIface); 191 } 192 } 193 194 NetworkStats.Entry entry = null; 195 for (int i = 0; i < delta.size(); i++) { 196 entry = delta.getValues(i, entry); 197 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface); 198 if (ident == null) { 199 unknownIfaces.add(entry.iface); 200 continue; 201 } 202 203 // skip when no delta occurred 204 if (entry.isEmpty()) continue; 205 206 // only record tag data when requested 207 if ((entry.tag == TAG_NONE) != mOnlyTags) { 208 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); 209 210 // also record against boot stats when present 211 if (mSinceBoot != null) { 212 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); 213 } 214 215 // also record against complete dataset when present 216 if (complete != null) { 217 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); 218 } 219 } 220 } 221 222 mLastSnapshot = snapshot; 223 224 if (LOGV && unknownIfaces.size() > 0) { 225 Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats"); 226 } 227 } 228 229 /** 230 * Consider persisting any pending deltas, if they are beyond 231 * {@link #mPersistThresholdBytes}. 232 */ 233 public void maybePersistLocked(long currentTimeMillis) { 234 final long pendingBytes = mPending.getTotalBytes(); 235 if (pendingBytes >= mPersistThresholdBytes) { 236 forcePersistLocked(currentTimeMillis); 237 } else { 238 mRotator.maybeRotate(currentTimeMillis); 239 } 240 } 241 242 /** 243 * Force persisting any pending deltas. 244 */ 245 public void forcePersistLocked(long currentTimeMillis) { 246 if (mPending.isDirty()) { 247 if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie); 248 try { 249 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis); 250 mRotator.maybeRotate(currentTimeMillis); 251 mPending.reset(); 252 } catch (IOException e) { 253 Log.wtf(TAG, "problem persisting pending stats", e); 254 recoverFromWtf(); 255 } catch (OutOfMemoryError e) { 256 Log.wtf(TAG, "problem persisting pending stats", e); 257 recoverFromWtf(); 258 } 259 } 260 } 261 262 /** 263 * Remove the given UID from all {@link FileRotator} history, migrating it 264 * to {@link TrafficStats#UID_REMOVED}. 265 */ 266 public void removeUidsLocked(int[] uids) { 267 try { 268 // Rewrite all persisted data to migrate UID stats 269 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids)); 270 } catch (IOException e) { 271 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e); 272 recoverFromWtf(); 273 } catch (OutOfMemoryError e) { 274 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e); 275 recoverFromWtf(); 276 } 277 278 // Remove any pending stats 279 mPending.removeUids(uids); 280 mSinceBoot.removeUids(uids); 281 282 // Clear UID from current stats snapshot 283 if (mLastSnapshot != null) { 284 mLastSnapshot = mLastSnapshot.withoutUids(uids); 285 } 286 287 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; 288 if (complete != null) { 289 complete.removeUids(uids); 290 } 291 } 292 293 /** 294 * Rewriter that will combine current {@link NetworkStatsCollection} values 295 * with anything read from disk, and write combined set to disk. Clears the 296 * original {@link NetworkStatsCollection} when finished writing. 297 */ 298 private static class CombiningRewriter implements FileRotator.Rewriter { 299 private final NetworkStatsCollection mCollection; 300 301 public CombiningRewriter(NetworkStatsCollection collection) { 302 mCollection = checkNotNull(collection, "missing NetworkStatsCollection"); 303 } 304 305 @Override 306 public void reset() { 307 // ignored 308 } 309 310 @Override 311 public void read(InputStream in) throws IOException { 312 mCollection.read(in); 313 } 314 315 @Override 316 public boolean shouldWrite() { 317 return true; 318 } 319 320 @Override 321 public void write(OutputStream out) throws IOException { 322 mCollection.write(new DataOutputStream(out)); 323 mCollection.reset(); 324 } 325 } 326 327 /** 328 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to 329 * the requested UID, only writing data back when modified. 330 */ 331 public static class RemoveUidRewriter implements FileRotator.Rewriter { 332 private final NetworkStatsCollection mTemp; 333 private final int[] mUids; 334 335 public RemoveUidRewriter(long bucketDuration, int[] uids) { 336 mTemp = new NetworkStatsCollection(bucketDuration); 337 mUids = uids; 338 } 339 340 @Override 341 public void reset() { 342 mTemp.reset(); 343 } 344 345 @Override 346 public void read(InputStream in) throws IOException { 347 mTemp.read(in); 348 mTemp.clearDirty(); 349 mTemp.removeUids(mUids); 350 } 351 352 @Override 353 public boolean shouldWrite() { 354 return mTemp.isDirty(); 355 } 356 357 @Override 358 public void write(OutputStream out) throws IOException { 359 mTemp.write(new DataOutputStream(out)); 360 } 361 } 362 363 public void importLegacyNetworkLocked(File file) throws IOException { 364 // legacy file still exists; start empty to avoid double importing 365 mRotator.deleteAll(); 366 367 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration); 368 collection.readLegacyNetwork(file); 369 370 final long startMillis = collection.getStartMillis(); 371 final long endMillis = collection.getEndMillis(); 372 373 if (!collection.isEmpty()) { 374 // process legacy data, creating active file at starting time, then 375 // using end time to possibly trigger rotation. 376 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis); 377 mRotator.maybeRotate(endMillis); 378 } 379 } 380 381 public void importLegacyUidLocked(File file) throws IOException { 382 // legacy file still exists; start empty to avoid double importing 383 mRotator.deleteAll(); 384 385 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration); 386 collection.readLegacyUid(file, mOnlyTags); 387 388 final long startMillis = collection.getStartMillis(); 389 final long endMillis = collection.getEndMillis(); 390 391 if (!collection.isEmpty()) { 392 // process legacy data, creating active file at starting time, then 393 // using end time to possibly trigger rotation. 394 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis); 395 mRotator.maybeRotate(endMillis); 396 } 397 } 398 399 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) { 400 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes()); 401 if (fullHistory) { 402 pw.println("Complete history:"); 403 getOrLoadCompleteLocked().dump(pw); 404 } else { 405 pw.println("History since boot:"); 406 mSinceBoot.dump(pw); 407 } 408 } 409 410 public void dumpCheckin(PrintWriter pw, long start, long end) { 411 // Only load and dump stats from the requested window 412 getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end); 413 } 414 415 /** 416 * Recover from {@link FileRotator} failure by dumping state to 417 * {@link DropBoxManager} and deleting contents. 418 */ 419 private void recoverFromWtf() { 420 if (DUMP_BEFORE_DELETE) { 421 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 422 try { 423 mRotator.dumpAll(os); 424 } catch (IOException e) { 425 // ignore partial contents 426 os.reset(); 427 } finally { 428 IoUtils.closeQuietly(os); 429 } 430 mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0); 431 } 432 433 mRotator.deleteAll(); 434 } 435} 436