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 17 package com.android.server.net; 18 19 import static android.net.NetworkStats.TAG_NONE; 20 import static android.net.TrafficStats.KB_IN_BYTES; 21 import static android.net.TrafficStats.MB_IN_BYTES; 22 import static android.text.format.DateUtils.YEAR_IN_MILLIS; 23 24 import android.annotation.NonNull; 25 import android.net.NetworkIdentitySet; 26 import android.net.NetworkStats; 27 import android.net.NetworkStats.NonMonotonicObserver; 28 import android.net.NetworkStatsAccess; 29 import android.net.NetworkStatsCollection; 30 import android.net.NetworkStatsHistory; 31 import android.net.NetworkTemplate; 32 import android.net.TrafficStats; 33 import android.os.Binder; 34 import android.os.DropBoxManager; 35 import android.service.NetworkStatsRecorderProto; 36 import android.util.IndentingPrintWriter; 37 import android.util.Log; 38 import android.util.proto.ProtoOutputStream; 39 40 import com.android.internal.util.FileRotator; 41 import com.android.net.module.util.NetworkStatsUtils; 42 43 import libcore.io.IoUtils; 44 45 import java.io.ByteArrayOutputStream; 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.io.OutputStream; 49 import java.io.PrintWriter; 50 import java.lang.ref.WeakReference; 51 import java.util.Arrays; 52 import java.util.HashSet; 53 import java.util.Map; 54 import java.util.Objects; 55 56 /** 57 * Logic to record deltas between periodic {@link NetworkStats} snapshots into 58 * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}. 59 * Keeps pending changes in memory until they pass a specific threshold, in 60 * bytes. Uses {@link FileRotator} for persistence logic if present. 61 * <p> 62 * Not inherently thread safe. 63 */ 64 public class NetworkStatsRecorder { 65 private static final String TAG = "NetworkStatsRecorder"; 66 private static final boolean LOGD = false; 67 private static final boolean LOGV = false; 68 69 private static final String TAG_NETSTATS_DUMP = "netstats_dump"; 70 71 /** Dump before deleting in {@link #recoverAndDeleteData()}. */ 72 private static final boolean DUMP_BEFORE_DELETE = true; 73 74 private final FileRotator mRotator; 75 private final NonMonotonicObserver<String> mObserver; 76 private final DropBoxManager mDropBox; 77 private final String mCookie; 78 79 private final long mBucketDuration; 80 private final boolean mOnlyTags; 81 private final boolean mWipeOnError; 82 83 private long mPersistThresholdBytes = 2 * MB_IN_BYTES; 84 private NetworkStats mLastSnapshot; 85 86 private final NetworkStatsCollection mPending; 87 private final NetworkStatsCollection mSinceBoot; 88 89 private final CombiningRewriter mPendingRewriter; 90 91 private WeakReference<NetworkStatsCollection> mComplete; 92 93 /** 94 * Non-persisted recorder, with only one bucket. Used by {@link NetworkStatsObservers}. 95 */ NetworkStatsRecorder()96 public NetworkStatsRecorder() { 97 mRotator = null; 98 mObserver = null; 99 mDropBox = null; 100 mCookie = null; 101 102 // set the bucket big enough to have all data in one bucket, but allow some 103 // slack to avoid overflow 104 mBucketDuration = YEAR_IN_MILLIS; 105 mOnlyTags = false; 106 mWipeOnError = true; 107 108 mPending = null; 109 mSinceBoot = new NetworkStatsCollection(mBucketDuration); 110 111 mPendingRewriter = null; 112 } 113 114 /** 115 * Persisted recorder. 116 */ NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer, DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags, boolean wipeOnError)117 public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer, 118 DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags, 119 boolean wipeOnError) { 120 mRotator = Objects.requireNonNull(rotator, "missing FileRotator"); 121 mObserver = Objects.requireNonNull(observer, "missing NonMonotonicObserver"); 122 mDropBox = Objects.requireNonNull(dropBox, "missing DropBoxManager"); 123 mCookie = cookie; 124 125 mBucketDuration = bucketDuration; 126 mOnlyTags = onlyTags; 127 mWipeOnError = wipeOnError; 128 129 mPending = new NetworkStatsCollection(bucketDuration); 130 mSinceBoot = new NetworkStatsCollection(bucketDuration); 131 132 mPendingRewriter = new CombiningRewriter(mPending); 133 } 134 setPersistThreshold(long thresholdBytes)135 public void setPersistThreshold(long thresholdBytes) { 136 if (LOGV) Log.v(TAG, "setPersistThreshold() with " + thresholdBytes); 137 mPersistThresholdBytes = NetworkStatsUtils.constrain( 138 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES); 139 } 140 resetLocked()141 public void resetLocked() { 142 mLastSnapshot = null; 143 if (mPending != null) { 144 mPending.reset(); 145 } 146 if (mSinceBoot != null) { 147 mSinceBoot.reset(); 148 } 149 if (mComplete != null) { 150 mComplete.clear(); 151 } 152 } 153 getTotalSinceBootLocked(NetworkTemplate template)154 public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) { 155 return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE, 156 NetworkStatsAccess.Level.DEVICE, Binder.getCallingUid()).getTotal(null); 157 } 158 getSinceBoot()159 public NetworkStatsCollection getSinceBoot() { 160 return mSinceBoot; 161 } 162 getBucketDuration()163 public long getBucketDuration() { 164 return mBucketDuration; 165 } 166 167 @NonNull getCookie()168 public String getCookie() { 169 return mCookie; 170 } 171 172 /** 173 * Load complete history represented by {@link FileRotator}. Caches 174 * internally as a {@link WeakReference}, and updated with future 175 * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long 176 * as reference is valid. 177 */ getOrLoadCompleteLocked()178 public NetworkStatsCollection getOrLoadCompleteLocked() { 179 Objects.requireNonNull(mRotator, "missing FileRotator"); 180 NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; 181 if (res == null) { 182 res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE); 183 mComplete = new WeakReference<NetworkStatsCollection>(res); 184 } 185 return res; 186 } 187 getOrLoadPartialLocked(long start, long end)188 public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) { 189 Objects.requireNonNull(mRotator, "missing FileRotator"); 190 NetworkStatsCollection res = mComplete != null ? mComplete.get() : null; 191 if (res == null) { 192 res = loadLocked(start, end); 193 } 194 return res; 195 } 196 loadLocked(long start, long end)197 private NetworkStatsCollection loadLocked(long start, long end) { 198 if (LOGD) Log.d(TAG, "loadLocked() reading from disk for " + mCookie); 199 final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration); 200 try { 201 mRotator.readMatching(res, start, end); 202 res.recordCollection(mPending); 203 } catch (IOException e) { 204 Log.wtf(TAG, "problem completely reading network stats", e); 205 recoverAndDeleteData(); 206 } catch (OutOfMemoryError e) { 207 Log.wtf(TAG, "problem completely reading network stats", e); 208 recoverAndDeleteData(); 209 } 210 return res; 211 } 212 213 /** 214 * Record any delta that occurred since last {@link NetworkStats} snapshot, using the given 215 * {@link Map} to identify network interfaces. First snapshot is considered bootstrap, and is 216 * not counted as delta. 217 */ recordSnapshotLocked(NetworkStats snapshot, Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis)218 public void recordSnapshotLocked(NetworkStats snapshot, 219 Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) { 220 final HashSet<String> unknownIfaces = new HashSet<>(); 221 222 // skip recording when snapshot missing 223 if (snapshot == null) return; 224 225 // assume first snapshot is bootstrap and don't record 226 if (mLastSnapshot == null) { 227 mLastSnapshot = snapshot; 228 return; 229 } 230 231 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; 232 233 final NetworkStats delta = NetworkStats.subtract( 234 snapshot, mLastSnapshot, mObserver, mCookie); 235 final long end = currentTimeMillis; 236 final long start = end - delta.getElapsedRealtime(); 237 238 NetworkStats.Entry entry = null; 239 for (int i = 0; i < delta.size(); i++) { 240 entry = delta.getValues(i, entry); 241 242 // As a last-ditch check, report any negative values and 243 // clamp them so recording below doesn't croak. 244 if (entry.isNegative()) { 245 if (mObserver != null) { 246 mObserver.foundNonMonotonic(delta, i, mCookie); 247 } 248 entry.rxBytes = Math.max(entry.rxBytes, 0); 249 entry.rxPackets = Math.max(entry.rxPackets, 0); 250 entry.txBytes = Math.max(entry.txBytes, 0); 251 entry.txPackets = Math.max(entry.txPackets, 0); 252 entry.operations = Math.max(entry.operations, 0); 253 } 254 255 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface); 256 if (ident == null) { 257 unknownIfaces.add(entry.iface); 258 continue; 259 } 260 261 // skip when no delta occurred 262 if (entry.isEmpty()) continue; 263 264 // only record tag data when requested 265 if ((entry.tag == TAG_NONE) != mOnlyTags) { 266 if (mPending != null) { 267 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); 268 } 269 270 // also record against boot stats when present 271 if (mSinceBoot != null) { 272 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); 273 } 274 275 // also record against complete dataset when present 276 if (complete != null) { 277 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); 278 } 279 } 280 } 281 282 mLastSnapshot = snapshot; 283 284 if (LOGV && unknownIfaces.size() > 0) { 285 Log.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats"); 286 } 287 } 288 289 /** 290 * Consider persisting any pending deltas, if they are beyond 291 * {@link #mPersistThresholdBytes}. 292 */ maybePersistLocked(long currentTimeMillis)293 public void maybePersistLocked(long currentTimeMillis) { 294 Objects.requireNonNull(mRotator, "missing FileRotator"); 295 final long pendingBytes = mPending.getTotalBytes(); 296 if (pendingBytes >= mPersistThresholdBytes) { 297 forcePersistLocked(currentTimeMillis); 298 } else { 299 mRotator.maybeRotate(currentTimeMillis); 300 } 301 } 302 303 /** 304 * Force persisting any pending deltas. 305 */ forcePersistLocked(long currentTimeMillis)306 public void forcePersistLocked(long currentTimeMillis) { 307 Objects.requireNonNull(mRotator, "missing FileRotator"); 308 if (mPending.isDirty()) { 309 if (LOGD) Log.d(TAG, "forcePersistLocked() writing for " + mCookie); 310 try { 311 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis); 312 mRotator.maybeRotate(currentTimeMillis); 313 mPending.reset(); 314 } catch (IOException e) { 315 Log.wtf(TAG, "problem persisting pending stats", e); 316 recoverAndDeleteData(); 317 } catch (OutOfMemoryError e) { 318 Log.wtf(TAG, "problem persisting pending stats", e); 319 recoverAndDeleteData(); 320 } 321 } 322 } 323 324 /** 325 * Remove the given UID from all {@link FileRotator} history, migrating it 326 * to {@link TrafficStats#UID_REMOVED}. 327 */ removeUidsLocked(int[] uids)328 public void removeUidsLocked(int[] uids) { 329 if (mRotator != null) { 330 try { 331 // Rewrite all persisted data to migrate UID stats 332 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids)); 333 } catch (IOException e) { 334 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e); 335 recoverAndDeleteData(); 336 } catch (OutOfMemoryError e) { 337 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e); 338 recoverAndDeleteData(); 339 } 340 } 341 342 // Remove any pending stats 343 if (mPending != null) { 344 mPending.removeUids(uids); 345 } 346 if (mSinceBoot != null) { 347 mSinceBoot.removeUids(uids); 348 } 349 350 // Clear UID from current stats snapshot 351 if (mLastSnapshot != null) { 352 mLastSnapshot.removeUids(uids); 353 } 354 355 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; 356 if (complete != null) { 357 complete.removeUids(uids); 358 } 359 } 360 361 /** 362 * Rewriter that will combine current {@link NetworkStatsCollection} values 363 * with anything read from disk, and write combined set to disk. 364 */ 365 private static class CombiningRewriter implements FileRotator.Rewriter { 366 private final NetworkStatsCollection mCollection; 367 CombiningRewriter(NetworkStatsCollection collection)368 public CombiningRewriter(NetworkStatsCollection collection) { 369 mCollection = Objects.requireNonNull(collection, "missing NetworkStatsCollection"); 370 } 371 372 @Override reset()373 public void reset() { 374 // ignored 375 } 376 377 @Override read(InputStream in)378 public void read(InputStream in) throws IOException { 379 mCollection.read(in); 380 } 381 382 @Override shouldWrite()383 public boolean shouldWrite() { 384 return true; 385 } 386 387 @Override write(OutputStream out)388 public void write(OutputStream out) throws IOException { 389 mCollection.write(out); 390 } 391 } 392 393 /** 394 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to 395 * the requested UID, only writing data back when modified. 396 */ 397 public static class RemoveUidRewriter implements FileRotator.Rewriter { 398 private final NetworkStatsCollection mTemp; 399 private final int[] mUids; 400 RemoveUidRewriter(long bucketDuration, int[] uids)401 public RemoveUidRewriter(long bucketDuration, int[] uids) { 402 mTemp = new NetworkStatsCollection(bucketDuration); 403 mUids = uids; 404 } 405 406 @Override reset()407 public void reset() { 408 mTemp.reset(); 409 } 410 411 @Override read(InputStream in)412 public void read(InputStream in) throws IOException { 413 mTemp.read(in); 414 mTemp.clearDirty(); 415 mTemp.removeUids(mUids); 416 } 417 418 @Override shouldWrite()419 public boolean shouldWrite() { 420 return mTemp.isDirty(); 421 } 422 423 @Override write(OutputStream out)424 public void write(OutputStream out) throws IOException { 425 mTemp.write(out); 426 } 427 } 428 429 /** 430 * Import a specified {@link NetworkStatsCollection} instance into this recorder, 431 * and write it into a standalone file. 432 * @param collection The target {@link NetworkStatsCollection} instance to be imported. 433 */ importCollectionLocked(@onNull NetworkStatsCollection collection)434 public void importCollectionLocked(@NonNull NetworkStatsCollection collection) 435 throws IOException { 436 if (mRotator != null) { 437 mRotator.rewriteSingle(new CombiningRewriter(collection), collection.getStartMillis(), 438 collection.getEndMillis()); 439 } 440 441 if (mComplete != null) { 442 throw new IllegalStateException("cannot import data when data already loaded"); 443 } 444 } 445 446 /** 447 * Rewriter that will remove any histories or persisted data points before the 448 * specified cutoff time, only writing data back when modified. 449 */ 450 public static class RemoveDataBeforeRewriter implements FileRotator.Rewriter { 451 private final NetworkStatsCollection mTemp; 452 private final long mCutoffMills; 453 RemoveDataBeforeRewriter(long bucketDuration, long cutoffMills)454 public RemoveDataBeforeRewriter(long bucketDuration, long cutoffMills) { 455 mTemp = new NetworkStatsCollection(bucketDuration); 456 mCutoffMills = cutoffMills; 457 } 458 459 @Override reset()460 public void reset() { 461 mTemp.reset(); 462 } 463 464 @Override read(InputStream in)465 public void read(InputStream in) throws IOException { 466 mTemp.read(in); 467 mTemp.clearDirty(); 468 mTemp.removeHistoryBefore(mCutoffMills); 469 } 470 471 @Override shouldWrite()472 public boolean shouldWrite() { 473 return mTemp.isDirty(); 474 } 475 476 @Override write(OutputStream out)477 public void write(OutputStream out) throws IOException { 478 mTemp.write(out); 479 } 480 } 481 482 /** 483 * Remove persisted data which contains or is before the cutoff timestamp. 484 */ removeDataBefore(long cutoffMillis)485 public void removeDataBefore(long cutoffMillis) throws IOException { 486 if (mRotator != null) { 487 try { 488 mRotator.rewriteAll(new RemoveDataBeforeRewriter( 489 mBucketDuration, cutoffMillis)); 490 } catch (IOException e) { 491 Log.wtf(TAG, "problem importing netstats", e); 492 recoverAndDeleteData(); 493 } catch (OutOfMemoryError e) { 494 Log.wtf(TAG, "problem importing netstats", e); 495 recoverAndDeleteData(); 496 } 497 } 498 499 // Clean up any pending stats 500 if (mPending != null) { 501 mPending.removeHistoryBefore(cutoffMillis); 502 } 503 if (mSinceBoot != null) { 504 mSinceBoot.removeHistoryBefore(cutoffMillis); 505 } 506 507 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; 508 if (complete != null) { 509 complete.removeHistoryBefore(cutoffMillis); 510 } 511 } 512 dumpLocked(IndentingPrintWriter pw, boolean fullHistory)513 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) { 514 if (mPending != null) { 515 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes()); 516 } 517 if (fullHistory) { 518 pw.println("Complete history:"); 519 getOrLoadCompleteLocked().dump(pw); 520 } else { 521 pw.println("History since boot:"); 522 mSinceBoot.dump(pw); 523 } 524 } 525 dumpDebugLocked(ProtoOutputStream proto, long tag)526 public void dumpDebugLocked(ProtoOutputStream proto, long tag) { 527 final long start = proto.start(tag); 528 if (mPending != null) { 529 proto.write(NetworkStatsRecorderProto.PENDING_TOTAL_BYTES, 530 mPending.getTotalBytes()); 531 } 532 getOrLoadCompleteLocked().dumpDebug(proto, 533 NetworkStatsRecorderProto.COMPLETE_HISTORY); 534 proto.end(start); 535 } 536 dumpCheckin(PrintWriter pw, long start, long end)537 public void dumpCheckin(PrintWriter pw, long start, long end) { 538 // Only load and dump stats from the requested window 539 getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end); 540 } 541 542 /** 543 * Recover from {@link FileRotator} failure by dumping state to 544 * {@link DropBoxManager} and deleting contents if this recorder 545 * sets {@code mWipeOnError} to true, otherwise keep the contents. 546 */ recoverAndDeleteData()547 void recoverAndDeleteData() { 548 if (DUMP_BEFORE_DELETE) { 549 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 550 try { 551 mRotator.dumpAll(os); 552 } catch (IOException e) { 553 // ignore partial contents 554 os.reset(); 555 } finally { 556 IoUtils.closeQuietly(os); 557 } 558 mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0); 559 } 560 // Delete all files if this recorder is set wipe on error. 561 if (mWipeOnError) { 562 mRotator.deleteAll(); 563 } 564 } 565 } 566