blob: cea084b51849d8753ad47041e3c2b64708d33815 [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
34import com.android.internal.util.FileRotator;
35import com.android.internal.util.IndentingPrintWriter;
36import com.google.android.collect.Sets;
37
Jeff Sharkey6de357e2012-05-09 13:33:52 -070038import java.io.ByteArrayOutputStream;
Jeff Sharkey63abc372012-01-11 18:38:16 -080039import java.io.DataOutputStream;
40import java.io.File;
41import java.io.IOException;
42import java.io.InputStream;
43import java.io.OutputStream;
44import java.lang.ref.WeakReference;
Jeff Sharkeydaa57e82012-09-19 14:10:39 -070045import java.util.Arrays;
Jeff Sharkey63abc372012-01-11 18:38:16 -080046import java.util.HashSet;
47import java.util.Map;
48
Jeff Sharkey6de357e2012-05-09 13:33:52 -070049import libcore.io.IoUtils;
50
Jeff Sharkey63abc372012-01-11 18:38:16 -080051/**
52 * Logic to record deltas between periodic {@link NetworkStats} snapshots into
53 * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
54 * Keeps pending changes in memory until they pass a specific threshold, in
55 * bytes. Uses {@link FileRotator} for persistence logic.
56 * <p>
57 * Not inherently thread safe.
58 */
59public class NetworkStatsRecorder {
60 private static final String TAG = "NetworkStatsRecorder";
Jeff Sharkeye7bb71d2012-02-28 15:13:08 -080061 private static final boolean LOGD = false;
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -080062 private static final boolean LOGV = false;
Jeff Sharkey63abc372012-01-11 18:38:16 -080063
Jeff Sharkey6de357e2012-05-09 13:33:52 -070064 private static final String TAG_NETSTATS_DUMP = "netstats_dump";
65
66 /** Dump before deleting in {@link #recoverFromWtf()}. */
67 private static final boolean DUMP_BEFORE_DELETE = true;
68
Jeff Sharkey63abc372012-01-11 18:38:16 -080069 private final FileRotator mRotator;
70 private final NonMonotonicObserver<String> mObserver;
Jeff Sharkey6de357e2012-05-09 13:33:52 -070071 private final DropBoxManager mDropBox;
Jeff Sharkey63abc372012-01-11 18:38:16 -080072 private final String mCookie;
73
74 private final long mBucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080075 private final boolean mOnlyTags;
76
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -070077 private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
Jeff Sharkey63abc372012-01-11 18:38:16 -080078 private NetworkStats mLastSnapshot;
79
80 private final NetworkStatsCollection mPending;
81 private final NetworkStatsCollection mSinceBoot;
82
83 private final CombiningRewriter mPendingRewriter;
84
85 private WeakReference<NetworkStatsCollection> mComplete;
86
87 public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
Jeff Sharkey6de357e2012-05-09 13:33:52 -070088 DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
Jeff Sharkey63abc372012-01-11 18:38:16 -080089 mRotator = checkNotNull(rotator, "missing FileRotator");
90 mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
Jeff Sharkey6de357e2012-05-09 13:33:52 -070091 mDropBox = checkNotNull(dropBox, "missing DropBoxManager");
Jeff Sharkey63abc372012-01-11 18:38:16 -080092 mCookie = cookie;
93
94 mBucketDuration = bucketDuration;
Jeff Sharkey63abc372012-01-11 18:38:16 -080095 mOnlyTags = onlyTags;
96
97 mPending = new NetworkStatsCollection(bucketDuration);
98 mSinceBoot = new NetworkStatsCollection(bucketDuration);
99
100 mPendingRewriter = new CombiningRewriter(mPending);
101 }
102
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700103 public void setPersistThreshold(long thresholdBytes) {
104 if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
105 mPersistThresholdBytes = MathUtils.constrain(
106 thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
107 }
108
Jeff Sharkey63abc372012-01-11 18:38:16 -0800109 public void resetLocked() {
110 mLastSnapshot = null;
111 mPending.reset();
112 mSinceBoot.reset();
113 mComplete.clear();
114 }
115
116 public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
117 return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
118 }
119
120 /**
121 * Load complete history represented by {@link FileRotator}. Caches
122 * internally as a {@link WeakReference}, and updated with future
123 * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
124 * as reference is valid.
125 */
126 public NetworkStatsCollection getOrLoadCompleteLocked() {
127 NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
128 if (complete == null) {
129 if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie);
130 try {
131 complete = new NetworkStatsCollection(mBucketDuration);
132 mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE);
133 complete.recordCollection(mPending);
134 mComplete = new WeakReference<NetworkStatsCollection>(complete);
135 } catch (IOException e) {
136 Log.wtf(TAG, "problem completely reading network stats", e);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700137 recoverFromWtf();
Jeff Sharkeye4984be2013-09-10 21:03:27 -0700138 } catch (OutOfMemoryError e) {
139 Log.wtf(TAG, "problem completely reading network stats", e);
140 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800141 }
142 }
143 return complete;
144 }
145
146 /**
147 * Record any delta that occurred since last {@link NetworkStats} snapshot,
148 * using the given {@link Map} to identify network interfaces. First
149 * snapshot is considered bootstrap, and is not counted as delta.
150 */
151 public void recordSnapshotLocked(NetworkStats snapshot,
152 Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
153 final HashSet<String> unknownIfaces = Sets.newHashSet();
154
Jeff Sharkeye8914c32012-05-01 16:26:09 -0700155 // skip recording when snapshot missing
156 if (snapshot == null) return;
157
Jeff Sharkey63abc372012-01-11 18:38:16 -0800158 // assume first snapshot is bootstrap and don't record
159 if (mLastSnapshot == null) {
160 mLastSnapshot = snapshot;
161 return;
162 }
163
164 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
165
166 final NetworkStats delta = NetworkStats.subtract(
167 snapshot, mLastSnapshot, mObserver, mCookie);
168 final long end = currentTimeMillis;
169 final long start = end - delta.getElapsedRealtime();
170
171 NetworkStats.Entry entry = null;
172 for (int i = 0; i < delta.size(); i++) {
173 entry = delta.getValues(i, entry);
174 final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
175 if (ident == null) {
176 unknownIfaces.add(entry.iface);
177 continue;
178 }
179
Jeff Sharkeyac3fcb12012-05-02 18:11:52 -0700180 // skip when no delta occurred
Jeff Sharkey63abc372012-01-11 18:38:16 -0800181 if (entry.isEmpty()) continue;
182
183 // only record tag data when requested
184 if ((entry.tag == TAG_NONE) != mOnlyTags) {
185 mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
186
187 // also record against boot stats when present
188 if (mSinceBoot != null) {
189 mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
190 }
191
192 // also record against complete dataset when present
193 if (complete != null) {
194 complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
195 }
196 }
197 }
198
199 mLastSnapshot = snapshot;
200
Jeff Sharkey1f8ea2d2012-02-07 12:05:43 -0800201 if (LOGV && unknownIfaces.size() > 0) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800202 Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
203 }
204 }
205
206 /**
207 * Consider persisting any pending deltas, if they are beyond
208 * {@link #mPersistThresholdBytes}.
209 */
210 public void maybePersistLocked(long currentTimeMillis) {
211 final long pendingBytes = mPending.getTotalBytes();
212 if (pendingBytes >= mPersistThresholdBytes) {
213 forcePersistLocked(currentTimeMillis);
214 } else {
215 mRotator.maybeRotate(currentTimeMillis);
216 }
217 }
218
219 /**
220 * Force persisting any pending deltas.
221 */
222 public void forcePersistLocked(long currentTimeMillis) {
223 if (mPending.isDirty()) {
224 if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
225 try {
226 mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
227 mRotator.maybeRotate(currentTimeMillis);
228 mPending.reset();
229 } catch (IOException e) {
230 Log.wtf(TAG, "problem persisting pending stats", e);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700231 recoverFromWtf();
Jeff Sharkeye4984be2013-09-10 21:03:27 -0700232 } catch (OutOfMemoryError e) {
233 Log.wtf(TAG, "problem persisting pending stats", e);
234 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800235 }
236 }
237 }
238
239 /**
240 * Remove the given UID from all {@link FileRotator} history, migrating it
241 * to {@link TrafficStats#UID_REMOVED}.
242 */
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700243 public void removeUidsLocked(int[] uids) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800244 try {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700245 // Rewrite all persisted data to migrate UID stats
246 mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
Jeff Sharkey63abc372012-01-11 18:38:16 -0800247 } catch (IOException e) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700248 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700249 recoverFromWtf();
Jeff Sharkeye4984be2013-09-10 21:03:27 -0700250 } catch (OutOfMemoryError e) {
251 Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
252 recoverFromWtf();
Jeff Sharkey63abc372012-01-11 18:38:16 -0800253 }
254
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700255 // Remove any pending stats
256 mPending.removeUids(uids);
257 mSinceBoot.removeUids(uids);
258
259 // Clear UID from current stats snapshot
Jeff Sharkey63abc372012-01-11 18:38:16 -0800260 if (mLastSnapshot != null) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700261 mLastSnapshot = mLastSnapshot.withoutUids(uids);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800262 }
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700263
264 final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
265 if (complete != null) {
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700266 complete.removeUids(uids);
Jeff Sharkeyb52e3e52012-04-06 11:12:08 -0700267 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800268 }
269
270 /**
271 * Rewriter that will combine current {@link NetworkStatsCollection} values
272 * with anything read from disk, and write combined set to disk. Clears the
273 * original {@link NetworkStatsCollection} when finished writing.
274 */
275 private static class CombiningRewriter implements FileRotator.Rewriter {
276 private final NetworkStatsCollection mCollection;
277
278 public CombiningRewriter(NetworkStatsCollection collection) {
279 mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
280 }
281
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700282 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800283 public void reset() {
284 // ignored
285 }
286
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700287 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800288 public void read(InputStream in) throws IOException {
289 mCollection.read(in);
290 }
291
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700292 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800293 public boolean shouldWrite() {
294 return true;
295 }
296
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700297 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800298 public void write(OutputStream out) throws IOException {
299 mCollection.write(new DataOutputStream(out));
300 mCollection.reset();
301 }
302 }
303
304 /**
305 * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
306 * the requested UID, only writing data back when modified.
307 */
308 public static class RemoveUidRewriter implements FileRotator.Rewriter {
309 private final NetworkStatsCollection mTemp;
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700310 private final int[] mUids;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800311
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700312 public RemoveUidRewriter(long bucketDuration, int[] uids) {
Jeff Sharkey63abc372012-01-11 18:38:16 -0800313 mTemp = new NetworkStatsCollection(bucketDuration);
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700314 mUids = uids;
Jeff Sharkey63abc372012-01-11 18:38:16 -0800315 }
316
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700317 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800318 public void reset() {
319 mTemp.reset();
320 }
321
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700322 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800323 public void read(InputStream in) throws IOException {
324 mTemp.read(in);
325 mTemp.clearDirty();
Jeff Sharkeydaa57e82012-09-19 14:10:39 -0700326 mTemp.removeUids(mUids);
Jeff Sharkey63abc372012-01-11 18:38:16 -0800327 }
328
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700329 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800330 public boolean shouldWrite() {
331 return mTemp.isDirty();
332 }
333
Jeff Sharkeybfdd6802012-04-09 10:49:19 -0700334 @Override
Jeff Sharkey63abc372012-01-11 18:38:16 -0800335 public void write(OutputStream out) throws IOException {
336 mTemp.write(new DataOutputStream(out));
337 }
338 }
339
340 public void importLegacyNetworkLocked(File file) throws IOException {
341 // legacy file still exists; start empty to avoid double importing
342 mRotator.deleteAll();
343
344 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
345 collection.readLegacyNetwork(file);
346
347 final long startMillis = collection.getStartMillis();
348 final long endMillis = collection.getEndMillis();
349
350 if (!collection.isEmpty()) {
351 // process legacy data, creating active file at starting time, then
352 // using end time to possibly trigger rotation.
353 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
354 mRotator.maybeRotate(endMillis);
355 }
356 }
357
358 public void importLegacyUidLocked(File file) throws IOException {
359 // legacy file still exists; start empty to avoid double importing
360 mRotator.deleteAll();
361
362 final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
363 collection.readLegacyUid(file, mOnlyTags);
364
365 final long startMillis = collection.getStartMillis();
366 final long endMillis = collection.getEndMillis();
367
368 if (!collection.isEmpty()) {
369 // process legacy data, creating active file at starting time, then
370 // using end time to possibly trigger rotation.
371 mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
372 mRotator.maybeRotate(endMillis);
373 }
374 }
375
376 public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
377 pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
378 if (fullHistory) {
379 pw.println("Complete history:");
380 getOrLoadCompleteLocked().dump(pw);
381 } else {
382 pw.println("History since boot:");
383 mSinceBoot.dump(pw);
384 }
385 }
Jeff Sharkey6de357e2012-05-09 13:33:52 -0700386
387 /**
388 * Recover from {@link FileRotator} failure by dumping state to
389 * {@link DropBoxManager} and deleting contents.
390 */
391 private void recoverFromWtf() {
392 if (DUMP_BEFORE_DELETE) {
393 final ByteArrayOutputStream os = new ByteArrayOutputStream();
394 try {
395 mRotator.dumpAll(os);
396 } catch (IOException e) {
397 // ignore partial contents
398 os.reset();
399 } finally {
400 IoUtils.closeQuietly(os);
401 }
402 mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
403 }
404
405 mRotator.deleteAll();
406 }
Jeff Sharkey63abc372012-01-11 18:38:16 -0800407}