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