blob: c09196006be2b33558b984aa6975ce5aae2dc1b7 [file] [log] [blame]
Jeff Sharkey63abc372012-01-11 18:38:16 -08001/*
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;
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070020import static android.net.TrafficStats.KB_IN_BYTES;
21import static android.net.TrafficStats.MB_IN_BYTES;
Jeff Sharkey63abc372012-01-11 18:38:16 -080022import 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;
Jeff Sharkey6de357e2012-05-09 13:33:52 -070029import android.os.DropBoxManager;
Jeff Sharkey63abc372012-01-11 18:38:16 -080030import android.util.Log;
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070031import android.util.MathUtils;
Jeff Sharkey63abc372012-01-11 18:38:16 -080032import android.util.Slog;
33
Wenchao Tongf5ea3402015-03-04 13:26:38 -080034import com.android.internal.net.VpnInfo;
Jeff Sharkey63abc372012-01-11 18:38:16 -080035import com.android.internal.util.FileRotator;
36import com.android.internal.util.IndentingPrintWriter;
37import com.google.android.collect.Sets;
38
Jeff Sharkey6de357e2012-05-09 13:33:52 -070039import java.io.ByteArrayOutputStream;
Jeff Sharkey63abc372012-01-11 18:38:16 -080040import java.io.DataOutputStream;
41import java.io.File;
42import java.io.IOException;
43import java.io.InputStream;
44import java.io.OutputStream;
Jeff Sharkey55a442e2014-11-18 18:22:21 -080045import java.io.PrintWriter;
Jeff Sharkey63abc372012-01-11 18:38:16 -080046import java.lang.ref.WeakReference;
Jeff Sharkeydaa57e82012-09-19 14:10:39 -070047import java.util.Arrays;
Jeff Sharkey63abc372012-01-11 18:38:16 -080048import java.util.HashSet;
49import java.util.Map;
50
Jeff Sharkey6de357e2012-05-09 13:33:52 -070051import libcore.io.IoUtils;
52
Jeff Sharkey63abc372012-01-11 18:38:16 -080053/**
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";
Jeff Sharkeye7bb71d2012-02-28 15:13:08 -080063 private static final boolean LOGD = false;
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -080064 private static final boolean LOGV = false;
Jeff Sharkey63abc372012-01-11 18:38:16 -080065
Jeff Sharkey6de357e2012-05-09 13:33:52 -070066 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
Jeff Sharkey63abc372012-01-11 18:38:16 -080071 private final FileRotator mRotator;
72 private final NonMonotonicObserver<String> mObserver;
Jeff Sharkey6de357e2012-05-09 13:33:52 -070073 private final DropBoxManager mDropBox;
Jeff Sharkey63abc372012-01-11 18:38:16 -080074 private final String mCookie;
75
76 private final long mBucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080077 private final boolean mOnlyTags;
78
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070079 private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
Jeff Sharkey63abc372012-01-11 18:38:16 -080080 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,
Jeff Sharkey6de357e2012-05-09 13:33:52 -070090 DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
Jeff Sharkey63abc372012-01-11 18:38:16 -080091 mRotator = checkNotNull(rotator, "missing FileRotator");
92 mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
Jeff Sharkey6de357e2012-05-09 13:33:52 -070093 mDropBox = checkNotNull(dropBox, "missing DropBoxManager");
Jeff Sharkey63abc372012-01-11 18:38:16 -080094 mCookie = cookie;
95
96 mBucketDuration = bucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080097 mOnlyTags = onlyTags;
98
99 mPending = new NetworkStatsCollection(bucketDuration);
100 mSinceBoot = new NetworkStatsCollection(bucketDuration);
101
102 mPendingRewriter = new CombiningRewriter(mPending);
103 }
104
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700105 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
Jeff Sharkey63abc372012-01-11 18:38:16 -0800111 public void resetLocked() {
112 mLastSnapshot = null;
113 mPending.reset();
114 mSinceBoot.reset();
115 mComplete.clear();
116 }
117
118 public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
Jeff Davidson1efb1332015-12-09 18:04:50 -0800119 return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE,
120 NetworkStatsAccess.Level.DEVICE).getTotal(null);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800121 }
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() {
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800130 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);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800134 }
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800135 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;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800160 }
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,
Wenchao Tongf5ea3402015-03-04 13:26:38 -0800168 Map<String, NetworkIdentitySet> ifaceIdent, VpnInfo[] vpnArray,
169 long currentTimeMillis) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800170 final HashSet<String> unknownIfaces = Sets.newHashSet();
171
Jeff Sharkeye8914c32012-05-01 16:26:09 -0700172 // skip recording when snapshot missing
173 if (snapshot == null) return;
174
Jeff Sharkey63abc372012-01-11 18:38:16 -0800175 // 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
Wenchao Tongf5ea3402015-03-04 13:26:38 -0800188 if (vpnArray != null) {
189 for (VpnInfo info : vpnArray) {
190 delta.migrateTun(info.ownerUid, info.vpnIface, info.primaryUnderlyingIface);
191 }
192 }
193
Jeff Sharkey63abc372012-01-11 18:38:16 -0800194 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
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700203 // skip when no delta occurred
Jeff Sharkey63abc372012-01-11 18:38:16 -0800204 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
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -0800224 if (LOGV && unknownIfaces.size() > 0) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800225 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);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700254 recoverFromWtf();
Jeff Sharkeye4984be2013-09-10 21:03:27 -0700255 } catch (OutOfMemoryError e) {
256 Log.wtf(TAG, "problem persisting pending stats", e);
257 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800258 }
259 }
260 }
261
262 /**
263 * Remove the given UID from all {@link FileRotator} history, migrating it
264 * to {@link TrafficStats#UID_REMOVED}.
265 */
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700266 public void removeUidsLocked(int[] uids) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800267 try {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700268 // Rewrite all persisted data to migrate UID stats
269 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
Jeff Sharkey63abc372012-01-11 18:38:16 -0800270 } catch (IOException e) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700271 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700272 recoverFromWtf();
Jeff Sharkeye4984be2013-09-10 21:03:27 -0700273 } catch (OutOfMemoryError e) {
274 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
275 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800276 }
277
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700278 // Remove any pending stats
279 mPending.removeUids(uids);
280 mSinceBoot.removeUids(uids);
281
282 // Clear UID from current stats snapshot
Jeff Sharkey63abc372012-01-11 18:38:16 -0800283 if (mLastSnapshot != null) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700284 mLastSnapshot = mLastSnapshot.withoutUids(uids);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800285 }
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700286
287 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
288 if (complete != null) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700289 complete.removeUids(uids);
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700290 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800291 }
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
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700305 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800306 public void reset() {
307 // ignored
308 }
309
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700310 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800311 public void read(InputStream in) throws IOException {
312 mCollection.read(in);
313 }
314
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700315 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800316 public boolean shouldWrite() {
317 return true;
318 }
319
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700320 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800321 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;
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700333 private final int[] mUids;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800334
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700335 public RemoveUidRewriter(long bucketDuration, int[] uids) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800336 mTemp = new NetworkStatsCollection(bucketDuration);
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700337 mUids = uids;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800338 }
339
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700340 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800341 public void reset() {
342 mTemp.reset();
343 }
344
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700345 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800346 public void read(InputStream in) throws IOException {
347 mTemp.read(in);
348 mTemp.clearDirty();
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700349 mTemp.removeUids(mUids);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800350 }
351
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700352 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800353 public boolean shouldWrite() {
354 return mTemp.isDirty();
355 }
356
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700357 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800358 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 }
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700409
Jeff Sharkey55a442e2014-11-18 18:22:21 -0800410 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
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700415 /**
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 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800435}