NetworkStatsRecorder.java revision bfdd680ab44da173a4a39fcd6feccdebb9d1f855
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 com.android.internal.util.Preconditions.checkNotNull; 21 22import android.net.NetworkStats; 23import android.net.NetworkStats.NonMonotonicObserver; 24import android.net.NetworkStatsHistory; 25import android.net.NetworkTemplate; 26import android.net.TrafficStats; 27import android.util.Log; 28import android.util.Slog; 29 30import com.android.internal.util.FileRotator; 31import com.android.internal.util.IndentingPrintWriter; 32import com.google.android.collect.Sets; 33 34import java.io.DataOutputStream; 35import java.io.File; 36import java.io.IOException; 37import java.io.InputStream; 38import java.io.OutputStream; 39import java.lang.ref.WeakReference; 40import java.util.HashSet; 41import java.util.Map; 42 43/** 44 * Logic to record deltas between periodic {@link NetworkStats} snapshots into 45 * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}. 46 * Keeps pending changes in memory until they pass a specific threshold, in 47 * bytes. Uses {@link FileRotator} for persistence logic. 48 * <p> 49 * Not inherently thread safe. 50 */ 51public class NetworkStatsRecorder { 52 private static final String TAG = "NetworkStatsRecorder"; 53 private static final boolean LOGD = false; 54 private static final boolean LOGV = false; 55 56 private final FileRotator mRotator; 57 private final NonMonotonicObserver<String> mObserver; 58 private final String mCookie; 59 60 private final long mBucketDuration; 61 private final long mPersistThresholdBytes; 62 private final boolean mOnlyTags; 63 64 private NetworkStats mLastSnapshot; 65 66 private final NetworkStatsCollection mPending; 67 private final NetworkStatsCollection mSinceBoot; 68 69 private final CombiningRewriter mPendingRewriter; 70 71 private WeakReference<NetworkStatsCollection> mComplete; 72 73 public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer, 74 String cookie, long bucketDuration, long persistThresholdBytes, boolean onlyTags) { 75 mRotator = checkNotNull(rotator, "missing FileRotator"); 76 mObserver = checkNotNull(observer, "missing NonMonotonicObserver"); 77 mCookie = cookie; 78 79 mBucketDuration = bucketDuration; 80 mPersistThresholdBytes = persistThresholdBytes; 81 mOnlyTags = onlyTags; 82 83 mPending = new NetworkStatsCollection(bucketDuration); 84 mSinceBoot = new NetworkStatsCollection(bucketDuration); 85 86 mPendingRewriter = new CombiningRewriter(mPending); 87 } 88 89 public void resetLocked() { 90 mLastSnapshot = null; 91 mPending.reset(); 92 mSinceBoot.reset(); 93 mComplete.clear(); 94 } 95 96 public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) { 97 return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null); 98 } 99 100 /** 101 * Load complete history represented by {@link FileRotator}. Caches 102 * internally as a {@link WeakReference}, and updated with future 103 * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long 104 * as reference is valid. 105 */ 106 public NetworkStatsCollection getOrLoadCompleteLocked() { 107 NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; 108 if (complete == null) { 109 if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie); 110 try { 111 complete = new NetworkStatsCollection(mBucketDuration); 112 mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE); 113 complete.recordCollection(mPending); 114 mComplete = new WeakReference<NetworkStatsCollection>(complete); 115 } catch (IOException e) { 116 Log.wtf(TAG, "problem completely reading network stats", e); 117 } 118 } 119 return complete; 120 } 121 122 /** 123 * Record any delta that occurred since last {@link NetworkStats} snapshot, 124 * using the given {@link Map} to identify network interfaces. First 125 * snapshot is considered bootstrap, and is not counted as delta. 126 */ 127 public void recordSnapshotLocked(NetworkStats snapshot, 128 Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) { 129 final HashSet<String> unknownIfaces = Sets.newHashSet(); 130 131 // assume first snapshot is bootstrap and don't record 132 if (mLastSnapshot == null) { 133 mLastSnapshot = snapshot; 134 return; 135 } 136 137 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; 138 139 final NetworkStats delta = NetworkStats.subtract( 140 snapshot, mLastSnapshot, mObserver, mCookie); 141 final long end = currentTimeMillis; 142 final long start = end - delta.getElapsedRealtime(); 143 144 NetworkStats.Entry entry = null; 145 for (int i = 0; i < delta.size(); i++) { 146 entry = delta.getValues(i, entry); 147 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface); 148 if (ident == null) { 149 unknownIfaces.add(entry.iface); 150 continue; 151 } 152 153 // skip when no delta occured 154 if (entry.isEmpty()) continue; 155 156 // only record tag data when requested 157 if ((entry.tag == TAG_NONE) != mOnlyTags) { 158 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); 159 160 // also record against boot stats when present 161 if (mSinceBoot != null) { 162 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); 163 } 164 165 // also record against complete dataset when present 166 if (complete != null) { 167 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry); 168 } 169 } 170 } 171 172 mLastSnapshot = snapshot; 173 174 if (LOGV && unknownIfaces.size() > 0) { 175 Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats"); 176 } 177 } 178 179 /** 180 * Consider persisting any pending deltas, if they are beyond 181 * {@link #mPersistThresholdBytes}. 182 */ 183 public void maybePersistLocked(long currentTimeMillis) { 184 final long pendingBytes = mPending.getTotalBytes(); 185 if (pendingBytes >= mPersistThresholdBytes) { 186 forcePersistLocked(currentTimeMillis); 187 } else { 188 mRotator.maybeRotate(currentTimeMillis); 189 } 190 } 191 192 /** 193 * Force persisting any pending deltas. 194 */ 195 public void forcePersistLocked(long currentTimeMillis) { 196 if (mPending.isDirty()) { 197 if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie); 198 try { 199 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis); 200 mRotator.maybeRotate(currentTimeMillis); 201 mPending.reset(); 202 } catch (IOException e) { 203 Log.wtf(TAG, "problem persisting pending stats", e); 204 } 205 } 206 } 207 208 /** 209 * Remove the given UID from all {@link FileRotator} history, migrating it 210 * to {@link TrafficStats#UID_REMOVED}. 211 */ 212 public void removeUidLocked(int uid) { 213 try { 214 // process all existing data to migrate uid 215 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid)); 216 } catch (IOException e) { 217 Log.wtf(TAG, "problem removing UID " + uid, e); 218 } 219 220 // clear UID from current stats snapshot 221 if (mLastSnapshot != null) { 222 mLastSnapshot = mLastSnapshot.withoutUid(uid); 223 } 224 225 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; 226 if (complete != null) { 227 complete.removeUid(uid); 228 } 229 } 230 231 /** 232 * Rewriter that will combine current {@link NetworkStatsCollection} values 233 * with anything read from disk, and write combined set to disk. Clears the 234 * original {@link NetworkStatsCollection} when finished writing. 235 */ 236 private static class CombiningRewriter implements FileRotator.Rewriter { 237 private final NetworkStatsCollection mCollection; 238 239 public CombiningRewriter(NetworkStatsCollection collection) { 240 mCollection = checkNotNull(collection, "missing NetworkStatsCollection"); 241 } 242 243 @Override 244 public void reset() { 245 // ignored 246 } 247 248 @Override 249 public void read(InputStream in) throws IOException { 250 mCollection.read(in); 251 } 252 253 @Override 254 public boolean shouldWrite() { 255 return true; 256 } 257 258 @Override 259 public void write(OutputStream out) throws IOException { 260 mCollection.write(new DataOutputStream(out)); 261 mCollection.reset(); 262 } 263 } 264 265 /** 266 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to 267 * the requested UID, only writing data back when modified. 268 */ 269 public static class RemoveUidRewriter implements FileRotator.Rewriter { 270 private final NetworkStatsCollection mTemp; 271 private final int mUid; 272 273 public RemoveUidRewriter(long bucketDuration, int uid) { 274 mTemp = new NetworkStatsCollection(bucketDuration); 275 mUid = uid; 276 } 277 278 @Override 279 public void reset() { 280 mTemp.reset(); 281 } 282 283 @Override 284 public void read(InputStream in) throws IOException { 285 mTemp.read(in); 286 mTemp.clearDirty(); 287 mTemp.removeUid(mUid); 288 } 289 290 @Override 291 public boolean shouldWrite() { 292 return mTemp.isDirty(); 293 } 294 295 @Override 296 public void write(OutputStream out) throws IOException { 297 mTemp.write(new DataOutputStream(out)); 298 } 299 } 300 301 public void importLegacyNetworkLocked(File file) throws IOException { 302 // legacy file still exists; start empty to avoid double importing 303 mRotator.deleteAll(); 304 305 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration); 306 collection.readLegacyNetwork(file); 307 308 final long startMillis = collection.getStartMillis(); 309 final long endMillis = collection.getEndMillis(); 310 311 if (!collection.isEmpty()) { 312 // process legacy data, creating active file at starting time, then 313 // using end time to possibly trigger rotation. 314 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis); 315 mRotator.maybeRotate(endMillis); 316 } 317 } 318 319 public void importLegacyUidLocked(File file) throws IOException { 320 // legacy file still exists; start empty to avoid double importing 321 mRotator.deleteAll(); 322 323 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration); 324 collection.readLegacyUid(file, mOnlyTags); 325 326 final long startMillis = collection.getStartMillis(); 327 final long endMillis = collection.getEndMillis(); 328 329 if (!collection.isEmpty()) { 330 // process legacy data, creating active file at starting time, then 331 // using end time to possibly trigger rotation. 332 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis); 333 mRotator.maybeRotate(endMillis); 334 } 335 } 336 337 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) { 338 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes()); 339 if (fullHistory) { 340 pw.println("Complete history:"); 341 getOrLoadCompleteLocked().dump(pw); 342 } else { 343 pw.println("History since boot:"); 344 mSinceBoot.dump(pw); 345 } 346 } 347} 348