2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.net;
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;
24 import static com.android.internal.util.Preconditions.checkNotNull;
26 import android.annotation.Nullable;
27 import android.net.NetworkStats;
28 import android.net.NetworkStats.NonMonotonicObserver;
29 import android.net.NetworkStatsHistory;
30 import android.net.NetworkTemplate;
31 import android.net.TrafficStats;
32 import android.os.Binder;
33 import android.os.DropBoxManager;
34 import android.service.NetworkStatsRecorderProto;
35 import android.util.Log;
36 import android.util.MathUtils;
37 import android.util.Slog;
38 import android.util.proto.ProtoOutputStream;
40 import com.android.internal.net.VpnInfo;
41 import com.android.internal.util.FileRotator;
42 import com.android.internal.util.IndentingPrintWriter;
44 import libcore.io.IoUtils;
46 import com.google.android.collect.Sets;
48 import java.io.ByteArrayOutputStream;
49 import java.io.DataOutputStream;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.OutputStream;
54 import java.io.PrintWriter;
55 import java.lang.ref.WeakReference;
56 import java.util.Arrays;
57 import java.util.HashSet;
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.
66 * Not inherently thread safe.
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;
73 private static final String TAG_NETSTATS_DUMP = "netstats_dump";
75 /** Dump before deleting in {@link #recoverFromWtf()}. */
76 private static final boolean DUMP_BEFORE_DELETE = true;
78 private final FileRotator mRotator;
79 private final NonMonotonicObserver<String> mObserver;
80 private final DropBoxManager mDropBox;
81 private final String mCookie;
83 private final long mBucketDuration;
84 private final boolean mOnlyTags;
86 private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
87 private NetworkStats mLastSnapshot;
89 private final NetworkStatsCollection mPending;
90 private final NetworkStatsCollection mSinceBoot;
92 private final CombiningRewriter mPendingRewriter;
94 private WeakReference<NetworkStatsCollection> mComplete;
97 * Non-persisted recorder, with only one bucket. Used by {@link NetworkStatsObservers}.
99 public NetworkStatsRecorder() {
105 // set the bucket big enough to have all data in one bucket, but allow some
106 // slack to avoid overflow
107 mBucketDuration = YEAR_IN_MILLIS;
111 mSinceBoot = new NetworkStatsCollection(mBucketDuration);
113 mPendingRewriter = null;
117 * Persisted recorder.
119 public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
120 DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
121 mRotator = checkNotNull(rotator, "missing FileRotator");
122 mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
123 mDropBox = checkNotNull(dropBox, "missing DropBoxManager");
126 mBucketDuration = bucketDuration;
127 mOnlyTags = onlyTags;
129 mPending = new NetworkStatsCollection(bucketDuration);
130 mSinceBoot = new NetworkStatsCollection(bucketDuration);
132 mPendingRewriter = new CombiningRewriter(mPending);
135 public void setPersistThreshold(long thresholdBytes) {
136 if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
137 mPersistThresholdBytes = MathUtils.constrain(
138 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
141 public void resetLocked() {
142 mLastSnapshot = null;
143 if (mPending != null) {
146 if (mSinceBoot != null) {
149 if (mComplete != null) {
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);
159 public NetworkStatsCollection getSinceBoot() {
164 * Load complete history represented by {@link FileRotator}. Caches
165 * internally as a {@link WeakReference}, and updated with future
166 * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
167 * as reference is valid.
169 public NetworkStatsCollection getOrLoadCompleteLocked() {
170 checkNotNull(mRotator, "missing FileRotator");
171 NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
173 res = loadLocked(Long.MIN_VALUE, Long.MAX_VALUE);
174 mComplete = new WeakReference<NetworkStatsCollection>(res);
179 public NetworkStatsCollection getOrLoadPartialLocked(long start, long end) {
180 checkNotNull(mRotator, "missing FileRotator");
181 NetworkStatsCollection res = mComplete != null ? mComplete.get() : null;
183 res = loadLocked(start, end);
188 private NetworkStatsCollection loadLocked(long start, long end) {
189 if (LOGD) Slog.d(TAG, "loadLocked() reading from disk for " + mCookie);
190 final NetworkStatsCollection res = new NetworkStatsCollection(mBucketDuration);
192 mRotator.readMatching(res, start, end);
193 res.recordCollection(mPending);
194 } catch (IOException e) {
195 Log.wtf(TAG, "problem completely reading network stats", e);
197 } catch (OutOfMemoryError e) {
198 Log.wtf(TAG, "problem completely reading network stats", e);
205 * Record any delta that occurred since last {@link NetworkStats} snapshot,
206 * using the given {@link Map} to identify network interfaces. First
207 * snapshot is considered bootstrap, and is not counted as delta.
209 * @param vpnArray Optional info about the currently active VPN, if any. This is used to
210 * redistribute traffic from the VPN app to the underlying responsible apps.
211 * This should always be set to null if the provided snapshot is aggregated
212 * across all UIDs (e.g. contains UID_ALL buckets), regardless of VPN state.
214 public void recordSnapshotLocked(NetworkStats snapshot,
215 Map<String, NetworkIdentitySet> ifaceIdent, @Nullable VpnInfo[] vpnArray,
216 long currentTimeMillis) {
217 final HashSet<String> unknownIfaces = Sets.newHashSet();
219 // skip recording when snapshot missing
220 if (snapshot == null) return;
222 // assume first snapshot is bootstrap and don't record
223 if (mLastSnapshot == null) {
224 mLastSnapshot = snapshot;
228 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
230 final NetworkStats delta = NetworkStats.subtract(
231 snapshot, mLastSnapshot, mObserver, mCookie);
232 final long end = currentTimeMillis;
233 final long start = end - delta.getElapsedRealtime();
235 if (vpnArray != null) {
236 for (VpnInfo info : vpnArray) {
237 delta.migrateTun(info.ownerUid, info.vpnIface, info.primaryUnderlyingIface);
241 NetworkStats.Entry entry = null;
242 for (int i = 0; i < delta.size(); i++) {
243 entry = delta.getValues(i, entry);
244 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
246 unknownIfaces.add(entry.iface);
250 // skip when no delta occurred
251 if (entry.isEmpty()) continue;
253 // only record tag data when requested
254 if ((entry.tag == TAG_NONE) != mOnlyTags) {
255 if (mPending != null) {
256 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
259 // also record against boot stats when present
260 if (mSinceBoot != null) {
261 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
264 // also record against complete dataset when present
265 if (complete != null) {
266 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
271 mLastSnapshot = snapshot;
273 if (LOGV && unknownIfaces.size() > 0) {
274 Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
279 * Consider persisting any pending deltas, if they are beyond
280 * {@link #mPersistThresholdBytes}.
282 public void maybePersistLocked(long currentTimeMillis) {
283 checkNotNull(mRotator, "missing FileRotator");
284 final long pendingBytes = mPending.getTotalBytes();
285 if (pendingBytes >= mPersistThresholdBytes) {
286 forcePersistLocked(currentTimeMillis);
288 mRotator.maybeRotate(currentTimeMillis);
293 * Force persisting any pending deltas.
295 public void forcePersistLocked(long currentTimeMillis) {
296 checkNotNull(mRotator, "missing FileRotator");
297 if (mPending.isDirty()) {
298 if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
300 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
301 mRotator.maybeRotate(currentTimeMillis);
303 } catch (IOException e) {
304 Log.wtf(TAG, "problem persisting pending stats", e);
306 } catch (OutOfMemoryError e) {
307 Log.wtf(TAG, "problem persisting pending stats", e);
314 * Remove the given UID from all {@link FileRotator} history, migrating it
315 * to {@link TrafficStats#UID_REMOVED}.
317 public void removeUidsLocked(int[] uids) {
318 if (mRotator != null) {
320 // Rewrite all persisted data to migrate UID stats
321 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
322 } catch (IOException e) {
323 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
325 } catch (OutOfMemoryError e) {
326 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
331 // Remove any pending stats
332 if (mPending != null) {
333 mPending.removeUids(uids);
335 if (mSinceBoot != null) {
336 mSinceBoot.removeUids(uids);
339 // Clear UID from current stats snapshot
340 if (mLastSnapshot != null) {
341 mLastSnapshot = mLastSnapshot.withoutUids(uids);
344 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
345 if (complete != null) {
346 complete.removeUids(uids);
351 * Rewriter that will combine current {@link NetworkStatsCollection} values
352 * with anything read from disk, and write combined set to disk. Clears the
353 * original {@link NetworkStatsCollection} when finished writing.
355 private static class CombiningRewriter implements FileRotator.Rewriter {
356 private final NetworkStatsCollection mCollection;
358 public CombiningRewriter(NetworkStatsCollection collection) {
359 mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
363 public void reset() {
368 public void read(InputStream in) throws IOException {
369 mCollection.read(in);
373 public boolean shouldWrite() {
378 public void write(OutputStream out) throws IOException {
379 mCollection.write(new DataOutputStream(out));
385 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
386 * the requested UID, only writing data back when modified.
388 public static class RemoveUidRewriter implements FileRotator.Rewriter {
389 private final NetworkStatsCollection mTemp;
390 private final int[] mUids;
392 public RemoveUidRewriter(long bucketDuration, int[] uids) {
393 mTemp = new NetworkStatsCollection(bucketDuration);
398 public void reset() {
403 public void read(InputStream in) throws IOException {
406 mTemp.removeUids(mUids);
410 public boolean shouldWrite() {
411 return mTemp.isDirty();
415 public void write(OutputStream out) throws IOException {
416 mTemp.write(new DataOutputStream(out));
420 public void importLegacyNetworkLocked(File file) throws IOException {
421 checkNotNull(mRotator, "missing FileRotator");
423 // legacy file still exists; start empty to avoid double importing
424 mRotator.deleteAll();
426 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
427 collection.readLegacyNetwork(file);
429 final long startMillis = collection.getStartMillis();
430 final long endMillis = collection.getEndMillis();
432 if (!collection.isEmpty()) {
433 // process legacy data, creating active file at starting time, then
434 // using end time to possibly trigger rotation.
435 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
436 mRotator.maybeRotate(endMillis);
440 public void importLegacyUidLocked(File file) throws IOException {
441 checkNotNull(mRotator, "missing FileRotator");
443 // legacy file still exists; start empty to avoid double importing
444 mRotator.deleteAll();
446 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
447 collection.readLegacyUid(file, mOnlyTags);
449 final long startMillis = collection.getStartMillis();
450 final long endMillis = collection.getEndMillis();
452 if (!collection.isEmpty()) {
453 // process legacy data, creating active file at starting time, then
454 // using end time to possibly trigger rotation.
455 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
456 mRotator.maybeRotate(endMillis);
460 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
461 if (mPending != null) {
462 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
465 pw.println("Complete history:");
466 getOrLoadCompleteLocked().dump(pw);
468 pw.println("History since boot:");
473 public void writeToProtoLocked(ProtoOutputStream proto, long tag) {
474 final long start = proto.start(tag);
475 if (mPending != null) {
476 proto.write(NetworkStatsRecorderProto.PENDING_TOTAL_BYTES, mPending.getTotalBytes());
478 getOrLoadCompleteLocked().writeToProto(proto, NetworkStatsRecorderProto.COMPLETE_HISTORY);
482 public void dumpCheckin(PrintWriter pw, long start, long end) {
483 // Only load and dump stats from the requested window
484 getOrLoadPartialLocked(start, end).dumpCheckin(pw, start, end);
488 * Recover from {@link FileRotator} failure by dumping state to
489 * {@link DropBoxManager} and deleting contents.
491 private void recoverFromWtf() {
492 if (DUMP_BEFORE_DELETE) {
493 final ByteArrayOutputStream os = new ByteArrayOutputStream();
495 mRotator.dumpAll(os);
496 } catch (IOException e) {
497 // ignore partial contents
500 IoUtils.closeQuietly(os);
502 mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
505 mRotator.deleteAll();